diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj
index 9c753d66..80f095af 100644
--- a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj
+++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj
@@ -16,8 +16,8 @@
-
-
+
+
diff --git a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs
index 4c9b9466..ff94e63e 100644
--- a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs
+++ b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs
@@ -23,8 +23,6 @@ internal class DetailView(IPage page)
private readonly IPage _page = page;
- internal ILocator AudiofileInput => _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Locator("input[type=\"file\"]");
-
internal ILocator CuesheetArtistInput => _page.GetByRole(AriaRole.Textbox, new() { Name = "Cuesheet artist" });
internal ILocator CuesheetTitleInput => _page.GetByRole(AriaRole.Textbox, new() { Name = "Cuesheet title" });
@@ -39,9 +37,19 @@ internal async Task GotoAsync()
await _page.WaitForFunctionAsync(@"() => window.Blazor !== undefined");
}
- internal async Task AddTrackAsync()
+ internal async Task AddAudiofileAsync()
+ {
+ await _page.GetByRole(AriaRole.Button, new() { Name = "Add file" }).ClickAsync();
+ }
+
+ internal async Task SetAudiofileInputFileAsync(int audiofileIndex, string file)
+ {
+ await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Nth(audiofileIndex).Locator("input[type=\"file\"]").SetInputFilesAsync(file);
+ }
+
+ internal async Task AddTrackAsync(int audiofileIndex)
{
- await _page.GetByRole(AriaRole.Button, new() { Name = "Add new track" }).ClickAsync();
+ await _page.GetByRole(AriaRole.Button, new() { Name = "Add new track" }).Nth(audiofileIndex).ClickAsync();
}
internal async Task EditTrackAsync(string? artist = null, string? title = null)
@@ -50,9 +58,6 @@ internal async Task EditTrackAsync(string? artist = null, string? title = null)
{
await _page.Locator("td:nth-child(3)").ClickAsync();
await _page.Locator("td:nth-child(3)").Last.GetByRole(AriaRole.Textbox).FillAsync(artist);
- // Autocomplete overlay will pop up, so we close it
- await _page.Locator(".mud-popover-open").WaitForAsync(new() { State = WaitForSelectorState.Visible });
- await _page.Keyboard.PressAsync("Escape");
// Click outside the autocomplete to have an focus lost event for getting the value written to model
await _page.GetByRole(AriaRole.Heading, new() { Name = "Playback" }).ClickAsync(new() { Force = true });
await _page.WaitForTimeoutAsync(100);
@@ -61,9 +66,6 @@ internal async Task EditTrackAsync(string? artist = null, string? title = null)
{
await _page.Locator("td:nth-child(4)").ClickAsync();
await _page.Locator("td:nth-child(4)").Last.GetByRole(AriaRole.Textbox).FillAsync(title);
- // Autocomplete overlay will pop up, so we close it
- await _page.Locator(".mud-popover-open").WaitForAsync(new() { State = WaitForSelectorState.Visible });
- await _page.Keyboard.PressAsync("Escape");
// Click outside the autocomplete to have an focus lost event for getting the value written to model
await _page.GetByRole(AriaRole.Heading, new() { Name = "Playback" }).ClickAsync(new() { Force = true });
await _page.WaitForTimeoutAsync(100);
@@ -105,16 +107,16 @@ internal async Task EditTracksModalAsync(string artist, string title, string end
await _page.GetByRole(AriaRole.Button, new() { Name = "Save changes" }).ClickAsync();
}
- internal async Task RenameAudiofileAsync(string filename)
+ internal async Task RenameAudiofileAsync(int audiofileIndex, string filename)
{
- await OpenRenameAudiofileDialogAsync();
+ await OpenRenameAudiofileDialogAsync(audiofileIndex);
await NewFileNameInput.FillAsync(filename);
await _page.GetByRole(AriaRole.Button, new() { Name = "Ok" }).ClickAsync();
}
- internal async Task OpenRenameAudiofileDialogAsync()
+ internal async Task OpenRenameAudiofileDialogAsync(int audiofileIndex)
{
- await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).GetByLabel("More").ClickAsync();
+ await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Nth(audiofileIndex).GetByLabel("More").ClickAsync();
await _page.GetByText("Rename file").ClickAsync();
}
}
diff --git a/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs b/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs
index be0c6185..90b7286b 100644
--- a/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs
+++ b/AudioCuesheetEditor.End2EndTests/Models/ImportView.cs
@@ -23,6 +23,9 @@ partial class ImportView(IPage page, bool mobile)
[GeneratedRegex("^Scheme common data$")]
private static partial Regex SchemeCommonData();
+ [GeneratedRegex("^Scheme audiofiles$")]
+ private static partial Regex SchemeAudiofiles();
+
internal const string BaseUrl = "http://localhost:5132/";
private readonly IPage _page = page;
@@ -105,6 +108,11 @@ internal async Task ClearSchemeCommonDataAsync()
await _page.Locator("div").Filter(new() { HasTextRegex = SchemeCommonData() }).GetByLabel("Clear").ClickAsync();
}
+ internal async Task ClearSchemeAudiofilesAsync()
+ {
+ await _page.Locator("div").Filter(new() { HasTextRegex = SchemeAudiofiles() }).GetByLabel("Clear").ClickAsync();
+ }
+
internal async Task SetSchemeCommonDataAsync(string schemeCommonData)
{
await _page.GetByRole(AriaRole.Textbox, new() { Name = "Scheme common data" }).FillAsync(schemeCommonData);
diff --git a/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt b/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt
index 5f80c195..9d06c8b8 100644
--- a/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt
+++ b/AudioCuesheetEditor.End2EndTests/Sample_Inputfile.txt
@@ -1,4 +1,5 @@
-CuesheetArtist - CuesheetTitle c:\AudioFile.mp3
+CuesheetArtist - CuesheetTitle
+- c:\AudioFile.mp3
Sample Artist 1 - Sample Title 1 00:05:00
Sample Artist 2 - Sample Title 2 00:09:23
Sample Artist 3 - Sample Title 3 00:15:54
diff --git a/AudioCuesheetEditor.End2EndTests/Sample_Project.ace b/AudioCuesheetEditor.End2EndTests/Sample_Project.ace
index cf2c12df..d192e583 100644
--- a/AudioCuesheetEditor.End2EndTests/Sample_Project.ace
+++ b/AudioCuesheetEditor.End2EndTests/Sample_Project.ace
@@ -1 +1 @@
-{"Tracks":[{"Position":1,"Artist":"Sample Artist 1","Title":"Sample Title 1","Begin":"00:00:00","End":"00:05:00","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":2,"Artist":"Sample Artist 2","Title":"Sample Title 2","Begin":"00:05:00","End":"00:09:23","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":3,"Artist":"Sample Artist 3","Title":"Sample Title 3","Begin":"00:09:23","End":"00:15:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":4,"Artist":"Sample Artist 4","Title":"Sample Title 4","Begin":"00:15:54","End":"00:20:13","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":5,"Artist":"Sample Artist 5","Title":"Sample Title 5","Begin":"00:20:13","End":"00:24:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":6,"Artist":"Sample Artist 6","Title":"Sample Title 6","Begin":"00:24:54","End":"00:31:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":7,"Artist":"Sample Artist 7","Title":"Sample Title 7","Begin":"00:31:54","End":"00:45:54","Flags":[],"IsLinkedToPreviousTrack":false},{"Position":8,"Artist":"Sample Artist 8","Title":"Sample Title 8","Begin":"00:45:54","Flags":[],"IsLinkedToPreviousTrack":false}],"Artist":"Sample CD Artist","Title":"Sample CD Title","Audiofile":{"Name":"Sample.mp3"},"Sections":[]}
\ No newline at end of file
+{"Artist":"Sample CD Artist","Title":"Sample CD Title","Audiofiles":[{"Tracks":[{"Position":1,"Artist":"Sample Artist 1","Title":"Sample Title 1","Begin":"00:00:00","End":"00:05:00","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":2,"Artist":"Sample Artist 2","Title":"Sample Title 2","Begin":"00:05:00","End":"00:09:23","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":3,"Artist":"Sample Artist 3","Title":"Sample Title 3","Begin":"00:09:23","End":"00:15:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":4,"Artist":"Sample Artist 4","Title":"Sample Title 4","Begin":"00:15:54","End":"00:20:13","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":5,"Artist":"Sample Artist 5","Title":"Sample Title 5","Begin":"00:20:13","End":"00:24:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":6,"Artist":"Sample Artist 6","Title":"Sample Title 6","Begin":"00:24:54","End":"00:31:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":7,"Artist":"Sample Artist 7","Title":"Sample Title 7","Begin":"00:31:54","End":"00:45:54","Flags":[],"IsLinkedToPreviousTrack":true},{"Position":8,"Artist":"Sample Artist 8","Title":"Sample Title 8","Begin":"00:45:54","End":"01:15:54","Flags":[],"IsLinkedToPreviousTrack":true}]}]}
\ No newline at end of file
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs
index e0e507d3..68c221c3 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs
@@ -45,8 +45,9 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename()
{
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
- await detailView.RenameAudiofileAsync("Kalimba test 123.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.RenameAudiofileAsync(0, "Kalimba test 123.mp3");
await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Kalimba test 123.mp3");
}
@@ -70,12 +71,65 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected()
await bar.ChangeLanguageAsync("German (Germany)");
await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Allgemeine Informationen" })).ToBeVisibleAsync();
await Expect(TestPage.GetByText("Aufnahmeansicht")).ToBeVisibleAsync();
- await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Titel" })).ToBeVisibleAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Dateien" })).ToBeVisibleAsync();
await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Wiedergabe" })).ToBeVisibleAsync();
await bar.OpenExportDialogAsync("Textdatei", "Datei");
- await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync("- tabpanel:\n - text: \"Export ist derzeit nicht möglich: Titel hat ungültige Anzahl (0)! Künstler hat keinen Wert! Titel hat keinen Wert! Audiodatei hat keinen Wert! YouTube\"\n - group \"Exportprofil auswählen\"\n - text: Exportprofil auswählen\n - group:\n - button \"Neues Exportprofil hinzufügen\"\n - button \"Ausgewähltes Exportprofil löschen\"\n - separator\n - textbox \"Name\": YouTube\n - group \"Name\"\n - text: Name\n - textbox \"Dateiname\": YouTube.txt\n - group \"Dateiname\"\n - text: Dateiname\n - textbox \"Schema Kopf\": \"%Cuesheet.Artist% - %Cuesheet.Title%\"\n - button \"Clear\"\n - button\n - group \"Schema Kopf\"\n - text: Schema Kopf\n - textbox \"Schema Titel\": \"%Track.Artist% - %Track.Title% %Track.Begin%\"\n - button \"Clear\"\n - button\n - group \"Schema Titel\"\n - text: Schema Titel\n - textbox \"Schema Fuß\"\n - button\n - group \"Schema Fuß\"\n - text: Schema Fuß");
+ await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync(@"- dialog ""Exportprofile Close"":
+ - heading ""Exportprofile"" [level=6]
+ - button ""Close""
+ - tablist:
+ - tab ""Export konfigurieren"" [selected]:
+ - paragraph: Export konfigurieren
+ - tab ""2 Export herunterladen"" [disabled]:
+ - text: ""2""
+ - paragraph: Export herunterladen
+ - tabpanel ""Export konfigurieren"":
+ - text: ""Export ist derzeit nicht möglich: Künstler hat keinen Wert! Titel hat keinen Wert! Audiodateien hat ungültige Anzahl (0)!""
+ - combobox ""Exportprofil auswählen"": YouTube
+ - group ""Exportprofil auswählen""
+ - text: Exportprofil auswählen
+ - group:
+ - button ""Neues Exportprofil hinzufügen""
+ - button ""Ausgewähltes Exportprofil löschen""
+ - separator
+ - textbox ""Name"":
+ - /placeholder: Geben Sie hier den Namen für dieses Profil ein
+ - text: YouTube
+ - group ""Name""
+ - text: Name
+ - textbox ""Dateiname"":
+ - /placeholder: Geben Sie hier den Dateinamen für dieses Profil ein
+ - text: YouTube.txt
+ - group ""Dateiname""
+ - text: Dateiname
+ - textbox ""Schema Kopf"":
+ - /placeholder: Geben Sie hier das Kopf-Schema für dieses Profil ein
+ - text: ""%Cuesheet.Artist% - %Cuesheet.Title%""
+ - button ""Clear""
+ - button
+ - group ""Schema Kopf""
+ - text: Schema Kopf
+ - textbox ""Schema Audiodateien"":
+ - /placeholder: Geben Sie hier das Audiodatei-Schema für dieses Profil ein
+ - button
+ - group ""Schema Audiodateien""
+ - text: Schema Audiodateien
+ - textbox ""Schema Titel"":
+ - /placeholder: Geben Sie hier das Titel-Schema für dieses Profil ein
+ - text: ""%Track.Artist% - %Track.Title% %Track.Begin%""
+ - button ""Clear""
+ - button
+ - group ""Schema Titel""
+ - text: Schema Titel
+ - textbox ""Schema Fuß"":
+ - /placeholder: Geben Sie hier das Fuß-Schema für dieses Profil ein
+ - button
+ - group ""Schema Fuß""
+ - text: Schema Fuß
+ - button ""Previous"" [disabled]
+ - button ""Next"" [disabled]");
await exportDialog.OpenSchemeMenuAsync("Schema Kopf");
- await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Audiodatei\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit");
+ await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit");
await TestPage.GetByText("CDTextdatei").ClickAsync();
await exportDialog.OpenSchemeMenuAsync("Schema Titel");
await Expect(TestPage.GetByTestId("menu-wrapper")).ToMatchAriaSnapshotAsync("- paragraph: Position\n- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Begin\n- paragraph: End\n- paragraph: Länge\n- paragraph: Markierungen\n- paragraph: Vorlücke\n- paragraph: Nachlücke");
@@ -84,23 +138,23 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected()
[TestMethod]
public async Task TrackTableControls_ShouldBeEnabled_WhenSelectingFirstTrackAsync()
{
- var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.AddTrackAsync(0);
+ await detailView.AddTrackAsync(0);
await detailView.SelectTracksAsync([1]);
- await bar.ChangeLanguageAsync("German (Germany)");
await Expect(TestPage.GetByLabel("Track table controls")).ToMatchAriaSnapshotAsync(@"- group:
- - button ""Neuen Titel hinzufügen""
- - button ""Ausgewählte Titel bearbeiten""
- - button ""Ausgewählten Titel kopieren""
- - button ""Ausgewählte Titel löschen""
- - button ""Alle Titel löschen""
+ - button ""Add new track""
+ - button ""Edit selected tracks""
+ - button ""Copy selected tracks""
+ - button ""Delete selected tracks""
+ - button ""Delete all tracks""
+- group:
+ - button ""Move selected tracks up"" [disabled]
+ - button ""Move selected tracks down""
- group:
- - button ""Ausgewählte Titel nach oben bewegen"" [disabled]
- - button ""Ausgewählte Titel nach unten bewegen""
-- button ""Fester Tabellenkopf""");
+ - button ""Fixed table header""");
}
[TestMethod]
@@ -112,26 +166,32 @@ public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAs
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenExportDialogAsync("Cuesheet");
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenExportDialogAsync("Projectfile");
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenExportDialogAsync("Textfile");
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenSettingsAsync();
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenDisplayHotkeysAsync();
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
- await detailView.OpenRenameAudiofileDialogAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.OpenRenameAudiofileDialogAsync(0);
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
await detailView.NewFileNameInput.FillAsync("Test 123");
await TestPage.Keyboard.PressAsync("Enter");
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs
index 8e856f81..a87c8844 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs
@@ -27,10 +27,11 @@ public async Task DownloadCuesheet_GeneratesCuesheetFile_WhenCuesheetIsValid()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1");
await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1");
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Track Artist 1", "Track Title 1");
await bar.OpenExportDialogAsync("Cuesheet");
var downloadTask = TestPage.WaitForDownloadAsync();
@@ -56,10 +57,11 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync(
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1");
await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1");
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Track Artist 1", "Track Title 1");
await bar.OpenExportDialogAsync("Projectfile");
var downloadTask = TestPage.WaitForDownloadAsync();
@@ -68,7 +70,7 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync(
using var stream = await download.CreateReadStreamAsync();
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync(TestContext.CancellationToken);
- Assert.AreEqual("{\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}],\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofile\":{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"}}}", content);
+ Assert.AreEqual("{\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofiles\":[{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"},\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}]}]}", content);
}
[TestMethod]
@@ -77,10 +79,11 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1");
await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1");
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Track Artist 1", "Track Title 1");
await bar.OpenExportDialogAsync("Textfile");
await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync();
@@ -92,6 +95,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync()
var content = await reader.ReadToEndAsync(TestContext.CancellationToken);
content = content.Replace("\n", Environment.NewLine);
Assert.AreEqual(@"Cuesheet Artist 1 - Cuesheet Title 1
+
Track Artist 1 - Track Title 1 00:00:00
", content);
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs
index e5d9d58e..f306ab23 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs
@@ -25,7 +25,6 @@ public class ImportTest : PlaywrightTestBase
public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile()
{
var importView = new ImportView(TestPage, DeviceName != null);
- var detailView = new DetailView(TestPage);
await importView.GotoAsync();
await importView.ImportFileAsync("Sample_Inputfile.txt");
await importView.Analyze();
@@ -212,7 +211,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- button
- rowgroup:
- row");
- await Expect(detailView.AudiofileInput).ToBeEmptyAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToHaveValueAsync(@"c:\AudioFile.mp3");
await importView.GotoAsync();
await Expect(TestPage.GetByRole(AriaRole.Button, new() { Name = "Analyze" })).ToBeVisibleAsync();
}
@@ -1183,6 +1182,7 @@ public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile2()
await importView.GotoAsync();
await importView.ImportFileAsync("Sample_Inputfile2.txt");
await importView.ClearSchemeCommonDataAsync();
+ await importView.ClearSchemeAudiofilesAsync();
await importView.Analyze();
await Expect(importView.CuesheetArtistInput).ToBeEmptyAsync();
await Expect(importView.CuesheetTitleInput).ToBeEmptyAsync();
@@ -2407,7 +2407,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- textbox: 00:14:00
- cell:
- button
- - row ""Select row 8 Sample Artist 8 Clear Sample Title 8 Clear 00:45:54"":
+ - row ""Select row 8 Sample Artist 8 Clear Sample Title 8 Clear 00:45:54 01:15:54 00:30:00"":
- cell ""Select row"":
- checkbox ""Select row""
- text: Select row
@@ -2422,16 +2422,16 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- button
- cell ""00:45:54"":
- textbox: 00:45:54
- - cell:
- - textbox
- - cell:
- - textbox
+ - cell ""01:15:54"":
+ - textbox: 01:15:54
+ - cell ""00:30:00"":
+ - textbox: 00:30:00
- cell:
- button
- rowgroup:
- row");
await appBar.UndoAsync();
- await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync();
}
[TestMethod]
@@ -2626,7 +2626,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- rowgroup:
- row");
await appBar.UndoAsync();
- await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync();
}
[TestMethod]
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs
index a6c48845..4bba8143 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs
@@ -27,7 +27,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoAndRedoAreUsed()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Test Artist 1");
await Expect(bar.UndoButton).ToBeEnabledAsync();
await Expect(bar.RedoButton).ToBeDisabledAsync();
@@ -187,7 +188,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenModalEdit()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.AddTrackAsync(0);
await detailView.SelectTracksAsync([1]);
await detailView.EditTracksModalAsync("Test Track Artist 1", "Test Track Title 1", "00:02:23", ["channel audio (4CH)", "Serial copy management system"]);
await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table:
@@ -239,7 +241,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- columnheader ""Length""
- columnheader ""Status""
- rowgroup:
- - row ""Select row 1 00:00:00 End has no value! Length has no value!"" [selected]:
+ - row ""Select row 1 00:00:00"" [selected]:
- cell ""Select row"":
- checkbox ""Select row"" [checked]
- text: Select row
@@ -252,12 +254,10 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- button
- cell ""00:00:00"":
- textbox: 00:00:00
- - cell ""End has no value!"":
+ - cell:
- textbox
- - text: End has no value!
- - cell ""Length has no value!"":
+ - cell:
- textbox
- - text: Length has no value!
- cell
- rowgroup:
- row");
@@ -308,6 +308,7 @@ public async Task UndoRedo_ShouldRestoreCuesheet_WhenUsingImport()
await importView.ImportFileAsync("Textimport with Cuesheetdata.txt");
await importView.SetSchemeCommonDataAsync("Artist - Title - ");
await importView.SelectSchemeCommonDataPlaceholderAsync("Cataloguenumber");
+ await importView.ClearSchemeAudiofilesAsync();
await importView.Analyze();
await Expect(bar.UndoButton).ToBeDisabledAsync();
await Expect(bar.RedoButton).ToBeDisabledAsync();
@@ -1162,9 +1163,10 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoWithAudiofile()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
+ await detailView.AddTrackAsync(0);
await Expect(bar.UndoButton).ToBeEnabledAsync();
await Expect(bar.RedoButton).ToBeDisabledAsync();
await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table:
@@ -1180,7 +1182,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- columnheader ""Length""
- columnheader ""Status""
- rowgroup:
- - row ""Select row 1 00:00:00 End has no value! Length has no value!"":
+ - row ""Select row 1 00:00:00"":
- cell ""Select row"":
- checkbox ""Select row""
- text: Select row
@@ -1193,12 +1195,10 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- button
- cell ""00:00:00"":
- textbox: 00:00:00
- - cell ""End has no value!"":
+ - cell:
- textbox
- - text: End has no value!
- - cell ""Length has no value!"":
+ - cell:
- textbox
- - text: Length has no value!
- cell
- row ""Select row 2 00:05:48.0608330"":
- cell ""Select row"":
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs
similarity index 67%
rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs
rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs
index 6c87dc81..ae8ab082 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs
@@ -19,7 +19,7 @@
namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone
{
[TestClass]
- public class BasicTestSmartphone : PlaywrightTestBase
+ public class BasicTest : PlaywrightTestBase
{
protected override string? DeviceName => "iPhone 13";
@@ -47,8 +47,9 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename()
{
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
- await detailView.RenameAudiofileAsync("Kalimba test 123.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.RenameAudiofileAsync(0, "Kalimba test 123.mp3");
await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Kalimba test 123.mp3");
}
@@ -72,12 +73,65 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected()
await bar.ChangeLanguageAsync("German (Germany)");
await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Allgemeine Informationen" })).ToBeVisibleAsync();
await Expect(TestPage.GetByText("Aufnahmeansicht")).ToBeVisibleAsync();
- await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Titel" })).ToBeVisibleAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Dateien" })).ToBeVisibleAsync();
await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Wiedergabe" })).ToBeVisibleAsync();
await bar.OpenExportDialogAsync("Textdatei", "Datei");
- await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync("- tabpanel:\n - text: \"Export ist derzeit nicht möglich: Titel hat ungültige Anzahl (0)! Künstler hat keinen Wert! Titel hat keinen Wert! Audiodatei hat keinen Wert! YouTube\"\n - group \"Exportprofil auswählen\"\n - text: Exportprofil auswählen\n - group:\n - button \"Neues Exportprofil hinzufügen\"\n - button \"Ausgewähltes Exportprofil löschen\"\n - separator\n - textbox \"Name\": YouTube\n - group \"Name\"\n - text: Name\n - textbox \"Dateiname\": YouTube.txt\n - group \"Dateiname\"\n - text: Dateiname\n - textbox \"Schema Kopf\": \"%Cuesheet.Artist% - %Cuesheet.Title%\"\n - button \"Clear\"\n - button\n - group \"Schema Kopf\"\n - text: Schema Kopf\n - textbox \"Schema Titel\": \"%Track.Artist% - %Track.Title% %Track.Begin%\"\n - button \"Clear\"\n - button\n - group \"Schema Titel\"\n - text: Schema Titel\n - textbox \"Schema Fuß\"\n - button\n - group \"Schema Fuß\"\n - text: Schema Fuß");
+ await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToMatchAriaSnapshotAsync(@"- dialog ""Exportprofile Close"":
+ - heading ""Exportprofile"" [level=6]
+ - button ""Close""
+ - tablist:
+ - tab ""Export konfigurieren"" [selected]:
+ - paragraph: Export konfigurieren
+ - tab ""2 Export herunterladen"" [disabled]:
+ - text: ""2""
+ - paragraph: Export herunterladen
+ - tabpanel ""Export konfigurieren"":
+ - text: ""Export ist derzeit nicht möglich: Künstler hat keinen Wert! Titel hat keinen Wert! Audiodateien hat ungültige Anzahl (0)!""
+ - combobox ""Exportprofil auswählen"": YouTube
+ - group ""Exportprofil auswählen""
+ - text: Exportprofil auswählen
+ - group:
+ - button ""Neues Exportprofil hinzufügen""
+ - button ""Ausgewähltes Exportprofil löschen""
+ - separator
+ - textbox ""Name"":
+ - /placeholder: Geben Sie hier den Namen für dieses Profil ein
+ - text: YouTube
+ - group ""Name""
+ - text: Name
+ - textbox ""Dateiname"":
+ - /placeholder: Geben Sie hier den Dateinamen für dieses Profil ein
+ - text: YouTube.txt
+ - group ""Dateiname""
+ - text: Dateiname
+ - textbox ""Schema Kopf"":
+ - /placeholder: Geben Sie hier das Kopf-Schema für dieses Profil ein
+ - text: ""%Cuesheet.Artist% - %Cuesheet.Title%""
+ - button ""Clear""
+ - button
+ - group ""Schema Kopf""
+ - text: Schema Kopf
+ - textbox ""Schema Audiodateien"":
+ - /placeholder: Geben Sie hier das Audiodatei-Schema für dieses Profil ein
+ - button
+ - group ""Schema Audiodateien""
+ - text: Schema Audiodateien
+ - textbox ""Schema Titel"":
+ - /placeholder: Geben Sie hier das Titel-Schema für dieses Profil ein
+ - text: ""%Track.Artist% - %Track.Title% %Track.Begin%""
+ - button ""Clear""
+ - button
+ - group ""Schema Titel""
+ - text: Schema Titel
+ - textbox ""Schema Fuß"":
+ - /placeholder: Geben Sie hier das Fuß-Schema für dieses Profil ein
+ - button
+ - group ""Schema Fuß""
+ - text: Schema Fuß
+ - button ""Previous"" [disabled]
+ - button ""Next"" [disabled]");
await exportDialog.OpenSchemeMenuAsync("Schema Kopf");
- await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Audiodatei\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit");
+ await Expect(TestPage.Locator("#app")).ToMatchAriaSnapshotAsync("- paragraph: Künstler\n- paragraph: Titel\n- paragraph: CDTextdatei\n- paragraph: Katalognummer\n- paragraph: Datum\n- paragraph: Datum & Uhrzeit\n- paragraph: Uhrzeit");
await TestPage.GetByText("CDTextdatei").ClickAsync();
await exportDialog.OpenSchemeMenuAsync("Schema Titel");
await Expect(TestPage.GetByTestId("menu-wrapper")).ToMatchAriaSnapshotAsync("- paragraph: Position\n- paragraph: Künstler\n- paragraph: Titel\n- paragraph: Begin\n- paragraph: End\n- paragraph: Länge\n- paragraph: Markierungen\n- paragraph: Vorlücke\n- paragraph: Nachlücke");
@@ -86,20 +140,18 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected()
[TestMethod]
public async Task TrackTableControls_ShouldBeEnabled_WhenSelectingFirstTrackAsync()
{
- var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.AddTrackAsync(0);
+ await detailView.AddTrackAsync(0);
await detailView.SelectTracksAsync([1]);
- await bar.ChangeLanguageAsync("German (Germany)");
await Expect(TestPage.GetByLabel("Track table controls")).ToMatchAriaSnapshotAsync(@"- group:
- - button ""Neuen Titel hinzufügen""
- - button ""Ausgewählte Titel bearbeiten""
- - button
- - button ""Alle Titel löschen""
+ - button ""Add new track""
+ - button ""Edit selected tracks""
- button
-- button ""Fester Tabellenkopf""");
+ - button ""Delete all tracks""
+ - button");
}
[TestMethod]
@@ -111,26 +163,32 @@ public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAs
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenExportDialogAsync("Cuesheet");
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenExportDialogAsync("Projectfile");
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenExportDialogAsync("Textfile");
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenSettingsAsync();
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
await bar.OpenDisplayHotkeysAsync();
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
+ await TestPage.GetByRole(AriaRole.Dialog).FocusAsync();
await TestPage.Keyboard.PressAsync("Escape");
await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached });
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
- await detailView.OpenRenameAudiofileDialogAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.OpenRenameAudiofileDialogAsync(0);
await Expect(TestPage.GetByRole(AriaRole.Dialog)).ToBeVisibleAsync();
await detailView.NewFileNameInput.FillAsync("Test 123");
await TestPage.Keyboard.PressAsync("Enter");
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTest.cs
similarity index 80%
rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs
rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTest.cs
index 22865d9b..11df1c78 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTest.cs
@@ -19,18 +19,21 @@
namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone
{
[TestClass]
- public class ExportTestSmartphone : PlaywrightTestBase
+ public class ExportTest : PlaywrightTestBase
{
+ protected override string? DeviceName => "iPhone 13";
+
[TestMethod]
public async Task DownloadCuesheet_GeneratesCuesheetFile_WhenCuesheetIsValid()
{
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1");
await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1");
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Track Artist 1", "Track Title 1");
await bar.OpenExportDialogAsync("Cuesheet");
var downloadTask = TestPage.WaitForDownloadAsync();
@@ -56,10 +59,11 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync(
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1");
await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1");
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Track Artist 1", "Track Title 1");
await bar.OpenExportDialogAsync("Projectfile");
var downloadTask = TestPage.WaitForDownloadAsync();
@@ -68,7 +72,7 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync(
using var stream = await download.CreateReadStreamAsync();
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync(TestContext.CancellationToken);
- Assert.AreEqual("{\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}],\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofile\":{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"}}}", content);
+ Assert.AreEqual("{\"Artist\":\"Cuesheet Artist 1\",\"Title\":\"Cuesheet Title 1\",\"Audiofiles\":[{\"Name\":\"Kalimba.mp3\",\"Duration\":\"00:05:48.0608330\",\"AudioCodec\":{\"MimeType\":\"audio/mpeg\",\"FileExtension\":\".mp3\",\"Name\":\"AudioCodec MP3\"},\"Tracks\":[{\"Position\":1,\"Artist\":\"Track Artist 1\",\"Title\":\"Track Title 1\",\"Begin\":\"00:00:00\",\"End\":\"00:05:48.0608330\",\"Flags\":[],\"IsLinkedToPreviousTrack\":true}]}]}", content);
}
[TestMethod]
@@ -77,10 +81,11 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1");
await detailView.CuesheetTitleInput.FillAsync("Cuesheet Title 1");
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Track Artist 1", "Track Title 1");
await bar.OpenExportDialogAsync("Textfile");
await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync();
@@ -92,6 +97,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync()
var content = await reader.ReadToEndAsync(TestContext.CancellationToken);
content = content.Replace("\n", Environment.NewLine);
Assert.AreEqual(@"Cuesheet Artist 1 - Cuesheet Title 1
+
Track Artist 1 - Track Title 1 00:00:00
", content);
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs
similarity index 99%
rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs
rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs
index 129f49ad..bb24ec81 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs
@@ -19,7 +19,7 @@
namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone
{
[TestClass]
- public class ImportTestSmartphone : PlaywrightTestBase
+ public class ImportTest : PlaywrightTestBase
{
protected override string? DeviceName => "iPhone 13";
@@ -27,7 +27,6 @@ public class ImportTestSmartphone : PlaywrightTestBase
public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile()
{
var importView = new ImportView(TestPage, DeviceName != null);
- var detailView = new DetailView(TestPage);
await importView.GotoAsync();
await importView.ImportFileAsync("Sample_Inputfile.txt");
await importView.Analyze();
@@ -249,7 +248,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- cell ""Status"":
- text: Status
- button");
- await Expect(detailView.AudiofileInput).ToBeEmptyAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToHaveValueAsync(@"c:\AudioFile.mp3");
await importView.GotoAsync();
await Expect(TestPage.GetByRole(AriaRole.Button, new() { Name = "Analyze" })).ToBeVisibleAsync();
}
@@ -1439,6 +1438,7 @@ public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile2()
await importView.GotoAsync();
await importView.ImportFileAsync("Sample_Inputfile2.txt");
await importView.ClearSchemeCommonDataAsync();
+ await importView.ClearSchemeAudiofilesAsync();
await importView.Analyze();
await Expect(importView.CuesheetArtistInput).ToBeEmptyAsync();
await Expect(importView.CuesheetTitleInput).ToBeEmptyAsync();
@@ -2956,7 +2956,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- cell ""Status"":
- text: Status
- button
- - 'row ""Select row # 8 Artist Sample Artist 8 Clear Title Sample Title 8 Clear Begin 00:45:54 End Length Status""':
+ - 'row ""Select row # 8 Artist Sample Artist 8 Clear Title Sample Title 8 Clear Begin 00:45:54 End 01:15:54 Length 00:30:00 Status""':
- cell ""Select row"":
- checkbox ""Select row""
- text: Select row
@@ -2974,17 +2974,17 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- cell ""Begin 00:45:54"":
- text: Begin
- textbox: 00:45:54
- - cell ""End"":
+ - cell ""End 01:15:54"":
- text: End
- - textbox
- - cell ""Length"":
+ - textbox: 01:15:54
+ - cell ""Length 00:30:00"":
- text: Length
- - textbox
+ - textbox: 00:30:00
- cell ""Status"":
- text: Status
- button");
await appBar.UndoAsync();
- await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync();
}
[TestMethod]
@@ -3214,7 +3214,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- text: Status
- button");
await appBar.UndoAsync();
- await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Tracks has invalid Count (0)!" })).ToBeVisibleAsync();
+ await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync();
}
[TestMethod]
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTest.cs
similarity index 97%
rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs
rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTest.cs
index 1174a2ce..a28c370f 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTest.cs
@@ -19,7 +19,7 @@
namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone
{
[TestClass]
- public class RecordTestSmartphone : PlaywrightTestBase
+ public class RecordTest : PlaywrightTestBase
{
protected override string? DeviceName => "iPhone 13";
[TestMethod]
diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTest.cs
similarity index 98%
rename from AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs
rename to AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTest.cs
index d5ac1b48..fff99f79 100644
--- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs
+++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTest.cs
@@ -19,7 +19,7 @@
namespace AudioCuesheetEditor.End2EndTests.Tests.Smartphone
{
[TestClass]
- public class TracingTestSmartphone : PlaywrightTestBase
+ public class TracingTest : PlaywrightTestBase
{
protected override string? DeviceName => "iPhone 13";
@@ -29,7 +29,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoAndRedoAreUsed()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.AddTrackAsync(0);
await detailView.EditTrackAsync("Test Artist 1");
await Expect(bar.UndoButton).ToBeEnabledAsync();
await Expect(bar.RedoButton).ToBeDisabledAsync();
@@ -161,7 +162,8 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenModalEdit()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.AddTrackAsync(0);
await detailView.SelectTracksAsync([1]);
await detailView.EditTracksModalAsync("Test Track Artist 1", "Test Track Title 1", "00:02:23", ["channel audio (4CH)", "Serial copy management system"]);
await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table:
@@ -196,7 +198,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table:
- rowgroup
- rowgroup:
- - 'row ""Select row # 1 Artist Title Begin 00:00:00 End End has no value! Length Length has no value! Status"" [selected]':
+ - 'row ""Select row # 1 Artist Title Begin 00:00:00 End Length Status"" [selected]':
- cell ""Select row"":
- checkbox ""Select row"" [checked]
- text: Select row
@@ -212,14 +214,12 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- cell ""Begin 00:00:00"":
- text: Begin
- textbox: 00:00:00
- - cell ""End End has no value!"":
+ - cell ""End"":
- text: End
- textbox
- - text: End has no value!
- - cell ""Length Length has no value!"":
+ - cell ""Length"":
- text: Length
- textbox
- - text: Length has no value!
- cell ""Status""");
await bar.RedoAsync();
await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table:
@@ -261,6 +261,7 @@ public async Task UndoRedo_ShouldRestoreCuesheet_WhenUsingImport()
await importView.ImportFileAsync("Textimport with Cuesheetdata.txt");
await importView.SetSchemeCommonDataAsync("Artist - Title - ");
await importView.SelectSchemeCommonDataPlaceholderAsync("Cataloguenumber");
+ await importView.ClearSchemeAudiofilesAsync();
await importView.Analyze();
await Expect(bar.UndoButton).ToBeDisabledAsync();
await Expect(bar.RedoButton).ToBeDisabledAsync();
@@ -1336,15 +1337,16 @@ public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoWithAudiofile()
var bar = new AppBar(TestPage);
var detailView = new DetailView(TestPage);
await detailView.GotoAsync();
- await detailView.AddTrackAsync();
- await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3");
- await detailView.AddTrackAsync();
+ await detailView.AddAudiofileAsync();
+ await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3");
+ await detailView.AddTrackAsync(0);
+ await detailView.AddTrackAsync(0);
await Expect(bar.UndoButton).ToBeEnabledAsync();
await Expect(bar.RedoButton).ToBeDisabledAsync();
await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- table:
- rowgroup
- rowgroup:
- - 'row ""Select row # 1 Artist Title Begin 00:00:00 End End has no value! Length Length has no value! Status""':
+ - 'row ""Select row # 1 Artist Title Begin 00:00:00 End Length Status""':
- cell ""Select row"":
- checkbox ""Select row""
- text: Select row
@@ -1360,14 +1362,12 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta
- cell ""Begin 00:00:00"":
- text: Begin
- textbox: 00:00:00
- - cell ""End End has no value!"":
+ - cell ""End"":
- text: End
- textbox
- - text: End has no value!
- - cell ""Length Length has no value!"":
+ - cell ""Length"":
- text: Length
- textbox
- - text: Length has no value!
- cell ""Status""
- 'row ""Select row # 2 Artist Title Begin End 00:05:48.0608330 Length Status""':
- cell ""Select row"":
diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj
index 1ddbafe8..9c8eba6b 100644
--- a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj
+++ b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj
@@ -16,7 +16,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/AudiofileTests.cs b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/AudiofileTests.cs
new file mode 100644
index 00000000..3feb39f8
--- /dev/null
+++ b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/AudiofileTests.cs
@@ -0,0 +1,102 @@
+//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
+//.
+using AudioCuesheetEditor.Model.AudioCuesheet;
+using AudioCuesheetEditor.Model.Entity;
+using AudioCuesheetEditor.Model.IO.Audio;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System;
+using System.Linq;
+
+namespace AudioCuesheetEditor.Tests.Model.AudioCuesheet
+{
+ [TestClass]
+ public class AudiofileTests
+ {
+ [TestMethod]
+ public void Validate_FilenameNull_ReturnsValidationStatusError()
+ {
+ // Arrange
+ var audiofile = new Audiofile();
+ // Act
+ var validationResult = audiofile.Validate(nameof(Audiofile.Name));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ }
+
+ [TestMethod]
+ public void Validate_TracksEmpty_ReturnsValidationStatusError()
+ {
+ // Arrange
+ var audiofile = new Audiofile();
+ // Act
+ var validationResult = audiofile.Validate(nameof(Audiofile.Tracks));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} has invalid count ({1})!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Audiofile.Tracks), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ }
+
+ [TestMethod]
+ public void Validate_TracksWithSamePosition_ReturnsValidationStatusError()
+ {
+ // Arrange
+ var track1 = new Track()
+ {
+ Position = 1
+ };
+ var track2 = new Track()
+ {
+ Position = 1
+ };
+ var audiofile = new Audiofile()
+ {
+ Tracks = [track1, track2]
+ };
+ // Act
+ var validationResult = audiofile.Validate(nameof(Audiofile.Tracks));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", validationResult.ValidationMessages.First().Message);
+ }
+
+ [TestMethod]
+ public void Validate_TracksOverlapping_ReturnsValidationStatusError()
+ {
+ // Arrange
+ var track1 = new Track()
+ {
+ Position = 1,
+ Begin = TimeSpan.Zero,
+ End = new TimeSpan(0, 3, 45)
+ };
+ var track2 = new Track()
+ {
+ Position = 2,
+ Begin = new TimeSpan(0, 3, 42),
+ End = new TimeSpan(0, 6, 32)
+ };
+ var audiofile = new Audiofile()
+ {
+ Tracks = [track1, track2]
+ };
+ // Act
+ var validationResult = audiofile.Validate(nameof(Audiofile.Tracks));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", validationResult.ValidationMessages.First().Message);
+ }
+ }
+}
diff --git a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs
index 6487ec33..2268757c 100644
--- a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs
+++ b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/CuesheetTests.cs
@@ -16,7 +16,6 @@
using AudioCuesheetEditor.Model.AudioCuesheet;
using AudioCuesheetEditor.Model.Entity;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
using System.Linq;
namespace AudioCuesheetEditor.Tests.Model.AudioCuesheet
@@ -25,12 +24,12 @@ namespace AudioCuesheetEditor.Tests.Model.AudioCuesheet
public class CuesheetTests
{
[TestMethod]
- public void Validate_AudiofileNull_ReturnsValidationStatusError()
+ public void Validate_AudiofilesEmpty_ReturnsValidationStatusError()
{
// Arrange
var cuesheet = new Cuesheet();
// Act
- var validationResult = cuesheet.Validate(nameof(Cuesheet.Audiofile));
+ var validationResult = cuesheet.Validate(nameof(Cuesheet.Audiofiles));
// Assert
Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
}
@@ -89,73 +88,5 @@ public void Validate_CataloguenumberLengthUnequal13_ReturnsValidationStatusError
Assert.AreEqual(nameof(Cuesheet.Cataloguenumber), validationResult.ValidationMessages.First().Parameter?.First().ToString());
Assert.AreEqual(13, validationResult.ValidationMessages.First().Parameter?.Last());
}
-
- [TestMethod]
- public void Validate_TracksEmpty_ReturnsValidationStatusError()
- {
- // Arrange
- var cuesheet = new Cuesheet();
- // Act
- var validationResult = cuesheet.Validate(nameof(Cuesheet.Tracks));
- // Assert
- Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
- Assert.AreEqual("{0} has invalid Count ({1})!", validationResult.ValidationMessages.First().Message);
- Assert.AreEqual(nameof(Cuesheet.Tracks), validationResult.ValidationMessages.First().Parameter?.First().ToString());
- Assert.AreEqual(0, validationResult.ValidationMessages.First().Parameter?.Last());
- }
-
- [TestMethod]
- public void Validate_TracksWithSamePosition_ReturnsValidationStatusError()
- {
- // Arrange
- var track1 = new Track()
- {
- Position = 1
- };
- var track2 = new Track()
- {
- Position = 1
- };
- var cuesheet = new Cuesheet()
- {
- Tracks = [track1, track2]
- };
- track1.Cuesheet = cuesheet;
- track2.Cuesheet = cuesheet;
- // Act
- var validationResult = cuesheet.Validate(nameof(Cuesheet.Tracks));
- // Assert
- Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
- Assert.AreEqual("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", validationResult.ValidationMessages.First().Message);
- }
-
- [TestMethod]
- public void Validate_TracksOverlapping_ReturnsValidationStatusError()
- {
- // Arrange
- var track1 = new Track()
- {
- Position = 1,
- Begin = TimeSpan.Zero,
- End = new TimeSpan(0, 3, 45)
- };
- var track2 = new Track()
- {
- Position = 2,
- Begin = new TimeSpan(0, 3, 42),
- End = new TimeSpan(0, 6, 32)
- };
- var cuesheet = new Cuesheet()
- {
- Tracks = [track1, track2]
- };
- track1.Cuesheet = cuesheet;
- track2.Cuesheet = cuesheet;
- // Act
- var validationResult = cuesheet.Validate(nameof(Cuesheet.Tracks));
- // Assert
- Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
- Assert.AreEqual("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", validationResult.ValidationMessages.First().Message);
- }
}
}
\ No newline at end of file
diff --git a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs
index d29aa2d1..46982836 100644
--- a/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs
+++ b/AudioCuesheetEditor.Tests/Model/AudioCuesheet/TrackTests.cs
@@ -60,10 +60,7 @@ public void Validate_PositionOvverlapping_ReturnsValidationStatusError()
{
Position = 1
};
- var cuesheet = new Cuesheet()
- {
- Tracks = [track1, track]
- };
+ var cuesheet = new Cuesheet();
track1.Cuesheet = cuesheet;
track.Cuesheet = cuesheet;
// Act
diff --git a/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs b/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs
index 2cdd8009..14e1dc34 100644
--- a/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs
+++ b/AudioCuesheetEditor.Tests/Model/IO/Export/ExportprofileTests.cs
@@ -16,7 +16,7 @@
using AudioCuesheetEditor.Model.Entity;
using AudioCuesheetEditor.Model.IO.Export;
using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System;
+using System.Linq;
namespace AudioCuesheetEditor.Tests.Model.IO.Export
{
@@ -24,23 +24,216 @@ namespace AudioCuesheetEditor.Tests.Model.IO.Export
public class ExportprofileTests
{
[TestMethod()]
- public void ValidateTest()
+ public void Validate_EmptyFilename_ReturnsError()
{
+ // Arrange
var exportprofile = new Exportprofile
{
Filename = string.Empty
};
- Assert.AreEqual(ValidationStatus.Error, exportprofile.Validate(nameof(Exportprofile.Filename)).Status);
- exportprofile.Filename = "Test123";
- Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.Filename)).Status);
- exportprofile.SchemeHead = "%Cuesheet.Artist%;%Cuesheet.Title%;%Cuesheet.Cataloguenumber%;%Cuesheet.CDTextfile%";
- Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.SchemeHead)).Status);
- exportprofile.SchemeTracks = "%Track.Position%;%Track.Artist%;%Track.Title%;%Track.Begin%;%Track.End%;%Track.Length%;%Track.PreGap%;%Track.PostGap%";
- Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.SchemeTracks)).Status);
- exportprofile.SchemeFooter = "Exported %Cuesheet.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%";
- Assert.AreEqual(ValidationStatus.Success, exportprofile.Validate(nameof(Exportprofile.SchemeFooter)).Status);
- exportprofile.SchemeFooter = "Exported %Track.Title% from %Cuesheet.Artist% using AudioCuesheetEditor at %Date%";
- Assert.AreEqual(ValidationStatus.Error, exportprofile.Validate(nameof(Exportprofile.SchemeFooter)).Status);
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.Filename));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} has no value!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.Filename), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_EmptyName_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ Name = string.Empty
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.Name));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} has no value!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.Name), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeHeadWithTrackPlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeHead = Exportprofile.SchemeTrackTitle
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeHead));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeHead), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeTrackTitle, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeHeadWithAudiofilePlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeHead = Exportprofile.SchemeAudiofileName
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeHead));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeHead), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeAudiofileName, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeTrackWithCuesheetPlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeTracks = Exportprofile.SchemeCuesheetArtist
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeTracks));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeTracks), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeCuesheetArtist, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeTrackWithAudiofilePlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeTracks = Exportprofile.SchemeAudiofileName
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeTracks));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeTracks), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeAudiofileName, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeFooterWithTrackPlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeFooter = Exportprofile.SchemeTrackBegin
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeFooter));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeFooter), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeTrackBegin, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeFooterWithAudiofilePlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeFooter = Exportprofile.SchemeAudiofileName
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeFooter));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeFooter), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeAudiofileName, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeHeadCorrectPlaceholder_ReturnsSuccess()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeHead = Exportprofile.SchemeCuesheetTitle
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeHead));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Success, validationResult.Status);
+ Assert.IsEmpty(validationResult.ValidationMessages);
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeTrackCorrectPlaceholder_ReturnsSuccess()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeTracks = Exportprofile.SchemeTrackPreGap
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeTracks));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Success, validationResult.Status);
+ Assert.IsEmpty(validationResult.ValidationMessages);
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeAudiofilesWithTrackPlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeAudiofiles = Exportprofile.SchemeTrackArtist
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeAudiofiles));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeAudiofiles), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeTrackArtist, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeAudiofilesWithCuesheetPlaceholder_ReturnsError()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeAudiofiles = Exportprofile.SchemeCuesheetCatalogueNumber
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeAudiofiles));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Error, validationResult.Status);
+ Assert.AreEqual("{0} contains placeholder '{1}' that can not be resolved!", validationResult.ValidationMessages.First().Message);
+ Assert.AreEqual(nameof(Exportprofile.SchemeAudiofiles), validationResult.ValidationMessages.First().Parameter?.First().ToString());
+ Assert.AreEqual(Exportprofile.SchemeCuesheetCatalogueNumber, validationResult.ValidationMessages.First().Parameter?.ElementAt(1).ToString());
+ }
+
+ [TestMethod()]
+ public void Validate_SchemeAudiofilesCorrectPlaceholder_ReturnsNoValidation()
+ {
+ // Arrange
+ var exportprofile = new Exportprofile
+ {
+ SchemeAudiofiles = Exportprofile.SchemeAudiofileName
+ };
+ // Act
+ var validationResult = exportprofile.Validate(nameof(Exportprofile.SchemeAudiofiles));
+ // Assert
+ Assert.AreEqual(ValidationStatus.Success, validationResult.Status);
+ Assert.IsEmpty(validationResult.ValidationMessages);
}
}
}
\ No newline at end of file
diff --git a/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs b/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs
index 335266f1..807be58d 100644
--- a/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs
+++ b/AudioCuesheetEditor.Tests/Model/IO/ProjectfileTests.cs
@@ -15,7 +15,6 @@
//.
using AudioCuesheetEditor.Model.AudioCuesheet;
using AudioCuesheetEditor.Model.IO;
-using AudioCuesheetEditor.Model.IO.Audio;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
@@ -36,11 +35,15 @@ public void GenerateFile_WithoutSections_GeneratesOneFile()
{
Artist = "CuesheetArtist",
Title = "CuesheetTitle",
- Audiofile = new Audiofile("AudioFile.mp3"),
+ Audiofiles = [
+ new () { Name = "AudioFile.mp3" },
+ new () { Name = "Other audiofile.wav" }
+ ],
CDTextfile = new CDTextfile("CDTextfile.cdt"),
Cataloguenumber = "A123"
};
var begin = TimeSpan.Zero;
+ var tracks = new List
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
diff --git a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs
index 11e24d17..6cc603bf 100644
--- a/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs
+++ b/AudioCuesheetEditor/Extensions/WebAssemblyHostExtension.cs
@@ -25,5 +25,11 @@ public async static Task SetCultureFromConfigurationAsync(this WebAssemblyHost h
var localizationService = host.Services.GetRequiredService();
await localizationService.SetCultureFromConfigurationAsync();
}
+
+ public async static Task InitializeSessionStateContainer(this WebAssemblyHost host)
+ {
+ var sessionStateContainer = host.Services.GetRequiredService();
+ await sessionStateContainer.InitializeAsync();
+ }
}
}
diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs
index 501f90cf..ee8fc393 100644
--- a/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs
+++ b/AudioCuesheetEditor/Model/AudioCuesheet/Cuesheet.cs
@@ -21,14 +21,11 @@ namespace AudioCuesheetEditor.Model.AudioCuesheet
{
public class Cuesheet() : Validateable, ICuesheet
{
- [JsonInclude]
- public IEnumerable Tracks { get; set; } = [];
-
public String? Artist { get; set; }
public String? Title { get; set; }
- public Audiofile? Audiofile { get; set; }
+ public IList Audiofiles { get; set; } = [];
public CDTextfile? CDTextfile { get; set; }
@@ -46,52 +43,12 @@ public override ValidationResult Validate(string property)
List? validationMessages = null;
switch (property)
{
- case nameof(Tracks):
- validationStatus = ValidationStatus.Success;
- if (!Tracks.Any())
- {
- validationMessages ??= [];
- validationMessages.Add(new ValidationMessage("{0} has invalid Count ({1})!", nameof(Tracks), 0));
- }
- else
- {
- //Check track overlapping
- var tracksWithSamePosition = Tracks
- .GroupBy(x => x.Position)
- .Where(grp => grp.Count() > 1);
- if (tracksWithSamePosition.Any())
- {
- validationMessages ??= [];
- foreach (var track in tracksWithSamePosition)
- {
- foreach (var trackWithSamePosition in track)
- {
- validationMessages.Add(new ValidationMessage("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", nameof(Track), nameof(Track.Position), track.Key != null ? track.Key : String.Empty, nameof(Track), trackWithSamePosition.Position != null ? trackWithSamePosition.Position : String.Empty, trackWithSamePosition.Artist ?? String.Empty, trackWithSamePosition.Title ?? String.Empty, trackWithSamePosition.Begin != null ? trackWithSamePosition.Begin : String.Empty, trackWithSamePosition.End != null ? trackWithSamePosition.End : String.Empty));
- }
- }
- }
- foreach (var track in Tracks.OrderBy(x => x.Position))
- {
- var tracksBetween = Tracks.Where(x => ((track.Begin >= x.Begin && track.Begin < x.End)
- || (x.Begin < track.End && track.End <= x.End))
- && (x.Equals(track) == false));
- if (tracksBetween.Any())
- {
- validationMessages ??= [];
- foreach (var trackBetween in tracksBetween)
- {
- validationMessages.Add(new ValidationMessage("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", nameof(Track), track.Position != null ? track.Position : String.Empty, track.Artist ?? String.Empty, track.Title ?? String.Empty, track.Begin != null ? track.Begin : String.Empty, track.End != null ? track.End : String.Empty, trackBetween.Position != null ? trackBetween.Position : String.Empty, trackBetween.Artist ?? String.Empty, trackBetween.Title ?? String.Empty, trackBetween.Begin != null ? trackBetween.Begin : String.Empty, trackBetween.End != null ? trackBetween.End : String.Empty));
- }
- }
- }
- }
- break;
- case nameof(Audiofile):
+ case nameof(Audiofiles):
validationStatus = ValidationStatus.Success;
- if (Audiofile == null)
+ if (Audiofiles.Count == 0)
{
validationMessages ??= [];
- validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Audiofile)));
+ validationMessages.Add(new ValidationMessage("{0} has invalid count ({1})!", nameof(Audiofiles), 0));
}
break;
case nameof(Artist):
diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/IAudiofile.cs b/AudioCuesheetEditor/Model/AudioCuesheet/IAudiofile.cs
new file mode 100644
index 00000000..0efa34b0
--- /dev/null
+++ b/AudioCuesheetEditor/Model/AudioCuesheet/IAudiofile.cs
@@ -0,0 +1,22 @@
+//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
+//.
+namespace AudioCuesheetEditor.Model.AudioCuesheet
+{
+ public interface IAudiofile
+ {
+ String? Name { get; set; }
+ }
+}
diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportAudiofile.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportAudiofile.cs
new file mode 100644
index 00000000..c158067d
--- /dev/null
+++ b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportAudiofile.cs
@@ -0,0 +1,8 @@
+namespace AudioCuesheetEditor.Model.AudioCuesheet.Import
+{
+ public class ImportAudiofile : IAudiofile
+ {
+ public String? Name { get; set; }
+ public IList Tracks { get; } = [];
+ }
+}
diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs
index 053228b6..47f401e0 100644
--- a/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs
+++ b/AudioCuesheetEditor/Model/AudioCuesheet/Import/ImportCuesheet.cs
@@ -21,10 +21,9 @@ public class ImportCuesheet : ICuesheet
{
public string? Artist { get; set; }
public string? Title { get; set; }
- public string? Audiofile { get;set; }
+ public IList Audiofiles { get; set; } = [];
///
public string? CDTextfile { get; set; }
public string? Cataloguenumber { get; set; }
- public ICollection Tracks { get; } = [];
}
}
diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs
index 1efc32ef..bd86d17c 100644
--- a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs
+++ b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs
@@ -14,6 +14,7 @@
//along with Foobar. If not, see
//.
using AudioCuesheetEditor.Model.Entity;
+using AudioCuesheetEditor.Model.IO.Audio;
using System.Text.Json.Serialization;
namespace AudioCuesheetEditor.Model.AudioCuesheet
@@ -65,10 +66,10 @@ public TimeSpan? Length
}
}
}
- [JsonInclude]
- public IEnumerable Flags { get; set; } = [];
[JsonIgnore]
public Cuesheet? Cuesheet { get; set; }
+ [JsonInclude]
+ public IEnumerable Flags { get; set; } = [];
///
public TimeSpan? PreGap { get; set; }
///
@@ -77,6 +78,8 @@ public TimeSpan? Length
/// Set that this track is linked to the previous track in cuesheet
///
public Boolean IsLinkedToPreviousTrack { get; set; }
+ [JsonIgnore]
+ public Audiofile? Audiofile { get; set; }
public override ValidationResult Validate(string property)
{
@@ -103,7 +106,7 @@ public override ValidationResult Validate(string property)
// Check correct track position
if (Cuesheet != null)
{
- var positionTrackShouldHave = Cuesheet.Tracks.OrderBy(x => x.Begin ?? TimeSpan.MaxValue).ThenBy(x => x.Position).ToList().IndexOf(this) + 1;
+ var positionTrackShouldHave = Cuesheet.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.Begin ?? TimeSpan.MaxValue).ThenBy(x => x.Position).ToList().IndexOf(this) + 1;
if (positionTrackShouldHave != Position)
{
validationMessages ??= [];
diff --git a/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx b/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx
index aa9e152e..2f6686f8 100644
--- a/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx
+++ b/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx
@@ -165,6 +165,9 @@
Titel
+
+ Audiodateien
+
Titel ({0},{1},{2},{3},{4}) hat nicht die korrekte Position '{5}'!
@@ -180,7 +183,7 @@
{0} hat eine ungültige Länge. Erlaubte Länge ist {1}!
-
+
{0} hat ungültige Anzahl ({1})!
diff --git a/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx b/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx
index 4e807726..95e63d1c 100644
--- a/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx
+++ b/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx
@@ -165,6 +165,9 @@
Title
+
+ Audiofiles
+
Track({0},{1},{2},{3},{4}) does not have the correct position '{5}'!
@@ -180,8 +183,8 @@
{0} has an invalid length. Allowed length is {1}!
-
- {0} has invalid Count ({1})!
+
+ {0} has invalid count ({1})!
{0} has no value!
diff --git a/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs
index b6d86c42..c8330b68 100644
--- a/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs
+++ b/AudioCuesheetEditor/Model/IO/Audio/Audiofile.cs
@@ -13,12 +13,13 @@
//You should have received a copy of the GNU General Public License
//along with Foobar. If not, see
//.
+using AudioCuesheetEditor.Model.AudioCuesheet;
+using AudioCuesheetEditor.Model.Entity;
using System.Text.Json.Serialization;
namespace AudioCuesheetEditor.Model.IO.Audio
{
- [method: JsonConstructor]
- public class Audiofile(String name)
+ public class Audiofile() : Validateable, IAudiofile
{
public static readonly AudioCodec AudioCodecWEBM = new("audio/webm", ".webm", "AudioCodec WEBM");
@@ -34,72 +35,119 @@ public class Audiofile(String name)
new AudioCodec("audio/flac", ".flac", "AudioCodec FLAC")
];
- private AudioCodec? audioCodec;
- private String name = name;
-
- public Audiofile(String name, String objectURL, AudioCodec? audioCodec, TimeSpan? duration = null) : this(name)
+ private AudioCodec? _audioCodec;
+ private String? _name;
+
+ public Audiofile(String? name, String? objectURL, AudioCodec? audioCodec, TimeSpan? duration = null) : this()
{
- if (String.IsNullOrEmpty(objectURL))
- {
- throw new ArgumentNullException(nameof(objectURL));
- }
+ Name = name;
ObjectURL = objectURL;
AudioCodec = audioCodec;
Duration = duration;
}
- public String Name
+ public String? Name
{
- get => name;
+ get => _name;
set
{
- if (String.IsNullOrEmpty(value))
- {
- throw new ArgumentNullException(nameof(value));
- }
var extension = Path.GetExtension(value);
- if (extension.Equals(audioCodec?.FileExtension, StringComparison.CurrentCultureIgnoreCase) == false)
+ if (extension?.Equals(_audioCodec?.FileExtension, StringComparison.CurrentCultureIgnoreCase) == false)
{
- value = $"{value}{audioCodec?.FileExtension}";
+ value = $"{value}{_audioCodec?.FileExtension}";
}
- name = value;
+ _name = value;
}
}
+
[JsonIgnore]
- public String? ObjectURL { get; private set; }
+ public String? ObjectURL { get; set; }
+
///
/// Duration of the audio file
///
- public TimeSpan? Duration { get; private set; }
+ public TimeSpan? Duration { get; set; }
public AudioCodec? AudioCodec
{
- get { return audioCodec; }
- private set
+ get { return _audioCodec; }
+ set
{
- audioCodec = value;
- if ((audioCodec != null) && (Name?.EndsWith(audioCodec.FileExtension) == false))
+ _audioCodec = value;
+ if ((_audioCodec != null) && (Name?.EndsWith(_audioCodec.FileExtension) == false))
{
//Replace file ending
- Name = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(Name), audioCodec.FileExtension);
+ Name = String.Format("{0}{1}", Path.GetFileNameWithoutExtension(Name), _audioCodec.FileExtension);
}
}
}
- [JsonIgnore]
- public String? AudioFileType
+ public ICollection Tracks { get; set; } = [];
+
+ public override ValidationResult Validate(string property)
{
- get
+ ValidationStatus validationStatus = ValidationStatus.NoValidation;
+ List? validationMessages = null;
+ switch (property)
{
- String? audioFileType = null;
- if (AudioCodec != null)
- {
- audioFileType = AudioCodec.FileExtension.Replace(".", "").ToUpper();
- }
- //Try to find by file name
- audioFileType ??= Path.GetExtension(Name)?.Replace(".", "").ToUpper();
- return audioFileType;
+ case nameof(Tracks):
+ validationStatus = ValidationStatus.Success;
+ if (Tracks.Count == 0)
+ {
+ validationMessages ??= [];
+ validationMessages.Add(new ValidationMessage("{0} has invalid count ({1})!", nameof(Tracks), 0));
+ }
+ else
+ {
+ //Check track overlapping
+ var tracksWithSamePosition = Tracks
+ .GroupBy(x => x.Position)
+ .Where(grp => grp.Count() > 1);
+ if (tracksWithSamePosition.Any())
+ {
+ validationMessages ??= [];
+ foreach (var track in tracksWithSamePosition)
+ {
+ foreach (var trackWithSamePosition in track)
+ {
+ validationMessages.Add(new ValidationMessage("{0} {1} '{2}' is used also by {3}({4},{5},{6},{7},{8}). Positions must be unique!", nameof(Track), nameof(Track.Position), track.Key != null ? track.Key : String.Empty, nameof(Track), trackWithSamePosition.Position != null ? trackWithSamePosition.Position : String.Empty, trackWithSamePosition.Artist ?? String.Empty, trackWithSamePosition.Title ?? String.Empty, trackWithSamePosition.Begin != null ? trackWithSamePosition.Begin : String.Empty, trackWithSamePosition.End != null ? trackWithSamePosition.End : String.Empty));
+ }
+ }
+ }
+ foreach (var track in Tracks.OrderBy(x => x.Position))
+ {
+ var tracksBetween = Tracks.Where(x => ((track.Begin >= x.Begin && track.Begin < x.End)
+ || (x.Begin < track.End && track.End <= x.End))
+ && (x.Equals(track) == false));
+ if (tracksBetween.Any())
+ {
+ validationMessages ??= [];
+ foreach (var trackBetween in tracksBetween)
+ {
+ validationMessages.Add(new ValidationMessage("{0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once!", nameof(Track), track.Position != null ? track.Position : String.Empty, track.Artist ?? String.Empty, track.Title ?? String.Empty, track.Begin != null ? track.Begin : String.Empty, track.End != null ? track.End : String.Empty, trackBetween.Position != null ? trackBetween.Position : String.Empty, trackBetween.Artist ?? String.Empty, trackBetween.Title ?? String.Empty, trackBetween.Begin != null ? trackBetween.Begin : String.Empty, trackBetween.End != null ? trackBetween.End : String.Empty));
+ }
+ }
+ }
+ }
+ break;
+ case nameof(Name):
+ validationStatus = ValidationStatus.Success;
+ if (String.IsNullOrEmpty(Name))
+ {
+ validationMessages ??= [];
+ validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(Name)));
+ }
+ break;
+ case nameof(AudioCodec):
+ validationStatus = ValidationStatus.Success;
+ if (AudioCodec == null)
+ {
+ validationMessages ??= [];
+ validationMessages.Add(new ValidationMessage("{0} has no value!", nameof(AudioCodec)));
+ }
+ break;
}
+ return ValidationResult.Create(validationStatus, validationMessages);
}
}
}
diff --git a/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs b/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs
index 1b044244..7fc829da 100644
--- a/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs
+++ b/AudioCuesheetEditor/Model/IO/Export/Exportprofile.cs
@@ -15,6 +15,7 @@
//.
using AudioCuesheetEditor.Model.AudioCuesheet;
using AudioCuesheetEditor.Model.Entity;
+using AudioCuesheetEditor.Model.IO.Audio;
namespace AudioCuesheetEditor.Model.IO.Export
{
@@ -22,118 +23,101 @@ public class Exportprofile : Validateable
{
public static readonly String DefaultFileName = "Export.txt";
- public static readonly String SchemeCuesheetArtist;
- public static readonly String SchemeCuesheetTitle;
- public static readonly String SchemeCuesheetAudiofile;
- public static readonly String SchemeCuesheetCDTextfile;
- public static readonly String SchemeCuesheetCatalogueNumber;
- public static readonly String SchemeTrackArtist;
- public static readonly String SchemeTrackTitle;
- public static readonly String SchemeTrackBegin;
- public static readonly String SchemeTrackEnd;
- public static readonly String SchemeTrackLength;
- public static readonly String SchemeTrackPosition;
- public static readonly String SchemeTrackFlags;
- public static readonly String SchemeTrackPreGap;
- public static readonly String SchemeTrackPostGap;
- public static readonly String SchemeDate;
- public static readonly String SchemeDateTime;
- public static readonly String SchemeTime;
+ public static readonly String SchemeCuesheetArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Artist), SchemeCharacter);
+ public static readonly String SchemeCuesheetTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Title), SchemeCharacter);
+ public static readonly String SchemeAudiofileName = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Audiofile), nameof(Audiofile.Name), SchemeCharacter);
+ public static readonly String SchemeCuesheetCDTextfile = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.CDTextfile), SchemeCharacter);
+ public static readonly String SchemeCuesheetCatalogueNumber = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Cataloguenumber), SchemeCharacter);
+ public static readonly String SchemeTrackArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Artist), SchemeCharacter);
+ public static readonly String SchemeTrackTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Title), SchemeCharacter);
+ public static readonly String SchemeTrackBegin = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Begin), SchemeCharacter);
+ public static readonly String SchemeTrackEnd = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.End), SchemeCharacter);
+ public static readonly String SchemeTrackLength = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Length), SchemeCharacter);
+ public static readonly String SchemeTrackPosition = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Position), SchemeCharacter);
+ public static readonly String SchemeTrackFlags = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Flags), SchemeCharacter);
+ public static readonly String SchemeTrackPreGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PreGap), SchemeCharacter);
+ public static readonly String SchemeTrackPostGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PostGap), SchemeCharacter);
+ public static readonly String SchemeDate = String.Format("{0}Date{1}", SchemeCharacter, SchemeCharacter);
+ public static readonly String SchemeDateTime = String.Format("{0}DateTime{1}", SchemeCharacter, SchemeCharacter);
+ public static readonly String SchemeTime = String.Format("{0}Time{1}", SchemeCharacter, SchemeCharacter);
- public static readonly Dictionary AvailableCuesheetSchemes;
- public static readonly Dictionary AvailableTrackSchemes;
-
- public const String SchemeCharacter = "%";
-
- private String schemeHead;
- private String schemeTracks;
- private String schemeFooter;
- private String filename;
- private String name;
-
- static Exportprofile()
+ public static readonly Dictionary AvailableCuesheetSchemes = new()
{
- SchemeDate = String.Format("{0}Date{1}", SchemeCharacter, SchemeCharacter);
- SchemeDateTime = String.Format("{0}DateTime{1}", SchemeCharacter, SchemeCharacter);
- SchemeTime = String.Format("{0}Time{1}", SchemeCharacter, SchemeCharacter);
-
- SchemeCuesheetArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Artist), SchemeCharacter);
- SchemeCuesheetTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Title), SchemeCharacter);
- SchemeCuesheetAudiofile = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Audiofile), SchemeCharacter);
- SchemeCuesheetCDTextfile = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.CDTextfile), SchemeCharacter);
- SchemeCuesheetCatalogueNumber = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Cuesheet), nameof(Cuesheet.Cataloguenumber), SchemeCharacter);
-
- AvailableCuesheetSchemes = new Dictionary
- {
- { nameof(Cuesheet.Artist), SchemeCuesheetArtist },
- { nameof(Cuesheet.Title), SchemeCuesheetTitle },
- { nameof(Cuesheet.Audiofile), SchemeCuesheetAudiofile },
- { nameof(Cuesheet.CDTextfile), SchemeCuesheetCDTextfile },
- { nameof(Cuesheet.Cataloguenumber), SchemeCuesheetCatalogueNumber },
- { "Date", SchemeDate },
- { "DateTime", SchemeDateTime },
- { "Time", SchemeTime }
- };
+ { nameof(Cuesheet.Artist), SchemeCuesheetArtist },
+ { nameof(Cuesheet.Title), SchemeCuesheetTitle },
+ { nameof(Cuesheet.CDTextfile), SchemeCuesheetCDTextfile },
+ { nameof(Cuesheet.Cataloguenumber), SchemeCuesheetCatalogueNumber },
+ { "Date", SchemeDate },
+ { "DateTime", SchemeDateTime },
+ { "Time", SchemeTime }
+ };
+ public static readonly Dictionary AvailableAudiofileSchemes = new()
+ {
+ { nameof(Audiofile.Name), SchemeAudiofileName }
+ };
+ public static readonly Dictionary AvailableTrackSchemes = new()
+ {
+ { nameof(Track.Position), SchemeTrackPosition },
+ { nameof(Track.Artist), SchemeTrackArtist },
+ { nameof(Track.Title), SchemeTrackTitle },
+ { nameof(Track.Begin), SchemeTrackBegin },
+ { nameof(Track.End), SchemeTrackEnd },
+ { nameof(Track.Length), SchemeTrackLength },
+ { nameof(Track.Flags), SchemeTrackFlags },
+ { nameof(Track.PreGap), SchemeTrackPreGap },
+ { nameof(Track.PostGap), SchemeTrackPostGap }
+ };
- SchemeTrackArtist = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Artist), SchemeCharacter);
- SchemeTrackTitle = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Title), SchemeCharacter);
- SchemeTrackBegin = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Begin), SchemeCharacter);
- SchemeTrackEnd = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.End), SchemeCharacter);
- SchemeTrackLength = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Length), SchemeCharacter);
- SchemeTrackPosition = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Position), SchemeCharacter);
- SchemeTrackFlags = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.Flags), SchemeCharacter);
- SchemeTrackPreGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PreGap), SchemeCharacter);
- SchemeTrackPostGap = String.Format("{0}{1}.{2}{3}", SchemeCharacter, nameof(Track), nameof(Track.PostGap), SchemeCharacter);
+ public const String SchemeCharacter = "%";
- AvailableTrackSchemes = new Dictionary()
- {
- { nameof(Track.Position), SchemeTrackPosition },
- { nameof(Track.Artist), SchemeTrackArtist },
- { nameof(Track.Title), SchemeTrackTitle },
- { nameof(Track.Begin), SchemeTrackBegin },
- { nameof(Track.End), SchemeTrackEnd },
- { nameof(Track.Length), SchemeTrackLength },
- { nameof(Track.Flags), SchemeTrackFlags },
- { nameof(Track.PreGap), SchemeTrackPreGap },
- { nameof(Track.PostGap), SchemeTrackPostGap }
- };
- }
+ private String _schemeHead;
+ private String _schemeAudiofiles;
+ private String _schemeTracks;
+ private String _schemeFooter;
+ private String _filename;
+ private String _name;
public Exportprofile()
{
Id = Guid.NewGuid();
- schemeHead = String.Empty;
- schemeTracks = String.Empty;
- schemeFooter = String.Empty;
- filename = DefaultFileName;
+ _schemeHead = String.Empty;
+ _schemeTracks = String.Empty;
+ _schemeAudiofiles = String.Empty;
+ _schemeFooter = String.Empty;
+ _filename = DefaultFileName;
var random = new Random();
- name = String.Format("{0}_{1}", nameof(Exportprofile), random.Next(1, 100));
+ _name = String.Format("{0}_{1}", nameof(Exportprofile), random.Next(1, 100));
}
public Guid Id { get; init; }
public String Name
{
- get => name;
- set { name = value; OnValidateablePropertyChanged(); }
+ get => _name;
+ set { _name = value; OnValidateablePropertyChanged(); }
}
public String SchemeHead
{
- get => schemeHead;
- set { schemeHead = value; OnValidateablePropertyChanged(); }
+ get => _schemeHead;
+ set { _schemeHead = value; OnValidateablePropertyChanged(); }
}
public String SchemeTracks
{
- get => schemeTracks;
- set { schemeTracks = value; OnValidateablePropertyChanged(); }
+ get => _schemeTracks;
+ set { _schemeTracks = value; OnValidateablePropertyChanged(); }
+ }
+ public String SchemeAudiofiles
+ {
+ get => _schemeAudiofiles;
+ set { _schemeAudiofiles = value; OnValidateablePropertyChanged(); }
}
public String SchemeFooter
{
- get => schemeFooter;
- set { schemeFooter = value; OnValidateablePropertyChanged(); }
+ get => _schemeFooter;
+ set { _schemeFooter = value; OnValidateablePropertyChanged(); }
}
public String Filename
{
- get => filename;
- set { filename = value; OnValidateablePropertyChanged(); }
+ get => _filename;
+ set { _filename = value; OnValidateablePropertyChanged(); }
}
public override ValidationResult Validate(string property)
@@ -144,39 +128,19 @@ public override ValidationResult Validate(string property)
{
case nameof(SchemeHead):
validationStatus = ValidationStatus.Success;
- foreach (var availableScheme in AvailableTrackSchemes)
- {
- if (SchemeHead.Contains(availableScheme.Value) == true)
- {
- validationMessages ??= [];
- validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeHead), availableScheme.Value));
- break;
- }
- }
+ validationMessages = CheckForUnresolvablePlaceholders(SchemeHead, nameof(SchemeHead), [AvailableTrackSchemes, AvailableAudiofileSchemes]);
break;
case nameof(SchemeTracks):
validationStatus = ValidationStatus.Success;
- foreach (var availableScheme in AvailableCuesheetSchemes)
- {
- if (SchemeTracks.Contains(availableScheme.Value) == true)
- {
- validationMessages ??= [];
- validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeTracks), availableScheme.Value));
- break;
- }
- }
+ validationMessages = CheckForUnresolvablePlaceholders(SchemeTracks, nameof(SchemeTracks), [AvailableCuesheetSchemes, AvailableAudiofileSchemes]);
+ break;
+ case nameof(SchemeAudiofiles):
+ validationStatus = ValidationStatus.Success;
+ validationMessages = CheckForUnresolvablePlaceholders(SchemeAudiofiles, nameof(SchemeAudiofiles), [AvailableTrackSchemes, AvailableCuesheetSchemes]);
break;
case nameof(SchemeFooter):
validationStatus = ValidationStatus.Success;
- foreach (var availableScheme in AvailableTrackSchemes)
- {
- if (SchemeFooter.Contains(availableScheme.Value) == true)
- {
- validationMessages ??= [];
- validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", nameof(SchemeFooter), availableScheme.Value));
- break;
- }
- }
+ validationMessages = CheckForUnresolvablePlaceholders(SchemeFooter, nameof(SchemeFooter), [AvailableTrackSchemes, AvailableAudiofileSchemes]);
break;
case nameof(Filename):
validationStatus = ValidationStatus.Success;
@@ -197,5 +161,23 @@ public override ValidationResult Validate(string property)
}
return ValidationResult.Create(validationStatus, validationMessages);
}
+
+ static List? CheckForUnresolvablePlaceholders(String scheme, string schemeName, List> schemesToCheck)
+ {
+ List? validationMessages = null;
+ foreach (var schemeToCheck in schemesToCheck)
+ {
+ foreach (var availableScheme in schemeToCheck)
+ {
+ if (scheme.Contains(availableScheme.Value) == true)
+ {
+ validationMessages ??= [];
+ validationMessages.Add(new ValidationMessage("{0} contains placeholder '{1}' that can not be resolved!", schemeName, availableScheme.Value));
+ break;
+ }
+ }
+ }
+ return validationMessages;
+ }
}
}
diff --git a/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs b/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs
index b1f824c4..822d1a4e 100644
--- a/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs
+++ b/AudioCuesheetEditor/Model/IO/Import/Importprofile.cs
@@ -13,7 +13,6 @@
//You should have received a copy of the GNU General Public License
//along with Foobar. If not, see
//.
-using AudioCuesheetEditor.Model.AudioCuesheet;
using AudioCuesheetEditor.Model.AudioCuesheet.Import;
using AudioCuesheetEditor.Model.Entity;
using AudioCuesheetEditor.Model.Utility;
@@ -22,18 +21,17 @@ namespace AudioCuesheetEditor.Model.IO.Import
{
public class Importprofile : Validateable
{
- public static readonly IEnumerable AvailableSchemeCuesheet;
- public static readonly IEnumerable AvailableSchemesTrack;
+ public static readonly IEnumerable AvailableSchemeCuesheet = [nameof(ImportCuesheet.Artist), nameof(ImportCuesheet.Title), nameof(ImportCuesheet.CDTextfile), nameof(ImportCuesheet.Cataloguenumber)];
+
+ public static readonly IEnumerable AvailableSchemeAudiofiles = [nameof(ImportAudiofile.Name)];
+
+ public static readonly IEnumerable AvailableSchemesTrack = [nameof(ImportTrack.Artist), nameof(ImportTrack.Title), nameof(ImportTrack.Begin), nameof(ImportTrack.End), nameof(ImportTrack.Length), nameof(ImportTrack.Position), nameof(ImportTrack.Flags), nameof(ImportTrack.PreGap), nameof(ImportTrack.PostGap), nameof(ImportTrack.StartDateTime)];
- static Importprofile()
- {
- AvailableSchemeCuesheet = [nameof(Cuesheet.Artist), nameof(Cuesheet.Title), nameof(Cuesheet.Audiofile), nameof(Cuesheet.CDTextfile), nameof(Cuesheet.Cataloguenumber)];
- AvailableSchemesTrack = [nameof(Track.Artist), nameof(Track.Title), nameof(Track.Begin), nameof(Track.End), nameof(Track.Length), nameof(Track.Position), nameof(Track.Flags), nameof(Track.PreGap), nameof(Track.PostGap), nameof(ImportTrack.StartDateTime)];
- }
public Guid Id { get; init; } = Guid.NewGuid();
public String? Name { get; set; }
public Boolean UseRegularExpression { get; set; }
public String? SchemeCuesheet { get; set; }
+ public String? SchemeAudiofiles { get; set; }
public String? SchemeTracks { get; set; }
public TimeSpanFormat? TimeSpanFormat { get; set; }
public override ValidationResult Validate(string property)
@@ -53,7 +51,7 @@ public override ValidationResult Validate(string property)
do
{
containsPlaceHolder = SchemeCuesheet?.Contains(enumerator.Current) == true;
- } while ((containsPlaceHolder == false) && (enumerator.MoveNext()));
+ } while ((containsPlaceHolder == false) && enumerator.MoveNext());
}
if (containsPlaceHolder == false)
{
@@ -62,6 +60,26 @@ public override ValidationResult Validate(string property)
}
}
break;
+ case nameof(SchemeAudiofiles):
+ validationStatus = ValidationStatus.Success;
+ if (String.IsNullOrEmpty(SchemeAudiofiles) == false)
+ {
+ var containsPlaceHolder = false;
+ var enumerator = AvailableSchemeAudiofiles.GetEnumerator();
+ if (enumerator.MoveNext())
+ {
+ do
+ {
+ containsPlaceHolder = SchemeAudiofiles?.Contains(enumerator.Current) == true;
+ } while ((containsPlaceHolder == false) && enumerator.MoveNext());
+ }
+ if (containsPlaceHolder == false)
+ {
+ validationMessages ??= [];
+ validationMessages.Add(new ValidationMessage("{0} contains no placeholder!", nameof(SchemeAudiofiles)));
+ }
+ }
+ break;
case nameof(SchemeTracks):
validationStatus = ValidationStatus.Success;
if (String.IsNullOrEmpty(SchemeTracks) == false)
@@ -73,7 +91,7 @@ public override ValidationResult Validate(string property)
do
{
containsPlaceHolder = SchemeTracks?.Contains(enumerator.Current) == true;
- } while ((containsPlaceHolder == false) && (enumerator.MoveNext()));
+ } while ((containsPlaceHolder == false) && enumerator.MoveNext());
}
if (containsPlaceHolder == false)
{
diff --git a/AudioCuesheetEditor/Model/IO/Projectfile.cs b/AudioCuesheetEditor/Model/IO/Projectfile.cs
index a1e187bc..767de0fc 100644
--- a/AudioCuesheetEditor/Model/IO/Projectfile.cs
+++ b/AudioCuesheetEditor/Model/IO/Projectfile.cs
@@ -39,9 +39,13 @@ public class Projectfile(Cuesheet cuesheet)
var cuesheet = JsonSerializer.Deserialize(fileContent, Options);
if (cuesheet != null)
{
- foreach (var track in cuesheet.Tracks)
+ foreach (var audiofile in cuesheet.Audiofiles)
{
- track.Cuesheet = cuesheet;
+ foreach (var track in audiofile.Tracks)
+ {
+ track.Audiofile = audiofile;
+ track.Cuesheet = cuesheet;
+ }
}
}
return cuesheet;
diff --git a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs
index 6773e70c..06ef0b15 100644
--- a/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs
+++ b/AudioCuesheetEditor/Model/Options/ApplicationOptions.cs
@@ -41,7 +41,6 @@ public CultureInfo Culture
}
public TimeSpanFormat? TimeSpanFormat { get; set; }
public Boolean DefaultIsLinkedToPreviousTrack { get; set; } = true;
- public Boolean FixedTracksTableHeader { get; set; } = false;
public String? DisplayTimeSpanFormat { get; set; }
public LogLevel MinimumLogLevel { get; set; } = DefaultLogLevel;
}
diff --git a/AudioCuesheetEditor/Model/Options/ImportOptions.cs b/AudioCuesheetEditor/Model/Options/ImportOptions.cs
index 0b186b9f..533c0771 100644
--- a/AudioCuesheetEditor/Model/Options/ImportOptions.cs
+++ b/AudioCuesheetEditor/Model/Options/ImportOptions.cs
@@ -25,7 +25,8 @@ public class ImportOptions : IOptions
{
Name = "Textfile (common data in first line)",
UseRegularExpression = false,
- SchemeCuesheet = $"{nameof(ImportCuesheet.Artist)} - {nameof(ImportCuesheet.Title)}\t{nameof(ImportCuesheet.Audiofile)}",
+ SchemeCuesheet = $"{nameof(ImportCuesheet.Artist)} - {nameof(ImportCuesheet.Title)}",
+ SchemeAudiofiles = $"- {nameof(ImportAudiofile.Name)}",
SchemeTracks = $"{nameof(ImportTrack.Artist)} - {nameof(ImportTrack.Title)}\t{nameof(ImportTrack.End)}"
};
diff --git a/AudioCuesheetEditor/Pages/Index.razor b/AudioCuesheetEditor/Pages/Index.razor
index 445ba103..a8b75db0 100644
--- a/AudioCuesheetEditor/Pages/Index.razor
+++ b/AudioCuesheetEditor/Pages/Index.razor
@@ -27,8 +27,8 @@ along with Foobar. If not, see
@inject ISnackbar _snackbar
@inject ICuesheetManager _cuesheetManager
-
-
+
+
@@ -42,14 +42,14 @@ along with Foobar. If not, see
@code{
- ViewOptions? options;
- ViewMode currentViewmode = ViewMode.DetailView;
+ ViewOptions? _viewOptions;
+ ViewMode _currentViewmode = ViewMode.DetailView;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
- options = await base.LocalStorageOptionsProvider.GetOptionsAsync();
- currentViewmode = options.ActiveTab;
+ _viewOptions = await base.LocalStorageOptionsProvider.GetOptionsAsync();
+ _currentViewmode = _viewOptions.ActiveTab;
base.LocalStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved;
_importManager.UploadFilesFinished += ImportManager_UploadFilesFinished;
}
@@ -66,16 +66,16 @@ along with Foobar. If not, see
async Task ActiveTabIndexChanged(int tabIndex)
{
- currentViewmode = (ViewMode)tabIndex;
- await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.ActiveTab, currentViewmode);
+ _currentViewmode = (ViewMode)tabIndex;
+ await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.ActiveTab, _currentViewmode);
}
void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions option)
{
if (option is ViewOptions viewOptions)
{
- options = viewOptions;
- currentViewmode = options.ActiveTab;
+ _viewOptions = viewOptions;
+ _currentViewmode = _viewOptions.ActiveTab;
StateHasChanged();
}
}
@@ -113,10 +113,6 @@ along with Foobar. If not, see
await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.ActiveTab, ViewMode.ImportView);
break;
}
- if (_sessionStateContainer.ImportAudiofile != null)
- {
- _cuesheetManager.SetProperty(x => x.Audiofile, _sessionStateContainer.ImportAudiofile);
- }
}
finally
{
diff --git a/AudioCuesheetEditor/Program.cs b/AudioCuesheetEditor/Program.cs
index 031766d3..0f2f478f 100644
--- a/AudioCuesheetEditor/Program.cs
+++ b/AudioCuesheetEditor/Program.cs
@@ -24,7 +24,6 @@
using AudioCuesheetEditor.Services.UI;
using AudioCuesheetEditor.Services.Validation;
using BlazorDownloadFile;
-using Howler.Blazor.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MudBlazor.Services;
@@ -41,21 +40,11 @@
config.PopoverOptions.OverflowPadding = 0;
});
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-
builder.Services.AddBlazorDownloadFile();
builder.Services.AddScoped();
builder.Services.AddScoped();
-
-builder.Services.AddScoped(x =>
-{
- var localStorageOptionsProvider = x.GetRequiredService();
- var sessionStateContainer = new SessionStateContainer(localStorageOptionsProvider);
- _ = sessionStateContainer.InitializeAsync();
- return sessionStateContainer;
-});
+builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
@@ -71,6 +60,7 @@
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.AddScoped();
builder.Services.AddLogging();
// Read out configuration for loglevel
@@ -83,5 +73,5 @@
var host = builder.Build();
await host.SetCultureFromConfigurationAsync();
-
+await host.InitializeSessionStateContainer();
await host.RunAsync();
\ No newline at end of file
diff --git a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
index 68142028..04f785ea 100644
--- a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
+++ b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs
@@ -16,21 +16,21 @@
using AudioCuesheetEditor.Model.AudioCuesheet;
using AudioCuesheetEditor.Model.IO.Audio;
using AudioCuesheetEditor.Services.UI;
-using Howler.Blazor.Components;
+using Microsoft.JSInterop;
namespace AudioCuesheetEditor.Services.Audio
{
- public class PlaybackService : IDisposable
+ public class PlaybackService(IJSRuntime jsRuntime, ISessionStateContainer sessionStateContainer) : IAsyncDisposable
{
- private readonly ISessionStateContainer _sessionStateContainer;
- private readonly IHowl _howl;
+ private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer;
+ private readonly IJSRuntime _jsRuntime = jsRuntime;
- private int? _currentPlayingSoundId;
private Audiofile? _currentlyPlayingAudiofile;
private Timer? _updateTimer;
- private bool _disposedValue;
private readonly Lock _timerLock = new();
private TimeSpan? _currentPosition;
+ private DotNetObjectReference? _dotNetObjectReference;
+ private TimeSpan? _audiofileDurationsBeforeCurrentlyPlayingAudiofile;
public event Action? CurrentPositionChanged;
@@ -46,64 +46,53 @@ private set
}
}
}
- public Track? CurrentlyPlayingTrack => _sessionStateContainer.Cuesheet.Tracks.SingleOrDefault(x => x.Begin.HasValue == true && x.End.HasValue == true && x.Begin <= CurrentPosition && x.End > CurrentPosition);
- public TimeSpan? TotalTime => _sessionStateContainer.Cuesheet.Audiofile?.Duration;
- public Boolean IsPlaying { get; private set; } = false;
- public Boolean IsPlaybackPossible
+ public Track? CurrentlyPlayingTrack => _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).SingleOrDefault(x => x.Begin.HasValue == true && x.End.HasValue == true && x.Begin <= CurrentPosition && x.End > CurrentPosition);
+ public TimeSpan? TotalTime
{
get
{
- var audiofile = _sessionStateContainer.Cuesheet.Audiofile;
- return String.IsNullOrEmpty(audiofile?.ObjectURL) == false && String.IsNullOrEmpty(audiofile?.AudioFileType) == false;
+ var durations = _sessionStateContainer.Cuesheet.Audiofiles.Where(a => a.Duration.HasValue).Select(x => x.Duration);
+ if (durations.Any())
+ {
+ return durations.Aggregate((sum, a) => sum + a);
+ }
+ return null;
}
}
- public Boolean IsPreviousPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Tracks.FirstOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin) != null;
- public Boolean IsNextPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Tracks.FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End) != null;
+ public Boolean IsPaused { get; private set; } = false;
+ public Boolean IsPlaybackPossible => _sessionStateContainer.Cuesheet.Audiofiles.Any(x => string.IsNullOrEmpty(x.ObjectURL) == false);
+ public Boolean IsPreviousPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin) != null;
+ public Boolean IsNextPossible => (CurrentlyPlayingTrack != null) && _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End) != null;
+ public Boolean IsPlaying => _currentlyPlayingAudiofile != null;
- public PlaybackService(ISessionStateContainer sessionStateContainer, IHowl howl)
+ public async Task InitializeAsync()
{
- _sessionStateContainer = sessionStateContainer;
- _howl = howl;
- _howl.OnPlay += Howl_OnPlay;
- _howl.OnPause += Howl_OnPause;
- _howl.OnEnd += Howl_OnEnd;
- _howl.OnStop += Howl_OnStop;
+ if (_dotNetObjectReference == null)
+ {
+ _dotNetObjectReference = DotNetObjectReference.Create(this);
+ await _jsRuntime.InvokeVoidAsync("audioInterop.register", _dotNetObjectReference);
+ }
}
public async Task PlayOrPauseAsync()
{
- //Reset if the last played audiofile is not the current one
- if (_currentlyPlayingAudiofile != _sessionStateContainer.Cuesheet.Audiofile)
+ if (_currentlyPlayingAudiofile != null)
{
- _currentPlayingSoundId = null;
- }
- //If the current audiofile already started, we just pause
- if (_currentPlayingSoundId != null)
- {
- await _howl.Pause(_currentPlayingSoundId.Value);
+ if (IsPaused == false)
+ {
+ await _jsRuntime.InvokeVoidAsync("audioInterop.pauseAudio");
+ }
+ else
+ {
+ await _jsRuntime.InvokeVoidAsync("audioInterop.playAudio");
+ }
}
else
{
- if (IsPlaybackPossible)
+ var audiofileToPlay = _sessionStateContainer.Cuesheet.Audiofiles.FirstOrDefault(x => string.IsNullOrEmpty(x.ObjectURL) == false);
+ if (audiofileToPlay != null)
{
- string[]? sources = null;
- string[]? formats = null;
- if (_sessionStateContainer.Cuesheet.Audiofile?.ObjectURL != null)
- {
- sources = [_sessionStateContainer.Cuesheet.Audiofile.ObjectURL];
- }
- if (_sessionStateContainer.Cuesheet.Audiofile?.AudioFileType != null)
- {
- formats = [_sessionStateContainer.Cuesheet.Audiofile.AudioFileType.ToLower()];
- }
- var options = new HowlOptions
- {
- Sources = sources,
- Formats = formats,
- Html5 = true
- };
- _currentPlayingSoundId = await _howl.Play(options);
- _currentlyPlayingAudiofile = _sessionStateContainer.Cuesheet.Audiofile;
+ await PlayAsync(audiofileToPlay);
}
}
}
@@ -112,30 +101,21 @@ public async Task PlayAsync(Track trackToPlay)
{
if (trackToPlay?.Begin.HasValue == true)
{
- if (IsPlaying == false)
- {
- await PlayOrPauseAsync();
- }
- if (_currentPlayingSoundId.HasValue)
- {
- await _howl.Seek(_currentPlayingSoundId.Value, trackToPlay.Begin.Value);
- }
+ await SeekAsync(trackToPlay.Begin.Value);
}
}
public async Task StopAsync()
{
- if (_currentPlayingSoundId != null)
- {
- await _howl.Stop(_currentPlayingSoundId.Value);
- }
+ Reset();
+ await _jsRuntime.InvokeVoidAsync("audioInterop.stopAudio");
}
public async Task PlayNextTrackAsync()
{
if (CurrentlyPlayingTrack != null)
{
- var trackToPlay = _sessionStateContainer.Cuesheet.Tracks.FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End);
+ var trackToPlay = _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Begin >= CurrentlyPlayingTrack.End);
if (trackToPlay != null)
{
await PlayAsync(trackToPlay);
@@ -147,7 +127,7 @@ public async Task PlayPreviousTrackAsync()
{
if (CurrentlyPlayingTrack != null)
{
- var trackToPlay = _sessionStateContainer.Cuesheet.Tracks.LastOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin);
+ var trackToPlay = _sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).LastOrDefault(x => x.End <= CurrentlyPlayingTrack.Begin);
if (trackToPlay != null)
{
await PlayAsync(trackToPlay);
@@ -157,92 +137,163 @@ public async Task PlayPreviousTrackAsync()
public async Task SeekAsync(TimeSpan time)
{
- if (_currentPlayingSoundId.HasValue == false)
+ TimeSpan cumulativeDuration = TimeSpan.Zero;
+ Audiofile? targetAudiofile = null;
+ TimeSpan targetPositionInAudiofile = TimeSpan.Zero;
+
+ foreach (var audiofile in _sessionStateContainer.Cuesheet.Audiofiles)
{
- await PlayOrPauseAsync();
+ if (string.IsNullOrEmpty(audiofile.ObjectURL))
+ {
+ continue;
+ }
+
+ if (audiofile.Duration.HasValue)
+ {
+ TimeSpan nextCumulativeDuration = cumulativeDuration + audiofile.Duration.Value;
+
+ if (time >= cumulativeDuration && time < nextCumulativeDuration)
+ {
+ targetAudiofile = audiofile;
+ targetPositionInAudiofile = time - cumulativeDuration;
+ break;
+ }
+
+ cumulativeDuration = nextCumulativeDuration;
+ }
}
- if (_currentPlayingSoundId.HasValue)
+
+ if (targetAudiofile == null)
{
- if (IsPlaying == false)
+ targetAudiofile = _sessionStateContainer.Cuesheet.Audiofiles.FirstOrDefault(x => string.IsNullOrEmpty(x.ObjectURL) == false);
+
+ if (targetAudiofile == null)
{
- await PlayOrPauseAsync();
+ return;
}
- await _howl.Seek(_currentPlayingSoundId.Value, time);
}
+
+ if (_currentlyPlayingAudiofile != targetAudiofile)
+ {
+ await PlayAsync(targetAudiofile);
+ }
+
+ if (IsPaused)
+ {
+ await PlayOrPauseAsync();
+ }
+
+ await _jsRuntime.InvokeVoidAsync("audioInterop.seekAudio", targetPositionInAudiofile.TotalSeconds);
}
- public void Dispose()
+ [JSInvokable]
+ public void OnPlaybackStarted()
{
- // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in der Methode "Dispose(bool disposing)" ein.
- Dispose(disposing: true);
- GC.SuppressFinalize(this);
+ IsPaused = false;
+ StartTimer();
}
- protected virtual void Dispose(bool disposing)
+ [JSInvokable]
+ public void OnPlaybackEnded(string objectUrlEnded)
{
- if (!_disposedValue)
+ var audioFileEnded = _sessionStateContainer.Cuesheet.Audiofiles.Single(x => x.ObjectURL == objectUrlEnded);
+ var index = _sessionStateContainer.Cuesheet.Audiofiles.IndexOf(audioFileEnded);
+ if (index < _sessionStateContainer.Cuesheet.Audiofiles.Count - 1)
{
- if (disposing)
+ var nextAudioFile = _sessionStateContainer.Cuesheet.Audiofiles.Where(x => string.IsNullOrEmpty(x.ObjectURL) == false).Skip(index + 1).FirstOrDefault();
+ if (nextAudioFile != null)
{
- _howl.OnPlay -= Howl_OnPlay;
- _howl.OnPause -= Howl_OnPause;
- _howl.OnEnd -= Howl_OnEnd;
- _howl.OnStop -= Howl_OnStop;
+ _ = PlayAsync(nextAudioFile);
}
- _disposedValue = true;
+ else
+ {
+ // No more audio files to play, we stop playback
+ Reset();
+ }
+ }
+ else
+ {
+ // No more audio files to play, we stop playback
+ Reset();
}
}
- private void Howl_OnStop(Howler.Blazor.Components.Events.HowlEventArgs obj)
+ [JSInvokable]
+ public void OnPlaybackPaused()
{
- IsPlaying = false;
- _currentPlayingSoundId = null;
+ IsPaused = true;
StopTimer();
- CurrentPosition = null;
- _currentlyPlayingAudiofile = _sessionStateContainer.Cuesheet.Audiofile;
+ UpdateCurrentPosition(null);
}
- private void Howl_OnEnd(Howler.Blazor.Components.Events.HowlEventArgs obj)
+ public async ValueTask DisposeAsync()
{
- IsPlaying = false;
- StopTimer();
- CurrentPosition = null;
+ GC.SuppressFinalize(this);
+ await _jsRuntime.InvokeVoidAsync("audioInterop.unregister");
+ _dotNetObjectReference?.Dispose();
}
- private void Howl_OnPause(Howler.Blazor.Components.Events.HowlEventArgs obj)
+ async Task PlayAsync(Audiofile audiofileToPlay)
{
- IsPlaying = false;
- StopTimer();
+ await _jsRuntime.InvokeVoidAsync("audioInterop.setAudioSource", audiofileToPlay.ObjectURL);
+ await _jsRuntime.InvokeVoidAsync("audioInterop.playAudio");
+ _currentlyPlayingAudiofile = audiofileToPlay;
+ _audiofileDurationsBeforeCurrentlyPlayingAudiofile = null;
}
- private void Howl_OnPlay(Howler.Blazor.Components.Events.HowlPlayEventArgs obj)
+ void Reset()
{
- IsPlaying = true;
- StartTimer();
+ StopTimer();
+ _currentlyPlayingAudiofile = null;
+ _audiofileDurationsBeforeCurrentlyPlayingAudiofile = null;
+ CurrentPosition = null;
+ IsPaused = false;
}
- private void StartTimer()
+ void StartTimer()
{
_updateTimer ??= new Timer(UpdateCurrentPosition, null, 0, 500);
}
- private void StopTimer()
+ void StopTimer()
{
_updateTimer?.Dispose();
_updateTimer = null;
}
- private async void UpdateCurrentPosition(object? state)
+ async void UpdateCurrentPosition(object? state)
{
- // Thread-safe access
lock (_timerLock)
{
- if (_currentPlayingSoundId == null || !IsPlaying) return;
+ if (_currentlyPlayingAudiofile == null)
+ {
+ StopTimer();
+ }
+ }
+ CalculateDurationsBeforeCurrentlyPlayingAudiofile();
+ var currentSecondsInCurrentlyPlayingAudiofile = await _jsRuntime.InvokeAsync("audioInterop.getAudioCurrentTime");
+ if (_audiofileDurationsBeforeCurrentlyPlayingAudiofile.HasValue)
+ {
+ CurrentPosition = _audiofileDurationsBeforeCurrentlyPlayingAudiofile + TimeSpan.FromSeconds(currentSecondsInCurrentlyPlayingAudiofile);
+ }
+ else
+ {
+ CurrentPosition = TimeSpan.FromSeconds(currentSecondsInCurrentlyPlayingAudiofile);
+ }
+ }
+
+ void CalculateDurationsBeforeCurrentlyPlayingAudiofile()
+ {
+ if ((_audiofileDurationsBeforeCurrentlyPlayingAudiofile != null) || (_currentlyPlayingAudiofile == null))
+ {
+ return;
}
- CurrentPosition = await _howl.GetCurrentTime(_currentPlayingSoundId.Value);
- if (_sessionStateContainer.Cuesheet.Audiofile != _currentlyPlayingAudiofile)
+ _audiofileDurationsBeforeCurrentlyPlayingAudiofile = TimeSpan.Zero;
+ var index = _sessionStateContainer.Cuesheet.Audiofiles.IndexOf(_currentlyPlayingAudiofile);
+ for (int i = 0; i < index; i++)
{
- await _howl.Stop(_currentPlayingSoundId.Value);
+ var audiofile = _sessionStateContainer.Cuesheet.Audiofiles[i];
+ _audiofileDurationsBeforeCurrentlyPlayingAudiofile += audiofile.Duration;
}
}
}
diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs
new file mode 100644
index 00000000..a169a3cb
--- /dev/null
+++ b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs
@@ -0,0 +1,224 @@
+//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
+//.
+using AudioCuesheetEditor.Model.AudioCuesheet;
+using AudioCuesheetEditor.Model.IO.Audio;
+using AudioCuesheetEditor.Services.IO;
+using AudioCuesheetEditor.Services.UI;
+using Microsoft.AspNetCore.Components.Forms;
+using Microsoft.JSInterop;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace AudioCuesheetEditor.Services.AudioCuesheet
+{
+ public class AudiofileManager(IFileInputManager fileInputManager, ITraceChangeManager traceChangeManager, IJSRuntime jsRuntime, ITrackManager trackManager, ISessionStateContainer sessionStateContainer) : IAudiofileManager
+ {
+ private readonly IFileInputManager _fileInputManager = fileInputManager;
+ private readonly ITraceChangeManager _traceChangeManager = traceChangeManager;
+ private readonly IJSRuntime _jsRuntime = jsRuntime;
+ private readonly ITrackManager _trackManager = trackManager;
+ private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer;
+
+ ///
+ public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId)
+ {
+ _traceChangeManager.BulkEdit = true;
+ if (browserFile == null)
+ {
+ if (string.IsNullOrEmpty(audiofile.ObjectURL) == false)
+ {
+ await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", audiofile.ObjectURL);
+ }
+ SetValue(audiofile, x => x.AudioCodec, null);
+ SetValue(audiofile, x => x.Name, null);
+ SetValue(audiofile, x => x.ObjectURL, null);
+ SetValue(audiofile, x => x.Duration, null);
+ }
+ else
+ {
+ var codec = _fileInputManager.GetAudioCodec(browserFile.ContentType, browserFile.Name);
+ var objectUrl = await _fileInputManager.GetObjectUrlAsync(fileInputId);
+ TimeSpan? duration = null;
+ if (String.IsNullOrEmpty(objectUrl) == false)
+ {
+ var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", objectUrl);
+ duration = TimeSpan.FromSeconds(durationSeconds);
+ }
+ SetValue(audiofile, x => x.AudioCodec, codec);
+ SetValue(audiofile, x => x.Name, browserFile.Name);
+ SetValue(audiofile, x => x.ObjectURL, objectUrl);
+ SetValue(audiofile, x => x.Duration, duration);
+ SetLastTrackEnd(audiofile);
+ }
+ _traceChangeManager.BulkEdit = false;
+ }
+
+ ///
+ public void SetProperty(Audiofile audiofile, Expression> propertyExpression, TProperty value)
+ {
+ SetValue(audiofile, propertyExpression, value);
+ SetLastTrackEnd(audiofile);
+ }
+
+ ///
+ public void AddTrack(Audiofile audiofile, Track track, Boolean setTracing = true)
+ {
+ if (setTracing)
+ {
+ _traceChangeManager.BulkEdit = true;
+ }
+ var cuesheet = _sessionStateContainer.GetActiveCuesheet();
+ track.Cuesheet = cuesheet;
+ track.Audiofile = audiofile;
+ if ((cuesheet?.IsRecording == true) && cuesheet.Audiofiles.SelectMany(x => x.Tracks).Any(x => x.Position >= 1))
+ {
+ _trackManager.SetProperty(track, x => x.Begin, DateTime.UtcNow - cuesheet.RecordingStart);
+ }
+ var lastTrack = GetLastTrack(audiofile);
+ if ((audiofile.Duration.HasValue == true) && (lastTrack?.End.HasValue == true) && (lastTrack.End == audiofile.Duration))
+ {
+ _trackManager.SetProperty(lastTrack, x => x.End, null);
+ }
+ var newValue = new List(audiofile.Tracks)
+ {
+ track
+ };
+ SetValue(audiofile, x => x.Tracks, newValue);
+ RecalculateTrackProperties(cuesheet!);
+ if (setTracing)
+ {
+ _traceChangeManager.BulkEdit = false;
+ }
+ }
+
+ ///
+ public void RemoveTracks(Audiofile audiofile, IEnumerable tracksToRemove, Boolean setTracing = true)
+ {
+ var cuesheet = _sessionStateContainer.GetActiveCuesheet();
+ var intersection = audiofile.Tracks.Intersect(tracksToRemove);
+ foreach (var track in intersection)
+ {
+ track.Audiofile = null;
+ }
+ ICollection newValue = [.. audiofile.Tracks.Except(intersection)];
+ if (setTracing)
+ {
+ _traceChangeManager.BulkEdit = true;
+ }
+ SetValue(audiofile, x => x.Tracks, newValue);
+ RecalculateTrackProperties(cuesheet!);
+ if (setTracing)
+ {
+ _traceChangeManager.BulkEdit = false;
+ }
+ }
+
+ void SetValue(Audiofile audiofile, Expression> propertyExpression, TProperty value)
+ {
+ if (propertyExpression.Body is not MemberExpression memberExpression)
+ {
+ throw new ArgumentException("Expression must be a property");
+ }
+
+ if (memberExpression.Member is not PropertyInfo propertyInfo)
+ {
+ throw new ArgumentException("Member is not a property");
+ }
+
+ var previousValue = (TProperty?)propertyInfo.GetValue(audiofile);
+ if (Equals(previousValue, value))
+ {
+ return;
+ }
+
+ propertyInfo.SetValue(audiofile, value);
+ _traceChangeManager.AddChange(new(audiofile, new(previousValue, propertyInfo.Name)));
+ }
+
+ void RecalculateTrackProperties(Cuesheet cuesheet)
+ {
+ // Unset first track begin
+ var firstTrack = GetFirstTrack(cuesheet);
+ if (firstTrack?.Begin == TimeSpan.Zero)
+ {
+ _trackManager.SetProperty(firstTrack, x => x.Begin, null);
+ }
+ // Recalculate position, begin and end ascending
+ ushort position = 1;
+ foreach (var audiofile in cuesheet.Audiofiles)
+ {
+ foreach (var track in audiofile.Tracks)
+ {
+ if (track.Position != position)
+ {
+ _trackManager.SetProperty(track, x => x.Position, position);
+ }
+ var previousTrack = _trackManager.GetPreviousLinkedTrack(track);
+ if (previousTrack?.End.HasValue == true)
+ {
+ _trackManager.SetProperty(track, x => x.Begin, previousTrack.End);
+ }
+ else
+ {
+ if (previousTrack != null)
+ {
+ _trackManager.SetProperty(previousTrack, x => x.End, track.Begin);
+ }
+ }
+ position++;
+ }
+ }
+ // Set first track begin
+ firstTrack = GetFirstTrack(cuesheet);
+ if (firstTrack?.Begin.HasValue == false)
+ {
+ _trackManager.SetProperty(firstTrack, x => x.Begin, TimeSpan.Zero);
+ }
+ // Set track ends based on audiofile duration
+ foreach (var audiofile in cuesheet.Audiofiles)
+ {
+ SetLastTrackEnd(audiofile);
+ }
+ }
+
+ Track? GetLastTrack(Audiofile audiofile)
+ {
+ return audiofile.Tracks.OrderByDescending(x => x.Position.HasValue).ThenBy(x => x.Position)
+ .ThenByDescending(x => x.Begin.HasValue).ThenBy(x => x.Begin)
+ .ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End)
+ .LastOrDefault();
+ }
+
+ void SetLastTrackEnd(Audiofile audiofile)
+ {
+ var lastTrack = GetLastTrack(audiofile);
+ if ((lastTrack?.End.HasValue == false) && (audiofile.Duration.HasValue == true))
+ {
+ _trackManager.SetProperty(lastTrack, x => x.End, audiofile.Duration);
+ }
+ }
+
+ static Track? GetFirstTrack(Cuesheet cuesheet)
+ {
+ return cuesheet.Audiofiles.SelectMany(x => x.Tracks)
+ .OrderByDescending(x => x.Position.HasValue).ThenBy(x => x.Position)
+ .ThenByDescending(x => x.Begin.HasValue).ThenBy(x => x.Begin)
+ .ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End)
+ .FirstOrDefault();
+ }
+
+ }
+}
diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs
index 6f45baa8..5ba287d8 100644
--- a/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs
+++ b/AudioCuesheetEditor/Services/AudioCuesheet/CuesheetManager.cs
@@ -14,18 +14,22 @@
//along with Foobar. If not, see
//.
using AudioCuesheetEditor.Model.AudioCuesheet;
+using AudioCuesheetEditor.Model.IO.Audio;
using AudioCuesheetEditor.Services.UI;
+using Microsoft.JSInterop;
using System.Linq.Expressions;
using System.Reflection;
namespace AudioCuesheetEditor.Services.AudioCuesheet
{
///
- public class CuesheetManager(ITraceChangeManager traceChangeManager, ISessionStateContainer sessionStateContainer, ITrackManager trackManager) : ICuesheetManager
+ public class CuesheetManager(ITraceChangeManager traceChangeManager, ISessionStateContainer sessionStateContainer, ITrackManager trackManager, IAudiofileManager audiofileManager, IJSRuntime jsRuntime) : ICuesheetManager
{
private readonly ITraceChangeManager _traceChangeManager = traceChangeManager;
private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer;
private readonly ITrackManager _trackManager = trackManager;
+ private readonly IAudiofileManager _audiofileManager = audiofileManager;
+ private readonly IJSRuntime _jsRuntime = jsRuntime;
public event EventHandler? IsRecordingChanged;
@@ -34,13 +38,7 @@ public void SetProperty(Expression> propert
{
_traceChangeManager.BulkEdit = true;
var cuesheet = _sessionStateContainer.GetActiveCuesheet();
- var audiofile = cuesheet?.Audiofile;
SetValue(cuesheet!, propertyExpression, value);
- // If audiofile has been set, we need to calculate last track end
- if (audiofile != cuesheet?.Audiofile)
- {
- SetLastTrackEnd(cuesheet!);
- }
_traceChangeManager.BulkEdit = false;
}
@@ -49,7 +47,11 @@ public Result IsRecordingPossible
{
get
{
- if (_sessionStateContainer.Cuesheet.Tracks.Any())
+ if (_sessionStateContainer.Cuesheet.IsRecording == true)
+ {
+ return Result.Failure(new Error(ErrorType.NotPossible, "Record is already running!"));
+ }
+ if (_sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).Any())
{
return Result.Failure(new Error(ErrorType.NotPossible, "Cuesheet already contains tracks!"));
}
@@ -69,6 +71,10 @@ public Result StartRecording()
return Result.Failure(new Error(ErrorType.NotPossible, "Record is already running!"));
}
cuesheet.RecordingStart = DateTime.UtcNow;
+ if (cuesheet.Audiofiles.Count == 0)
+ {
+ cuesheet.Audiofiles.Add(new Audiofile());
+ }
IsRecordingChanged?.Invoke(this, EventArgs.Empty);
return Result.Success();
}
@@ -81,7 +87,7 @@ public void StopRecording()
var cuesheet = _sessionStateContainer.Cuesheet;
if (cuesheet.IsRecording == true)
{
- var lastTrack = cuesheet.Tracks.LastOrDefault();
+ var lastTrack = GetLastTrack(cuesheet);
if ((lastTrack != null) && cuesheet.RecordingStart.HasValue)
{
lastTrack.End = DateTime.UtcNow - cuesheet.RecordingStart.Value;
@@ -92,145 +98,186 @@ public void StopRecording()
}
///
- public void AddTrack(Track track)
+ public bool IsMoveUpPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Min(x => x.Position) >= 2;
+
+ ///
+ public bool IsMoveUpPossible(HashSet selectedAudiofiles)
{
+ if (selectedAudiofiles.Count == 0)
+ {
+ return false;
+ }
var cuesheet = _sessionStateContainer.GetActiveCuesheet();
- track.Cuesheet = cuesheet;
- // Calculate track properties
- _traceChangeManager.BulkEdit = true;
- if (cuesheet?.IsRecording == true)
+ if (cuesheet?.Audiofiles.Count > 0)
{
- _trackManager.SetProperty(track, x => x.Begin, DateTime.UtcNow - cuesheet.RecordingStart);
+ return !selectedAudiofiles.Contains(cuesheet.Audiofiles.First());
}
- if (cuesheet?.Tracks.Any() == false)
+ return false;
+ }
+
+ ///
+ public bool IsMoveDownPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Max(x => x.Position) < _sessionStateContainer.GetActiveCuesheet()?.Audiofiles.SelectMany(x => x.Tracks).Max(x => x.Position);
+
+ ///
+ public bool IsMoveDownPossible(HashSet selectedAudiofiles)
+ {
+ if (selectedAudiofiles.Count == 0)
{
- _trackManager.SetProperty(track, x => x.Position, (ushort)(1));
- if ((track.Begin.HasValue == false) || cuesheet.IsRecording)
- {
- _trackManager.SetProperty(track, x => x.Begin, TimeSpan.Zero);
- }
+ return false;
}
- else
+ var cuesheet = _sessionStateContainer.GetActiveCuesheet();
+ if (cuesheet?.Audiofiles.Count > 0)
{
- var lastTrack = GetLastTrack(cuesheet!);
- if ((cuesheet?.Audiofile?.Duration.HasValue == true) && (lastTrack?.End.HasValue == true) && (lastTrack.End == cuesheet.Audiofile.Duration))
- {
- _trackManager.SetProperty(lastTrack, x => x.End, null);
- }
- if (track.Position.HasValue == false)
- {
- _trackManager.SetProperty(track, x => x.Position, (ushort?)(lastTrack?.Position + 1));
- }
- if (track.Begin.HasValue == false)
+ return !selectedAudiofiles.Contains(cuesheet.Audiofiles.Last());
+ }
+ return false;
+ }
+
+ ///
+ public Result MoveUp(HashSet selectedTracks)
+ {
+ if (IsMoveUpPossible(selectedTracks) == false)
+ {
+ return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks up is not possible!"));
+ }
+ _traceChangeManager.BulkEdit = true;
+ var cuesheet = _sessionStateContainer.GetActiveCuesheet();
+ foreach (var selectedTrack in selectedTracks.OrderBy(x => x.Position))
+ {
+ var previousTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position - 1);
+ if (previousTrack?.Audiofile != null && previousTrack.Audiofile != selectedTrack.Audiofile)
{
- _trackManager.SetProperty(track, x => x.Begin, lastTrack?.End);
+ SwitchAudiofile(selectedTrack, previousTrack.Audiofile);
}
else
{
- if (lastTrack?.End.HasValue == false)
+ var newBegin = previousTrack?.Begin;
+ var newEnd = previousTrack?.End;
+ if (previousTrack != null)
{
- _trackManager.SetProperty(lastTrack, x => x.End, track.Begin);
+ _trackManager.SetProperty(previousTrack, x => x.Position, selectedTrack.Position);
+ _trackManager.SetProperty(previousTrack, x => x.Begin, selectedTrack.Begin);
+ _trackManager.SetProperty(previousTrack, x => x.End, selectedTrack.End);
}
+ _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position - 1));
+ _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin);
+ _trackManager.SetProperty(selectedTrack, x => x.End, newEnd);
}
- if (cuesheet?.IsRecording == true && lastTrack != null)
- {
- _trackManager.SetProperty(lastTrack, x => x.End, track.Begin);
- }
+
}
- var newValue = new List(cuesheet!.Tracks)
+ foreach (var audiofile in cuesheet!.Audiofiles)
{
- track
- };
- SetValue(cuesheet, x => x.Tracks, newValue);
- SetLastTrackEnd(cuesheet);
+ var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList();
+ _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks);
+ }
_traceChangeManager.BulkEdit = false;
+ return Result.Success();
}
///
- public void RemoveTracks(IEnumerable tracksToRemove)
+ public Result MoveUp(HashSet selectedAudiofiles)
{
+ if (IsMoveUpPossible(selectedAudiofiles) == false)
+ {
+ return Result.Failure(new Error(ErrorType.NotPossible, "Moving audiofiles up is not possible!"));
+ }
var cuesheet = _sessionStateContainer.GetActiveCuesheet();
- var intersection = cuesheet!.Tracks.Intersect(tracksToRemove);
- ICollection newValue = [.. cuesheet.Tracks.Except(intersection)];
- //Calculate position and begin of new tracks
- ushort position = 1;
- foreach (var track in newValue.OrderBy(x => x.Position))
+ _traceChangeManager.BulkEdit = true;
+ var newAudiofiles = new List(cuesheet!.Audiofiles);
+ foreach (var audiofile in selectedAudiofiles)
{
- track.Position = position;
- position++;
- var previousTrack = _trackManager.GetPreviousLinkedTrack(track);
- if (previousTrack?.End.HasValue == true)
+ var index = newAudiofiles.IndexOf(audiofile);
+ var previousAudiofile = newAudiofiles[index - 1];
+ newAudiofiles[index] = previousAudiofile;
+ newAudiofiles[index - 1] = audiofile;
+ var previousAudiofileTracks = previousAudiofile.Tracks;
+ var audiofileTracks = audiofile.Tracks;
+ _audiofileManager.RemoveTracks(previousAudiofile, previousAudiofileTracks, false);
+ _audiofileManager.RemoveTracks(audiofile, audiofileTracks, false);
+ foreach (var track in previousAudiofileTracks)
{
- track.Begin = previousTrack.End;
+ _audiofileManager.AddTrack(audiofile, track, false);
+ }
+ foreach (var track in audiofileTracks)
+ {
+ _audiofileManager.AddTrack(previousAudiofile, track, false);
}
}
- _traceChangeManager.BulkEdit = true;
- SetValue(cuesheet, x => x.Tracks, newValue);
- SetLastTrackEnd(cuesheet);
+ SetValue(cuesheet, x => x.Audiofiles, newAudiofiles);
_traceChangeManager.BulkEdit = false;
+ return Result.Success();
}
///
- public bool IsMoveTracksUpPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Min(x => x.Position) >= 2;
-
- ///
- public bool IsMoveTracksDownPossible(HashSet selectedTracks) => selectedTracks.Count > 0 && selectedTracks.Max(x => x.Position) < _sessionStateContainer.GetActiveCuesheet()?.Tracks.Max(x => x.Position);
-
- ///
- public Result MoveTracksUp(HashSet selectedTracks)
+ public Result MoveDown(HashSet selectedTracks)
{
- if (IsMoveTracksUpPossible(selectedTracks) == false)
+ if (IsMoveDownPossible(selectedTracks) == false)
{
- return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks up is not possible!"));
+ return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks down is not possible!"));
}
_traceChangeManager.BulkEdit = true;
var cuesheet = _sessionStateContainer.GetActiveCuesheet();
- foreach (var selectedTrack in selectedTracks.OrderBy(x => x.Position))
+ foreach (var selectedTrack in selectedTracks.OrderByDescending(x => x.Position))
{
- var previousTrack = cuesheet?.Tracks.FirstOrDefault(x => x.Position == selectedTrack.Position - 1);
- var newBegin = previousTrack?.Begin;
- var newEnd = previousTrack?.End;
- if (previousTrack != null)
+ var nextTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position + 1);
+ if (nextTrack?.Audiofile != null && nextTrack.Audiofile != selectedTrack.Audiofile)
{
- _trackManager.SetProperty(previousTrack, x => x.Position, selectedTrack.Position);
- _trackManager.SetProperty(previousTrack, x => x.Begin, selectedTrack.Begin);
- _trackManager.SetProperty(previousTrack, x => x.End, selectedTrack.End);
+ SwitchAudiofile(selectedTrack, nextTrack.Audiofile);
+ }
+ else
+ {
+ var newBegin = nextTrack?.Begin;
+ var newEnd = nextTrack?.End;
+ if (nextTrack != null)
+ {
+ _trackManager.SetProperty(nextTrack, x => x.Position, selectedTrack.Position);
+ _trackManager.SetProperty(nextTrack, x => x.Begin, selectedTrack.Begin);
+ _trackManager.SetProperty(nextTrack, x => x.End, selectedTrack.End);
+ }
+ _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position + 1));
+ _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin);
+ _trackManager.SetProperty(selectedTrack, x => x.End, newEnd);
}
- _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position - 1));
- _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin);
- _trackManager.SetProperty(selectedTrack, x => x.End, newEnd);
}
- SetValue(cuesheet!, x => x.Tracks, cuesheet?.Tracks.OrderBy(x => x.Position));
+ foreach (var audiofile in cuesheet!.Audiofiles)
+ {
+ var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList();
+ _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks);
+ }
_traceChangeManager.BulkEdit = false;
return Result.Success();
}
///
- public Result MoveTracksDown(HashSet selectedTracks)
+ public Result MoveDown(HashSet selectedAudiofiles)
{
- var cuesheet = _sessionStateContainer.GetActiveCuesheet();
- if (IsMoveTracksDownPossible(selectedTracks) == false)
+ if (IsMoveDownPossible(selectedAudiofiles) == false)
{
- return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks down is not possible!"));
+ return Result.Failure(new Error(ErrorType.NotPossible, "Moving audiofiles down is not possible!"));
}
+ var cuesheet = _sessionStateContainer.GetActiveCuesheet();
_traceChangeManager.BulkEdit = true;
- foreach (var selectedTrack in selectedTracks.OrderByDescending(x => x.Position))
+ var newAudiofiles = new List(cuesheet!.Audiofiles);
+ foreach (var audiofile in selectedAudiofiles)
{
- var nextTrack = cuesheet?.Tracks.FirstOrDefault(x => x.Position == selectedTrack.Position + 1);
- var newBegin = nextTrack?.Begin;
- var newEnd = nextTrack?.End;
- if (nextTrack != null)
+ var index = newAudiofiles.IndexOf(audiofile);
+ var nextAudiofile = newAudiofiles[index + 1];
+ newAudiofiles[index] = nextAudiofile;
+ newAudiofiles[index + 1] = audiofile;
+ var nextAudiofileTracks = nextAudiofile.Tracks;
+ var audiofileTracks = audiofile.Tracks;
+ _audiofileManager.RemoveTracks(nextAudiofile, nextAudiofileTracks, false);
+ _audiofileManager.RemoveTracks(audiofile, audiofileTracks, false);
+ foreach (var track in nextAudiofileTracks)
+ {
+ _audiofileManager.AddTrack(audiofile, track, false);
+ }
+ foreach (var track in audiofileTracks)
{
- _trackManager.SetProperty(nextTrack, x => x.Position, selectedTrack.Position);
- _trackManager.SetProperty(nextTrack, x => x.Begin, selectedTrack.Begin);
- _trackManager.SetProperty(nextTrack, x => x.End, selectedTrack.End);
+ _audiofileManager.AddTrack(nextAudiofile, track, false);
}
- var newPosition = (ushort?)(selectedTrack.Position + 1);
- _trackManager.SetProperty(selectedTrack, x => x.Position, newPosition);
- _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin);
- _trackManager.SetProperty(selectedTrack, x => x.End, newEnd);
}
- SetValue(cuesheet!, x => x.Tracks, cuesheet?.Tracks.OrderBy(x => x.Position));
+ SetValue(cuesheet, x => x.Audiofiles, newAudiofiles);
_traceChangeManager.BulkEdit = false;
return Result.Success();
}
@@ -256,20 +303,44 @@ void SetValue(Cuesheet cuesheet, Expression
propertyInfo.SetValue(cuesheet, value);
_traceChangeManager.AddChange(new(cuesheet, new(previousValue, propertyInfo.Name)));
+ _ = RevokeObjectUrlOfRemovedAudiofilesAsync(cuesheet, propertyInfo, previousValue);
}
- void SetLastTrackEnd(Cuesheet cuesheet)
+ async Task RevokeObjectUrlOfRemovedAudiofilesAsync(Cuesheet cuesheet, PropertyInfo propertyInfo, object? previousValue)
{
- var lastTrack = GetLastTrack(cuesheet);
- if ((lastTrack?.End.HasValue == false) && (cuesheet.Audiofile?.Duration.HasValue == true))
+ if (propertyInfo.Name == nameof(Cuesheet.Audiofiles))
{
- _trackManager.SetProperty(lastTrack, x => x.End, cuesheet.Audiofile.Duration);
+ var deletedAudiofiles = ((IList)previousValue!).Except(cuesheet.Audiofiles);
+ foreach (var deletedAudiofile in deletedAudiofiles)
+ {
+ if (!string.IsNullOrEmpty(deletedAudiofile.ObjectURL))
+ {
+ await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", deletedAudiofile.ObjectURL);
+ }
+ }
}
}
+ void SwitchAudiofile(Track trackToMove, Audiofile audiofileToMoveTo)
+ {
+ var currentTrackPositionAudiofile = trackToMove.Audiofile;
+ //Switch audiofiles without audiofilemanager since methods there capsulate much logic
+ var currentTrackPositionAudiofileTracks = new List(currentTrackPositionAudiofile!.Tracks);
+ currentTrackPositionAudiofileTracks.Remove(trackToMove);
+ _traceChangeManager.AddChange(new(currentTrackPositionAudiofile, new(currentTrackPositionAudiofile.Tracks, nameof(Audiofile.Tracks))));
+ currentTrackPositionAudiofile.Tracks = currentTrackPositionAudiofileTracks;
+ var audiofileToMoveToTracks = new List(audiofileToMoveTo.Tracks)
+ {
+ trackToMove
+ };
+ trackToMove.Audiofile = audiofileToMoveTo;
+ _traceChangeManager.AddChange(new(audiofileToMoveTo, new(audiofileToMoveTo.Tracks, nameof(Audiofile.Tracks))));
+ audiofileToMoveTo.Tracks = audiofileToMoveToTracks;
+ }
+
static Track? GetLastTrack(Cuesheet cuesheet)
{
- return cuesheet.Tracks
+ return cuesheet.Audiofiles.SelectMany(x => x.Tracks)
.OrderByDescending(x => x.Position.HasValue).ThenBy(x => x.Position)
.ThenByDescending(x => x.Begin.HasValue).ThenBy(x => x.Begin)
.ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End)
diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs
new file mode 100644
index 00000000..2509a3da
--- /dev/null
+++ b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs
@@ -0,0 +1,55 @@
+//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
+//.
+using AudioCuesheetEditor.Model.AudioCuesheet;
+using AudioCuesheetEditor.Model.IO.Audio;
+using Microsoft.AspNetCore.Components.Forms;
+using System.Linq.Expressions;
+
+namespace AudioCuesheetEditor.Services.AudioCuesheet
+{
+ public interface IAudiofileManager
+ {
+ ///
+ /// Set properties from a file upload
+ ///
+ ///
+ ///
+ ///
+ Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId);
+ ///
+ /// Set property for an audio file
+ ///
+ ///
+ ///
+ ///
+ ///
+ void SetProperty(Audiofile audiofile, Expression> propertyExpression, TProperty value);
+ ///
+ /// Adds a track to the audiofile
+ ///
+ ///
+ ///
+ /// Parameter controlling if tracing should be handled by this service or by calling services
+ void AddTrack(Audiofile audiofile, Track track, Boolean setTracing = true);
+ ///
+ /// Remove tracks from the audiofile
+ ///
+ ///
+ ///
+ /// Parameter controlling if tracing should be handled by this service or by calling services
+ void RemoveTracks(Audiofile audiofile, IEnumerable tracksToRemove, Boolean setTracing = true);
+ }
+}
diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs
index 460264ff..bb4daed6 100644
--- a/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs
+++ b/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs
@@ -14,6 +14,7 @@
//along with Foobar. If not, see
//.
using AudioCuesheetEditor.Model.AudioCuesheet;
+using AudioCuesheetEditor.Model.IO.Audio;
using System.Linq.Expressions;
namespace AudioCuesheetEditor.Services.AudioCuesheet
@@ -50,37 +51,51 @@ public interface ICuesheetManager
///
void StopRecording();
///
- /// Adds a track to the cuesheet
+ /// Determines if moving tracks up is possible
///
- ///
- void AddTrack(Track track);
+ ///
+ ///
+ Boolean IsMoveUpPossible(HashSet selectedTracks);
///
- /// Remove tracks from cuesheet
+ /// Determines if moving audiofiles up is possible
///
- ///
- void RemoveTracks(IEnumerable tracksToRemove);
+ ///
+ ///
+ Boolean IsMoveUpPossible(HashSet selectedAudiofiles);
///
- /// Determines if moving tracks up is possible
+ /// Determines if moving tracks down is possible
///
///
///
- Boolean IsMoveTracksUpPossible(HashSet selectedTracks);
+ Boolean IsMoveDownPossible(HashSet selectedTracks);
///
- /// Determines if moving tracks down is possible
+ /// Determines if moving audiofiles down is possible
///
///
///
- Boolean IsMoveTracksDownPossible(HashSet selectedTracks);
+ Boolean IsMoveDownPossible(HashSet selectedAudiofiles);
///
/// Moves selected tracks up
///
///
- Result MoveTracksUp(HashSet selectedTracks);
+ Result MoveUp(HashSet selectedTracks);
+ ///
+ /// Moves selected audiofiles up
+ ///
+ ///
+ ///
+ Result MoveUp(HashSet selectedAudiofiles);
///
/// Moves selected tracks down
///
///
///
- Result MoveTracksDown(HashSet selectedTracks);
+ Result MoveDown(HashSet selectedTracks);
+ ///
+ /// Moves selected audiofiles down
+ ///
+ ///
+ ///
+ Result MoveDown(HashSet selectedAudiofiles);
}
}
diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs
index cbffeb9b..9bcf0463 100644
--- a/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs
+++ b/AudioCuesheetEditor/Services/AudioCuesheet/TrackManager.cs
@@ -14,6 +14,7 @@
//along with Foobar. If not, see
//.
using AudioCuesheetEditor.Model.AudioCuesheet;
+using AudioCuesheetEditor.Model.IO.Audio;
using AudioCuesheetEditor.Services.UI;
using System.Linq.Expressions;
using System.Reflection;
@@ -39,8 +40,14 @@ public Track Clone(ITrack track)
{
setLength = false;
}
+ Audiofile? audiofile = null;
+ if (track is Track trackReference)
+ {
+ audiofile = trackReference.Audiofile;
+ }
var clone = new Track()
{
+ Audiofile = audiofile,
IsLinkedToPreviousTrack = track.IsLinkedToPreviousTrack,
Position = track.Position,
Artist = track.Artist,
@@ -110,13 +117,13 @@ public void CopyValues(ITrack source, Track target, bool setIsLinkedToPreviousTr
{
return null;
}
- if (track.Position.HasValue && (track.Cuesheet?.Tracks.All(x => x.Position.HasValue) == true))
+ if (track.Position.HasValue && (track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).All(x => x.Position.HasValue) == true))
{
- return track.Cuesheet?.Tracks.LastOrDefault(x => x.Position == track.Position - 1 && Equals(x, track) == false);
+ return track.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).LastOrDefault(x => x.Position == track.Position - 1 && Equals(x, track) == false);
}
if (track.Begin.HasValue)
{
- return track.Cuesheet?.Tracks.OrderBy(x => x.End).LastOrDefault(x => x.End <= track.Begin && Equals(x, track) == false);
+ return track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.End).LastOrDefault(x => x.End <= track.Begin && Equals(x, track) == false);
}
return null;
}
@@ -124,13 +131,13 @@ public void CopyValues(ITrack source, Track target, bool setIsLinkedToPreviousTr
///
public Track? GetNextLinkedTrack(Track track)
{
- if (track.Position.HasValue && (track.Cuesheet?.Tracks.All(x => x.Position.HasValue) == true))
+ if (track.Position.HasValue && (track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).All(x => x.Position.HasValue) == true))
{
- return track.Cuesheet?.Tracks.OrderBy(x => x.Begin).FirstOrDefault(x => x.Position >= track.Position.Value + 1 && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false);
+ return track.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.Begin).FirstOrDefault(x => x.Position >= track.Position.Value + 1 && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false);
}
if (track.End.HasValue)
{
- return track.Cuesheet?.Tracks.OrderBy(x => x.Begin).LastOrDefault(x => x.Begin <= track.End && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false);
+ return track.Cuesheet?.Audiofiles.SelectMany(x => x.Tracks).OrderBy(x => x.Begin).LastOrDefault(x => x.Begin <= track.End && x.IsLinkedToPreviousTrack == true && Equals(x, track) == false);
}
return null;
}
diff --git a/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs b/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs
index 2c34ee94..2879a7aa 100644
--- a/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs
+++ b/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs
@@ -38,7 +38,8 @@ public Result CanGenerateExportfile(string? filename)
validationMessages.Add(new ValidationMessage("File extension is not '{0}'", FileExtensions.Cuesheet));
}
validationMessages.AddRange(_sessionStateContainer.Cuesheet.Validate().ValidationMessages);
- validationMessages.AddRange(_sessionStateContainer.Cuesheet.Tracks.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages));
+ validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages));
+ validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).Select(x => x.Validate()).SelectMany(x => x.ValidationMessages));
if (validationMessages.Count != 0)
{
return Result.Failure(new Error(ErrorType.ValidationFailed, string.Join(Environment.NewLine, validationMessages.Select(x => x.GetMessageLocalized(_localizer)))));
@@ -53,20 +54,10 @@ public Result GenerateExportfile(string? filename)
{
return Result.Failure(new Error(ErrorType.ValidationFailed, validationResult.Error!.Message));
}
- string? content = null;
- var extension = Path.GetExtension(filename);
- if (extension?.Equals(FileExtensions.Cuesheet, StringComparison.OrdinalIgnoreCase) == false)
- {
- filename = $"{filename}{FileExtensions.Cuesheet}";
- }
- if (_sessionStateContainer.Cuesheet.Audiofile != null)
- {
- content = WriteCuesheet(_sessionStateContainer.Cuesheet.Audiofile.Name);
- }
- return Result.Success(new Exportfile() { Name = filename!, Content = content });
+ return Result.Success(new Exportfile() { Name = filename!, Content = WriteCuesheet() });
}
- private string WriteCuesheet(string? audiofileName)
+ string WriteCuesheet()
{
var builder = new StringBuilder();
if (string.IsNullOrEmpty(_sessionStateContainer.Cuesheet.Cataloguenumber) == false)
@@ -79,15 +70,12 @@ private string WriteCuesheet(string? audiofileName)
}
builder.AppendLine(string.Format("{0} \"{1}\"", CuesheetConstants.CuesheetTitle, _sessionStateContainer.Cuesheet.Title));
builder.AppendLine(string.Format("{0} \"{1}\"", CuesheetConstants.CuesheetArtist, _sessionStateContainer.Cuesheet.Artist));
- builder.AppendLine(string.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, audiofileName, _sessionStateContainer.Cuesheet.Audiofile?.AudioFileType));
- IEnumerable tracks = _sessionStateContainer.Cuesheet.Tracks.OrderBy(x => x.Position);
- if (tracks.Any())
+ foreach (var audiofile in _sessionStateContainer.Cuesheet.Audiofiles)
{
- //Position and begin should always start from 0 even with splitpoints
- int positionDifference = 1 - Convert.ToInt32(tracks.First().Position);
- foreach (var track in tracks)
+ builder.AppendLine(string.Format("{0} \"{1}\" {2}", CuesheetConstants.CuesheetFileName, audiofile.Name, audiofile.AudioCodec?.FileExtension.Replace(".",string.Empty).ToUpper()));
+ foreach(var track in audiofile.Tracks)
{
- builder.AppendLine(string.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position + positionDifference, CuesheetConstants.CuesheetTrackAudio));
+ builder.AppendLine(string.Format("{0}{1} {2:00} {3}", CuesheetConstants.Tab, CuesheetConstants.CuesheetTrack, track.Position, CuesheetConstants.CuesheetTrackAudio));
builder.AppendLine(string.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackTitle, track.Title));
builder.AppendLine(string.Format("{0}{1}{2} \"{3}\"", CuesheetConstants.Tab, CuesheetConstants.Tab, CuesheetConstants.TrackArtist, track.Artist));
if (track.Flags.Any())
diff --git a/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs b/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs
index 50975019..8d900df1 100644
--- a/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs
+++ b/AudioCuesheetEditor/Services/IO/CuesheetImportService.cs
@@ -57,6 +57,7 @@ public static IImportfile Analyse(string fileContent)
var regexCDTextfile = new Regex("^" + CuesheetConstants.CuesheetCDTextfile + " \"(?'" + cuesheetCDTextfileGroupName + "'.{0,})\"");
var regexCatalogueNumber = new Regex("^" + CuesheetConstants.CuesheetCatalogueNumber + " (?'" + cuesheetCatalogueNumberGroupName + "'.{0,})");
ImportTrack? track = null;
+ ImportAudiofile? audiofile = null;
StringBuilder recognizedContent = new();
foreach (var line in fileContent.Split(Environment.NewLine))
{
@@ -100,8 +101,8 @@ public static IImportfile Analyse(string fileContent)
var matchGroup = match.Groups.GetValueOrDefault(cuesheetFileNameGroupName);
if (matchGroup != null)
{
- var audioFile = matchGroup.Value;
- importfile.AnalyzedCuesheet.Audiofile = audioFile;
+ audiofile = new() { Name = matchGroup.Value };
+ importfile.AnalyzedCuesheet.Audiofiles.Add(audiofile);
}
else
{
@@ -203,12 +204,12 @@ public static IImportfile Analyse(string fileContent)
var matchGroup = match.Groups.GetValueOrDefault(trackPreGapGroupName);
if (matchGroup != null)
{
- var minutes = int.Parse(matchGroup.Value.Substring(0, matchGroup.Value.IndexOf(':')));
+ var minutes = int.Parse(matchGroup.Value[..matchGroup.Value.IndexOf(':')]);
var seconds = int.Parse(matchGroup.Value.Substring(matchGroup.Value.IndexOf(':') + 1, 2));
- var frames = int.Parse(matchGroup.Value.Substring(matchGroup.Value.LastIndexOf(':') + 1));
+ var frames = int.Parse(matchGroup.Value[(matchGroup.Value.LastIndexOf(':') + 1)..]);
if (track != null)
{
- track.PreGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32((frames / 75.0) * 1000));
+ track.PreGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32(frames / 75.0 * 1000));
}
else
{
@@ -227,12 +228,12 @@ public static IImportfile Analyse(string fileContent)
var matchGroup = match.Groups.GetValueOrDefault(trackIndex01GroupName);
if (matchGroup != null)
{
- var minutes = int.Parse(matchGroup.Value.Substring(0, matchGroup.Value.IndexOf(':')));
+ var minutes = int.Parse(matchGroup.Value[..matchGroup.Value.IndexOf(':')]);
var seconds = int.Parse(matchGroup.Value.Substring(matchGroup.Value.IndexOf(':') + 1, 2));
- var frames = int.Parse(matchGroup.Value.Substring(matchGroup.Value.LastIndexOf(':') + 1));
+ var frames = int.Parse(matchGroup.Value[(matchGroup.Value.LastIndexOf(':') + 1)..]);
if (track != null)
{
- track.Begin = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32((frames / 75.0) * 1000));
+ track.Begin = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32(frames / 75.0 * 1000));
}
else
{
@@ -243,14 +244,7 @@ public static IImportfile Analyse(string fileContent)
{
throw new ArgumentException(String.Format("Group '{0}' was null!", trackIndex01GroupName));
}
- if (track != null)
- {
- importfile.AnalyzedCuesheet.Tracks.Add(track);
- }
- else
- {
- throw new NullReferenceException(String.Format("Track was null during input {0}", line));
- }
+ audiofile?.Tracks.Add(track);
}
if (regexTrackPostGap.IsMatch(line) == true)
{
@@ -259,12 +253,12 @@ public static IImportfile Analyse(string fileContent)
var matchGroup = match.Groups.GetValueOrDefault(trackPostGapGroupName);
if (matchGroup != null)
{
- var minutes = int.Parse(matchGroup.Value.Substring(0, matchGroup.Value.IndexOf(':')));
+ var minutes = int.Parse(matchGroup.Value[..matchGroup.Value.IndexOf(':')]);
var seconds = int.Parse(matchGroup.Value.Substring(matchGroup.Value.IndexOf(':') + 1, 2));
- var frames = int.Parse(matchGroup.Value.Substring(matchGroup.Value.LastIndexOf(':') + 1));
+ var frames = int.Parse(matchGroup.Value[(matchGroup.Value.LastIndexOf(':') + 1)..]);
if (track != null)
{
- track.PostGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32((frames / 75.0) * 1000));
+ track.PostGap = new TimeSpan(0, 0, minutes, seconds, Convert.ToInt32(frames / 75.0 * 1000));
}
else
{
diff --git a/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs b/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs
index 4b1e3464..2b7a570b 100644
--- a/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs
+++ b/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs
@@ -33,7 +33,8 @@ public Result CanGenerateExportfile(Exportprofile exportprofile)
List validationMessages = [];
validationMessages.AddRange(exportprofile.Validate().ValidationMessages);
validationMessages.AddRange(_sessionStateContainer.Cuesheet.Validate().ValidationMessages);
- validationMessages.AddRange(_sessionStateContainer.Cuesheet.Tracks.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages));
+ validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.Select(x => x.Validate()).SelectMany(x => x.ValidationMessages));
+ validationMessages.AddRange(_sessionStateContainer.Cuesheet.Audiofiles.SelectMany(x => x.Tracks).Select(x => x.Validate()).SelectMany(x => x.ValidationMessages));
if (validationMessages.Count != 0)
{
return Result.Failure(new Error(ErrorType.ValidationFailed, string.Join(Environment.NewLine, validationMessages.Select(x => x.GetMessageLocalized(_localizer)))));
@@ -48,15 +49,10 @@ public Result GenerateExportfile(Exportprofile exportprofile)
{
return Result.Failure(new Error(ErrorType.ValidationFailed, validationResult.Error!.Message));
}
- string? content = null;
- if (_sessionStateContainer.Cuesheet.Audiofile != null)
- {
- content = WriteExport(exportprofile, _sessionStateContainer.Cuesheet.Audiofile.Name);
- }
- return Result.Success(new Exportfile() { Name = exportprofile.Filename, Content = content});
+ return Result.Success(new Exportfile() { Name = exportprofile.Filename, Content = WriteExport(exportprofile) });
}
- private string WriteExport(Exportprofile exportprofile, string? audiofileName)
+ private string WriteExport(Exportprofile exportprofile)
{
var builder = new StringBuilder();
if (exportprofile != null)
@@ -64,50 +60,43 @@ private string WriteExport(Exportprofile exportprofile, string? audiofileName)
var header = exportprofile.SchemeHead
.Replace(Exportprofile.SchemeCuesheetArtist, _sessionStateContainer.Cuesheet.Artist)
.Replace(Exportprofile.SchemeCuesheetTitle, _sessionStateContainer.Cuesheet.Title)
- .Replace(Exportprofile.SchemeCuesheetAudiofile, audiofileName)
.Replace(Exportprofile.SchemeCuesheetCDTextfile, _sessionStateContainer.Cuesheet.CDTextfile?.Name)
.Replace(Exportprofile.SchemeCuesheetCatalogueNumber, _sessionStateContainer.Cuesheet.Cataloguenumber)
.Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString())
.Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString())
.Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString());
builder.AppendLine(header);
- IEnumerable tracks = _sessionStateContainer.Cuesheet.Tracks.OrderBy(x => x.Position);
- if (tracks.Any())
+ foreach (var audiofile in _sessionStateContainer.Cuesheet.Audiofiles)
{
- //Position, Begin and End should always start from 0 even with splitpoints
- int positionDifference = 1 - Convert.ToInt32(tracks.First().Position);
- foreach (var track in tracks)
+ var audiofileLine = exportprofile.SchemeAudiofiles
+ .Replace(Exportprofile.SchemeAudiofileName, audiofile.Name);
+ builder.AppendLine(audiofileLine);
+ IEnumerable tracks = audiofile.Tracks.OrderBy(x => x.Position);
+ if (tracks.Any())
{
- TimeSpan begin;
- var end = track.End;
- if (track.Begin.HasValue)
- {
- begin = track.Begin.Value;
- }
- else
+ foreach (var track in tracks)
{
- throw new NullReferenceException(string.Format("{0} may not be null!", nameof(Track.Begin)));
+ var trackLine = exportprofile.SchemeTracks
+ .Replace(Exportprofile.SchemeTrackArtist, track.Artist)
+ .Replace(Exportprofile.SchemeTrackTitle, track.Title)
+ .Replace(Exportprofile.SchemeTrackPosition, track.Position.ToString())
+ .Replace(Exportprofile.SchemeTrackBegin, track.Begin.ToString())
+ .Replace(Exportprofile.SchemeTrackEnd, track.End.ToString())
+ .Replace(Exportprofile.SchemeTrackLength, track.Length.ToString())
+ .Replace(Exportprofile.SchemeTrackFlags, string.Join(" ", track.Flags.Select(x => x.CuesheetLabel)))
+ .Replace(Exportprofile.SchemeTrackPreGap, track.PreGap != null ? track.PreGap.Value.ToString() : string.Empty)
+ .Replace(Exportprofile.SchemeTrackPostGap, track.PostGap != null ? track.PostGap.Value.ToString() : string.Empty)
+ .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString())
+ .Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString())
+ .Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString());
+ builder.AppendLine(trackLine);
}
- var trackLine = exportprofile.SchemeTracks
- .Replace(Exportprofile.SchemeTrackArtist, track.Artist)
- .Replace(Exportprofile.SchemeTrackTitle, track.Title)
- .Replace(Exportprofile.SchemeTrackPosition, (track.Position + positionDifference).ToString())
- .Replace(Exportprofile.SchemeTrackBegin, begin.ToString())
- .Replace(Exportprofile.SchemeTrackEnd, end.ToString())
- .Replace(Exportprofile.SchemeTrackLength, (end - begin).ToString())
- .Replace(Exportprofile.SchemeTrackFlags, string.Join(" ", track.Flags.Select(x => x.CuesheetLabel)))
- .Replace(Exportprofile.SchemeTrackPreGap, track.PreGap != null ? track.PreGap.Value.ToString() : string.Empty)
- .Replace(Exportprofile.SchemeTrackPostGap, track.PostGap != null ? track.PostGap.Value.ToString() : string.Empty)
- .Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString())
- .Replace(Exportprofile.SchemeDateTime, DateTime.Now.ToString())
- .Replace(Exportprofile.SchemeTime, DateTime.Now.ToLongTimeString());
- builder.AppendLine(trackLine);
}
}
+
var footer = exportprofile.SchemeFooter
.Replace(Exportprofile.SchemeCuesheetArtist, _sessionStateContainer.Cuesheet.Artist)
.Replace(Exportprofile.SchemeCuesheetTitle, _sessionStateContainer.Cuesheet.Title)
- .Replace(Exportprofile.SchemeCuesheetAudiofile, audiofileName)
.Replace(Exportprofile.SchemeCuesheetCDTextfile, _sessionStateContainer.Cuesheet.CDTextfile?.Name)
.Replace(Exportprofile.SchemeCuesheetCatalogueNumber, _sessionStateContainer.Cuesheet.Cataloguenumber)
.Replace(Exportprofile.SchemeDate, DateTime.Now.ToShortDateString())
diff --git a/AudioCuesheetEditor/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs
index 32836dc8..160a7701 100644
--- a/AudioCuesheetEditor/Services/IO/FileInputManager.cs
+++ b/AudioCuesheetEditor/Services/IO/FileInputManager.cs
@@ -21,10 +21,9 @@
namespace AudioCuesheetEditor.Services.IO
{
- public class FileInputManager(IJSRuntime jsRuntime, HttpClient httpClient, ILogger logger) : IFileInputManager
+ public class FileInputManager(IJSRuntime jsRuntime, ILogger logger) : IFileInputManager
{
private readonly IJSRuntime _jsRuntime = jsRuntime;
- private readonly HttpClient _httpClient = httpClient;
private readonly ILogger _logger = logger;
public AudioCodec? GetAudioCodec(string? fileContentType, string fileName)
@@ -46,6 +45,12 @@ public class FileInputManager(IJSRuntime jsRuntime, HttpClient httpClient, ILogg
return foundAudioCodec;
}
+ ///
+ public async Task GetObjectUrlAsync(string fileInputId)
+ {
+ return await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", fileInputId);
+ }
+
public bool IsValidAudiofile(string? fileContentType, string fileName)
{
return GetAudioCodec(fileContentType, fileName) != null;
diff --git a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs
index f79f88a0..e5f4ff39 100644
--- a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs
+++ b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs
@@ -26,6 +26,12 @@ public interface IFileInputManager
bool IsValidAudiofile(string? fileContentType, string fileName);
AudioCodec? GetAudioCodec(string? fileContentType, string fileName);
///
+ /// Get object url from a mud file upload
+ ///
+ ///
+ ///
+ Task GetObjectUrlAsync(string fileInputId);
+ ///
/// Checks if a file content type and name matches given parameters
///
///
diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs
index 1a17a2dc..6637210f 100644
--- a/AudioCuesheetEditor/Services/IO/ImportManager.cs
+++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs
@@ -13,6 +13,7 @@
//You should have received a copy of the GNU General Public License
//along with Foobar. If not, see
//.
+using AngleSharp.Media.Dom;
using AudioCuesheetEditor.Model.AudioCuesheet;
using AudioCuesheetEditor.Model.AudioCuesheet.Import;
using AudioCuesheetEditor.Model.IO;
@@ -108,7 +109,7 @@ public void ImportCuesheet()
ResetTracing();
if (_sessionStateContainer.ImportCuesheet != null)
{
- var newCuesheet = _sessionStateContainer.ImportCuesheet;
+ var newCuesheet = new Cuesheet();
CopyCuesheet(newCuesheet, _sessionStateContainer.ImportCuesheet);
var previousValue = _sessionStateContainer.Cuesheet;
_sessionStateContainer.Cuesheet = newCuesheet;
@@ -163,7 +164,10 @@ public async Task UploadFilesAsync(IEnumerable files)
if (_fileInputManager.IsValidAudiofile(file.ContentType, file.Name))
{
var audioFile = await _fileInputManager.CreateAudiofileAsync(file);
- _sessionStateContainer.ImportAudiofile = audioFile;
+ if (audioFile != null)
+ {
+ _sessionStateContainer.ImportAudiofiles.Add(audioFile);
+ }
}
}
else
@@ -179,33 +183,55 @@ public async Task UploadFilesAsync(IEnumerable files)
}
}
- private void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy)
+ void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy)
{
target.Artist = cuesheetToCopy.Artist;
target.Title = cuesheetToCopy.Title;
target.Cataloguenumber = cuesheetToCopy.Cataloguenumber;
- IEnumerable? tracks = null;
if (cuesheetToCopy is Cuesheet originCuesheet)
{
- tracks = originCuesheet.Tracks;
- target.Audiofile = originCuesheet.Audiofile;
target.CDTextfile = originCuesheet.CDTextfile;
- target.Cataloguenumber = originCuesheet.Cataloguenumber;
+ AttachClonedAudiofiles(target, originCuesheet.Audiofiles);
}
if (cuesheetToCopy is ImportCuesheet importCuesheet)
{
- tracks = importCuesheet.Tracks;
- if (String.IsNullOrEmpty(importCuesheet.Audiofile) == false)
- {
- target.Audiofile = new Audiofile(importCuesheet.Audiofile);
- }
if (String.IsNullOrEmpty(importCuesheet.CDTextfile) == false)
{
target.CDTextfile = new CDTextfile(importCuesheet.CDTextfile);
}
+ AttachClonedAudiofiles(target, importCuesheet.Audiofiles);
}
- if (tracks != null)
+ }
+
+ void AttachClonedAudiofiles(Cuesheet target, IEnumerable audiofiles)
+ {
+ foreach (var audiofile in audiofiles)
{
+ Audiofile? targetAudiofile = null;
+ IEnumerable? tracks = null;
+ // Map uploaded import audiofiles by name
+ var importAudiofileFound = _sessionStateContainer.ImportAudiofiles.FirstOrDefault(x => x.Name == audiofile.Name);
+ if (importAudiofileFound != null)
+ {
+ targetAudiofile = new Audiofile(importAudiofileFound.Name, importAudiofileFound.ObjectURL, importAudiofileFound.AudioCodec, importAudiofileFound.Duration);
+ }
+ if (audiofile is ImportAudiofile importAudiofile)
+ {
+ targetAudiofile ??= new Audiofile()
+ {
+ Name = importAudiofile.Name,
+ };
+ tracks = importAudiofile.Tracks;
+ }
+ if (audiofile is Audiofile sourceAudiofile)
+ {
+ targetAudiofile ??= new Audiofile(sourceAudiofile.Name, sourceAudiofile.ObjectURL, sourceAudiofile.AudioCodec, sourceAudiofile.Duration);
+ tracks = sourceAudiofile.Tracks;
+ }
+ if (targetAudiofile == null || tracks == null)
+ {
+ throw new NullReferenceException();
+ }
IOrderedEnumerable sortedTracks;
if (tracks.All(x => x.Position.HasValue))
{
@@ -231,56 +257,52 @@ private void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy)
{
sortedTracks = sortedTracks.ThenByDescending(x => x.End.HasValue).ThenBy(x => x.End);
}
- List targetTracks = [];
TimeSpan? begin = TimeSpan.Zero;
ushort position = 1;
- foreach (var (importTrack, index) in sortedTracks.Select((track, i) => (track, i)))
+ for (int i = 0; i < sortedTracks.Count(); i++)
{
+ var track = sortedTracks.ElementAt(i);
ITrack? nextTrack = null;
- if (index < sortedTracks.Count() - 1)
+ if (i < sortedTracks.Count() - 1)
{
- nextTrack = sortedTracks.ElementAt(index + 1);
+ nextTrack = sortedTracks.ElementAt(i + 1);
}
- // Copy track
- var track = _trackManager.Clone(importTrack);
- track.Cuesheet = target;
+ var clone = _trackManager.Clone(track);
+ clone.Cuesheet = target;
+ clone.Audiofile = targetAudiofile;
// Special treatment for StartDateTime of ImportTrack
- if (importTrack is ImportTrack importTrackReference && importTrackReference.StartDateTime != null && nextTrack is ImportTrack nextImportTrackReference)
+ if (track is ImportTrack importTrack && importTrack.StartDateTime != null && nextTrack is ImportTrack nextImportTrack)
{
- var length = nextImportTrackReference.StartDateTime - importTrackReference.StartDateTime;
- track.Begin = begin;
- track.End = begin + length;
+ var length = nextImportTrack.StartDateTime - importTrack.StartDateTime;
+ clone.Begin = begin;
+ clone.End = begin + length;
}
// Calculate properties
- if (track.Position.HasValue == false)
+ if (clone.Position.HasValue == false)
{
- track.Position = position;
+ clone.Position = position;
}
- if (track.Begin.HasValue == false)
+ if (clone.Begin.HasValue == false)
{
- track.Begin = begin;
+ clone.Begin = begin;
}
- if ((track.End.HasValue == false) && (nextTrack?.Begin.HasValue == true))
+ if ((clone.End.HasValue == false) && (nextTrack?.Begin.HasValue == true))
{
- track.End = nextTrack.Begin;
+ clone.End = nextTrack.Begin;
}
- begin = track.End;
+ begin = clone.End;
position++;
- targetTracks.Add(track);
+ targetAudiofile.Tracks.Add(clone);
}
- target.Tracks = targetTracks;
- }
- else
- {
- throw new NullReferenceException();
+ target.Audiofiles.Add(targetAudiofile);
}
}
- private void ResetTracing()
+ void ResetTracing()
{
if (_sessionStateContainer.ImportCuesheet != null)
{
- _traceChangeManager.RemoveTracedChanges([_sessionStateContainer.ImportCuesheet, .. _sessionStateContainer.ImportCuesheet.Tracks]);
+ _traceChangeManager.RemoveTracedChanges([_sessionStateContainer.ImportCuesheet, .. _sessionStateContainer.ImportCuesheet.Audiofiles, .. _sessionStateContainer.ImportCuesheet.Audiofiles.SelectMany(x => x.Tracks)]);
}
}
}
diff --git a/AudioCuesheetEditor/Services/IO/TextImportService.cs b/AudioCuesheetEditor/Services/IO/TextImportService.cs
index ab975076..e196ca0f 100644
--- a/AudioCuesheetEditor/Services/IO/TextImportService.cs
+++ b/AudioCuesheetEditor/Services/IO/TextImportService.cs
@@ -31,13 +31,14 @@ public class TextImportService(ILocalStorageOptionsProvider localStorageOptionsP
{
private readonly ILocalStorageOptionsProvider _localStorageOptionsProvider = localStorageOptionsProvider;
+ private readonly Dictionary _audiofileStartIndices = [];
+
public async Task AnalyseAsync(string fileContent)
{
Importfile importFile = new()
{
FileContent = fileContent,
FileContentRecognized = fileContent,
- AnalyzedCuesheet = new ImportCuesheet(),
FileType = ImportFileType.Textfile
};
try
@@ -46,6 +47,7 @@ public async Task AnalyseAsync(string fileContent)
var applicationOptions = await _localStorageOptionsProvider.GetOptionsAsync();
var importProfile = importOptions.SelectedImportProfile ?? throw new InvalidOperationException("Selected import profiles is not set!");
SearchForCuesheetData(ref importFile, fileContent, importProfile);
+ SearchForAudiofileData(ref importFile, fileContent, importProfile);
SearchForTrackData(ref importFile, fileContent, importProfile, applicationOptions.DefaultIsLinkedToPreviousTrack);
}
catch (Exception ex)
@@ -57,7 +59,7 @@ public async Task AnalyseAsync(string fileContent)
return importFile;
}
- private static string ApplyRegexAndMarkGroups(object entity, Regex regex, string input, TimeSpanFormat? timeSpanFormat)
+ static string ApplyRegexAndMarkGroups(object entity, Regex regex, string input, TimeSpanFormat? timeSpanFormat)
{
return regex.Replace(input, match =>
{
@@ -93,107 +95,180 @@ private static string ApplyRegexAndMarkGroups(object entity, Regex regex, string
});
}
- private static void SearchForCuesheetData(ref Importfile importFile, string fileContent, Importprofile importProfile)
+ static void SearchForCuesheetData(ref Importfile importFile, string fileContent, Importprofile importProfile)
{
+ var cuesheet = new ImportCuesheet();
if (string.IsNullOrWhiteSpace(importProfile.SchemeCuesheet) == false)
{
- var cuesheet = importFile.AnalyzedCuesheet;
- Regex regex;
- if (importProfile.UseRegularExpression == true)
+ importFile.FileContentRecognized ??= fileContent;
+ if (importProfile.UseRegularExpression)
{
- regex = new Regex(importProfile.SchemeCuesheet, RegexOptions.Multiline);
+ var regex = new Regex(importProfile.SchemeCuesheet);
+ importFile.FileContentRecognized = regex.Replace(importFile.FileContentRecognized,
+ match =>
+ {
+ string marked = ApplyRegexAndMarkGroups(cuesheet, regex, match.Value, importProfile.TimeSpanFormat);
+ return marked;
+ }
+ );
+ importFile.FileContentRecognized = ApplyRegexAndMarkGroups(cuesheet, regex, fileContent, importProfile.TimeSpanFormat);
}
else
{
- regex = CreateCuesheetRegexPattern(importProfile.SchemeCuesheet);
+ var regex = CreateRegexPattern(importProfile.SchemeCuesheet, [
+ nameof(ImportCuesheet.Artist),
+ nameof(ImportCuesheet.Title),
+ nameof(ImportCuesheet.CDTextfile),
+ nameof(ImportCuesheet.Cataloguenumber)
+ ]);
+ importFile.FileContentRecognized = SearchLineByLineForEntry(importFile.FileContentRecognized, () => new ImportCuesheet(), (position, entity) =>
+ {
+ cuesheet = (ImportCuesheet)entity;
+ return true;
+ }, regex, importProfile);
}
+ }
+ importFile.AnalyzedCuesheet = cuesheet;
+ }
+ void SearchForAudiofileData(ref Importfile importFile, string fileContent, Importprofile importProfile)
+ {
+ _audiofileStartIndices.Clear();
+ var cuesheet = importFile.AnalyzedCuesheet;
+ if (string.IsNullOrWhiteSpace(importProfile.SchemeAudiofiles) == false)
+ {
+ importFile.FileContentRecognized ??= fileContent;
if (importProfile.UseRegularExpression)
{
- importFile.FileContentRecognized = ApplyRegexAndMarkGroups(cuesheet!, regex, fileContent, importProfile.TimeSpanFormat);
+ var regex = new Regex(importProfile.SchemeAudiofiles);
+ importFile.FileContentRecognized = regex.Replace(importFile.FileContentRecognized,
+ match =>
+ {
+ var audiofile = new ImportAudiofile();
+ string marked = ApplyRegexAndMarkGroups(audiofile, regex, match.Value, importProfile.TimeSpanFormat);
+ cuesheet!.Audiofiles.Add(audiofile);
+ _audiofileStartIndices.Add(audiofile, match.Index);
+ return marked;
+ }
+ );
}
else
{
- var sb = new StringBuilder();
- using (var reader = new StringReader(fileContent))
+ var regex = CreateRegexPattern(importProfile.SchemeAudiofiles, [nameof(ImportAudiofile.Name)]);
+ importFile.FileContentRecognized = SearchLineByLineForEntry(importFile.FileContentRecognized, () => new ImportAudiofile(), (position, entity) =>
{
- string? line;
- while ((line = reader.ReadLine()) != null)
- {
- var markedLine = ApplyRegexAndMarkGroups(cuesheet!, regex, line, importProfile.TimeSpanFormat);
- sb.AppendLine(markedLine);
- if (!string.Equals(markedLine, line))
- {
- //We found the first occurrence, break the loop
- //Attach the rest of the file to FileContentRecognized
- sb.Append(reader.ReadToEnd());
- break;
- }
- }
- }
- importFile.FileContentRecognized = sb.ToString();
+ var audiofile = (ImportAudiofile)entity;
+ cuesheet!.Audiofiles.Add(audiofile);
+ _audiofileStartIndices.Add(audiofile, position);
+ return false;
+ }, regex, importProfile);
}
}
+ else
+ {
+ //Add an empty audiofile to the cuesheet if no scheme is provided, so that tracks can be added to it
+ var audiofile = new ImportAudiofile();
+ _audiofileStartIndices.Add(audiofile, 0);
+ cuesheet!.Audiofiles.Add(audiofile);
+ }
}
- private static void SearchForTrackData(ref Importfile importFile, string fileContent, Importprofile importProfile, bool defaultIsLinkedToPreviousTrack)
+ void SearchForTrackData(ref Importfile importFile, string fileContent, Importprofile importProfile, bool defaultIsLinkedToPreviousTrack)
{
if (string.IsNullOrWhiteSpace(importProfile.SchemeTracks) == false)
{
- Regex regex;
- if (importProfile.UseRegularExpression == true)
- {
- regex = new Regex(importProfile.SchemeTracks, RegexOptions.Multiline);
- }
- else
- {
- regex = CreateTrackRegexPattern(importProfile.SchemeTracks);
- }
var cuesheet = importFile.AnalyzedCuesheet;
importFile.FileContentRecognized ??= fileContent;
if (importProfile.UseRegularExpression)
{
+ var regex = new Regex(importProfile.SchemeTracks);
importFile.FileContentRecognized = regex.Replace(importFile.FileContentRecognized,
match =>
{
var track = new ImportTrack() { IsLinkedToPreviousTrack = defaultIsLinkedToPreviousTrack };
- string marked = ApplyRegexAndMarkGroups(track, regex, match.Value, importProfile.TimeSpanFormat);
- cuesheet!.Tracks.Add(track);
- return marked;
+ var audiofile = _audiofileStartIndices
+ .Where(kv => kv.Value <= match.Index)
+ .OrderBy(kv => kv.Value)
+ .Select(kv => kv.Key)
+ .LastOrDefault();
+ if (audiofile != null)
+ {
+ audiofile.Tracks.Add(track);
+ return ApplyRegexAndMarkGroups(track, regex, match.Value, importProfile.TimeSpanFormat);
+ }
+ return match.Value;
}
);
}
else
{
- var sb = new StringBuilder();
- using (var reader = new StringReader(importFile.FileContentRecognized))
+ var regex = CreateRegexPattern(importProfile.SchemeTracks,
+ [
+ nameof(ImportTrack.Artist),
+ nameof(ImportTrack.Title),
+ nameof(ImportTrack.Begin),
+ nameof(ImportTrack.End),
+ nameof(ImportTrack.Length),
+ nameof(ImportTrack.Position),
+ nameof(ImportTrack.Flags),
+ nameof(ImportTrack.PreGap),
+ nameof(ImportTrack.PostGap),
+ nameof(ImportTrack.StartDateTime)
+ ]);
+ if (_audiofileStartIndices.Count > 0)
{
- string? line;
- while ((line = reader.ReadLine()) != null)
+ importFile.FileContentRecognized = SearchLineByLineForEntry(importFile.FileContentRecognized, () => new ImportTrack() { IsLinkedToPreviousTrack = defaultIsLinkedToPreviousTrack }, (position, entity) =>
{
- // Check if this line is already analyzed
- if (line.Contains(CuesheetConstants.MarkHTMLStart) == false)
- {
- var track = new ImportTrack() { IsLinkedToPreviousTrack = defaultIsLinkedToPreviousTrack };
- var markedLine = ApplyRegexAndMarkGroups(track, regex, line, importProfile.TimeSpanFormat);
- if (!string.Equals(markedLine, line))
- {
- cuesheet!.Tracks.Add(track);
- }
- sb.AppendLine(markedLine);
- }
- else
- {
- sb.AppendLine(line);
- }
+ var audiofile = _audiofileStartIndices
+ .Where(kv => kv.Value <= position)
+ .OrderBy(kv => kv.Value)
+ .Select(kv => kv.Key)
+ .LastOrDefault();
+ audiofile?.Tracks.Add((ImportTrack)entity);
+ return false;
+ }, regex, importProfile);
+ }
+ }
+ }
+ }
+
+ static string SearchLineByLineForEntry(string fileContent, Func entityCreation, Func ifEntityFound, Regex regex, Importprofile importProfile)
+ {
+ var sb = new StringBuilder();
+ using (var reader = new StringReader(fileContent))
+ {
+ string? line;
+ int pos = 0;
+ while ((line = reader.ReadLine()) != null)
+ {
+ Boolean stopSearch = false;
+ // Check if this line is already analyzed
+ if (line.Contains(CuesheetConstants.MarkHTMLStart) == false)
+ {
+ var entity = entityCreation.Invoke();
+ var markedLine = ApplyRegexAndMarkGroups(entity, regex, line, importProfile.TimeSpanFormat);
+ if (!string.Equals(markedLine, line))
+ {
+ stopSearch = ifEntityFound.Invoke(pos, entity);
+ }
+ sb.AppendLine(markedLine);
+ if (stopSearch)
+ {
+ sb.Append(reader.ReadToEnd());
+ break;
}
}
- importFile.FileContentRecognized = sb.ToString().TrimEnd(Environment.NewLine.ToCharArray());
+ else
+ {
+ sb.AppendLine(line);
+ }
+ pos += line.Length + Environment.NewLine.Length;
}
}
+ return sb.ToString().TrimEnd(Environment.NewLine.ToCharArray());
}
- private static void SetValue(object entity, PropertyInfo property, string value, TimeSpanFormat? timeSpanFormat)
+ static void SetValue(object entity, PropertyInfo property, string value, TimeSpanFormat? timeSpanFormat)
{
if (property.PropertyType == typeof(TimeSpan?))
{
@@ -213,7 +288,7 @@ private static void SetValue(object entity, PropertyInfo property, string value,
}
if (property.PropertyType == typeof(Audiofile))
{
- property.SetValue(entity, new Audiofile(value));
+ property.SetValue(entity, new Audiofile() { Name = value });
}
if (property.PropertyType == typeof(DateTime?))
{
@@ -224,85 +299,8 @@ private static void SetValue(object entity, PropertyInfo property, string value,
}
}
- private static Regex CreateCuesheetRegexPattern(string scheme)
- {
- string[] fieldNames =
- [
- nameof(ImportCuesheet.Artist),
- nameof(ImportCuesheet.Title),
- nameof(ImportCuesheet.Audiofile),
- nameof(ImportCuesheet.CDTextfile),
- nameof(ImportCuesheet.Cataloguenumber)
- ];
- var parts = new List();
- int idx = 0;
- while (idx < scheme.Length)
- {
- var field = fieldNames.FirstOrDefault(fn => scheme.IndexOf(fn, idx, StringComparison.Ordinal) == idx);
- if (field != null)
- {
- parts.Add(field);
- idx += field.Length;
- }
- else
- {
- int nextFieldIdx = scheme.Length;
- foreach (var fn in fieldNames)
- {
- int pos = scheme.IndexOf(fn, idx, StringComparison.Ordinal);
- if (pos >= 0 && pos < nextFieldIdx)
- {
- nextFieldIdx = pos;
- }
- }
- string separator = scheme[idx..nextFieldIdx];
- parts.Add(separator);
- idx = nextFieldIdx;
- }
- }
-
- var regexBuilder = new StringBuilder("^");
- for (int i = 0; i < parts.Count; i++)
- {
- var part = parts[i];
- if (fieldNames.Contains(part))
- {
- bool isLast = i == parts.Count - 1 || parts.Skip(i + 1).All(p => !fieldNames.Contains(p));
- if (isLast)
- {
- regexBuilder.Append($@"(?<{part}>.+)");
- }
- else
- {
- regexBuilder.Append($@"(?<{part}>.+?)");
- }
- }
- else
- {
- string sep = Regex.Escape(part).Replace("\\t", @"\t{1,}");
- regexBuilder.Append(sep);
- }
- }
- regexBuilder.Append('$');
-
- return new Regex(regexBuilder.ToString());
- }
-
- private static Regex CreateTrackRegexPattern(string scheme)
+ static Regex CreateRegexPattern(string scheme, string[] fieldNames)
{
- string[] fieldNames =
- [
- nameof(ImportTrack.Artist),
- nameof(ImportTrack.Title),
- nameof(ImportTrack.Begin),
- nameof(ImportTrack.End),
- nameof(ImportTrack.Length),
- nameof(ImportTrack.Position),
- nameof(ImportTrack.Flags),
- nameof(ImportTrack.PreGap),
- nameof(ImportTrack.PostGap),
- nameof(ImportTrack.StartDateTime)
- ];
var parts = new List();
int idx = 0;
while (idx < scheme.Length)
diff --git a/AudioCuesheetEditor/Services/UI/DialogManager.cs b/AudioCuesheetEditor/Services/UI/DialogManager.cs
index 16b48a45..a98841fa 100644
--- a/AudioCuesheetEditor/Services/UI/DialogManager.cs
+++ b/AudioCuesheetEditor/Services/UI/DialogManager.cs
@@ -194,7 +194,7 @@ public async Task ShowLoadingDialogAsync()
if (_loadingDialog == null)
{
var options = new DialogOptions() { BackdropClick = false, FullWidth = true, MaxWidth = MaxWidth.ExtraSmall, NoHeader = true };
- _loadingDialog = await _dialogService.ShowAsync(null, options);
+ _loadingDialog = await _dialogService.ShowAsync(options);
await Task.Delay(1);
}
}
diff --git a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs
index e3f31f3f..24ed1896 100644
--- a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs
+++ b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs
@@ -25,7 +25,7 @@ public interface ISessionStateContainer
public event EventHandler? ImportCuesheetChanged;
public Cuesheet Cuesheet { get; set; }
public Cuesheet? ImportCuesheet { get; set; }
- public Audiofile? ImportAudiofile { get; set; }
+ public IList ImportAudiofiles { get; set; }
public IImportfile? Importfile { get; set; }
public Boolean ImportIsAnalyzed { get; set; }
public void ResetImport();
diff --git a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs
index f4e00e55..68418a8f 100644
--- a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs
+++ b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs
@@ -63,7 +63,7 @@ public Cuesheet? ImportCuesheet
ImportCuesheetChanged?.Invoke(this, EventArgs.Empty);
}
}
- public Audiofile? ImportAudiofile { get; set; }
+ public IList ImportAudiofiles { get; set; } = [];
public IImportfile? Importfile{ get; set; }
public Boolean ImportIsAnalyzed { get; set; } = false;
@@ -76,7 +76,7 @@ public async Task InitializeAsync()
public void ResetImport()
{
Importfile = null;
- ImportAudiofile = null;
+ ImportAudiofiles = [];
ImportCuesheet = null;
}
diff --git a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor
index 122d0742..c3fe120a 100644
--- a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor
+++ b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor
@@ -22,54 +22,45 @@ along with Foobar. If not, see
@inject PlaybackService _playbackService
@inject HotKeys _hotKeys
-
-
-
- @_localizer["Playback"]
-
-
-
-
- @if (_playbackService.CurrentPosition.HasValue)
- {
- @_playbackService.CurrentPosition.Value.ToString("hh\\:mm\\:ss")
- }
- else
- {
- @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator)
- }
-
-
- @GetSliderTimeValue()
-
-
- @if (_playbackService.TotalTime.HasValue)
- {
- @_playbackService.TotalTime.Value.ToString("hh\\:mm\\:ss")
- }
- else
- {
- @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator)
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ @if (_playbackService.CurrentPosition.HasValue)
+ {
+ @_playbackService.CurrentPosition.Value.ToString("hh\\:mm\\:ss")
+ }
+ else
+ {
+ @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator)
+ }
+
+
+ @GetSliderTimeValue()
+
+
+ @if (_playbackService.TotalTime.HasValue)
+ {
+ @_playbackService.TotalTime.Value.ToString("hh\\:mm\\:ss")
+ }
+ else
+ {
+ @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator)
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@code {
double sliderValue;
@@ -82,9 +73,9 @@ along with Foobar. If not, see
hotKeysContext?.DisposeAsync();
}
- protected override void OnInitialized()
+ protected override async Task OnInitializedAsync()
{
- base.OnInitialized();
+ await base.OnInitializedAsync();
_playbackService.CurrentPositionChanged += PlaybackService_CurrentPositionChanged;
hotKeysContext = _hotKeys.CreateContext()
.Add(ModKey.Ctrl, Key.p, OnPlayOrPauseClicked)
@@ -95,6 +86,7 @@ along with Foobar. If not, see
.Add(Key.MediaTrackNext, PlayNextTrackAsync)
.Add(Key.MediaTrackPrevious, PlayPreviousTrackAsync)
.Add(Key.MediaStop, StopAsync);
+ await _playbackService.InitializeAsync();
}
async Task OnPlayOrPauseClicked()
diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx
new file mode 100644
index 00000000..1735ab5d
--- /dev/null
+++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Audiodatei
+
+
+ Dateiname ändern
+
+
+ Geben Sie hier den neuen Dateinamen ein
+
+
+ Neuer Dateiname
+
+
+ Validierungsfehler
+
+
\ No newline at end of file
diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor
new file mode 100644
index 00000000..c422360c
--- /dev/null
+++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor
@@ -0,0 +1,127 @@
+
+@inherits BaseLocalizedComponent
+
+@inject IStringLocalizer _localizer
+@inject IStringLocalizer _validationMessageLocalizer
+@inject ISessionStateContainer _sessionStateContainer
+@inject ICuesheetManager _cuesheetManager
+@inject IAudiofileManager _audiofileManager
+@inject IDialogService _dialogService
+
+@switch (CurrentViewMode)
+{
+ case ViewMode.DetailView:
+ case ViewMode.ImportView:
+ var validationResult = _cuesheet?.Validate(nameof(Cuesheet.Audiofiles));
+ if (validationResult?.Status == ValidationStatus.Error)
+ {
+
+ @_localizer["Validation errors"]
+ @foreach (var message in validationResult.ValidationMessages)
+ {
+ @message.GetMessageLocalized(_validationMessageLocalizer)
+ }
+
+
+ }
+ break;
+}
+
+
+ @foreach (var file in Files)
+ {
+
+
+
+
+ @{
+ var fileInputId = $"Audiofile_{Guid.NewGuid()}";
+ }
+ x.MimeType))"
+ FileRenameDisabled="file.Name == null" Error="file.Validate().Status == ValidationStatus.Error" />
+
+
+
+
+
+
+ }
+
+
+@code {
+ Cuesheet? _cuesheet => _sessionStateContainer.GetActiveCuesheet();
+
+ [Parameter]
+ [EditorRequired]
+ public ICollection Files { get; set; }
+
+ [CascadingParameter]
+ public ViewMode CurrentViewMode { get; set; }
+
+ [Parameter]
+ public HashSet SelectedFiles { get; set; } = [];
+
+ [Parameter]
+ public EventCallback> SelectedFilesChanged { get; set; }
+
+ async Task ShowInputDialog(string? initialValue)
+ {
+ var parameters = new DialogParameters
+ {
+ { x => x.Placeholder, _localizer["Enter the new file name here"] },
+ { x => x.Label, _localizer["New file name"] },
+ { x => x.InitialValue, initialValue }
+ };
+ var options = new DialogOptions() { CloseOnEscapeKey = true, BackdropClick = false, FullWidth = true, CloseButton = true };
+ var dialog = await _dialogService.ShowAsync(_localizer["Change file name"], parameters, options);
+ var result = await dialog.Result;
+ var newFileName = result?.Data as string;
+ if ((result?.Canceled == false) && (String.IsNullOrEmpty(newFileName) == false))
+ {
+ return newFileName;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ async Task FileRenameClicked(Audiofile audiofile)
+ {
+ var newFileName = await ShowInputDialog(audiofile.Name);
+ if (String.IsNullOrEmpty(newFileName) == false)
+ {
+ _audiofileManager.SetProperty(audiofile, x => x.Name, newFileName);
+ }
+ }
+
+ void SelectedChanged(Audiofile audiofile, Boolean selected)
+ {
+ if (selected)
+ {
+ SelectedFiles.Add(audiofile);
+ }
+ else
+ {
+ SelectedFiles.Remove(audiofile);
+ }
+ SelectedFilesChanged.InvokeAsync(SelectedFiles);
+ }
+}
diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx
new file mode 100644
index 00000000..7c5456d4
--- /dev/null
+++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Audiofile
+
+
+ Change file name
+
+
+ Enter the new file name here
+
+
+ New file name
+
+
+ Validation errors
+
+
\ No newline at end of file
diff --git a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor
index 6de35334..097d8ee1 100644
--- a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor
+++ b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor
@@ -23,24 +23,21 @@ along with Foobar. If not, see
@inject IDialogService _dialogService
@inject ISessionStateContainer _sessionStateContainer
@inject ICuesheetManager _cuesheetManager
-@inject ITrackManager _trackManager
-@if (Cuesheet != null)
+@if (_cuesheet != null)
{
-
-
+
-
@switch(CurrentViewMode)
{
case ViewMode.ImportView:
case ViewMode.DetailView:
- x.MimeType))" OnFileRenameClicked="AudioFileRename" FileRenameDisabled="Cuesheet.Audiofile == null" />
-
-
+
break;
@@ -49,24 +46,10 @@ along with Foobar. If not, see
}
@code {
- public Cuesheet? Cuesheet
- {
- get
- {
- if (CurrentViewMode == ViewMode.ImportView)
- {
- return _sessionStateContainer.ImportCuesheet;
- }
- return _sessionStateContainer.Cuesheet;
- }
- }
-
[CascadingParameter]
public ViewMode CurrentViewMode { get; set; }
- string? _fileInputAudiofileId;
- string? _fileInputAudiofileErrorText;
- string? _fileInputCDTextfileErrorText;
+ Cuesheet? _cuesheet => _sessionStateContainer.GetActiveCuesheet();
string? _catalogueNumber;
MudForm? _form;
MudTextField? _catalogueNumberTextField;
@@ -74,10 +57,6 @@ along with Foobar. If not, see
protected override void OnInitialized()
{
base.OnInitialized();
- if (_fileInputAudiofileId == null)
- {
- _fileInputAudiofileId = $"Input_Audiofile_{Guid.NewGuid()}";
- }
TraceChangeManager.UndoDone += TraceChangeManager_UndoDone;
TraceChangeManager.RedoDone += TraceChangeManager_RedoDone;
_sessionStateContainer.CuesheetChanged += SessionStateContainer_CuesheetChanged;
@@ -96,86 +75,29 @@ along with Foobar. If not, see
protected override void OnParametersSet()
{
base.OnParametersSet();
- if (_form?.IsTouched == true)
- {
- SetAudiofileValidationText();
- }
- _catalogueNumber = Cuesheet?.Cataloguenumber;
- }
-
- async Task OnAudiofileSelected(IBrowserFile? browserFile)
- {
- if (Cuesheet == null)
- {
- return;
- }
- _fileInputAudiofileErrorText = null;
- try
- {
- Audiofile? audiofile = null;
- if (browserFile != null)
- {
- var fileUpload = await _fileInputManager.CreateFileUploadsAsync([browserFile], _fileInputAudiofileId);
- audiofile = await _fileInputManager.CreateAudiofileAsync(fileUpload.Single());
- }
- _cuesheetManager.SetProperty(x => x.Audiofile, audiofile);
- }
- catch(ArgumentException ae)
- {
- _fileInputAudiofileErrorText = ae.Message;
- }
- // Just validate the cuesheet if there is no error already
- if (_fileInputAudiofileErrorText == null)
- {
- SetAudiofileValidationText();
- }
- }
-
- void SetAudiofileValidationText()
- {
- if (Cuesheet == null)
- {
- return;
- }
- var validationMessages = _validationService.Validate(Cuesheet, nameof(Cuesheet.Audiofile));
- if (validationMessages.Count() > 0)
- {
- _fileInputAudiofileErrorText = String.Join(Environment.NewLine, validationMessages);
- }
- else
- {
- _fileInputAudiofileErrorText = null;
- }
+ _catalogueNumber = _cuesheet?.Cataloguenumber;
}
void OnCDTextfileSelected(IBrowserFile? browserFile)
{
- if (Cuesheet == null)
+ if (_cuesheet == null)
{
return;
}
- _fileInputCDTextfileErrorText = null;
- try
+ CDTextfile? newValue = null;
+ if (browserFile != null)
{
- CDTextfile? newValue = null;
- if (browserFile != null)
- {
- newValue = _fileInputManager.CreateCDTextfile(browserFile.ContentType, browserFile.Name);
- }
- _cuesheetManager.SetProperty(x => x.CDTextfile, newValue);
- }
- catch (ArgumentException ae)
- {
- _fileInputCDTextfileErrorText = ae.Message;
+ newValue = _fileInputManager.CreateCDTextfile(browserFile.ContentType, browserFile.Name);
}
+ _cuesheetManager.SetProperty(x => x.CDTextfile, newValue);
}
async Task CDTextFileRename()
{
- var newFileName = await ShowInputDialog(Cuesheet?.CDTextfile?.Name);
+ var newFileName = await ShowInputDialog(_cuesheet?.CDTextfile?.Name);
if (String.IsNullOrEmpty(newFileName) == false)
{
- var cdTextFile = Cuesheet?.CDTextfile;
+ var cdTextFile = _cuesheet?.CDTextfile;
if (cdTextFile != null)
{
cdTextFile.Name = newFileName;
@@ -183,19 +105,6 @@ along with Foobar. If not, see
}
}
- async Task AudioFileRename()
- {
- var newFileName = await ShowInputDialog(Cuesheet?.Audiofile?.Name);
- if (String.IsNullOrEmpty(newFileName) == false)
- {
- var audioFile = Cuesheet?.Audiofile;
- if (audioFile != null)
- {
- audioFile.Name = newFileName;
- }
- }
- }
-
async Task ShowInputDialog(string? initialValue)
{
var parameters = new DialogParameters
@@ -226,13 +135,13 @@ along with Foobar. If not, see
void TraceChangeManager_RedoDone(object? sender, EventArgs e)
{
- _catalogueNumber = Cuesheet?.Cataloguenumber;
+ _catalogueNumber = _cuesheet?.Cataloguenumber;
_catalogueNumberTextField?.ResetAsync();
}
void TraceChangeManager_UndoDone(object? sender, EventArgs e)
{
- _catalogueNumber = Cuesheet?.Cataloguenumber;
+ _catalogueNumber = _cuesheet?.Cataloguenumber;
_catalogueNumberTextField?.ResetAsync();
}
diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx
index 13c2296c..41808472 100644
--- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx
+++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx
@@ -154,7 +154,7 @@
Schema Titel
- Geben Sie hier das Spuren-Schema für dieses Profil ein
+ Geben Sie hier das Titel-Schema für dieses Profil ein
Schema Fuß
@@ -207,4 +207,10 @@
Kein Exportprofil ausgewählt!
+
+ Schema Audiodateien
+
+
+ Geben Sie hier das Audiodatei-Schema für dieses Profil ein
+
\ No newline at end of file
diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor
index 7444765e..08c77475 100644
--- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor
+++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor
@@ -82,6 +82,24 @@ along with Foobar. If not, see
}
+
+
+ @foreach (var placeholder in Exportprofile.AvailableAudiofileSchemes)
+ {
+
+ @_localizer[placeholder.Key]
+
+ }
+
_displayContentDialogVisible = new();
private readonly DialogOptions _displayContentDialogOptions = new() { CloseButton = true, CloseOnEscapeKey = true, FullWidth = true, MaxWidth = MaxWidth.ExtraExtraLarge };
diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx
index d3274d80..219748e9 100644
--- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx
+++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx
@@ -207,4 +207,10 @@
No export profile selected!
+
+ Scheme Audiofiles
+
+
+ Enter the audiofiles scheme for this profile here
+
\ No newline at end of file
diff --git a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx
index 48c3b602..c8138d4c 100644
--- a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx
+++ b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.de.resx
@@ -123,7 +123,7 @@
Fehler während des Textimports
-
- Titel
+
+ Dateien
\ No newline at end of file
diff --git a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor
index 60e287c7..99cee2d2 100644
--- a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor
+++ b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.razor
@@ -36,29 +36,25 @@ along with Foobar. If not, see
}
else
{
-
-
-
- @_localizer["Common data"]
-
-
-
-
-
-
-
- @_localizer["Tracks"]
-
-
-
-
-
-
+
+
+ @_localizer["Common data"]
+
+
+
+
+
+
+
+ @_localizer["Files"]
+
+
+
+
+
}
@code {
- Boolean cuesheetDataExpanded = true, cuesheetTracksExpanded = true;
-
public String? FileContentRecognized => _sessionStateContainer.Importfile?.FileContentRecognized;
string SanitizeHTML(string input)
diff --git a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx
index f4f133c6..d5f1bf42 100644
--- a/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx
+++ b/AudioCuesheetEditor/Shared/Import/DisplayAnalyzedResult.resx
@@ -123,7 +123,7 @@
Error during textimport
-
- Tracks
+
+ Files
\ No newline at end of file
diff --git a/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx b/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx
index 56ba6509..be523a1e 100644
--- a/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx
+++ b/AudioCuesheetEditor/Shared/Import/Importprofiles.de.resx
@@ -147,6 +147,9 @@
Ende
+
+ Geben Sie hier das Audiodatei Schema für das Profil an
+
Geben Sie hier das Allgemeine Informationen Schema für das Profil an
@@ -198,6 +201,9 @@
Importprofile zurücksetzen
+
+ Schema Audiodateien
+
Schema Allgemeine Informationen
diff --git a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor
index ffbd2063..53c7c68f 100644
--- a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor
+++ b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor
@@ -1,5 +1,4 @@
-@using System.Linq.Expressions
-