From 0135fe1d5a01367cc28555bb565326c7d68e2602 Mon Sep 17 00:00:00 2001
From: Nianna <31940002+Nianna@users.noreply.github.com>
Date: Wed, 2 Oct 2024 20:17:52 +0200
Subject: [PATCH] Feat/add support for all tags from 1.1.0 format (#51)
* Add keys for all tags present in version 1.1.0
* Update track name on P1, P2 tag value change
* Refactor TagKey using java 17 switch
* Add validator for VERSION tag
* Auto-load audio files defined by AUDIO, VOCALS and INSTRUMENTAL tags
* Add CALCMEDLEY tag with validator
* Suppress LineLength checkstyle warning for regex
---
config/checkstyle/checkstyle.xml | 5 ++
.../nianna/karedi/context/AudioContext.java | 6 +-
.../nianna/karedi/context/TxtContext.java | 12 +++-
.../context/actions/ImportAudioAction.java | 2 +-
.../karedi/context/actions/NewSongAction.java | 6 +-
.../karedi/context/actions/RenameAction.java | 2 +-
.../com/github/nianna/karedi/song/Song.java | 46 +++++++-------
.../github/nianna/karedi/song/tag/TagKey.java | 60 ++++++++++---------
.../nianna/karedi/song/tag/TagValidators.java | 6 ++
.../nianna/karedi/util/StringValidators.java | 56 ++++++++++-------
src/main/resources/messages.properties | 2 +
src/main/resources/messages_en_GB.properties | 2 +
src/main/resources/messages_pl_PL.properties | 2 +
13 files changed, 128 insertions(+), 79 deletions(-)
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index e5fab71..5ff7fa2 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -64,6 +64,9 @@
+
+
+
@@ -158,6 +161,8 @@
+
+
\ No newline at end of file
diff --git a/src/main/java/com/github/nianna/karedi/context/AudioContext.java b/src/main/java/com/github/nianna/karedi/context/AudioContext.java
index a5af32a..d2b859a 100644
--- a/src/main/java/com/github/nianna/karedi/context/AudioContext.java
+++ b/src/main/java/com/github/nianna/karedi/context/AudioContext.java
@@ -104,11 +104,13 @@ public void removeAudioFile(PreloadedAudioFile file) {
file.releaseResources();
}
- public void loadAudioFile(File file) {
+ public void loadAudioFile(File file, boolean setAsDefault) {
AudioFileLoader.loadAudioFile(file, (newAudio -> {
if (newAudio.isPresent()) {
player.addAudioFile(newAudio.get());
- setActiveAudioFile(newAudio.get());
+ if (setAsDefault) {
+ setActiveAudioFile(newAudio.get());
+ }
LOGGER.info(I18N.get("import.audio.success"));
} else {
LOGGER.severe(I18N.get("import.audio.fail"));
diff --git a/src/main/java/com/github/nianna/karedi/context/TxtContext.java b/src/main/java/com/github/nianna/karedi/context/TxtContext.java
index 9ec8eb3..806e544 100644
--- a/src/main/java/com/github/nianna/karedi/context/TxtContext.java
+++ b/src/main/java/com/github/nianna/karedi/context/TxtContext.java
@@ -14,7 +14,9 @@
import java.io.File;
import java.util.List;
+import java.util.Optional;
import java.util.logging.Logger;
+import java.util.stream.Stream;
public class TxtContext {
@@ -60,8 +62,10 @@ public void loadSongFile(File file, boolean resetPlayer) {
reset(resetPlayer);
setActiveFile(file);
Song song = txtFacade.loadFromTxtFile(file);
- song.getTagValue(TagKey.MP3)
- .ifPresent(audioFileName -> audioContext.loadAudioFile(new File(file.getParent(), audioFileName)));
+ song.getMainAudioTagValue().ifPresent(filename -> loadAudioFileWithName(file, filename, true));
+ Stream.of(song.getTagValue(TagKey.INSTRUMENTAL), song.getTagValue(TagKey.VOCALS))
+ .flatMap(Optional::stream)
+ .forEach(filename -> loadAudioFileWithName(file, filename, false));
activeSongContext.setSong(song);
LOGGER.info(I18N.get("load.success"));
} else {
@@ -69,6 +73,10 @@ public void loadSongFile(File file, boolean resetPlayer) {
}
}
+ private void loadAudioFileWithName(File txtFile, String filename, boolean setAsDefault) {
+ audioContext.loadAudioFile(new File(txtFile.getParent(), filename), setAsDefault);
+ }
+
public boolean saveSongToFile(File file) {
if (file != null) {
if (txtFacade.saveSongToFile(file, activeSongContext.getSong())) {
diff --git a/src/main/java/com/github/nianna/karedi/context/actions/ImportAudioAction.java b/src/main/java/com/github/nianna/karedi/context/actions/ImportAudioAction.java
index 0a4a1ff..9f29173 100644
--- a/src/main/java/com/github/nianna/karedi/context/actions/ImportAudioAction.java
+++ b/src/main/java/com/github/nianna/karedi/context/actions/ImportAudioAction.java
@@ -18,7 +18,7 @@ class ImportAudioAction extends ContextfulKarediAction {
protected void onAction(ActionEvent event) {
File file = KarediApp.getInstance().getAudioFileToOpen();
if (file != null) {
- audioContext.loadAudioFile(file);
+ audioContext.loadAudioFile(file, true);
}
}
}
diff --git a/src/main/java/com/github/nianna/karedi/context/actions/NewSongAction.java b/src/main/java/com/github/nianna/karedi/context/actions/NewSongAction.java
index 60c409d..e5f760d 100644
--- a/src/main/java/com/github/nianna/karedi/context/actions/NewSongAction.java
+++ b/src/main/java/com/github/nianna/karedi/context/actions/NewSongAction.java
@@ -49,7 +49,7 @@ protected void onAction(ActionEvent event) {
private boolean finish() {
txtContext.reset(true);
if (outputDir == null && audioFile != null) {
- audioContext.loadAudioFile(audioFile);
+ audioContext.loadAudioFile(audioFile, true);
}
activeSongContext.setSong(song);
audioContext.setMarkerBeat(0);
@@ -78,7 +78,7 @@ private void createTxtFile(File songFolder) {
private void copyAudioFile(File songFolder) {
if (audioFile != null) {
- song.getTagValue(TagKey.MP3).ifPresent(audioFilename -> {
+ song.getMainAudioTagValue().ifPresent(audioFilename -> {
File newAudioFile = new File(songFolder, audioFilename);
if (canProceedToWriteFile(newAudioFile)) {
try {
@@ -88,7 +88,7 @@ private void copyAudioFile(File songFolder) {
LOGGER.warning(I18N.get("creator.copy_audio.fail"));
e.printStackTrace();
}
- audioContext.loadAudioFile(newAudioFile);
+ audioContext.loadAudioFile(newAudioFile, true);
}
});
}
diff --git a/src/main/java/com/github/nianna/karedi/context/actions/RenameAction.java b/src/main/java/com/github/nianna/karedi/context/actions/RenameAction.java
index 87c2031..49e011d 100644
--- a/src/main/java/com/github/nianna/karedi/context/actions/RenameAction.java
+++ b/src/main/java/com/github/nianna/karedi/context/actions/RenameAction.java
@@ -24,7 +24,7 @@ protected void onAction(ActionEvent event) {
activeSongContext.getSong().getTagValue(TagKey.ARTIST).ifPresent(dialog::setSongArtist);
activeSongContext.getSong().getTagValue(TagKey.TITLE).ifPresent(dialog::setSongTitle);
- activeSongContext.getSong().getTagValue(TagKey.MP3).ifPresent(dialog::setAudioFilename);
+ activeSongContext.getSong().getMainAudioTagValue().ifPresent(dialog::setAudioFilename);
activeSongContext.getSong().getTagValue(TagKey.COVER).ifPresent(dialog::setCoverFilename);
Optional optVideoFilename = activeSongContext.getSong().getTagValue(TagKey.VIDEO);
diff --git a/src/main/java/com/github/nianna/karedi/song/Song.java b/src/main/java/com/github/nianna/karedi/song/Song.java
index de96162..df98aa1 100644
--- a/src/main/java/com/github/nianna/karedi/song/Song.java
+++ b/src/main/java/com/github/nianna/karedi/song/Song.java
@@ -72,6 +72,10 @@ public Optional getTagValue(TagKey key) {
return getTagValue(key.toString());
}
+ public Optional getMainAudioTagValue() {
+ return getTagValue(TagKey.AUDIO).or(() -> getTagValue(TagKey.MP3));
+ }
+
public Optional getTagValue(String key) {
return getTag(key).map(Tag::getValue);
}
@@ -128,27 +132,27 @@ private void onTagValueChanged(Tag tag) {
Optional tagKey = TagKey.optionalValueOf(tag.getKey());
tagKey.ifPresent(key -> {
switch (key) {
- case MEDLEYSTARTBEAT:
- medley.setStartBeat(
- Converter.toInteger(tag.getValue()).orElse(medley.getEndBeat()));
- break;
- case MEDLEYENDBEAT:
- medley.setEndBeat(
- Converter.toInteger(tag.getValue()).orElse(medley.getStartBeat()));
- break;
- case BPM:
- Converter.toDouble(tag.getValue()).ifPresent(value -> converter.setBpm(value));
- break;
- case GAP:
- Converter.toInteger(tag.getValue()).ifPresent(value -> converter.setGap(value));
- break;
- case DUETSINGERP1:
- renameTrack(0, tag.getValue());
- break;
- case DUETSINGERP2:
- renameTrack(1, tag.getValue());
- break;
- default:
+ case MEDLEYSTARTBEAT:
+ medley.setStartBeat(
+ Converter.toInteger(tag.getValue()).orElse(medley.getEndBeat()));
+ break;
+ case MEDLEYENDBEAT:
+ medley.setEndBeat(
+ Converter.toInteger(tag.getValue()).orElse(medley.getStartBeat()));
+ break;
+ case BPM:
+ Converter.toDouble(tag.getValue()).ifPresent(value -> converter.setBpm(value));
+ break;
+ case GAP:
+ Converter.toInteger(tag.getValue()).ifPresent(value -> converter.setGap(value));
+ break;
+ case DUETSINGERP1, P1:
+ renameTrack(0, tag.getValue());
+ break;
+ case DUETSINGERP2, P2:
+ renameTrack(1, tag.getValue());
+ break;
+ default:
}
});
}
diff --git a/src/main/java/com/github/nianna/karedi/song/tag/TagKey.java b/src/main/java/com/github/nianna/karedi/song/tag/TagKey.java
index 03e5114..b0ca284 100644
--- a/src/main/java/com/github/nianna/karedi/song/tag/TagKey.java
+++ b/src/main/java/com/github/nianna/karedi/song/tag/TagKey.java
@@ -13,7 +13,9 @@ public enum TagKey {
LANGUAGE,
COVER,
MP3,
+ AUDIO,
VOCALS,
+ INSTRUMENTAL,
VIDEO,
BACKGROUND,
BPM,
@@ -24,8 +26,15 @@ public enum TagKey {
PREVIEWSTART,
DUETSINGERP1,
DUETSINGERP2,
+ P1,
+ P2,
MEDLEYSTARTBEAT,
- MEDLEYENDBEAT;
+ MEDLEYENDBEAT,
+ VERSION,
+ TAGS,
+ COMMENT,
+ PROVIDEDBY,
+ CALCMEDLEY;
public static Optional optionalValueOf(String str) {
str = str.trim().toUpperCase(Locale.ROOT);
@@ -37,39 +46,32 @@ public static Optional optionalValueOf(String str) {
}
public static boolean expectsAnInteger(TagKey key) {
- switch (key) {
- case YEAR:
- case GAP:
- case MEDLEYSTARTBEAT:
- case MEDLEYENDBEAT:
- case END:
- return true;
- default:
- return false;
- }
+ return switch (key) {
+ case YEAR, GAP, MEDLEYSTARTBEAT, MEDLEYENDBEAT, END -> true;
+ default -> false;
+ };
}
public static boolean expectsADouble(TagKey key) {
- switch (key) {
- case START:
- case VIDEOGAP:
- case BPM:
- return true;
- default:
- return false;
- }
+ return switch (key) {
+ case START, VIDEOGAP, BPM -> true;
+ default -> false;
+ };
}
public static boolean expectsAFileName(TagKey key) {
- switch (key) {
- case MP3:
- case COVER:
- case VIDEO:
- case BACKGROUND:
- case VOCALS:
- return true;
- default:
- return false;
- }
+ return switch (key) {
+ case MP3, COVER, AUDIO, VIDEO, BACKGROUND, VOCALS, INSTRUMENTAL -> true;
+ default -> false;
+ };
+ }
+
+ public static boolean expectsASemVer(TagKey key) {
+ return key == VERSION;
+ }
+
+ public static boolean expectsOnOff(TagKey key) {
+ return key == CALCMEDLEY;
}
+
}
diff --git a/src/main/java/com/github/nianna/karedi/song/tag/TagValidators.java b/src/main/java/com/github/nianna/karedi/song/tag/TagValidators.java
index a9a99f0..66e29b9 100644
--- a/src/main/java/com/github/nianna/karedi/song/tag/TagValidators.java
+++ b/src/main/java/com/github/nianna/karedi/song/tag/TagValidators.java
@@ -92,6 +92,12 @@ private static Validator legalInputValidator(TagKey key) {
return StringValidators.forNonNegativeDouble();
}
}
+ if (TagKey.expectsASemVer(key)) {
+ return StringValidators.forSemVer();
+ }
+ if (TagKey.expectsOnOff(key)) {
+ return StringValidators.forOnOff();
+ }
return StringValidators.noForbiddenCharacters(forbiddenCharacterRegex(key).orElse(null));
}
diff --git a/src/main/java/com/github/nianna/karedi/util/StringValidators.java b/src/main/java/com/github/nianna/karedi/util/StringValidators.java
index 73cc557..0de1dbe 100644
--- a/src/main/java/com/github/nianna/karedi/util/StringValidators.java
+++ b/src/main/java/com/github/nianna/karedi/util/StringValidators.java
@@ -9,6 +9,10 @@
import com.github.nianna.karedi.I18N;
public class StringValidators {
+
+ @SuppressWarnings("checkstyle:linelength")
+ private static final String SEM_VER_REGEX = "^(0|[1-9][0-9]*).(0|[1-9][0-9]*).(0|[1-9][0-9]*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*)(.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(.[0-9A-Za-z-]+)*)?$";
+
private StringValidators() {
}
@@ -24,6 +28,14 @@ public static Validator forNonNegativeDouble() {
return StringValidators::nonNegativeDoubleValidator;
}
+ public static Validator forSemVer() {
+ return StringValidators::semVerValidator;
+ }
+
+ public static Validator forOnOff() {
+ return StringValidators::onOffValidator;
+ }
+
public static Validator forDouble() {
return StringValidators::doubleValidator;
}
@@ -39,35 +51,39 @@ public static Validator noForbiddenCharacters(String forbiddenCharRegex)
}
private static ValidationResult nonNegativeIntegerValidator(Control c, String newValue) {
- ValidationResult result = ValidationResult.fromErrorIf(c,
- I18N.get("validator.nonnegative_number_required"),
- Converter.toInteger(newValue).map(value -> {
- return value < 0;
- }).orElse(true));
- return result;
+ return ValidationResult.fromErrorIf(c,
+ I18N.get("validator.nonnegative_number_required"),
+ Converter.toInteger(newValue).map(value -> value < 0).orElse(true));
}
private static ValidationResult integerValidator(Control c, String newValue) {
- ValidationResult result = ValidationResult.fromErrorIf(c,
- I18N.get("validator.number_required"),
- !Converter.toInteger(newValue).isPresent());
- return result;
+ return ValidationResult.fromErrorIf(c,
+ I18N.get("validator.number_required"),
+ Converter.toInteger(newValue).isEmpty());
}
private static ValidationResult nonNegativeDoubleValidator(Control c, String newValue) {
- ValidationResult result = ValidationResult.fromErrorIf(c,
- I18N.get("validator.nonnegative_number_required"),
- Converter.toDouble(newValue).map(value -> {
- return value < 0;
- }).orElse(true));
- return result;
+ return ValidationResult.fromErrorIf(c,
+ I18N.get("validator.nonnegative_number_required"),
+ Converter.toDouble(newValue).map(value -> value < 0).orElse(true));
}
private static ValidationResult doubleValidator(Control c, String newValue) {
- ValidationResult result = ValidationResult.fromErrorIf(c,
- I18N.get("validator.number_required"),
- !Converter.toDouble(newValue).isPresent());
- return result;
+ return ValidationResult.fromErrorIf(c,
+ I18N.get("validator.number_required"),
+ Converter.toDouble(newValue).isEmpty());
+ }
+
+ private static ValidationResult semVerValidator(Control c, String newValue) {
+ return ValidationResult.fromErrorIf(c,
+ I18N.get("validator.sem_ver_required"),
+ newValue != null && !newValue.matches(SEM_VER_REGEX));
+ }
+
+ private static ValidationResult onOffValidator(Control c, String newValue) {
+ return ValidationResult.fromErrorIf(c,
+ I18N.get("validator.on_off_required"),
+ newValue != null && !newValue.matches("on|off"));
}
}
diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties
index cec88a2..ea64a03 100644
--- a/src/main/resources/messages.properties
+++ b/src/main/resources/messages.properties
@@ -358,6 +358,8 @@ validator.tag.year.too_big=The year should be lower than or equal to the current
validator.number_required=Must be a valid number
validator.nonnegative_number_required=Must be a valid non-negative number
validator.contains_illegal_characters=Contains illegal characters
+validator.sem_ver_required=Must be a valid SemVer version e.g. 1.1.0
+validator.on_off_required=Must be either `on` or `off`
common.paste=Paste
common.cut=Cut
diff --git a/src/main/resources/messages_en_GB.properties b/src/main/resources/messages_en_GB.properties
index cec88a2..ea64a03 100644
--- a/src/main/resources/messages_en_GB.properties
+++ b/src/main/resources/messages_en_GB.properties
@@ -358,6 +358,8 @@ validator.tag.year.too_big=The year should be lower than or equal to the current
validator.number_required=Must be a valid number
validator.nonnegative_number_required=Must be a valid non-negative number
validator.contains_illegal_characters=Contains illegal characters
+validator.sem_ver_required=Must be a valid SemVer version e.g. 1.1.0
+validator.on_off_required=Must be either `on` or `off`
common.paste=Paste
common.cut=Cut
diff --git a/src/main/resources/messages_pl_PL.properties b/src/main/resources/messages_pl_PL.properties
index 3f08ae3..060a22b 100644
--- a/src/main/resources/messages_pl_PL.properties
+++ b/src/main/resources/messages_pl_PL.properties
@@ -366,6 +366,8 @@ validator.tag.year.too_big=Rok powinien by\u0107 mniejszy od obecnego albo mu r
validator.number_required=Oczekiwano liczby
validator.nonnegative_number_required=Oczekiwano nieujemnej liczby
validator.contains_illegal_characters=Zawiera niedozwolone znaki
+validator.sem_ver_required=Musi by\u0107 zgodna z regu\u0142ami SemVer np. 1.1.0
+validator.on_off_required=Musi mie\u0107 warto\u015B\u0107 `on` lub `off`
common.paste=Wklej
common.cut=Wytnij