Skip to content
Merged
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
3 changes: 2 additions & 1 deletion AudioCuesheetEditor/AudioCuesheetEditor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@

<ItemGroup>
<PackageReference Include="Blazorise.Bootstrap5" Version="1.1.1" />
<PackageReference Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.9" PrivateAssets="all" />
<PackageReference Include="BlazorDownloadFile" Version="2.4.0.2" />
<PackageReference Include="BlazorDownloadFile" Version="2.4.0.2" />
<PackageReference Include="Blazorise.Components" Version="1.1.1" />
<PackageReference Include="Howler.Blazor" Version="0.9.8" />
<PackageReference Include="Markdig" Version="0.30.4" />
Expand Down
143 changes: 143 additions & 0 deletions AudioCuesheetEditor/Data/Services/MusicBrainzDataProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//This file is part of AudioCuesheetEditor.

//AudioCuesheetEditor is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//AudioCuesheetEditor is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with Foobar. If not, see
//<http: //www.gnu.org/licenses />.
using MetaBrainz.MusicBrainz;
using MetaBrainz.MusicBrainz.Interfaces.Entities;
using MetaBrainz.MusicBrainz.Interfaces.Searches;
using Microsoft.JSInterop;
using System.Collections.Immutable;
using System.Reflection;

namespace AudioCuesheetEditor.Data.Services
{
public class MusicBrainzArtist
{
public Guid Id { get; init; }
public String? Name { get; init; }
public String? Disambiguation { get; init; }
}
public class MusicBrainzTrack
{
public Guid Id { get; init; }
public String? Artist { get; init; }
public String? Title { get; init; }
public TimeSpan? Length { get; init; }
public String? Disambiguation { get; init; }
}
public class MusicBrainzDataProvider
{
public const String Application = "AudioCuesheetEditor";
public const String ProjectUrl = "https://github.com/NeoCoderMatrix86/AudioCuesheetEditor";

private String? applicationVersion = null;

public async Task<IReadOnlyCollection<MusicBrainzArtist>> SearchArtistAsync(String searchString)
{
List<MusicBrainzArtist> artistSearchResult = new();
if (String.IsNullOrEmpty(searchString) == false)
{
using var query = new Query(Application, ApplicationVersion, ProjectUrl);
var findArtistsResult = await query.FindArtistsAsync(searchString, simple: true);
artistSearchResult = findArtistsResult.Results.ToList().ConvertAll(x => new MusicBrainzArtist() { Id = x.Item.Id, Name = x.Item.Name, Disambiguation = x.Item.Disambiguation });
}
return artistSearchResult.AsReadOnly();
}

public async Task<IReadOnlyCollection<MusicBrainzTrack>> SearchTitleAsync(String searchString, String? artist = null)
{
List<MusicBrainzTrack> titleSearchResult = new();
if (String.IsNullOrEmpty(searchString) == false)
{
using var query = new Query(Application, ApplicationVersion, ProjectUrl);
ISearchResults<ISearchResult<IRecording>> findRecordingsResult;
if (String.IsNullOrEmpty(artist))
{
findRecordingsResult = await query.FindRecordingsAsync(searchString, simple: true);
}
else
{
findRecordingsResult = await query.FindRecordingsAsync(String.Format("{0} AND artistname:{1}", searchString, artist));
}
foreach (var result in findRecordingsResult.Results)
{
String artistString = String.Empty;
if (result.Item.ArtistCredit != null)
{
foreach (var artistCredit in result.Item.ArtistCredit)
{
artistString += artistCredit.Name;
if (String.IsNullOrEmpty(artistCredit.JoinPhrase) == false)
{
artistString += artistCredit.JoinPhrase;
}
}
}
titleSearchResult.Add(new MusicBrainzTrack()
{
Id = result.Item.Id,
Artist = artistString,
Title = result.Item.Title,
Length = result.Item.Length,
Disambiguation = result.Item.Disambiguation
});
}
}
return titleSearchResult.AsReadOnly();
}

public async Task<MusicBrainzTrack?> GetDetailsAsync(Guid id)
{
MusicBrainzTrack? track = null;
if (id != Guid.Empty)
{
var query = new Query(Application, ApplicationVersion, ProjectUrl);
var recording = await query.LookupRecordingAsync(id, Include.Artists);
if (recording != null)
{
String artist = String.Empty;
if (recording.ArtistCredit != null)
{
foreach (var artistCredit in recording.ArtistCredit)
{
artist += artistCredit.Name;
if (String.IsNullOrEmpty(artistCredit.JoinPhrase) == false)
{
artist += artistCredit.JoinPhrase;
}
}
}
track = new MusicBrainzTrack() { Id = recording.Id, Title = recording.Title, Artist = artist, Length = recording.Length };
}
}
return track;
}

private String? ApplicationVersion
{
get
{
if (applicationVersion == null)
{
var versionAttribute = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>();
if (versionAttribute != null)
{
applicationVersion = versionAttribute.InformationalVersion;
}
}
return applicationVersion;
}
}
}
}
114 changes: 100 additions & 14 deletions AudioCuesheetEditor/Pages/ViewModeRecord.razor
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ along with Foobar. If not, see
@inject LocalStorageOptionsProvider _localStorageOptionsProvider
@inject ITextLocalizerService _localizationService
@inject SessionStateContainer _sessionStateContainer
@inject MusicBrainzDataProvider _musicBrainzDataProvider

<Column>
<Accordion>
Expand Down Expand Up @@ -102,20 +103,42 @@ along with Foobar. If not, see
<Field Horizontal="true">
<FieldLabel ColumnSize="ColumnSize.Is2.OnFullHD.Is4.OnDesktop.Is5.OnTablet.Is6.OnMobile">@_localizer["Artist"]</FieldLabel>
<FieldBody ColumnSize="ColumnSize.Is10.OnFullHD.Is8.OnDesktop.Is7.OnTablet.Is12.OnMobile">
<TextEdit @ref="enterCurrentTrackArtist" @bind-Text="currentRecordingTrack.Artist" Disabled="!_sessionStateContainer.Cuesheet.IsRecording" KeyDown="OnKeyDownRecordArtist" DelayTextOnKeyPressInterval="50"></TextEdit>
<Autocomplete @ref="autocompleteArtist" TItem="MusicBrainzArtist" TValue="Guid" Data="autocompleteArtists" FreeTyping TextField="@((item) => item.Name)" ValueField="@((item) => item.Id)" @bind-SelectedText="currentRecordingTrack.Artist" Disabled="!_sessionStateContainer.Cuesheet.IsRecording" ReadData="OnReadDataAutocompleteArtist" @onkeydown="OnKeyDownRecordArtist">
<ItemContent>
@if (context.Item.Disambiguation != null)
{
<Paragraph>@String.Format("{0} ({1})", context.Text, context.Item.Disambiguation)</Paragraph>
}
else
{
<Paragraph>@context.Text</Paragraph>
}
</ItemContent>
</Autocomplete>
</FieldBody>
</Field>
</Column>
<Column ColumnSize="ColumnSize.Is5.OnDesktop.Is12.OnTablet.Is12.OnMobile">
<Field Horizontal="true">
<FieldLabel ColumnSize="ColumnSize.Is2.OnFullHD.Is4.OnDesktop.Is5.OnTablet.Is6.OnMobile">@_localizer["Title"]</FieldLabel>
<FieldBody ColumnSize="ColumnSize.Is10.OnFullHD.Is8.OnDesktop.Is7.OnTablet.Is12.OnMobile">
<TextEdit @ref="enterCurrentTrackTitle" @bind-Text="currentRecordingTrack.Title" Disabled="!_sessionStateContainer.Cuesheet.IsRecording" KeyDown="OnKeyDownRecordTitle" DelayTextOnKeyPressInterval="50"></TextEdit>
<Autocomplete @ref="autocompleteTitle" TItem="MusicBrainzTrack" TValue="Guid" Data="autocompleteTitles" FreeTyping TextField="@((item) => item.Title)" ValueField="@((item) => item.Id)" @bind-SelectedText="currentRecordingTrack.Title" Disabled="!_sessionStateContainer.Cuesheet.IsRecording" ReadData="OnReadDataAutocompleteTitle" SelectedValueChanged="OnSelectedValueChangedTrackTitle" @onkeydown="OnKeyDownRecordTitle">
<ItemContent>
@if (context.Item.Disambiguation != null)
{
<Paragraph>@String.Format("{0} ({1})", context.Text, context.Item.Disambiguation)</Paragraph>
}
else
{
<Paragraph>@context.Text</Paragraph>
}
</ItemContent>
</Autocomplete>
</FieldBody>
</Field>
</Column>
<Column>
<Button Color="Color.Primary" Clicked="AddTrackRecordingClicked" Disabled="!_sessionStateContainer.Cuesheet.IsRecording">@_localizer["Add new track"]</Button>
<Button Color="Color.Primary" Clicked="AddTrackRecordingClicked" Disabled="!_sessionStateContainer.Cuesheet.IsRecording" @onkeydown="OnKeyDownAddRecording">@_localizer["Add new track"]</Button>
</Column>
</Row>
</CollapseBody>
Expand Down Expand Up @@ -177,11 +200,11 @@ along with Foobar. If not, see

DateTime recordTimerStarted;

TextEdit enterCurrentTrackTitle = default!;
TextEdit enterCurrentTrackArtist = default!;

ModalDialog modalDialog = default!;

Autocomplete<MusicBrainzArtist, Guid>? autocompleteArtist;
Autocomplete<MusicBrainzTrack, Guid>? autocompleteTitle;

Track currentRecordingTrack = new Track();

Boolean cuesheetDataVisible = false;
Expand All @@ -190,6 +213,9 @@ along with Foobar. If not, see
Boolean cuesheetTracksVisible = true;
Boolean processingHintsVisible = false;

IEnumerable<MusicBrainzArtist>? autocompleteArtists;
IEnumerable<MusicBrainzTrack>? autocompleteTitles;

protected override async Task OnInitializedAsync()
{
var dotNetReference = DotNetObjectReference.Create(this);
Expand Down Expand Up @@ -244,7 +270,7 @@ along with Foobar. If not, see
return resultString;
}

private async Task StartRecordingClicked()
async Task StartRecordingClicked()
{
//Check for empty cuesheet and warn!
if (_sessionStateContainer.Cuesheet.Tracks.Count > 0)
Expand All @@ -265,7 +291,10 @@ along with Foobar. If not, see
await _jsRuntime.InvokeVoidAsync("URL.revokeObjectURL", _sessionStateContainer.Cuesheet.Audiofile.ObjectURL);
}
_sessionStateContainer.Cuesheet.Audiofile = null;
await enterCurrentTrackArtist.Focus();
if (autocompleteArtist != null)
{
await autocompleteArtist.Focus();
}
}
}

Expand All @@ -277,17 +306,20 @@ along with Foobar. If not, see
//Open processing hints
processingHintsVisible = true;
}
private void OnKeyDownRecordArtist(KeyboardEventArgs args)

async Task OnKeyDownRecordArtist(KeyboardEventArgs args)
{
_logger.LogDebug("args = {0}", args);
if ((args.Key == "Enter") && (args.CtrlKey == false) && (args.AltKey == false) && (args.MetaKey == false) && (args.Repeat == false) && (args.ShiftKey == false))
{
enterCurrentTrackTitle.Focus();
if (autocompleteTitle != null)
{
await autocompleteTitle.Focus();
}
}
}

private async Task OnKeyDownRecordTitle(KeyboardEventArgs args)
async Task OnKeyDownRecordTitle(KeyboardEventArgs args)
{
_logger.LogDebug("args = {0}", args);
if ((args.Key == "Enter") && (args.CtrlKey == false) && (args.AltKey == false) && (args.MetaKey == false) && (args.Repeat == false) && (args.ShiftKey == false))
Expand All @@ -296,14 +328,32 @@ along with Foobar. If not, see
}
}

private async Task AddTrackRecordingClicked()
async Task OnKeyDownAddRecording(KeyboardEventArgs args)
{
_logger.LogDebug("args = {0}", args);
if ((args.Key == "Enter") && (args.CtrlKey == false) && (args.AltKey == false) && (args.MetaKey == false) && (args.Repeat == false) && (args.ShiftKey == false))
{
await AddTrackRecordingClicked();
}
}

async Task AddTrackRecordingClicked()
{
if (_sessionStateContainer.Cuesheet.IsRecording == true)
{
var options = await _localStorageOptionsProvider.GetOptions<ApplicationOptions>();
await enterCurrentTrackArtist.Focus();
_sessionStateContainer.Cuesheet.AddTrack(currentRecordingTrack, options);
currentRecordingTrack = new Track();
if (autocompleteTitle != null)
{
await autocompleteTitle.Clear();
await autocompleteTitle.Focus();
}
if (autocompleteArtist != null)
{
await autocompleteArtist.Clear();
await autocompleteArtist.Focus();
}
}
}

Expand All @@ -326,4 +376,40 @@ along with Foobar. If not, see
{
StateHasChanged();
}

async Task OnReadDataAutocompleteArtist(AutocompleteReadDataEventArgs autocompleteReadDataEventArgs)
{
if (!autocompleteReadDataEventArgs.CancellationToken.IsCancellationRequested)
{
var artists = await _musicBrainzDataProvider.SearchArtistAsync(autocompleteReadDataEventArgs.SearchValue);
if (!autocompleteReadDataEventArgs.CancellationToken.IsCancellationRequested)
{
autocompleteArtists = artists;
}
}
}

async Task OnReadDataAutocompleteTitle(AutocompleteReadDataEventArgs autocompleteReadDataEventArgs)
{
if (!autocompleteReadDataEventArgs.CancellationToken.IsCancellationRequested)
{
var titles = await _musicBrainzDataProvider.SearchTitleAsync(autocompleteReadDataEventArgs.SearchValue, currentRecordingTrack.Artist);
if (!autocompleteReadDataEventArgs.CancellationToken.IsCancellationRequested)
{
autocompleteTitles = titles;
}
}
}

async Task OnSelectedValueChangedTrackTitle(Guid selectedValue)
{
if (String.IsNullOrEmpty(currentRecordingTrack.Artist))
{
var trackDetails = await _musicBrainzDataProvider.GetDetailsAsync(selectedValue);
if (trackDetails != null)
{
currentRecordingTrack.Artist = trackDetails.Artist;
}
}
}
}
2 changes: 2 additions & 0 deletions AudioCuesheetEditor/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using AudioCuesheetEditor;
using AudioCuesheetEditor.Controller;
using AudioCuesheetEditor.Data.Options;
using AudioCuesheetEditor.Data.Services;
using AudioCuesheetEditor.Extensions;
using AudioCuesheetEditor.Model.UI;
using AudioCuesheetEditor.Model.Utility;
Expand Down Expand Up @@ -32,6 +33,7 @@

builder.Services.AddScoped<CuesheetController>();
builder.Services.AddScoped<LocalStorageOptionsProvider>();
builder.Services.AddScoped<MusicBrainzDataProvider>();

builder.Services.AddSingleton<SessionStateContainer>();
builder.Services.AddSingleton<TraceChangeManager>();
Expand Down
Loading