Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .env.example

This file was deleted.

40 changes: 0 additions & 40 deletions DiscordPresence.cs

This file was deleted.

50 changes: 0 additions & 50 deletions Meio.Api/Api.cs

This file was deleted.

9 changes: 9 additions & 0 deletions Meio.Api/Interfaces/Services/IAudioMetadataService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;

namespace Meio.Api.Interfaces.Services;

public interface IAudioMetadataService
{
public Task<string?> GetReleasePictureAsync(string releaseId, CancellationToken cancellationToken = default);
}
23 changes: 23 additions & 0 deletions Meio.Api/Interfaces/Services/IAudioPlayerService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;
using LibVLCSharp.Shared;

namespace Meio.Api.Interfaces.Services;

public interface IAudioPlayerService
{
public Media? Play(string audioFilePath);

public Media? Play(Uri audioUri);

Check notice on line 10 in Meio.Api/Interfaces/Services/IAudioPlayerService.cs

View workflow job for this annotation

GitHub Actions / qodana

Type member is never used: Non-private accessibility

Method 'Play' is never used

public void Stop();

public void Pause();

Check notice on line 14 in Meio.Api/Interfaces/Services/IAudioPlayerService.cs

View workflow job for this annotation

GitHub Actions / qodana

Type member is never used: Non-private accessibility

Method 'Pause' is never used

public void ChangeVolume(int newVolume);

public void ChangePlaybackSpeed(float speed);

Check notice on line 18 in Meio.Api/Interfaces/Services/IAudioPlayerService.cs

View workflow job for this annotation

GitHub Actions / qodana

Type member is never used: Non-private accessibility

Method 'ChangePlaybackSpeed' is never used

public void Mute();

Check notice on line 20 in Meio.Api/Interfaces/Services/IAudioPlayerService.cs

View workflow job for this annotation

GitHub Actions / qodana

Type member is never used: Non-private accessibility

Method 'Mute' is never used

public void Unmute();

Check notice on line 22 in Meio.Api/Interfaces/Services/IAudioPlayerService.cs

View workflow job for this annotation

GitHub Actions / qodana

Type member is never used: Non-private accessibility

Method 'Unmute' is never used
}
7 changes: 6 additions & 1 deletion Meio.Api/Meio.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetEnv" Version="3.1.1"/>
<PackageReference Include="LibVLCSharp" Version="3.9.4"/>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="11.0.0-preview.6.26359.118"/>
<PackageReference Include="Microsoft.Extensions.Http" Version="11.0.0-preview.6.26359.118"/>
<PackageReference Include="PrettyLogging.Console" Version="1.0.3"/>
<PackageReference Include="TagLibSharp" Version="2.3.0"/>
<PackageReference Include="VideoLAN.LibVLC.Mac" Version="3.1.3.1"/>
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.21"/>
</ItemGroup>

<ItemGroup>
<Folder Include="Samples\"/>
</ItemGroup>

</Project>
47 changes: 47 additions & 0 deletions Meio.Api/MeioLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using System;
using Microsoft.Extensions.Logging;

namespace Meio.Api;

public interface IMeioLogger
{
public void LogDebug(string message, params object[] args);

public void LogInformation(string message, params object[] args);

public void LogWarning(string message, params object[] args);

public void LogError(string message, params object[] args);

public void LogError(Exception ex, string message, params object[] args);

Check notice on line 16 in Meio.Api/MeioLogger.cs

View workflow job for this annotation

GitHub Actions / qodana

Type member is never used: Non-private accessibility

Method 'LogError' is never used
}

internal sealed class MeioLogger(ILoggerFactory loggerFactory) : IMeioLogger
{
private readonly ILogger _logger = loggerFactory.CreateLogger("Meio.Api");

public void LogDebug(string message, params object[] args)
{
_logger.LogDebug(message, args);
}

public void LogInformation(string message, params object[] args)
{
_logger.LogInformation(message, args);
}

public void LogWarning(string message, params object[] args)
{
_logger.LogWarning(message, args);
}

public void LogError(string message, params object[] args)
{
_logger.LogError(message, args);
}

public void LogError(Exception ex, string message, params object[] args)
{
_logger.LogError(ex, message, args);
}
}
56 changes: 56 additions & 0 deletions Meio.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using Meio.Api;
using Meio.Api.Interfaces.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using PrettyLogging.Console;

/* This demonstrates how to set up and use the Meio.Api with
* - Dependency Injection
* - Logging with PrettyConsole
* - HTTP client configuration
* - AudioPlayerService example usage
*
* We build a generic host, that will handle the container for dependencies,
* manage the application's lifetime and provide a basic logging infrastructure.
*/
var host = Host.CreateDefaultBuilder(args).ConfigureServices((_, services) =>
{
// Configure logging via PrettyConsole
services.AddLogging(builder =>
{
builder.ClearProviders();
builder.SetMinimumLevel(LogLevel.Debug);
builder.AddPrettyConsole(opt =>
{
opt.ShowLogLevel = true;
opt.SingleLine = true;
opt.IncludeScopes = true;
opt.LogLevelCase = LogLevelCase.Upper;
});
});

// Configure HTTP client for external API calls (MusicBrainz, etc.).
services.AddHttpClient("Meio",
client =>
{
client.DefaultRequestHeaders.Add("User-Agent", "Meio/1.0");
client.Timeout = TimeSpan.FromSeconds(30);
});

services.AddMeioApi(); // Services are registered here.
}).Build();

host.Start(); // Start the host to initialize registered services.

// Resolve the audio player service.
var audioPlayerService = host.Services.GetRequiredService<IAudioPlayerService>();
// var metadataService = host.Services.GetRequiredService<IAudioMetadataService>(); // Uncomment when needed

Console.WriteLine("Playing example audio");
audioPlayerService.Play("/example/audio/path"); // TODO: Add audio samples

// Waiting until graceful shutdown (CTRL+C)
Console.WriteLine("Press CTRL+C to exit...");
host.WaitForShutdown();
16 changes: 16 additions & 0 deletions Meio.Api/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Meio.Api.Interfaces.Services;
using Meio.Api.Services;
using Microsoft.Extensions.DependencyInjection;

namespace Meio.Api;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMeioApi(this IServiceCollection services)
{
services.AddSingleton<IMeioLogger, MeioLogger>();
services.AddSingleton<IAudioPlayerService, AudioPlayerService>();
services.AddSingleton<IAudioMetadataService, AudioMetadataService>();
return services;
}
}
79 changes: 23 additions & 56 deletions Meio.Api/Services/AudioMetadataService.cs
Original file line number Diff line number Diff line change
@@ -1,71 +1,38 @@
using System.IO;
using Microsoft.Extensions.Logging;
using TagLib;
using File = TagLib.File;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Meio.Api.Interfaces.Services;

namespace Meio.Api.Services;

public class MetadataInfo
internal sealed class AudioMetadataService(IMeioLogger logger, IHttpClientFactory httpClientFactory) : IAudioMetadataService
{
public string? Title { get; init; }
// https://musicbrainz.org/ws/2/
// https://coverartarchive.org/release/(id)

public string[]? Artists { get; init; }
private const string CoverBaseUrl = "https://coverartarchive.org/release/";

public string? Album { get; init; }

public uint Year { get; init; }
public async Task<string?> GetReleasePictureAsync(string releaseId, CancellationToken cancellationToken = default)
{
var releaseCoverUrl = $"{CoverBaseUrl}{releaseId}";

public string[]? Genres { get; init; }
var client = httpClientFactory.CreateClient("Meio");

public byte[]? AlbumArt { get; init; }
}
var response = await client.GetAsync(releaseCoverUrl, cancellationToken);

public static class AudioMetadataService
{
/// <summary>
/// Loads the metadata info of a given audio file.
/// </summary>
/// <param name="filePath">Path of the audio file to get the metadata from.</param>
/// <returns>The metadata info of the audio file.</returns>
public static MetadataInfo? LoadMetadata(string filePath)
{
try
if (response.IsSuccessStatusCode)
{
var file = File.Create(filePath);
var tag = file.Tag;
var jsonString = await response.Content.ReadAsStringAsync(cancellationToken);
var deserializedResult = JsonSerializer.Deserialize<CoverArtArchiveResponse>(jsonString);

Api.Logger?.LogDebug("Current metadata of {filePath}.", filePath);
Api.Logger?.LogTrace("Title: {tagTitle}", tag.Title);
Api.Logger?.LogTrace("Artists: {tagArtists}", string.Join(", ", tag.AlbumArtists));
Api.Logger?.LogTrace("Album: {tagAlbum}", tag.Album);
Api.Logger?.LogTrace("Year: {tagYear}", tag.Year);
Api.Logger?.LogTrace("Genre: {tagGenres}", string.Join(", ", tag.Genres));
Api.Logger?.LogTrace(tag.Pictures.Length > 0 ? "Got an album art." : "No album art was found.");

return new MetadataInfo
{
Title = tag.Title,
Artists = tag.AlbumArtists,
Album = tag.Album,
Genres = tag.Genres,
Year = tag.Year,
AlbumArt = tag.Pictures.Length > 0 ? tag.Pictures[0].Data.Data : null
};
}
catch (CorruptFileException corruptFileException)
{
Api.Logger?.LogError(corruptFileException, "Failed to load metadata. File is corrupted.");
return null;
}
catch (UnsupportedFormatException unsupportedFormatException)
{
Api.Logger?.LogError(unsupportedFormatException, "Failed to load metadata. File format is unsupported.");
return null;
}
catch (FileNotFoundException fileNotFoundException)
{
Api.Logger?.LogError(fileNotFoundException, "Failed to load metadata. File not found.");
return null;
logger.LogInformation("Retrieved cover art picture for release {releaseId}.", releaseId);
return deserializedResult!.Images.First().ImageUrl;
}

logger.LogWarning("Could not retrieve cover art picture for release {releaseId}: {error}", releaseId, response.StatusCode);
return null;
}
}
Loading
Loading