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