From 7408173246decabadea21c21994fbd3ad269872b Mon Sep 17 00:00:00 2001 From: Profpatsch Date: Sun, 7 Jan 2024 14:31:34 +0100 Subject: [PATCH 1/2] LocaleCompat.forLanguageTag: return Optional if parsing fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s not obvious that the function will fail in some cases and throw an `IllegalArgumentException`. So instead of just failing if parsing fails, return an Optional that all callers have to decide what to do (e.g. the YoutubeExtractor can just ignore the locale in that case, like it does with most other fields in the json if they are unexpected). --- .../extractor/localization/Localization.java | 22 +++++++++++++------ .../extractors/MediaCCCStreamExtractor.java | 5 ++++- .../extractors/YoutubeStreamExtractor.java | 11 +++++++--- .../extractor/stream/SubtitlesStream.java | 4 +++- .../newpipe/extractor/utils/LocaleCompat.java | 19 ++++++++-------- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java index 80ac26663..5727150fe 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/localization/Localization.java @@ -11,10 +11,12 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nonnull; import javax.annotation.Nullable; + public class Localization implements Serializable { public static final Localization DEFAULT = new Localization("en", "GB"); @@ -26,20 +28,28 @@ public class Localization implements Serializable { /** * @param localizationCodeList a list of localization code, formatted like {@link * #getLocalizationCode()} + * @throws IllegalArgumentException If any of the localizationCodeList is formatted incorrectly + * @return list of Localization objects */ + @Nonnull public static List listFrom(final String... localizationCodeList) { final List toReturn = new ArrayList<>(); for (final String localizationCode : localizationCodeList) { - toReturn.add(fromLocalizationCode(localizationCode)); + toReturn.add(fromLocalizationCode(localizationCode) + .orElseThrow(() -> new IllegalArgumentException( + "Not a localization code: " + localizationCode + ))); } return Collections.unmodifiableList(toReturn); } /** * @param localizationCode a localization code, formatted like {@link #getLocalizationCode()} + * @return A Localization, if the code was valid. */ - public static Localization fromLocalizationCode(final String localizationCode) { - return fromLocale(LocaleCompat.forLanguageTag(localizationCode)); + @Nonnull + public static Optional fromLocalizationCode(final String localizationCode) { + return LocaleCompat.forLanguageTag(localizationCode).map(Localization::fromLocale); } public Localization(@Nonnull final String languageCode, @Nullable final String countryCode) { @@ -61,10 +71,6 @@ public class Localization implements Serializable { return countryCode == null ? "" : countryCode; } - public Locale asLocale() { - return new Locale(getLanguageCode(), getCountryCode()); - } - public static Localization fromLocale(@Nonnull final Locale locale) { return new Localization(locale.getLanguage(), locale.getCountry()); } @@ -72,6 +78,8 @@ public class Localization implements Serializable { /** * Return a formatted string in the form of: {@code language-Country}, or * just {@code language} if country is {@code null}. + * + * @return A correctly formatted localizationCode for this localization. */ public String getLocalizationCode() { return languageCode + (countryCode == null ? "" : "-" + countryCode); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java index 9f299a71a..5e7641f0f 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java @@ -130,7 +130,10 @@ public class MediaCCCStreamExtractor extends StreamExtractor { // track with multiple languages, so there is no specific language for this stream // Don't set the audio language in this case if (language != null && !language.contains("-")) { - builder.setAudioLocale(LocaleCompat.forLanguageTag(language)); + builder.setAudioLocale(LocaleCompat.forLanguageTag(language).orElseThrow(() -> + new ExtractionException( + "Cannot convert this language to a locale: " + language) + )); } // Not checking containsSimilarStream here, since MediaCCC does not provide enough diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index e77ea16b4..fd6b94445 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -190,7 +190,7 @@ public class YoutubeStreamExtractor extends StreamExtractor { try { // Premiered 20 hours ago final TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor( - Localization.fromLocalizationCode("en")); + Localization.fromLocalizationCode("en").get()); final OffsetDateTime parsedTime = timeAgoParser.parse(time).offsetDateTime(); return DateTimeFormatter.ISO_LOCAL_DATE.format(parsedTime); } catch (final Exception ignored) { @@ -1378,8 +1378,13 @@ public class YoutubeStreamExtractor extends StreamExtractor { final int audioTrackIdLastLocaleCharacter = audioTrackId.indexOf("."); if (audioTrackIdLastLocaleCharacter != -1) { // Audio tracks IDs are in the form LANGUAGE_CODE.TRACK_NUMBER - itagItem.setAudioLocale(LocaleCompat.forLanguageTag( - audioTrackId.substring(0, audioTrackIdLastLocaleCharacter))); + @Nullable final Locale locale = + LocaleCompat.forLanguageTag( + audioTrackId.substring(0, audioTrackIdLastLocaleCharacter + )).orElse(null); + if (locale != null) { + itagItem.setAudioLocale(locale); + } } itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl)); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java index 983d20ce9..278b4e9a6 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java @@ -231,7 +231,9 @@ public final class SubtitlesStream extends Stream { final boolean autoGenerated, @Nullable final String manifestUrl) { super(id, content, isUrl, mediaFormat, deliveryMethod, manifestUrl); - this.locale = LocaleCompat.forLanguageTag(languageCode); + this.locale = LocaleCompat.forLanguageTag(languageCode).orElseThrow( + () -> new IllegalArgumentException( + "not a valid locale language code: " + languageCode)); this.code = languageCode; this.format = mediaFormat; this.autoGenerated = autoGenerated; diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java index a8f85f47b..082a56824 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/LocaleCompat.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.extractor.utils; import java.util.Locale; +import java.util.Optional; /** * This class contains a simple implementation of {@link Locale#forLanguageTag(String)} for Android @@ -15,29 +16,29 @@ public final class LocaleCompat { // Source: The AndroidX LocaleListCompat class's private forLanguageTagCompat() method. // Use Locale.forLanguageTag() on Android API level >= 21 / Java instead. - public static Locale forLanguageTag(final String str) { + public static Optional forLanguageTag(final String str) { if (str.contains("-")) { final String[] args = str.split("-", -1); if (args.length > 2) { - return new Locale(args[0], args[1], args[2]); + return Optional.of(new Locale(args[0], args[1], args[2])); } else if (args.length > 1) { - return new Locale(args[0], args[1]); + return Optional.of(new Locale(args[0], args[1])); } else if (args.length == 1) { - return new Locale(args[0]); + return Optional.of(new Locale(args[0])); } } else if (str.contains("_")) { final String[] args = str.split("_", -1); if (args.length > 2) { - return new Locale(args[0], args[1], args[2]); + return Optional.of(new Locale(args[0], args[1], args[2])); } else if (args.length > 1) { - return new Locale(args[0], args[1]); + return Optional.of(new Locale(args[0], args[1])); } else if (args.length == 1) { - return new Locale(args[0]); + return Optional.of(new Locale(args[0])); } } else { - return new Locale(str); + return Optional.of(new Locale(str)); } - throw new IllegalArgumentException("Can not parse language tag: [" + str + "]"); + return Optional.empty(); } } From 23fc7aa209caf554c86ef9ce8f7e002369214171 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 29 Mar 2024 13:41:23 +0100 Subject: [PATCH 2/2] Throw ParsingException instead of IllegalArg --- .../extractors/MediaCCCStreamExtractor.java | 2 +- .../youtube/extractors/YoutubeStreamExtractor.java | 12 ++++-------- .../newpipe/extractor/stream/SubtitlesStream.java | 7 ++++--- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java index 5e7641f0f..99ddf5e08 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/extractors/MediaCCCStreamExtractor.java @@ -131,7 +131,7 @@ public class MediaCCCStreamExtractor extends StreamExtractor { // Don't set the audio language in this case if (language != null && !language.contains("-")) { builder.setAudioLocale(LocaleCompat.forLanguageTag(language).orElseThrow(() -> - new ExtractionException( + new ParsingException( "Cannot convert this language to a locale: " + language) )); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java index fd6b94445..6dd3520a1 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeStreamExtractor.java @@ -190,7 +190,7 @@ public class YoutubeStreamExtractor extends StreamExtractor { try { // Premiered 20 hours ago final TimeAgoParser timeAgoParser = TimeAgoPatternsManager.getTimeAgoParserFor( - Localization.fromLocalizationCode("en").get()); + new Localization("en")); final OffsetDateTime parsedTime = timeAgoParser.parse(time).offsetDateTime(); return DateTimeFormatter.ISO_LOCAL_DATE.format(parsedTime); } catch (final Exception ignored) { @@ -1378,13 +1378,9 @@ public class YoutubeStreamExtractor extends StreamExtractor { final int audioTrackIdLastLocaleCharacter = audioTrackId.indexOf("."); if (audioTrackIdLastLocaleCharacter != -1) { // Audio tracks IDs are in the form LANGUAGE_CODE.TRACK_NUMBER - @Nullable final Locale locale = - LocaleCompat.forLanguageTag( - audioTrackId.substring(0, audioTrackIdLastLocaleCharacter - )).orElse(null); - if (locale != null) { - itagItem.setAudioLocale(locale); - } + LocaleCompat.forLanguageTag( + audioTrackId.substring(0, audioTrackIdLastLocaleCharacter) + ).ifPresent(itagItem::setAudioLocale); } itagItem.setAudioTrackType(YoutubeParsingHelper.extractAudioTrackType(streamUrl)); } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java index 278b4e9a6..08886dcac 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/SubtitlesStream.java @@ -1,6 +1,7 @@ package org.schabi.newpipe.extractor.stream; import org.schabi.newpipe.extractor.MediaFormat; +import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.services.youtube.ItagItem; import org.schabi.newpipe.extractor.utils.LocaleCompat; @@ -170,7 +171,7 @@ public final class SubtitlesStream extends Stream { * not set, or have been set as {@code null} */ @Nonnull - public SubtitlesStream build() { + public SubtitlesStream build() throws ParsingException { if (content == null) { throw new IllegalStateException("No valid content was specified. Please specify a " + "valid one with setContent."); @@ -229,10 +230,10 @@ public final class SubtitlesStream extends Stream { @Nonnull final DeliveryMethod deliveryMethod, @Nonnull final String languageCode, final boolean autoGenerated, - @Nullable final String manifestUrl) { + @Nullable final String manifestUrl) throws ParsingException { super(id, content, isUrl, mediaFormat, deliveryMethod, manifestUrl); this.locale = LocaleCompat.forLanguageTag(languageCode).orElseThrow( - () -> new IllegalArgumentException( + () -> new ParsingException( "not a valid locale language code: " + languageCode)); this.code = languageCode; this.format = mediaFormat;