Skip to content

Commit

Permalink
#15: Add current time to the variables
Browse files Browse the repository at this point in the history
  • Loading branch information
Aldaviva committed Oct 30, 2023
1 parent d032f65 commit 9434ecd
Show file tree
Hide file tree
Showing 10 changed files with 191 additions and 103 deletions.
13 changes: 7 additions & 6 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This is a plugin for [Winamp](http://www.winamp.com/) that saves text informatio
- [Installation](#installation)
- [Configuration](#configuration)
- [Text](#text)
- [Metadata fields](#metadata-fields)
- [Placeholder fields](#placeholder-fields)
- [Helpers](#helpers)
- [Formatting](#formatting)
- [Album art](#album-art)
Expand Down Expand Up @@ -70,7 +70,7 @@ U2 – Exit – The Joshua Tree

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

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
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
```handlebars
{{Artist}} – {{Title}} – {{Album}}
```
Expand All @@ -79,9 +79,9 @@ To customize the text file location and contents, go to the plugin preferences i
When Winamp is not playing a song, this text file will be truncated to 0 bytes.
#### Metadata fields
#### Placeholder fields
Metadata values that are missing or empty will be rendered as the empty string.
Placeholder values that are missing or empty will be rendered as the empty string.
|Field name|Type|Examples|Notes|
|-|-|-|-|
Expand All @@ -96,6 +96,7 @@ Metadata values that are missing or empty will be rendered as the empty string.
|`Conductor`|string|||
|`Director`|string||Most commonly used for video files|
|`Disc`|int|`1`|If it can't be parsed as an int (like `1/2`) it will be a string|
|`Elapsed`|TimeSpan|`00:00:28.5080000`|Updated 1hz, millisecond resolution. See [formatting](#formatting) for `m:ss` and other formats.|
|`Family`|string|`MPEG Layer 3 Audio File`|Codec or container format|
|`FileBasename`|string|`Exit.mp3`|Filename without path|
|`FileBasenameWithoutExtension`|string|`Exit`|Filename without path or extension|
Expand Down Expand Up @@ -124,11 +125,11 @@ Metadata values that are missing or empty will be rendered as the empty string.
|`VBR`|bool|`false`|`true` for variable bitrate, `false` for constant bitrate|
|`Year`|int|`1987`|If it can't be parsed as an int (like `1987-01-01`) it will be a string|
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.
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.
#### Helpers
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}}`.
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}}`.
For example, you can conditionally include artist and album only if those fields exist on your song and are nonempty.
```handlebars
Expand Down
27 changes: 23 additions & 4 deletions Test/Business/NowPlayingToFileManagerTest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using Daniel15.Sharpamp;
using FakeItEasy;
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Daniel15.Sharpamp;
using FakeItEasy;
using FluentAssertions;
using System.Threading;
using WinampNowPlayingToFile.Business;
using WinampNowPlayingToFile.Facade;
using WinampNowPlayingToFile.Settings;
Expand Down Expand Up @@ -51,6 +52,7 @@ private static void onManagerError(object _, NowPlayingException exception) {

public void Dispose() {
cleanUp();
manager.renderTextTimer.Stop();
}

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

[Fact]
public void elapsedTriggersPeriodicRenders() {
CountdownEvent latch = new(5);

manager.renderTextTimer.Interval = 100;
A.CallTo(() => winampController.fetchMetadataFieldValue("Elapsed")).ReturnsLazily(() => {
latch.Signal();
return TimeSpan.FromSeconds(1);
});

A.CallTo(() => settings.textTemplate).Returns("{{Elapsed:m\\:ss}}");
settings.settingsUpdated += Raise.WithEmpty();

latch.Wait(10_000);
A.CallTo(() => winampController.fetchMetadataFieldValue("Elapsed")).MustHaveHappenedANumberOfTimesMatching(i => i >= latch.InitialCount);
}

}
8 changes: 4 additions & 4 deletions Test/Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

<ItemGroup>
<PackageReference Include="FakeItEasy" Version="7.4.0" />
<PackageReference Include="FluentAssertions" Version="6.11.0" />
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="mwinapi" Version="0.3.0.5" />
<PackageReference Include="xunit" Version="2.5.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
58 changes: 47 additions & 11 deletions WinampNowPlayingToFile/Business/NowPlayingToFileManager.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#nullable enable

using Daniel15.Sharpamp;
using Mustache;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Daniel15.Sharpamp;
using Mustache;
using System.Timers;
using WinampNowPlayingToFile.Facade;
using WinampNowPlayingToFile.Settings;
using Song = WinampNowPlayingToFile.Facade.Song;
Expand All @@ -23,40 +24,69 @@ public interface INowPlayingToFileManager {

public class NowPlayingToFileManager: INowPlayingToFileManager {

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

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

private readonly WinampController winampController;
private readonly ISettings settings;
private readonly WinampController winampController;
private readonly ISettings settings;
private readonly FormatCompiler templateCompiler = new();
internal readonly Timer renderTextTimer = new(1000);

private Generator? cachedTemplate;
private bool _textTemplateDependsOnTime;

private bool textTemplateDependsOnTime {
get => _textTemplateDependsOnTime;
set {
if (_textTemplateDependsOnTime != value) {
_textTemplateDependsOnTime = value;
startOrStopTextRenderingTimer();
}
}
}

public event EventHandler<NowPlayingException>? error;

public NowPlayingToFileManager(ISettings settings, WinampController winampController) {
this.winampController = winampController;
this.settings = settings;

this.winampController.songChanged += delegate { update(); };
this.winampController.statusChanged += delegate { update(); };
this.winampController.songChanged += delegate { update(); };

this.winampController.statusChanged += (_, args) => {
update();
startOrStopTextRenderingTimer(args.Status);
};

this.settings.settingsUpdated += delegate {
cachedTemplate = null;
cachedTemplate = null;
textTemplateDependsOnTime = false;
update();
};

templateCompiler.PlaceholderFound += (_, args) => {
if (args.Key.Equals("Elapsed", StringComparison.CurrentCultureIgnoreCase)) {
textTemplateDependsOnTime = true;
}
};

renderTextTimer.Elapsed += (_, _) => { update(false); };

update();
}

internal void update() {
internal void update(bool updateAlbumArt = true) {
try {
if (winampController.currentSong is { Filename: not "" } currentSong) {
saveText(renderText(currentSong));
saveImage(findAlbumArt(currentSong));

if (updateAlbumArt) {
saveImage(findAlbumArt(currentSong));
}
}
} catch (Exception e) when (e is not OutOfMemoryException) {
error?.Invoke(this, new NowPlayingException("Exception while updating song", e, winampController.currentSong));
Expand All @@ -73,7 +103,7 @@ private void saveText(string nowPlayingText) {

private Generator getTemplate() {
if (cachedTemplate == null) {
cachedTemplate = TEMPLATE_COMPILER.Compile(settings.textTemplate);
cachedTemplate = templateCompiler.Compile(settings.textTemplate);
cachedTemplate.KeyNotFound += fetchExtraMetadata;
}

Expand Down Expand Up @@ -174,7 +204,13 @@ private void saveImage(byte[]? imageData) {
}
}

private void startOrStopTextRenderingTimer(Status? playbackStatus = null) {
playbackStatus ??= winampController.status;
renderTextTimer.Enabled = playbackStatus == Status.Playing && textTemplateDependsOnTime;
}

public virtual void onQuit() {
renderTextTimer.Stop();
saveText(string.Empty);
saveImage(albumArtWhenStopped);
}
Expand Down
24 changes: 18 additions & 6 deletions WinampNowPlayingToFile/Facade/WinampControllerImpl.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#nullable enable

using Daniel15.Sharpamp;
using System;
using System.IO;
using System.Reflection;
using Daniel15.Sharpamp;

namespace WinampNowPlayingToFile.Facade;

Expand All @@ -26,7 +26,10 @@ public interface WinampController {

public class WinampControllerImpl: WinampController {

private readonly Winamp winamp;
private readonly Winamp winamp;

// ReSharper disable once InconsistentNaming - this is how the method is named in Sharpamp
private readonly Func<int, int> sendIPCCommandInt;
private readonly Func<string, string, string> getMetadata;

public event SongChangedEventHandler? songChanged;
Expand All @@ -40,6 +43,12 @@ public WinampControllerImpl(Winamp winamp) {
getMetadata = (Func<string, string, string>) winamp.GetType()
.GetMethod("GetMetadata", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(string), typeof(string) }, null)!
.CreateDelegate(typeof(Func<string, string, string>), winamp);

Type ipcCommand = winamp.GetType().GetNestedType("IPCCommand", BindingFlags.NonPublic);

sendIPCCommandInt = (Func<int, int>) winamp.GetType()
.GetMethod("SendIPCCommandInt", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { ipcCommand }, null)!
.CreateDelegate(typeof(Func<int, int>), winamp);
}

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

try {
if (metadataFieldName == "filebasename") {
return Path.GetFileName(songFilename);
} else if (metadataFieldName == "filebasenamewithoutextension") {
return Path.GetFileNameWithoutExtension(songFilename);
switch (metadataFieldName) {
case "filebasename":
return Path.GetFileName(songFilename);
case "filebasenamewithoutextension":
return Path.GetFileNameWithoutExtension(songFilename);
case "elapsed":
return TimeSpan.FromMilliseconds(sendIPCCommandInt(105));
}
} catch (ArgumentException) {
return string.Empty;
Expand Down
2 changes: 1 addition & 1 deletion WinampNowPlayingToFile/NowPlayingToFilePlugin.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#nullable enable

using Daniel15.Sharpamp;
using System;
using System.Reflection;
using System.Windows.Forms;
using Daniel15.Sharpamp;
using WinampNowPlayingToFile.Business;
using WinampNowPlayingToFile.Facade;
using WinampNowPlayingToFile.Presentation;
Expand Down
Loading

0 comments on commit 9434ecd

Please sign in to comment.