Fixes ASS converter, adds some doc
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.github.gtache.autosubtitle.subtitle.converter.impl;
|
||||
|
||||
import com.github.gtache.autosubtitle.VideoInfo;
|
||||
import com.github.gtache.autosubtitle.subtitle.Bounds;
|
||||
import com.github.gtache.autosubtitle.subtitle.Font;
|
||||
import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.SubtitleCollection;
|
||||
@@ -8,19 +9,21 @@ import com.github.gtache.autosubtitle.subtitle.converter.FormatOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.SubtitleConverter;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.BoundsImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.FontImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
|
||||
import com.github.gtache.autosubtitle.translation.Translator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -34,7 +37,30 @@ public class ASSSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
private static final String DIALOGUE = "Dialogue:";
|
||||
private static final String EVENTS_SECTION = "[Events]";
|
||||
private static final String STYLES_SECTION = "[V4+ Styles]";
|
||||
private static final String START_FIELD = "Start";
|
||||
private static final String END_FIELD = "End";
|
||||
private static final String STYLE_FIELD = "Style";
|
||||
private static final String TEXT_FIELD = "Text";
|
||||
private static final String NAME_FIELD = "Name";
|
||||
private static final String FONTNAME_FIELD = "Fontname";
|
||||
private static final String FONTSIZE_FIELD = "Fontsize";
|
||||
private static final String MARGIN_L_FIELD = "MarginL";
|
||||
private static final String MARGIN_R_FIELD = "MarginR";
|
||||
private static final String MARGIN_V_FIELD = "MarginV";
|
||||
private static final List<String> STYLE_FIELDS = List.of(NAME_FIELD, FONTNAME_FIELD, FONTSIZE_FIELD, "PrimaryColour",
|
||||
"SecondaryColour", "OutlineColour", "BackColour", "Bold", "Italic",
|
||||
"Underline", "StrikeOut", "ScaleX", "ScaleY", "Spacing", "Angle", "BorderStyle", "Outline", "Shadow", "Alignment",
|
||||
MARGIN_L_FIELD, MARGIN_R_FIELD, MARGIN_V_FIELD, "AlphaLevel", "Encoding");
|
||||
private static final Set<String> REQUIRED_STYLE_FIELDS = Set.of(NAME_FIELD, FONTNAME_FIELD, FONTSIZE_FIELD);
|
||||
private static final List<String> EVENT_FIELDS = List.of("Layer", START_FIELD, END_FIELD, STYLE_FIELD, "Name", MARGIN_L_FIELD, MARGIN_R_FIELD, MARGIN_V_FIELD, "Effect", TEXT_FIELD);
|
||||
private static final Set<String> REQUIRED_EVENT_FIELDS = Set.of(START_FIELD, END_FIELD, STYLE_FIELD, TEXT_FIELD);
|
||||
|
||||
private static final Pattern NEWLINE_PATTERN = Pattern.compile("\\n");
|
||||
private static final Pattern POS_PATTERN = Pattern.compile("^\\{\\\\pos\\((?<x>\\d+),(?<y>\\d+)\\)}");
|
||||
private static final Pattern START_DIALOG_PATTERN = Pattern.compile("^Dialogue: ");
|
||||
private static final Pattern START_ZERO_PATTERN = Pattern.compile("^0+");
|
||||
private static final Pattern PLAY_RES_Y_PATTERN = Pattern.compile("^PlayResY: (?<height>\\d+)", Pattern.MULTILINE);
|
||||
|
||||
private final Map<String, Translator<?>> translators;
|
||||
|
||||
@Inject
|
||||
@@ -45,22 +71,32 @@ public class ASSSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
@Override
|
||||
public String format(final SubtitleCollection<?> collection, final FormatOptions options) {
|
||||
final var subtitles = collection.subtitles().stream().sorted(Comparator.comparing(Subtitle::start).thenComparing(Subtitle::end)).toList();
|
||||
final var scriptInfo = getScriptInfo(options.videoInfo());
|
||||
final var styles = getStyles(subtitles, options.defaultFont());
|
||||
final var events = getEvents(subtitles, options.defaultFont());
|
||||
final var scriptInfo = writeScriptInfo(options.videoInfo());
|
||||
final var styles = writeStyles(subtitles, options.defaultFont());
|
||||
final var events = writeEvents(subtitles, options.defaultFont());
|
||||
return scriptInfo + "\n\n" + styles + "\n\n" + events + "\n";
|
||||
}
|
||||
|
||||
private static String getEvents(final Collection<? extends Subtitle> subtitles, final Font defaultFont) {
|
||||
return EVENTS_SECTION + "\n" + "Format: Start, End, Style, Text\n" +
|
||||
subtitles.stream().map(s -> getEvent(s, defaultFont)).collect(Collectors.joining("\n"));
|
||||
private static String writeEvents(final Collection<? extends Subtitle> subtitles, final Font defaultFont) {
|
||||
return EVENTS_SECTION + "\n" + "Format: " + String.join(", ", EVENT_FIELDS) + "\n" +
|
||||
subtitles.stream().map(s -> writeEvent(s, defaultFont)).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static String getEvent(final Subtitle subtitle, final Font defaultFont) {
|
||||
return DIALOGUE + " " + formatTime(subtitle.start()) + "," + formatTime(subtitle.end()) + "," + getName(subtitle.font(), defaultFont) + "," + subtitle.content();
|
||||
private static String writeEvent(final Subtitle subtitle, final Font defaultFont) {
|
||||
return DIALOGUE + " 0," +
|
||||
formatTime(subtitle.start()) + "," +
|
||||
formatTime(subtitle.end()) + "," +
|
||||
getStyleName(subtitle.font(), defaultFont) + "," +
|
||||
"X," +
|
||||
"0000," +
|
||||
"0000," +
|
||||
"0000," +
|
||||
"," +
|
||||
(subtitle.bounds() == null ? "" : "{\\pos(" + (long) subtitle.bounds().x() + "," + (long) subtitle.bounds().y() + ")}") +
|
||||
subtitle.content();
|
||||
}
|
||||
|
||||
private static String getName(final Font font, final Font defaultFont) {
|
||||
private static String getStyleName(final Font font, final Font defaultFont) {
|
||||
final String fontName;
|
||||
final int fontSize;
|
||||
if (font == null) {
|
||||
@@ -73,7 +109,7 @@ public class ASSSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
return fontName + fontSize;
|
||||
}
|
||||
|
||||
private static String getStyles(final Collection<? extends Subtitle> subtitles, final Font defaultFont) {
|
||||
private static String writeStyles(final Collection<? extends Subtitle> subtitles, final Font defaultFont) {
|
||||
return STYLES_SECTION + "\n" + "Format: Name, Fontname, Fontsize\n" + listStyles(subtitles, defaultFont);
|
||||
}
|
||||
|
||||
@@ -82,10 +118,10 @@ public class ASSSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
if (subtitles.stream().anyMatch(s -> s.font() == null)) {
|
||||
uniqueStyles.add(defaultFont);
|
||||
}
|
||||
return uniqueStyles.stream().map(f -> STYLE + " " + getName(f, defaultFont) + ", " + f.name() + ", " + f.size()).collect(Collectors.joining("\n"));
|
||||
return uniqueStyles.stream().map(f -> STYLE + " " + getStyleName(f, defaultFont) + ", " + f.name() + ", " + f.size()).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static String getScriptInfo(final VideoInfo videoInfo) {
|
||||
private static String writeScriptInfo(final VideoInfo videoInfo) {
|
||||
return """
|
||||
[Script Info]
|
||||
PlayResX: %d
|
||||
@@ -95,72 +131,127 @@ public class ASSSubtitleConverter implements SubtitleConverter<SubtitleImpl> {
|
||||
|
||||
@Override
|
||||
public SubtitleCollectionImpl<SubtitleImpl> parse(final String content, final ParseOptions options) throws ParseException {
|
||||
final var fonts = parseFonts(content, options.defaultFont());
|
||||
final var subtitles = parseSubtitles(content, fonts);
|
||||
final var videoHeight = parseVideoHeight(content);
|
||||
final var fonts = parseFonts(content);
|
||||
final var subtitles = parseSubtitles(content, fonts, options.defaultFont(), videoHeight);
|
||||
final var text = subtitles.stream().map(Subtitle::content).collect(Collectors.joining());
|
||||
final var language = translators.values().iterator().next().getLanguage(text); //Use any translator for now
|
||||
return new SubtitleCollectionImpl<>(text, subtitles, language);
|
||||
}
|
||||
|
||||
private static List<SubtitleImpl> parseSubtitles(final String content, final Map<String, ? extends Font> fonts) throws ParseException {
|
||||
final var fontIndex = content.indexOf(EVENTS_SECTION);
|
||||
if (fontIndex == -1) {
|
||||
private static int parseVideoHeight(final CharSequence content) {
|
||||
final var matcher = PLAY_RES_Y_PATTERN.matcher(content);
|
||||
if (matcher.find()) {
|
||||
return Integer.parseInt(matcher.group("height"));
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<SubtitleImpl> parseSubtitles(final String content, final Map<String, Font> fonts, final Font defaultFont, final int videoHeight) throws ParseException {
|
||||
final var eventsIndex = content.indexOf(EVENTS_SECTION);
|
||||
if (eventsIndex == -1) {
|
||||
throw new ParseException("Events section not found in " + content);
|
||||
} else {
|
||||
final var split = NEWLINE_PATTERN.split(content.substring(fontIndex));
|
||||
final List<String> fields;
|
||||
if (split[0].startsWith(EVENTS_SECTION)) {
|
||||
fields = getFields(split[1]);
|
||||
} else {
|
||||
throw new ParseException("Couldn't parse events : " + content);
|
||||
final var split = NEWLINE_PATTERN.split(content.substring(eventsIndex));
|
||||
final var fields = getFields(split[1]);
|
||||
final var fieldsMapping = EVENT_FIELDS.stream().collect(Collectors.toMap(e -> e, fields::indexOf));
|
||||
if (REQUIRED_EVENT_FIELDS.stream().anyMatch(s -> fieldsMapping.get(s) == -1)) {
|
||||
throw new ParseException("Missing required fields : " + content);
|
||||
}
|
||||
final var startIndex = fields.indexOf("Start");
|
||||
final var endIndex = fields.indexOf("End");
|
||||
final var styleIndex = fields.indexOf("Style");
|
||||
final var textIndex = fields.indexOf("Text");
|
||||
if (startIndex == -1 || endIndex == -1 || styleIndex == -1 || textIndex == -1) {
|
||||
throw new ParseException("Couldn't parse events : " + content);
|
||||
if (fieldsMapping.get("Text") != fields.size() - 1) {
|
||||
throw new ParseException("Text field should be the last field : " + content);
|
||||
}
|
||||
//Ignore effect for now
|
||||
return Arrays.stream(split).filter(s -> s.startsWith(DIALOGUE))
|
||||
.map(s -> {
|
||||
final var values = Arrays.stream(s.replace(DIALOGUE, "").split(",")).map(String::trim).filter(w -> !w.isBlank()).toList();
|
||||
final var start = parseTime(values.get(startIndex));
|
||||
final var end = parseTime(values.get(endIndex));
|
||||
final var style = values.get(styleIndex);
|
||||
final var font = fonts.get(style);
|
||||
final var text = values.stream().skip(textIndex).collect(Collectors.joining(", "));
|
||||
return new SubtitleImpl(text, start, end, font, null); //TODO pos style overrides
|
||||
final var values = parseValues(s, fields.size());
|
||||
final var start = parseTime(values.get(fieldsMapping.get(START_FIELD)));
|
||||
final var end = parseTime(values.get(fieldsMapping.get(END_FIELD)));
|
||||
final var style = values.get(fieldsMapping.get(STYLE_FIELD));
|
||||
final var marginBounds = getMarginBounds(values, fieldsMapping, videoHeight);
|
||||
final var font = fonts.getOrDefault(style, defaultFont);
|
||||
final var rawText = values.get(fieldsMapping.get(TEXT_FIELD));
|
||||
final var matcher = POS_PATTERN.matcher(rawText);
|
||||
if (matcher.find()) {
|
||||
final var x = Integer.parseInt(matcher.group("x"));
|
||||
final var y = Integer.parseInt(matcher.group("y"));
|
||||
final var bounds = new BoundsImpl(x, y, 0, 0);
|
||||
final var text = matcher.replaceAll("");
|
||||
return new SubtitleImpl(text, start, end, font, bounds);
|
||||
} else {
|
||||
return new SubtitleImpl(rawText, start, end, font, marginBounds);
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Font> parseFonts(final String content, final Font defaultFont) throws ParseException {
|
||||
final var fontIndex = content.indexOf(STYLES_SECTION);
|
||||
if (fontIndex == -1) {
|
||||
private static List<String> parseValues(final CharSequence value, final int size) {
|
||||
final var trimmed = START_DIALOG_PATTERN.matcher(value).replaceAll("").trim();
|
||||
final var values = new ArrayList<String>(EVENT_FIELDS.size());
|
||||
var substring = trimmed;
|
||||
while (!substring.isEmpty()) {
|
||||
final var index = substring.indexOf(',');
|
||||
if (index == -1 || values.size() == size - 1) {
|
||||
values.add(substring);
|
||||
substring = "";
|
||||
} else {
|
||||
values.add(substring.substring(0, index));
|
||||
substring = substring.substring(index + 1);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private static Bounds getMarginBounds(final List<String> values, final Map<String, Integer> fieldsMapping, final int videoHeight) {
|
||||
final var marginLIndex = fieldsMapping.get(MARGIN_L_FIELD);
|
||||
final var marginRIndex = fieldsMapping.get(MARGIN_R_FIELD);
|
||||
final var marginVIndex = fieldsMapping.get(MARGIN_V_FIELD);
|
||||
if (marginLIndex == -1 || marginRIndex == -1 || marginVIndex == -1) {
|
||||
return null;
|
||||
} else {
|
||||
final var marginL = parseMargin(values.get(marginLIndex));
|
||||
final var marginR = parseMargin(values.get(marginRIndex));
|
||||
final var marginV = parseMargin(values.get(marginVIndex));
|
||||
if (marginL == -1 || marginR == -1 || marginV == -1) {
|
||||
return null;
|
||||
} else {
|
||||
final var y = videoHeight <= 0 ? 0 : videoHeight - marginV;
|
||||
return new BoundsImpl(marginL, y, marginR - (double) marginL, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseMargin(final CharSequence margin) {
|
||||
final var removedZero = START_ZERO_PATTERN.matcher(margin).replaceAll("");
|
||||
return removedZero.isEmpty() ? -1 : Integer.parseInt(removedZero);
|
||||
}
|
||||
|
||||
private static Map<String, Font> parseFonts(final String content) throws ParseException {
|
||||
final var stylesIndex = content.indexOf(STYLES_SECTION);
|
||||
if (stylesIndex == -1) {
|
||||
throw new ParseException("Styles section not found in " + content);
|
||||
} else {
|
||||
final var split = NEWLINE_PATTERN.split(content.substring(fontIndex));
|
||||
final List<String> fields;
|
||||
if (split[0].startsWith(STYLES_SECTION)) {
|
||||
fields = getFields(split[1]);
|
||||
} else {
|
||||
throw new ParseException("Couldn't parse styles : " + content);
|
||||
}
|
||||
final var fontNameIndex = fields.indexOf("Fontname");
|
||||
final var fontSizeIndex = fields.indexOf("Fontsize");
|
||||
if (fontNameIndex == -1 || fontSizeIndex == -1) {
|
||||
throw new ParseException("Couldn't parse styles : " + content);
|
||||
final var split = NEWLINE_PATTERN.split(content.substring(stylesIndex));
|
||||
final var fields = getFields(split[1]);
|
||||
final var fieldsMapping = STYLE_FIELDS.stream().collect(Collectors.toMap(e -> e, fields::indexOf));
|
||||
if (REQUIRED_STYLE_FIELDS.stream().anyMatch(s -> fieldsMapping.get(s) == -1)) {
|
||||
throw new ParseException("Missing required fields : " + content);
|
||||
}
|
||||
return Arrays.stream(split).filter(s -> s.startsWith(STYLE))
|
||||
.map(s -> {
|
||||
final var values = Arrays.stream(s.replace(STYLE, "").split(",")).map(String::trim).filter(w -> !w.isBlank()).toList();
|
||||
final var name = values.get(fontNameIndex);
|
||||
final var size = Integer.parseInt(values.get(fontSizeIndex));
|
||||
return new FontImpl(name, size);
|
||||
}).collect(Collectors.toMap(f -> getName(f, defaultFont), f -> f));
|
||||
final var name = values.get(fieldsMapping.get(NAME_FIELD));
|
||||
final var fontName = values.get(fieldsMapping.get(FONTNAME_FIELD));
|
||||
final var fontSize = Integer.parseInt(values.get(fieldsMapping.get(FONTSIZE_FIELD)));
|
||||
return new NamedFont(name, new FontImpl(fontName, fontSize));
|
||||
}).collect(Collectors.toMap(f -> f.name, f -> f.font));
|
||||
}
|
||||
}
|
||||
|
||||
private record NamedFont(String name, Font font) {
|
||||
}
|
||||
|
||||
private static List<String> getFields(final String string) {
|
||||
return Arrays.stream(string.replace(FORMAT, "").split(",")).map(String::trim).filter(s -> !s.isBlank()).toList();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.github.gtache.autosubtitle.subtitle.Subtitle;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.FormatOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseException;
|
||||
import com.github.gtache.autosubtitle.subtitle.converter.ParseOptions;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.BoundsImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.FontImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleCollectionImpl;
|
||||
import com.github.gtache.autosubtitle.subtitle.impl.SubtitleImpl;
|
||||
@@ -13,10 +14,14 @@ import com.github.gtache.autosubtitle.translation.Translator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
@@ -56,6 +61,28 @@ class TestASSSubtitleConverter {
|
||||
when(translator.getLanguage(anyString())).thenReturn(language);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFormatDefaultFont() {
|
||||
final var font = new FontImpl("Times New Roman", 13);
|
||||
when(formatOptions.defaultFont()).thenReturn(font);
|
||||
final var subtitle = new SubtitleImpl("Test", 0, 1000, null, null);
|
||||
final var expected = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize
|
||||
Style: Times New Roman13, Times New Roman, 13
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,00:00:00.00,00:00:01.00,Times New Roman13,X,0000,0000,0000,,Test
|
||||
""";
|
||||
assertEquals(expected, converter.format(new SubtitleCollectionImpl<>(subtitle.content(), List.of(subtitle), language), formatOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseFormat() throws ParseException {
|
||||
final var in = """
|
||||
@@ -70,24 +97,119 @@ class TestASSSubtitleConverter {
|
||||
Style: Arial12, Arial, 12
|
||||
|
||||
[Events]
|
||||
Format: Start, End, Style, Text
|
||||
Dialogue: 00:00:00.00,00:00:00.41,Arial12,Test ?
|
||||
Dialogue: 123:45:54.32,124:00:00.00,Times New Roman13,Test2.
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,00:00:00.00,00:00:00.41,Arial12,X,0000,0000,0000,,Test ?
|
||||
Dialogue: 0,00:00:00.00,00:00:00.54,Arial12,X,0000,0000,0000,,{\\pos(1000,880)}Testmargin
|
||||
Dialogue: 0,00:00:00.00,00:00:11.00,Arial12,X,0000,0000,0000,,{\\pos(800,600)}Testpos
|
||||
Dialogue: 0,123:45:54.32,124:00:00.00,Times New Roman13,X,0000,0000,0000,,Test2.
|
||||
""";
|
||||
|
||||
final var start1 = 0L;
|
||||
final var end1 = 410L;
|
||||
final var start2 = 123 * 3600 * 1000 + 45 * 60 * 1000 + 54 * 1000 + 320;
|
||||
final var end2 = 124 * 3600 * 1000;
|
||||
final var end2 = 540L;
|
||||
final var end3 = 11000L;
|
||||
final var start4 = 123 * 3600 * 1000 + 45 * 60 * 1000 + 54 * 1000 + 320;
|
||||
final var end4 = 124 * 3600 * 1000;
|
||||
final var arial = new FontImpl("Arial", 12);
|
||||
final var times = new FontImpl("Times New Roman", 13);
|
||||
final var subtitle1 = new SubtitleImpl("Test ?", start1, end1, arial, null);
|
||||
final var subtitle2 = new SubtitleImpl("Test2.", start2, end2, times, null);
|
||||
final var subtitles = new SubtitleCollectionImpl<>(subtitle1.content() + subtitle2.content(), Arrays.asList(subtitle1, subtitle2), language);
|
||||
final var subtitle2 = new SubtitleImpl("Testmargin", start1, end2, arial, new BoundsImpl(1000, 880, 0, 0));
|
||||
final var subtitle3 = new SubtitleImpl("Testpos", start1, end3, arial, new BoundsImpl(800, 600, 0, 0));
|
||||
final var subtitle4 = new SubtitleImpl("Test2.", start4, end4, times, null);
|
||||
final var subtitles = new SubtitleCollectionImpl<>(subtitle1.content() + subtitle2.content()
|
||||
+ subtitle3.content() + subtitle4.content(), List.of(subtitle1, subtitle2, subtitle3, subtitle4), language);
|
||||
assertEquals(subtitles, converter.parse(in, parseOptions));
|
||||
assertEquals(in, converter.format(subtitles, formatOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMargin() throws ParseException {
|
||||
final var in = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize
|
||||
Style: Arial12, Arial, 12
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,00:00:00.00,00:00:00.54,Arial12,X,1000,1500,0200,,Testmargin
|
||||
""";
|
||||
|
||||
final var start = 0L;
|
||||
final var end = 540L;
|
||||
final var arial = new FontImpl("Arial", 12);
|
||||
final var subtitle = new SubtitleImpl("Testmargin", start, end, arial, new BoundsImpl(1000, 880, 500, 0));
|
||||
final var subtitles = new SubtitleCollectionImpl<>(subtitle.content(), List.of(subtitle), language);
|
||||
assertEquals(subtitles, converter.parse(in, parseOptions));
|
||||
|
||||
final var out = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize
|
||||
Style: Arial12, Arial, 12
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,00:00:00.00,00:00:00.54,Arial12,X,0000,0000,0000,,{\\pos(1000,880)}Testmargin
|
||||
""";
|
||||
assertEquals(out, converter.format(subtitles, formatOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMarginNoSize() throws ParseException {
|
||||
final var in = """
|
||||
[Script Info]
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize
|
||||
Style: Arial12, Arial, 12
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,00:00:00.00,00:00:00.54,Arial12,X,1000,1500,0200,,Testmargin
|
||||
""";
|
||||
|
||||
final var start = 0L;
|
||||
final var end = 540L;
|
||||
final var arial = new FontImpl("Arial", 12);
|
||||
final var subtitle = new SubtitleImpl("Testmargin", start, end, arial, new BoundsImpl(1000, 0, 500, 0));
|
||||
final var subtitles = new SubtitleCollectionImpl<>(subtitle.content(), List.of(subtitle), language);
|
||||
assertEquals(subtitles, converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseUnknownFont() throws ParseException {
|
||||
final var defaultFont = new FontImpl("Times New Roman", 13);
|
||||
when(parseOptions.defaultFont()).thenReturn(defaultFont);
|
||||
final var in = """
|
||||
[Script Info]
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize
|
||||
Style: Arial12, Arial, 12
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,00:00:00.00,00:00:00.54,blabla,X,1000,1500,0200,,Testmargin
|
||||
""";
|
||||
|
||||
final var start = 0L;
|
||||
final var end = 540L;
|
||||
final var subtitle = new SubtitleImpl("Testmargin", start, end, defaultFont, new BoundsImpl(1000, 0, 500, 0));
|
||||
final var subtitles = new SubtitleCollectionImpl<>(subtitle.content(), List.of(subtitle), language);
|
||||
assertEquals(subtitles, converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseDifferentFormat() throws ParseException {
|
||||
final var in = """
|
||||
@@ -119,6 +241,110 @@ class TestASSSubtitleConverter {
|
||||
assertEquals(subtitles, converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseNoEvents() {
|
||||
final var in = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontsize, Fontname
|
||||
Style: Arial12, 12, Arial
|
||||
""";
|
||||
assertThrows(ParseException.class, () -> converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseNoStyles() {
|
||||
final var in = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[Events]
|
||||
Format: Style, End, Start, Text
|
||||
Dialogue: Arial12,0:00:00.41,0:00:00.00,Test ?
|
||||
Dialogue: Times New Roman13,124:00:00.00,123:45:54.32,Test2.
|
||||
""";
|
||||
assertThrows(ParseException.class, () -> converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"Name", "Fontsize", "Fontname"})
|
||||
void testParseMissingRequiredStyleFields(final String missing) {
|
||||
final var present = new ArrayList<String>();
|
||||
present.add("Name");
|
||||
present.add("Fontname");
|
||||
present.add("Fontsize");
|
||||
present.remove(missing);
|
||||
final var in = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: %s
|
||||
Style: Arial12, 12, Arial
|
||||
|
||||
[Events]
|
||||
Format: Start, End, Text, Style
|
||||
Dialogue: Arial12,0:00:00.41,0:00:00.00,Test ?
|
||||
Dialogue: Times New Roman13,124:00:00.00,123:45:54.32,Test2.
|
||||
""".formatted(String.join(", ", present));
|
||||
assertThrows(ParseException.class, () -> converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"Start", "End", "Style", "Text"})
|
||||
void testParseMissingRequiredEventFields(final String missing) {
|
||||
final var present = new ArrayList<String>();
|
||||
present.add("Start");
|
||||
present.add("End");
|
||||
present.add("Style");
|
||||
present.add("Text");
|
||||
present.remove(missing);
|
||||
final var in = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontsize, Fontname
|
||||
Style: Arial12, 12, Arial
|
||||
|
||||
[Events]
|
||||
Format: %s
|
||||
Dialogue: Arial12,0:00:00.41,0:00:00.00,Test ?
|
||||
Dialogue: Times New Roman13,124:00:00.00,123:45:54.32,Test2.
|
||||
""".formatted(String.join(", ", present));
|
||||
assertThrows(ParseException.class, () -> converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseTextNotLast() {
|
||||
final var in = """
|
||||
[Script Info]
|
||||
PlayResX: 1920
|
||||
PlayResY: 1080
|
||||
WrapStyle: 1
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontsize, Fontname
|
||||
Style: Arial12, 12, Arial
|
||||
|
||||
[Events]
|
||||
Format: Start, End, Text, Style
|
||||
Dialogue: Arial12,0:00:00.41,0:00:00.00,Test ?
|
||||
Dialogue: Times New Roman13,124:00:00.00,123:45:54.32,Test2.
|
||||
""";
|
||||
assertThrows(ParseException.class, () -> converter.parse(in, parseOptions));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseException() {
|
||||
final var in = """
|
||||
|
||||
Reference in New Issue
Block a user