Skip to content

Commit 9434ecd

Browse files
committed
#15: Add current time to the variables
1 parent d032f65 commit 9434ecd

File tree

10 files changed

+191
-103
lines changed

10 files changed

+191
-103
lines changed

Readme.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This is a plugin for [Winamp](http://www.winamp.com/) that saves text informatio
1212
- [Installation](#installation)
1313
- [Configuration](#configuration)
1414
- [Text](#text)
15-
- [Metadata fields](#metadata-fields)
15+
- [Placeholder fields](#placeholder-fields)
1616
- [Helpers](#helpers)
1717
- [Formatting](#formatting)
1818
- [Album art](#album-art)
@@ -70,7 +70,7 @@ U2 – Exit – The Joshua Tree
7070

7171
To customize the text file location and contents, go to the plugin preferences in Winamp.
7272

73-
1. You can change the file contents by editing the **Text template** and inserting placeholders inside `{{` `}}`, either with the **Insert** button or by typing them manually. See [Metadata fields](#metadata-fields) below for all the fields you can use in a placeholder. For example, a simple template that could render the above example text is
73+
1. You can change the file contents by editing the **Text template** and inserting placeholders inside `{{` `}}`, either with the **Insert** button or by typing them manually. See [Placeholder fields](#placeholder-fields) below for all the fields you can use in a placeholder. For example, a simple template that could render the above example text is
7474
```handlebars
7575
{{Artist}} – {{Title}} – {{Album}}
7676
```
@@ -79,9 +79,9 @@ To customize the text file location and contents, go to the plugin preferences i
7979
8080
When Winamp is not playing a song, this text file will be truncated to 0 bytes.
8181
82-
#### Metadata fields
82+
#### Placeholder fields
8383
84-
Metadata values that are missing or empty will be rendered as the empty string.
84+
Placeholder values that are missing or empty will be rendered as the empty string.
8585
8686
|Field name|Type|Examples|Notes|
8787
|-|-|-|-|
@@ -96,6 +96,7 @@ Metadata values that are missing or empty will be rendered as the empty string.
9696
|`Conductor`|string|||
9797
|`Director`|string||Most commonly used for video files|
9898
|`Disc`|int|`1`|If it can't be parsed as an int (like `1/2`) it will be a string|
99+
|`Elapsed`|TimeSpan|`00:00:28.5080000`|Updated 1hz, millisecond resolution. See [formatting](#formatting) for `m:ss` and other formats.|
99100
|`Family`|string|`MPEG Layer 3 Audio File`|Codec or container format|
100101
|`FileBasename`|string|`Exit.mp3`|Filename without path|
101102
|`FileBasenameWithoutExtension`|string|`Exit`|Filename without path or extension|
@@ -124,11 +125,11 @@ Metadata values that are missing or empty will be rendered as the empty string.
124125
|`VBR`|bool|`false`|`true` for variable bitrate, `false` for constant bitrate|
125126
|`Year`|int|`1987`|If it can't be parsed as an int (like `1987-01-01`) it will be a string|
126127
127-
Any other values you use in a placeholder will be requested directly from Winamp, and the response will be output as-is. If you can find other fields that Winamp handles for audio files, please [file an enhancement issue](https://github.com/Aldaviva/WinampNowPlayingToFile/issues/new?labels=enhancement&title=New%20metadata%20field:%20) so it can be added to this program and documentation.
128+
Any other values you use in a placeholder will be requested directly from Winamp as file metadata, and the response will be output as-is. If you can find other fields that Winamp handles for audio files, please [file an enhancement issue](https://github.com/Aldaviva/WinampNowPlayingToFile/issues/new?labels=enhancement&title=New%20metadata%20field:%20) so it can be added to this program and documentation.
128129
129130
#### Helpers
130131
131-
Template logic can be added using [Handlebars expressions](https://handlebarsjs.com/), including the [built-in helpers](https://handlebarsjs.com/guide/builtin-helpers.html) like `{{#if expr}}`, `{{#elif expr}}`, `{{#else}}`, and `{{/if}}`. To output a line break (CRLF), use `{{#newline}}`.
132+
Template logic can be added using [Handlebars expressions](https://github.com/jehugaleahsa/mustache-sharp/blob/v1.0/README.md), including the [built-in helpers](https://github.com/jehugaleahsa/mustache-sharp/blob/v1.0/README.md#placeholders) like `{{#if expr}}`, `{{#elif expr}}`, `{{#else}}`, and `{{/if}}`. To output a line break (CRLF), use `{{#newline}}`.
132133
133134
For example, you can conditionally include artist and album only if those fields exist on your song and are nonempty.
134135
```handlebars

Test/Business/NowPlayingToFileManagerTest.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
using System;
1+
using Daniel15.Sharpamp;
2+
using FakeItEasy;
3+
using FluentAssertions;
4+
using System;
25
using System.Collections.Generic;
36
using System.IO;
47
using System.Text;
5-
using Daniel15.Sharpamp;
6-
using FakeItEasy;
7-
using FluentAssertions;
8+
using System.Threading;
89
using WinampNowPlayingToFile.Business;
910
using WinampNowPlayingToFile.Facade;
1011
using WinampNowPlayingToFile.Settings;
@@ -51,6 +52,7 @@ private static void onManagerError(object _, NowPlayingException exception) {
5152

5253
public void Dispose() {
5354
cleanUp();
55+
manager.renderTextTimer.Stop();
5456
}
5557

5658
private void cleanUp() {
@@ -338,4 +340,21 @@ public void queryCustomMetadataFieldFromWinamp() {
338340
A.CallTo(() => winampController.fetchMetadataFieldValue("CustomField")).MustHaveHappenedOnceExactly();
339341
}
340342

343+
[Fact]
344+
public void elapsedTriggersPeriodicRenders() {
345+
CountdownEvent latch = new(5);
346+
347+
manager.renderTextTimer.Interval = 100;
348+
A.CallTo(() => winampController.fetchMetadataFieldValue("Elapsed")).ReturnsLazily(() => {
349+
latch.Signal();
350+
return TimeSpan.FromSeconds(1);
351+
});
352+
353+
A.CallTo(() => settings.textTemplate).Returns("{{Elapsed:m\\:ss}}");
354+
settings.settingsUpdated += Raise.WithEmpty();
355+
356+
latch.Wait(10_000);
357+
A.CallTo(() => winampController.fetchMetadataFieldValue("Elapsed")).MustHaveHappenedANumberOfTimesMatching(i => i >= latch.InitialCount);
358+
}
359+
341360
}

Test/Test.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="FakeItEasy" Version="7.4.0" />
14-
<PackageReference Include="FluentAssertions" Version="6.11.0" />
14+
<PackageReference Include="FluentAssertions" Version="6.12.0" />
1515
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
16-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
16+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
1717
<PackageReference Include="mwinapi" Version="0.3.0.5" />
18-
<PackageReference Include="xunit" Version="2.5.0" />
19-
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
18+
<PackageReference Include="xunit" Version="2.5.3" />
19+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
2020
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2121
<PrivateAssets>all</PrivateAssets>
2222
</PackageReference>

WinampNowPlayingToFile/Business/NowPlayingToFileManager.cs

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#nullable enable
22

3+
using Daniel15.Sharpamp;
4+
using Mustache;
35
using System;
46
using System.Collections.Generic;
57
using System.IO;
68
using System.Linq;
79
using System.Text;
8-
using Daniel15.Sharpamp;
9-
using Mustache;
10+
using System.Timers;
1011
using WinampNowPlayingToFile.Facade;
1112
using WinampNowPlayingToFile.Settings;
1213
using Song = WinampNowPlayingToFile.Facade.Song;
@@ -23,40 +24,69 @@ public interface INowPlayingToFileManager {
2324

2425
public class NowPlayingToFileManager: INowPlayingToFileManager {
2526

26-
private static readonly FormatCompiler TEMPLATE_COMPILER = new();
2727
private static readonly UTF8Encoding UTF8 = new(false, true);
2828
private static readonly IEnumerable<string> ARTWORK_EXTENSIONS = new[] { ".bmp", ".gif", ".jpeg", ".jpg", ".png" };
2929
private static readonly IEnumerable<string> ARTWORK_BASE_NAMES = new[] { "cover", "folder", "front", "albumart" };
3030

3131
private static byte[]? albumArtWhenMissingFromSong => getInstallationDirectoryImageOrFallback("emptyAlbumArt.png");
3232
private static byte[]? albumArtWhenStopped => getInstallationDirectoryImageOrFallback("stoppedAlbumArt.png");
3333

34-
private readonly WinampController winampController;
35-
private readonly ISettings settings;
34+
private readonly WinampController winampController;
35+
private readonly ISettings settings;
36+
private readonly FormatCompiler templateCompiler = new();
37+
internal readonly Timer renderTextTimer = new(1000);
3638

3739
private Generator? cachedTemplate;
40+
private bool _textTemplateDependsOnTime;
41+
42+
private bool textTemplateDependsOnTime {
43+
get => _textTemplateDependsOnTime;
44+
set {
45+
if (_textTemplateDependsOnTime != value) {
46+
_textTemplateDependsOnTime = value;
47+
startOrStopTextRenderingTimer();
48+
}
49+
}
50+
}
3851

3952
public event EventHandler<NowPlayingException>? error;
4053

4154
public NowPlayingToFileManager(ISettings settings, WinampController winampController) {
4255
this.winampController = winampController;
4356
this.settings = settings;
4457

45-
this.winampController.songChanged += delegate { update(); };
46-
this.winampController.statusChanged += delegate { update(); };
58+
this.winampController.songChanged += delegate { update(); };
59+
60+
this.winampController.statusChanged += (_, args) => {
61+
update();
62+
startOrStopTextRenderingTimer(args.Status);
63+
};
64+
4765
this.settings.settingsUpdated += delegate {
48-
cachedTemplate = null;
66+
cachedTemplate = null;
67+
textTemplateDependsOnTime = false;
4968
update();
5069
};
5170

71+
templateCompiler.PlaceholderFound += (_, args) => {
72+
if (args.Key.Equals("Elapsed", StringComparison.CurrentCultureIgnoreCase)) {
73+
textTemplateDependsOnTime = true;
74+
}
75+
};
76+
77+
renderTextTimer.Elapsed += (_, _) => { update(false); };
78+
5279
update();
5380
}
5481

55-
internal void update() {
82+
internal void update(bool updateAlbumArt = true) {
5683
try {
5784
if (winampController.currentSong is { Filename: not "" } currentSong) {
5885
saveText(renderText(currentSong));
59-
saveImage(findAlbumArt(currentSong));
86+
87+
if (updateAlbumArt) {
88+
saveImage(findAlbumArt(currentSong));
89+
}
6090
}
6191
} catch (Exception e) when (e is not OutOfMemoryException) {
6292
error?.Invoke(this, new NowPlayingException("Exception while updating song", e, winampController.currentSong));
@@ -73,7 +103,7 @@ private void saveText(string nowPlayingText) {
73103

74104
private Generator getTemplate() {
75105
if (cachedTemplate == null) {
76-
cachedTemplate = TEMPLATE_COMPILER.Compile(settings.textTemplate);
106+
cachedTemplate = templateCompiler.Compile(settings.textTemplate);
77107
cachedTemplate.KeyNotFound += fetchExtraMetadata;
78108
}
79109

@@ -174,7 +204,13 @@ private void saveImage(byte[]? imageData) {
174204
}
175205
}
176206

207+
private void startOrStopTextRenderingTimer(Status? playbackStatus = null) {
208+
playbackStatus ??= winampController.status;
209+
renderTextTimer.Enabled = playbackStatus == Status.Playing && textTemplateDependsOnTime;
210+
}
211+
177212
public virtual void onQuit() {
213+
renderTextTimer.Stop();
178214
saveText(string.Empty);
179215
saveImage(albumArtWhenStopped);
180216
}

WinampNowPlayingToFile/Facade/WinampControllerImpl.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#nullable enable
22

3+
using Daniel15.Sharpamp;
34
using System;
45
using System.IO;
56
using System.Reflection;
6-
using Daniel15.Sharpamp;
77

88
namespace WinampNowPlayingToFile.Facade;
99

@@ -26,7 +26,10 @@ public interface WinampController {
2626

2727
public class WinampControllerImpl: WinampController {
2828

29-
private readonly Winamp winamp;
29+
private readonly Winamp winamp;
30+
31+
// ReSharper disable once InconsistentNaming - this is how the method is named in Sharpamp
32+
private readonly Func<int, int> sendIPCCommandInt;
3033
private readonly Func<string, string, string> getMetadata;
3134

3235
public event SongChangedEventHandler? songChanged;
@@ -40,6 +43,12 @@ public WinampControllerImpl(Winamp winamp) {
4043
getMetadata = (Func<string, string, string>) winamp.GetType()
4144
.GetMethod("GetMetadata", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(string), typeof(string) }, null)!
4245
.CreateDelegate(typeof(Func<string, string, string>), winamp);
46+
47+
Type ipcCommand = winamp.GetType().GetNestedType("IPCCommand", BindingFlags.NonPublic);
48+
49+
sendIPCCommandInt = (Func<int, int>) winamp.GetType()
50+
.GetMethod("SendIPCCommandInt", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { ipcCommand }, null)!
51+
.CreateDelegate(typeof(Func<int, int>), winamp);
4352
}
4453

4554
public Status status => winamp.Status;
@@ -71,10 +80,13 @@ public object fetchMetadataFieldValue(string metadataFieldName) {
7180
string songFilename = winamp.CurrentSong.Filename;
7281

7382
try {
74-
if (metadataFieldName == "filebasename") {
75-
return Path.GetFileName(songFilename);
76-
} else if (metadataFieldName == "filebasenamewithoutextension") {
77-
return Path.GetFileNameWithoutExtension(songFilename);
83+
switch (metadataFieldName) {
84+
case "filebasename":
85+
return Path.GetFileName(songFilename);
86+
case "filebasenamewithoutextension":
87+
return Path.GetFileNameWithoutExtension(songFilename);
88+
case "elapsed":
89+
return TimeSpan.FromMilliseconds(sendIPCCommandInt(105));
7890
}
7991
} catch (ArgumentException) {
8092
return string.Empty;

WinampNowPlayingToFile/NowPlayingToFilePlugin.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#nullable enable
22

3+
using Daniel15.Sharpamp;
34
using System;
45
using System.Reflection;
56
using System.Windows.Forms;
6-
using Daniel15.Sharpamp;
77
using WinampNowPlayingToFile.Business;
88
using WinampNowPlayingToFile.Facade;
99
using WinampNowPlayingToFile.Presentation;

0 commit comments

Comments
 (0)