diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ea6ca2d..daa9887 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,138 +5,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -389,7 +318,10 @@ 1727983986841 - + + + + @@ -397,6 +329,9 @@ + + com.github.gtache.autosubtitle.subtitle.converter.impl.* + \ No newline at end of file diff --git a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/ASSSubtitleConverter.java b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/ASSSubtitleConverter.java index 63c50f0..3c266f3 100644 --- a/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/ASSSubtitleConverter.java +++ b/core/src/main/java/com/github/gtache/autosubtitle/subtitle/converter/impl/ASSSubtitleConverter.java @@ -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 { 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 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 REQUIRED_STYLE_FIELDS = Set.of(NAME_FIELD, FONTNAME_FIELD, FONTSIZE_FIELD); + private static final List 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 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\\((?\\d+),(?\\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: (?\\d+)", Pattern.MULTILINE); + private final Map> translators; @Inject @@ -45,22 +71,32 @@ public class ASSSubtitleConverter implements SubtitleConverter { @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 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 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 { return fontName + fontSize; } - private static String getStyles(final Collection subtitles, final Font defaultFont) { + private static String writeStyles(final Collection 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 { 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 { @Override public SubtitleCollectionImpl 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 parseSubtitles(final String content, final Map 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 parseSubtitles(final String content, final Map 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 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 parseFonts(final String content, final Font defaultFont) throws ParseException { - final var fontIndex = content.indexOf(STYLES_SECTION); - if (fontIndex == -1) { + private static List parseValues(final CharSequence value, final int size) { + final var trimmed = START_DIALOG_PATTERN.matcher(value).replaceAll("").trim(); + final var values = new ArrayList(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 values, final Map 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 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 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 getFields(final String string) { return Arrays.stream(string.replace(FORMAT, "").split(",")).map(String::trim).filter(s -> !s.isBlank()).toList(); } diff --git a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestASSSubtitleConverter.java b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestASSSubtitleConverter.java index a1995d9..4618d24 100644 --- a/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestASSSubtitleConverter.java +++ b/core/src/test/java/com/github/gtache/autosubtitle/subtitle/converter/impl/TestASSSubtitleConverter.java @@ -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(); + 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(); + 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 = """ diff --git a/documentation/ASS File Format Specification.htm b/documentation/ASS File Format Specification.htm new file mode 100644 index 0000000..87aba18 --- /dev/null +++ b/documentation/ASS File Format Specification.htm @@ -0,0 +1,2071 @@ + + + +ASS File Format Specification + + + + + + +
+ +

Sub Station Alpha +v4.00+ Script Format

+ +

 

+ +

 

+ +

1.   +General +information

+ +

2.   +The +[sections] of a Sub Station Alpha script

+ +

3.   +The line +types in a Sub Station Alpha script

+ +

4.   +Header +lines, [Script Info] section

+ +

5.   +Style +lines, [v4 Styles] section

+ +

6.   +Dialogue +event lines, [Events] section

+ +

7.   +Comment +lines, [Events] section

+ +

8.   +Picture +event lines, [Events] section

+ +

9.   +Movie event +line, [Events] section

+ +

10. Sound event lines, [Events] section

+ +

11. Command event lines, [Events] section

+ +

 

+ +

                     Appendix +A: Style override codes

+ +

                     + Appendix +B: Embedded font/picture encoding

+ +

 

+ +

This document was SSAˇŻs format specification originally (can be +found at http://www.eswat.demon.co.uk). Updates and differences are marked red.
+1. General Information

+ +

 

+ +

The +information in this document assumes that you are familiar with the terms and +concepts used by Sub Station Alpha (SSA). These are documented in SSA's help +file, ssa.hlp which is distributed with the program, or can be downloaded +separatelyfrom http://www.eswat.demon.co.uk.

+ +

 

+ +

1.   +The SSA +v4.00 script format is different to previous versions of SSA
+
SSA v4.00 will read +scripts from older versions, but v4.00 scripts will not load into older +versions of SSA correctly.
+
+Some of the changes in the script format are intended to allow all versions of +SSA from v4.00 onwards to read any present or future SSA scripts. In +particular, the new "Format" lines allow SSA to read only the +information it recognises and discard any new information that is added in +future scripts.
+
+

+ +

2.   +Scripts are +plain (DOS) text files.
+
This means they can +be "manually" editied using any text editor, but caution must be +exercised when doing this - SSA assumes that scripts will adhere to the +"rules" set out in this document, and any errors may lead to +unpredictable  results when the script is loaded into SSA.

+ +

 

+ +

2.   +The script +is divided into ˇ°ini fileˇ± style sections
+
However, SSA scripts +are not true Windows .ini files and you cannot do certain things you might +expect!
+
+

+ +

3.   +Most lines +in each section begin with some sort of code - a "line descriptor", to say what information is held in it. +The descriptor is terminated by a colon.

+ +

 

+ +

3.  The information fields in +each line are separated by a commas.

+ +

     This makes it +illegal to use commas in character names and style names (SSA prevents you +putting commas in these). It also makes it quite easy to load chunks of an SSA +script into a spreadsheet as a CSV file, and chop out columns of information +you need for another subtitling program.

+ +

 

+ +

4.  SSA does not care what order +events are entered in.

+ +

     They could be +entered in complete reverse order, and SSA would still play everything +correctly in the right order ie. you cannot assume that each dialogue line is +in chronological order in the script file.

+ +

 

+ +

5.  Incorrectly formatted lines +are ignored.
+
SSA will discard any +lines it doesn't understand, and give a warning after the script has loaded +giving the number of lines it discarded.

+ +

 

+ +

6.  Lines cannot be split

+ +

     Each entry in +a script contains all its information in a single line.

+ +

 

+ +

7.  If unknown styles are used in +the script, the *Default style will be used.

+ +

     For example, +if lines have been pasted in from another script, without the corresponding +Style information then when SSA plays the script, the Default style settings +will be used.

+ +

 

+ +

8.  If +a Style specifies a font which is not installed, then Arial will be used +instead.

+ +

     This can +happen with scripts which you did not create yourself - the original authors +may have fonts installed which you don't have.

+ +

 

+ +
+
+ +

2. The sections in a +Sub Station Alpha script

+ +

 

+ +

[Script +Info]

+ +

This section +contains headers and general information about the script.

+ +

The line that +says ˇ°[Script Info]ˇ± must be the first line in a v4 script.

+ +

 

+ +

[v4 +Styles]

+ +

This section +contains all Style definitions required by the script. Each ˇ°Styleˇ± used by +subtitles in the script should be defined here.

+ +

 

+ +

ASS +uses [v4 Styles+]

+ +

 

+ +

[Events]

+ +

This section +contains all the events for the script - all the subtitles, comments, pictures, +sounds, movies and commands. Basically, everything that you see in Sub Station +AlphaˇŻs main-screen ˇ°gridˇ± is in this section.

+ +

 

+ +

[Fonts]

+ +

This section +contains text-encoded font files, if the user opted to embed non-standard fonts +in the script. Only truetype fonts can be embedded in SSA scripts. Each font +file is started with a single line in the format:
+
+

+ +

fontname: +<name of file>

+ +

 

+ +

The word ˇ°fontnameˇ± +must be in lower case (upper case will be interpretted as part of a +text-encoded file).

+ +

 

+ +

<name +of file> is the +file name that SSA will use when saving the font file. It is:

+ +

the name of +the original truetype font,

+ +

plus an +underscore,

+ +

plus an +optional ˇ°Bˇ± if Bold,

+ +

plus an +optional ˇ°Iˇ± if Italic,

+ +

plus a number +specifying the font encoding (character set),

+ +

plus ˇ°.ttfˇ±

+ +

Eg.

+ +

fontname: +chaucer_B0.ttf

+ +

fontname: +comic_0.ttf

+ +

 

+ +

The fontname +line is followed by lines of printable characters, representing the binary +values which make up the font file. Each line is 80 characters long, except the +last one which may be less.
+
+The conversion from binary to printable characters is a form of Uuencoding, the +details of this encoding is contained in "Appendix B", below.

+ +

 

+ +

[Graphics]

+ +

This sections +contains text-encoded graphic files, if the user opted to embed any pictures +they used in the script. The binary picture files are text-encoded, which is +inefficient, but ensures that SSA scripts can still be handled by any text editor, +if required.

+ +

 

+ +

Each graphic +file is started with a single line in the format:

+ +

filename: +<name of file>

+ +

 

+ +

The word ˇ°filenameˇ± +must be in lower case (upper case will be interpreted as part of a text-encoded +file).

+ +

<name +of file> is the +file name that SSA will use when saving the picture file. It will match the +filename of a picture used in the script.
+
+SSA saves any files found in the script in a subdirectory off SSA's program +directory, "Pictures"

+ +

eg. +c:\program files\Sub Station Alpha v4.00\Pictures. SSA will attempt to load +files using the paths specified in the script, but if they are not found, it +will look in the "Pictures" subdirectory for them.

+ +

 

+ +

The fontname +line is followed by lines of printable characters, fontrepresenting the binary +values which make up the picture font file - format is as per embedded font +files.

+ +
+
+ +

3. The line types in +a Sub Station Alpha script

+ +

 

+ +

 

+ +

This briefly +describes each of the line types that can appear in a Sub Station Alpha Script. +Full details of the information held in each line type is in the next chapter.

+ +

 

+ +

!:                               + This +is a comment used in the script file only. It is not visible when you +load the +script into SSA.

+ +

 

+ +

Title:                         + This +is a description of the script

+ +

 

+ +

Original +Script:         The +original author(s) of the script

+ +

 

+ +

Original +Translation: (optional) +The original translator of the dialogue

+ +

 

+ +

Original Editing:       (optional) The original script +editor(s), typically whoever took the raw translation and turned it into +idiomatic english and reworded for readability.

+ +

 

+ +

Original +Timing:        (optional) +Whoever timed the original script

+ +

 

+ +

Synch Point:             (optional) +Description of where in the video the script should begin playback.

+ +

 

+ +

Script Updated By:    (optional) Names of +any other subtitling groups who edited the original script.

+ +

 

+ +

Update Details:          The +details of any updates to the original script made by other subtilting groups.

+ +

 

+ +

ScriptType:               This +is the SSA script format version eg. "V3.00".

+ +

 

+ +

Collisions:                This +determines how subtitles are moved, when preventing onscreen collisions

+ +

 

+ +

PlayResY:                 This +is the height of the screen used by the authors when playing the script.

+ +

 

+ +

PlayResX:                 This +is the width of the screen used by the authors when playing the script.

+ +

 

+ +

PlayDepth:                This is the colour depth used by the +authors when playing the script.

+ +

 

+ +

Timer:           This +is the Timer Speed for the script, as a percentage.

+ +

                                 + eg. +"100.0000" is exactly 100%.

+ +

 

+ +

                                 + The +timer speed is a time multiplier applied to SSA's clock to provide a +ramp time.

+ +

 

+ +

Style:                        + This +is a Style definition, used to format text displayed by the script.

+ +

 

+ +

Dialogue:                  This +is a Dialogue event, ie. Some text to display.

+ +

 

+ +

Comment:     This is a +"comment" event.

+ +

                                 + This +contains the same information as a Dialogue, Picture, Sound, Movie, or +Command +event, but it is ignored during script playback.

+ +

 

+ +

Picture:                     This +is a "picture" event, which means SSA will display the specified +.bmp, .jpg, .gif, .ico or .wmf graphic.

+ +

 

+ +

Sound:                      + This +is a "sound" event, which means SSA will play the specified .wav +file.

+ +

 

+ +

Movie:                       + This +is a "movie" event, which means SSA will play the specified .avi +file.

+ +

 

+ +

Command:                This +is a "command" event, which means SSA will execute the specified +program as a background task.

+ +

 

+ +
+
+ +

4. Header lines, +[Script Info] section

+ +

 

+ +

;                                + Semicolon. +Any text can follow the semicolon

+ +

                                 + This +is a comment used in the script file only. It is not visible when you +load the +script into SSA. The semicolon must be the first character in the line. +This replaces the !: line type used in previous script versions.

+ +

 

+ +

Title:                         + This +is a description of the script. If the original author(s) did not +provide this +information then <untitled> is automatically substituted.

+ +

 

+ +

Original Script:         The +original author(s) of the script. If the original author(s) did not provide +this information then <unknown> is automatically substituted.

+ +

 

+ +

Original Translation: (optional) The original translator of +the dialogue. This entry does not appear if no information was entered by the +author.

+ +

 

+ +

Original Editing:       (optional) The original script +editor(s), typically whoever took the raw translation and turned it into +idiomatic english and reworded for readability. This entry does not appear if +no information was entered by the author.

+ +

 

+ +

Original Timing:        (optional) +Whoever timed the original script. This entry does not appear if no information +was entered by the author.

+ +

 

+ +

Synch Point:             (optional) +Description of where in the video the script should begin playback. This entry +does not appear if no information was entered by the author.

+ +

 

+ +

Script Updated By:    (optional) Names of +any other subtitling groups who edited the original script. This entry does not +appear if subsequent editors did not enter the information.

+ +

 

+ +

Update Details:          The +details of any updates to the original script - made by other subtitling +groups. This entry does not appear if subsequent editors did not enter any +information.

+ +

 

+ +

Script Type:              This +is the SSA script format version eg. "V4.00". It is used by SSA to +give a warning if you are using a version of SSA older than the version that +created the script.

+ +

 

+ +

                                 + ASS version is ˇ°V4.00+ˇ±.

+ +

 

+ +

Collisions:                This +determines how subtitles are moved, when automatically preventing onscreen +collisions.

+ +

 

+ +

                                 + If +the entry says "Normal" then SSA will attempt to position +subtitles in the position specified by the "margins". However, +subtitles can be shifted vertically to prevent onscreen collisions. With +"normal" collision prevention, the subtitles will "stack +up" one above the other - but they will always be positioned as close the +vertical (bottom) margin as possible - filling in "gaps" in other subtitles +if one large enough is available.

+ +

 

+ +

                                 + If +the entry says "Reverse" then subtitles will be shifted +upwards to make room for subsequent overlapping subtitles. This means the +subtitles can nearly always be read top-down - but it also means that the first +subtitle can appear half way up the screen before the subsequent overlapping +subtitles appear. It can use a lot of screen area.

+ +

 

+ +

PlayResY:                 This +is the height of the screen used by the script's author(s) when playing the +script. SSA v4 will automatically select the nearest enabled setting, if you +are using Directdraw playback.

+ +

 

+ +

PlayResX:                 This +is the width of the screen used by the script's author(s) when playing the +script. SSA will automatically select the nearest enabled, setting if you are +using Directdraw playback.

+ +

 

+ +

PlayDepth:                This is the colour depth used by the +script's author(s) when playing the script. SSA will automatically select the +nearest enabled setting if you are using Directdraw playback.

+ +

 

+ +

Timer:                       + This +is the Timer Speed for the script, as a percentage.

+ +

                                 + eg. +"100.0000" is exactly 100%. It has four digits following the decimal +point.

+ +

 

+ +

                                 + The +timer speed is a time multiplier applied to SSA's clock to stretch or +compress +the duration of a script. A speed greater than 100% will reduce the +overall +duration, and means that subtitles will progressively appear sooner and +sooner. +A speed less than 100% will increase the overall duration of the script +means +subtitles will progressively appear later and later (like a positive +ramp +time).
+
+The stretching or compressing only occurs during script playback - this value +does not change the actual timings for each event listed in the script.

+ +

 

+ +

                                 + Check +the SSA user guide if you want to know why "Timer Speed" is more +powerful than "Ramp Time", even though they both achieve the same +result.

+ +

 

+ +

WrapStyle:                Defines +the default wrapping style.

+ +

0: smart wrapping, lines are evenly broken

+ +

1: end-of-line word wrapping, only \N breaks

+ +

2: no word wrapping, \n \N both breaks

+ +

3: same as 0, but lower line gets wider.

+ +

 

+ +

 

+ +
+
+ +

5. Style Lines, [v4+ Styles] section

+ +

 

+ +

Styles define +the appearance and position of subtitles. All styles used by the script are are +defined by a Style line in the script.

+ +

 

+ +

Any of the +the settings in the Style, (except shadow/outline type and depth) can +overridden by control codes in the subtitle text.

+ +

 

+ +

The fields +which appear in each Style definition line are named in a special line with the +line type ˇ°Format:ˇ±. The Format line must appear before any Styles - because it +defines how SSA will interpret the Style definition lines. The field names +listed in the format line must be correctly spelled! The fields are as follows:

+ +

 

+ +

Name, +Fontname, Fontsize, PrimaryColour, SecondaryColour, TertiaryColour, BackColour, +Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, +Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, +MarginR, MarginV, AlphaLevel, Encoding

+ +

 

+ +

The format +line allows new fields to be added to the script format in future, and yet +allow old versions of the software to read the fields it recognises - even if +the field order is changed.

+ +

 

+ +

Field 1:      Name. +The name of the Style. Case sensitive. Cannot include commas.

+ +

 

+ +

Field 2:      Fontname. +The fontname as used by Windows. Case-sensitive.

+ +

 

+ +

Field 3:      Fontsize.

+ +

 

+ +

Field 4:      PrimaryColour. +A long integer BGR (blue-green-red)  value. ie. the byte order in the +hexadecimal equivelent of this number is BBGGRR

+ +

 

+ +

                 This +is the colour that a subtitle will normally appear in.

+ +

 

+ +

Field 5:      SecondaryColour. +A long integer BGR (blue-green-red)  value. ie. the byte order in the +hexadecimal equivelent of this number is BBGGRR

+ +

 

+ +

                 This +colour may be used instead of the Primary colour when a subtitle is +automatically shifted to prevent an onscreen collsion, to distinguish the +different subtitles.

+ +

 

+ +

Field 6:      OutlineColor (TertiaryColour). A long +integer BGR (blue-green-red)  value. ie. the byte order in the hexadecimal +equivelent of this number is BBGGRR

+ +

 

+ +

                 This +colour may be used instead of the Primary or Secondary colour when a subtitle +is automatically shifted to prevent an onscreen collsion, to distinguish the +different subtitles.

+ +

 

+ +

Field 7:     BackColour. +This is the colour of the subtitle outline or shadow, if these are used. A long +integer BGR (blue-green-red)  value. ie. the byte order in the hexadecimal +equivelent of this number is BBGGRR.

+ +

 

+ +

Field 4-7:  The color +format contains the alpha channel, too. (AABBGGRR)

+ +

 

+ +

Field 8:      Bold. +This defines whether text is bold (true) or not (false). -1 is True, 0 is +False. This is independant of the Italic attribute - you can have have text +which is both bold and italic.

+ +

 

+ +

Field 9:      Italic. +This defines whether text is italic (true) or not (false). -1 is True, 0 is +False. This is independant of the bold attribute - you can have have text which +is both bold and italic.

+ +

 

+ +

Field 9.1:  Underline. [-1 +or 0]

+ +

 

+ +

Field 9.2:  Strikeout. [-1 +or 0]

+ +

 

+ +

Field 9.3:  ScaleX. +Modifies the width of the font. [percent]

+ +

 

+ +

Field 9.4:  ScaleY. +Modifies the height of the font. [percent]

+ +

 

+ +

Field 9.5:  Spacing. +Extra space between characters. [pixels]

+ +

 

+ +

Field 9.6:  Angle.  +The origin of the rotation is defined by the alignment. Can be a floating point +number. [degrees]

+ +

 

+ +

Field 10:    BorderStyle. +1=Outline + drop shadow, 3=Opaque box

+ +

 

+ +

Field 11:    Outline. +If BorderStyle is 1,  then this specifies the width of the outline around +the text, in pixels.
+Values may be 0, 1, 2, 3 or 4.

+ +

 

+ +

Field 12:    Shadow. +If BorderStyle is 1,  then this specifies the depth of the drop shadow +behind the text, in pixels. Values may be 0, 1, 2, 3 or 4. Drop shadow is +always used in addition to an outline - SSA will force an outline of 1 pixel if +no outline width is given.

+ +

 

+ +

Field 13:    Alignment. +This sets how text is "justified" within the Left/Right onscreen +margins, and also the vertical placing. Values may be 1=Left, 2=Centered, +3=Right. Add 4 to the value for a "Toptitle". Add 8 to the value for +a "Midtitle".
+eg. 5 = left-justified toptitle

+ +

 

+ +

Field 13:   Alignment, +but after the layout of the numpad (1-3 sub, 4-6 mid, 7-9 top).

+ +

 

+ +

Field 14:    MarginL. +This defines the Left Margin in pixels. It is the distance from the left-hand +edge of the screen.The three onscreen margins (MarginL, MarginR, MarginV) +define areas in which the subtitle text will be displayed.

+ +

 

+ +

Field 15:    MarginR. +This defines the Right Margin in pixels. It is the distance from the right-hand +edge of the screen. The three onscreen margins (MarginL, MarginR, MarginV) +define areas in which the subtitle text will be displayed.

+ +

 

+ +

Field 16:    MarginV. +This defines the vertical Left Margin in pixels.
+For a subtitle, it is the distance from the bottom of the screen.
+For a toptitle, it is the distance from the top of the screen.
+For a midtitle, the value is ignored - the text will be vertically +centred

+ +

Field 17:    AlphaLevel. +This defines the transparency of the text. SSA does not use this yet.

+ +

 

+ +

Field +17:   Not present in ASS.

+ +

 

+ +

Field 18:    Encoding. +This specifies the font character set or encoding and on multi-lingual +Windows installations it provides access to characters used in multiple than +one languages. It is usually 0 (zero) for English (Western, ANSI) Windows.

+ +

                

+ +

                 When the file is Unicode, this field is useful during file +format conversions.

+ +

 

+ +
+
+ +

 

+ +

5. Dialogue event +lines, [Events] section

+ +

 

+ +

These contain +the subtitle text, their timings, and how it should be displayed.

+ +

The fields +which appear in each Dialogue line are defined by a Format: line, which +must appear before any events in the section. The format line specifies how SSA +will interpret all following Event lines. The field names must be spelled +correctly, and are as follows:

+ +

 

+ +

Marked, +Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text

+ +

 

+ +

The last +field will always be the Text field, so that it can contain commas. The format +line allows new fields to be added to the script format in future, and yet +allow old versions of the software to read the fields it recognises - even if +the field order is changed.

+ +

 

+ +

Field 1:      Marked
+
Marked=0 means the line is not shown as "marked" in SSA.

+ +

                 Marked=1 +means the line is shown as "marked" in SSA.

+ +

 

+ +

Field 1:     Layer +(any integer)

+ +

 

+ +

                 Subtitles +having different layer number will be ignored during the collusion detection.

+ +

 

+ +

                 Higher +numbered layers will be drawn over the lower numbered.

+ +

 

+ +

Field 2:      Start
+Start Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. +This is the time elapsed during script playback at which the text will appear +onscreen. Note that there is a single digit for the hours!

+ +

 

+ +

Field 3:      End
+End Time of the Event, in 0:00:00:00 format ie. Hrs:Mins:Secs:hundredths. This +is the time elapsed during script playback at which the text will disappear offscreen. +Note that there is a single digit for the hours!

+ +

 

+ +

Field 4:      Style
+
Style name. If it is "Default", then your own *Default +style will be subtituted.

+ +

 

+ +

                 However, +the Default style used by the script author IS stored in the script even though +SSA ignores it - so if you want to use it, the information is there - you could +even change the Name in the Style definition line, so that it will appear in +the list of "script" styles.

+ +

 

+ +

Field 5:      Name
+
Character name. This is the name of the character who speaks the dialogue. +It is for information only, to make the script is easier to follow when +editing/timing.

+ +

 

+ +

Field 6:      MarginL
+4-figure Left Margin override. The values are in pixels. All zeroes means the +default margins defined by the style are used.

+ +

 

+ +

Field 7:      MarginR
+4-figure Right Margin override. The values are in pixels. All zeroes means the +default margins defined by the style are used.

+ +

 

+ +

Field 8:      MarginV
+4-figure Bottom Margin override. The values are in pixels. All zeroes means the +default margins defined by the style are used.

+ +

 

+ +

Field 9:      Effect
+Transition Effect. This is either empty, or contains information for one of the +three transition effects implemented in SSA v4.x

+ +

 

+ +

                 The +effect names are case sensitive and must appear exactly as shown. The effect +names do not have quote marks around them.

+ +

 

+ +

                 "Karaoke" +means that the text will be successively highlighted one word at a time.

+ +

 

+ +

                 Karaoke as an effect type is obsolete.

+ +

 

+ +

                 "Scroll +up;y1;y2;delay[;fadeawayheight]"means +that the text/picture will scroll up the screen. The parameters after the words +"Scroll up" are separated by semicolons.

+ +

 

+ +

                 The +y1 and y2 values define a vertical region on the screen in which the text will +scroll. The values are in pixels, and it doesn't matter which value (top or +bottom) comes first. If the values are zeroes then the text will scroll up the +full height of the screen.

+ +

 

+ +

                 The +delay value can be a number from 1 to 100, and it slows down the speed of the +scrolling - zero means no delay and the scrolling will be as fast as possible.

+ +

 

+ +

                 ˇ°Banner;delayˇ± +means that text will be forced into a single line, regardless of length, +and scrolled from right to left accross the screen.
+
+The delay value can be a number from 1 to 100, and it slows down the speed of +the scrolling - zero means no delay and the scrolling will be as fast as +possible.

+ +

 

+ +

                 "Scroll down;y1;y2;delay[;fadeawayheight]"

+ +

 

+ +

                 ˇ°Banner;delay[;lefttoright;fadeawaywidth]ˇ±

+ +

 

+ +

                 lefttoright +0 or 1. This field is optional.  Default value is 0 to make it backwards +compatible.

+ +

 

+ +

                 When +delay is greater than 0, moving one pixel will take (1000/delay) second.

+ +

 

+ +

                 (WARNING: +Avery LeeˇŻs ˇ°subtitlerˇ± plugin reads the ˇ°Scroll upˇ± effect parameters as delay;y1;y2)

+ +


+fadeawayheight and fadeawaywidth parameters can be used to make the scrolling +text at the sides transparent.

+ +

 

+ +

 

+ +

Field 10:    Text
+Subtitle Text. This is the actual text which will be displayed as a subtitle +onscreen. Everything after the 9th comma is treated as the subtitle text, so it +can include commas.

+ +

 

+ +

                 The +text can include \n codes which is a line break, and can include Style Override +control codes, which appear between braces { }.

+ +

 

+ +
+
+ +

6. Comment event +lines, [Events] section

+ +

 

+ +

These can +contain the same information as any of the other event line types, but they +will be ignored when the script is played.

+ +

 

+ +
+
+ +

7. + Picture event lines, [Events] section

+ +

 

+ +

These contain +the same information as Dialogue events, but Field 10 contains the full path +and filename of the picture to display, instead of subtitle text.

+ +

 

+ +

The Style +specified is ignored. The "scroll up" transition effect can be used +for picture events.

+ +

 

+ +

The Left and +Vertical Margin Overrides specify the bottom-left corner position of the +picture. A left margin of all zeroes means that the picture will be +horizontally centered. A vertical margin of all zeroes means that the picture +will be vertically centered.

+ +

 

+ +

 

+ +
+
+ +

8. + Sound event lines, [Events] section

+ +

 

+ +

These contain +the same information as Dialogue events, but Field 10 contains the full path +and filename of the wav file to play, instead of subtitle text.

+ +

 

+ +

The Style and +margins are ignored. The End time is also ignored - the wav will play until it +finishes, or until another wav file is played.

+ +

 

+ +

If an avi +movie is played at the same time as a wav is already playing, then any sound in +the avi will not be heard. Similarly, if a wav starts playing when an avi movie +with sound is already playing then the wav will not be heard.

+ +

 

+ +

 

+ +
+
+ +

9. + Movie event lines, [Events] section

+ +

 

+ +

These contain +the same information as Dialogue events, but Field 10 contains the full path +and filename of the avi file to play, instead of subtitle text.

+ +

 

+ +

The Style is +ignored. Transition effects are ignored.

+ +

 

+ +

The End time +specifies when the movie picture will disappear - but if th eavi file lasts +longer, then the sound will continue to be heard.

+ +

 

+ +

The Left and +vertical Margin Overrides specify the TOP-LEFT corner position of the picture +(unlike picture events). A left margin of all zeroes means that the picture +will be horizontally centered. a vertical margin of all zeroes means that the +picture will be verticall centered.

+ +

 

+ +

If an avi +movie is played at the same time as a wav is already playing, then any sound in +the avi will not be heard. Similarly, if a wav starts playing when an avi movie +with sound is already playing then the wav will not be heard.

+ +

 

+ +
+
+ +

10. +Command event lines, [Events] section

+ +

 

+ +

These contain +the same information as Dialogue events, but Field 10 contains the full path +and filename of the program to execute, instead of subtitle text.

+ +

 

+ +

The Style is +ignored. The margins are ignored. Transition effects are ignored. The End time +is also ignored - the program will execute until it ends, or is +"manually" closed.

+ +

 

+ +

There are +also internal SSA commands which can appear in SSA scripts - the +"SSA:Pause", ˇ°SSA:Wait for triggerˇ± command events, and genlock +control commands. These all begin with "SSA:"

+ +

 

+ +

The SSA:Pause +command has the same effect as pressing "P" during script playback. +It is useful as a second "synch point" to resume subtitling after +switching sides of a laserdisk.

+ +

 

+ +

The ˇ°SSA:Wait +for audio triggerˇ± command has the same effect as pressing "P" during +script playback, but pausing is automatically cancelled if the audio input to +the computer exceeds a specified ˇ°triggerˇ± level. It is useful as a second +"synch point" to resume subtitling after switching sides of a +laserdisk. The audio triggering can be overridden to resume playback - by +pressing "P".

+ +

 

+ +

Audio +triggering "times out" after 10 minutes - If no audio peak of +sufficient magnitude is received, and "P" is not pressed within 10 +minutes - then playback will resume anyway.

+ +
+
+ +

Appendix A: Style +override codes

+ +

 

+ +

This is a +reference which may be useful for those of you who wish to learn the style +override codes, so you can type them in manually without using the +"override style" dialogue box.

+ +

 

+ +

All Override +codes appear within braces { } except the newline \n and \N codes.

+ +

All override +codes are always preceded by a backslash \

+ +

Several +overrides can be used within one set of braces.

+ +

 

+ +

Each override +affects all text following the override. To apply an override only to selected +text, you need a second "cancelling" override after the selected +text, to "undo" the effect of the first override.

+ +

 

+ +

Some +overrides automatically apply to ALL the text - currently this is just +alignment overrides, but more may be added later (eg. Shadow/outline depth +overrides).

+ +

 

+ +

\n                              + New +line (carriage return)
+                                 + \n +is ignored by SSA if ˇ°smart-wrappingˇ± is enabled

+ +

                                 + eg. +This is the first line\nand this is the second
+
+\N                              +
New +line (carriage return). This is used by SSA instead of \n if

+ +

                                 + ˇ°smart-wrappingˇ± +is enabled.

+ +

 

+ +

\b<0 or 1>                  \b1 +makes the text bold. \b0 forces non-bold text.

+ +

                                 + eg. +There is a {\b1}bold {\b0}word here

+ +

 

+ +

                                 + When this parameter is greater than 1, it will be used as the +weight of the font. (400 = Normal, 700 = Bold, note: most fonts will quantize +to 2 or 3 levels of thickness)

+ +

 

+ +

\i<0 or 1>                   \i1 +makes the text italic. \i0 forces non-italic text.

+ +

                                 + eg. +There is an {\i1}italicised {\i0}word here

+ +

 

+ +

\u<0 or 1>                 underline

+ +

 

+ +

\s<0 +or 1>                  strikeout

+ +

 

+ +

\bord<width>               border

+ +

 

+ +

\shad<depth>            shadow

+ +

 

+ +

\be<0 +or 1>                blur +edges

+ +

 

+ +

\fn<font name>          <font +name> specifies a font which you have installed in Windows. This is case +sensitive.

+ +

                                 + eg. +Here is some {\fnCourier New}fixed space text

+ +

 

+ +

                                 + If +you use a font name that doesn't exist, then Arial will be used instead.
+
+

+ +

\fs<font size>             <font +size> is a number specifying a font point size.

+ +

                                 + eg. +{\fs16}This is small text. {\fs28}This is large text
+
+

+ +

\fsc<x +or y><percent> 

+ +

                                 + <x +or y> x scales horizontally, y scales vertically

+ +

                                 + <percent> +

+ +

 

+ +

\fsp<pixels +>             <pixels> +changes the distance between letters. (default: 0)

+ +

 

+ +

\fr[<x/y/z>]<degrees>

+ +

                                 + <degrees> +sets the rotation angle around the x/y/z axis.

+ +

 

+ +

                                 + \fr +defaults to \frz.

+ +

 

+ +

\fe<charset>               <charset> +is a number specifying the character set (font encoding)

+ +

 

+ +

 

+ +

\c&H<bbggrr>&          <bbggrr> +is a hexadecimal RGB value, but in reverse order. Leading zeroes are not +required.

+ +

                                 + eg. +{\c&HFF&}This is pure, full intensity red

+ +

                                    +    +{\c&HFF00&}This is pure, full intensity Green

+ +

                                    +    +{\c&HFF0000&}This is pure, full intensity Blue

+ +

                                    +    +{\c&HFFFFFF&}This is White

+ +

                                    +    +{\c&HA0A0A&}This is dark grey

+ +

 

+ +

                                 + \1c&Hbbggrr&, \2c&Hbbggrr&, +\3c&Hbbggrr&, \4c&Hbbggrr& to set specific colors.

+ +

 

+ +

                                 + \1a&Haa&, +\2a&Haa&, \3a&Haa&, \4a&Haa& to set specific +alpha +channels.

+ +

 

+ +

                                 + \alpha +defaults to \1a

+ +

 

+ +

\a<alignment>            <alignment> +is a number specifying the onscreen alignment/positioning of a subtitle.

+ +

 

+ +

                                 + A +value of 1 specifies a left-justified subtitle

+ +

                                 + A +value of 2 specifies a centered subtitle

+ +

                                 + A +value of 3 specifies a right-justified subtitle

+ +

 

+ +

                                 + Adding +4 to the value specifies a "Toptitle"

+ +

                                 + Adding +8 to the value specifies a "Midtitle"

+ +

 

+ +

                                 + 0 or nothing resets to the style default (which is usually 2)

+ +

 

+ +

                                 + eg. +{\a1}This is a left-justified subtitle

+ +

                                    +    +{\a2}This is a centered subtitle

+ +

                                    +    +{\a3}This is a right-justified subtitle

+ +

 

+ +

                                    +    +{\a5}This is a left-justified toptitle

+ +

                                    +    +{\a11}This is a right-justified midtitle

+ +

 

+ +

                                 + Only +the first appearance counts.

+ +

 

+ +

\an<alignment>         numpad +layout

+ +

 

+ +

                                 + Only +the first appearance counts.

+ +

 

+ +

\k<duration>              <duration> +is the amount of time that each section of text is highlighted for in a +dialogue event with the Karaoke effect. The durations are in hundredths of +seconds.

+ +

 

+ +

                                 + eg. +{\k94}This {\k48}is {\k24}a {\k150}karaoke {\k94}line

+ +

 

+ +

                                 + \k<duration> +highlight by words

+ +

                                 + \kf +or \K<duration> fill up from left to right

+ +

                                 + \ko<duration> +outline highlighting from left to right

+ +

 

+ +

\q<num>   +      +            +<num> +wrapping style

+ +

 

+ +

\r[<style>] This cancels all previous style overrides in a line

+ +

 

+ +

                                 + <style> Restores to <style> instead of the +dialogue line default.

+ +

 

+ +

Any style modifier followed +by no recognizable parameter resets to the default.

+ +

 

+ +

Functions:

+ +

 

+ +

\t([<t1>, +<t2>, ] [<accel>,] <style modifiers>)

+ +

 

+ +

                                 + <t1>, +<t2> Animation beginning, ending time offset [ms] (optional)

+ +

 

+ +

                                 + <accel> +Modifies the linearity of the transformation (optional)

+ +

 

+ +

The following calculation is performed to get the +coefficient needed to interpolate between the given style modifiers: +pow((t-t1)/(t2-t1), accel), where t is the time offset for the subtitle.

+ +

 

+ +

The meaning of <accel>:

+ +

1: the transformation is linear

+ +

between 0 and 1: will start fast and slow down

+ +

greater than 1: will start slow and get faster

+ +

 

+ +

As an example, using 2 will make growing the +letters (by {\fscx200\fscy200}) look linear rather than slowering.

+ +

 

+ +

                                 + <style +modifiers>Any style modifier which can be animated:

+ +

\c,\1-4c,\alpha,\1-4a,\fs,\fr,\fscx,\fscy,\fsp,\bord,\shad,\clip +(only the rectangular \clip)

+ +

 

+ +

\move(<x1>, +<y1>, <x2>, <y2>[, <t1>, <t2>])

+ +

 

+ +

                                 + <x1>, +<y1> The coordinate to start at.

+ +

                                 + <x2>, +<y2> The coordinate to end at.

+ +

                                 + <t1>, +<t2> Animation beginning, ending time offset [ms] (optional)

+ +

 

+ +

                                 + The +origin of the movement is defined by the alignment type.

+ +

 

+ +

\pos(<x>, +<y>)           Defaults +to \move(<x>, <y>, <x>, <y>, 0, 0)

+ +

 

+ +

\org(<x>, <y>)           Moves +the default origin at (x,y). This is useful when moving subtitles in the +direction of rotation.

+ +

 

+ +

WARNING: +\t, \move and \pos will ignore collusion detection.

+ +

 

+ +

\fade(<a1>, +<a2>, <a3>, <t1>, <t2>, <t3>, <t4>)

+ +

 

+ +

<a1> Alpha value +before <t1>

+ +

<a2> Alpha value +between <t2> and <t3>

+ +

<a3> Alpha value after +<t4>

+ +

<t1>, <t4> +Animation beginning, ending time offset [ms]

+ +

<t1> - <t2> +Alpha value will be interpolated between <a1> and <a2>

+ +

<t2> - <t3> +Alpha value will be set to <a2>

+ +

<t3> - <t4> +Alpha value will be interpolated between <a2> and <a3>

+ +

 

+ +

\fad(<t1>, +<t2>)          <t1> the +time length of fading in

+ +

<t2> the time length +of fading out

+ +

 

+ +

\clip(<x1>, <y1>, <x2>, <y2>)

+ +

 

+ +

                                 + Clips +any drawing outside the rectangle defined by the parameters.

+ +

 

+ +

\clip([<scale>,] <drawing commands>)

+ +

 

+ +

                                 + Clipping +against drawn shapes.

+ +

 

+ +

                                 + <scale> +has the same meaning as in the case of \p<scale>

+ +

 

+ +

Drawings:

+ +

 

+ +

\p<scale>                  <scale> +

+ +

 

+ +

     + +                             + Turns +on drawing mode and sets the magnification level of the coordinates at +the same +time. Scale is interpreted as two to the power of (<scale> minus +one). +For example {\p4} and the coordinate (8,16) will mean the same as {\p1} +and +(1,2). This feature can be useful for sub-pixel accuracy.

+ +

 

+ +

                                 + If +0, drawing mode is turned off and the text is interpreted as usual.

+ +

 

+ +

\pbo<y>                    + <y> +baseline offset. By default any drawings are positioned on the current +baseline. With this value it is possible to move them up or down by +<y> +pixels. (up: y<0, down: y>0)

+ +

 

+ +

Drawing commands:

+ +

 

+ +

m + <x> +<y>                    + Moves +the cursor to <x>, <y>

+ +

 

+ +

n + <x> +<y>                  + Moves +the cursor to <x>, <y> (unclosed shapes will be left open)

+ +

 

+ +

l + <x> +<y>                   + Draws +a line to <x>, <y>

+ +

 

+ +

b <x1> <y1> +<x2> <y2> <x3> <y3>

+ +

 

+ +

                                 + 3rd +degree bezier curve to point 3 using point 1 and 2 as the control points

+ +

 

+ +

s <x1> <y1> +<x2> <y2> <x3> <y3> .. <xN> <yN>

+ +

 

+ +

                                 + 3rd +degree uniform b-spline to point N, must contain at least 3 coordinates

+ +

 

+ +

p + <x> +<y>                  + extend +b-spline to <x>, <y>

+ +

 

+ +

c   + +                             + close +b-spline

+ +

 

+ +

Things you should know:

+ +

 

+ +

     Commands +must appear after {\p1+} and before {\p0}.         (except +for \clip(..))

+ +

 

+ +

     Drawings +must always start with a move to command.

+ +

 

+ +

     Drawings +must form a closed shape.

+ +

 

+ +

     All +unclosed shape will be closed with a straight line automatically.

+ +

 

+ +

     Overlapping +shapes in the Dialogue line will be XOR-ed with each-other.

+ +

 

+ +

     If +the same command follows another, it isnˇŻt needed to write its identifier +letter again, only the coordinates.

+ +

 

+ +

     The +coordinates are relative to the current cursor position (baseline) and the +alignment mode.

+ +

 

+ +

     Commands +p and c should only follow other b-spline commands.

+ +

 

+ +

Examples:

+ +

 

+ +

     Square: +m 0 0 l 100 0 100 100 0 100

+ +

 

+ +

     Rounded +square: m 0 0 s 100 0 100 100 0 100 c (c equals to ˇ°p 0 0 100 +0 100 100ˇ± in this case)

+ +

 

+ +

     Circle +(almost): m 50 0 b 100 0 100 100 50 100 b 0 100 0 0 50 0 +(note that the 2nd ˇ®bˇŻ is optional here)

+ +

 

+ +

 

+ +
+
+ +

Appendix +B: embedded font/picture encoding

+ +

 

+ +

SSAˇŻs font +and picture file embeddeding is a form of UUEncoding.

+ +

 

+ +

It takes a +binary file, three bytes at a time, and converts the 24bits of those bytes into +four 6-bit numbers. 33 is added to each of these four numbers, and the +corresponding ascii character for each number is written into the script file.

+ +

 

+ +

The offset of +33 means that lower-case characters cannot appear in the encoded output, and +this is why the ˇ°filenameˇ± lines are always lower case.

+ +

 

+ +

Each line of +an encoded file is 80 characters long, except the last one, which may be +shorter.

+ +

 

+ +

If the length +of the file being encoded is not an exact multiple of 3, then for odd-number +filelengths, the last byte is multiplied by hexadecimal 100, and the most +significant 12 bits are converted to two characters as above. For even-number +filelengths, the last two bytes are multiplied by hexadecimal 10000, and the +most significant 18 bits are converted to three characters as above.

+ +

 

+ +

There is no +terminating code for the embedded files. If a new [section] starts in the +script, or if another filename line is found, or the end of the script file is +reached then the file is considered complete.

+ +

 

+ +
+ + + + + \ No newline at end of file diff --git a/documentation/Subtitles.htm b/documentation/Subtitles.htm new file mode 100644 index 0000000..08f1ec4 --- /dev/null +++ b/documentation/Subtitles.htm @@ -0,0 +1,814 @@ + + + + + + + + Subtitles + + + + + + + + + + + +
+ +
+
+

Subtitles

+ +

Because Matroska is a general container format, we try to avoid specifying the formats +to store in it. This type of work is really outside of the scope of a container-only format. +However, because the use of subtitles in A/V containers has been so limited (with the exception of DVD) +we are taking the time to specify how to store some of the more common subtitle formats in Matroska. +This is being done to help facilitate their growth. Otherwise, incompatibilities could prevent +the standardization and use of subtitle storage.

+ +

This page is not meant to be a complete listing of all subtitle formats that will be used in Matroska, +it is only meant to be a guide for the more common, current formats. It is possible that +we will add future formats to this page as they are created, but it is not likely as any +other new subtitle format designer would likely have their own specifications. +Any specification listed here SHOULD be strictly adhered to or it SHOULD NOT +use the corresponding Codec ID.

+ +

Here is a list of pointers for storing subtitles in Matroska:

+ +
    +
  • Any Matroska file containing only subtitles SHOULD use the extension “.mks”.
  • +
  • As a general rule of thumb for all codecs, information that is global to an entire stream +SHOULD be stored in the CodecPrivate element.
  • +
  • Start and stop timestamps that are used in a timestamps native storage format SHOULD +be removed when being placed in Matroska as they could interfere if the file is edited +afterwards. Instead, the Blocks timestamp and Duration SHOULD be used to say when the timestamp is displayed.
  • +
  • Because a “subtitle” stream is actually just an overlay stream, anything with a transparency +layer could be use, including video.
  • +
+ +

Images Subtitles

+ +

The first image format that is a goal to import into Matroska is the VobSub subtitle format. +This subtitle type is generated by exporting the subtitles from a DVD [@?DVD-Video].

+ +

The requirement for muxing VobSub into Matroska is v7 subtitles (see first line of the .IDX file). +If the version is smaller, you must remux them using the SubResync utility from +VobSub 2.23 (or MPC) into v7 format. Generally any newly created subs will be in v7 format.

+ +

The .IFO file will not be used at all.

+ +

If there is more than one subtitle stream in the VobSub set, each stream will need to be +separated into separate tracks for storage in Matroska. E.g. the VobSub file contains +streams for both English and German subtitles. Then the resulting Matroska file SHOULD +contain two tracks. That way the language information can be dropped and mapped +to Matroska’s language tags.

+ +

The .IDX file is reformatted (see below) and placed in the CodecPrivate.

+ +

Each .BMP will be stored in its own Block. The Timestamp with be stored in the Blocks Timestamp +and the duration will be stored in the Default Duration.

+ +

Here is an example .IDX file:

+ +
 # VobSub index file, v7 (do not modify this line!)
+ #
+ # To repair desynchronization, you can insert gaps this way:
+ # (it usually happens after vob id changes)
+ #
+ # delay: [sign]hh:mm:ss:ms
+ #
+ # Where:
+ # [sign]: +, - (optional)
+ # hh: hours (0 <= hh)
+ # mm/ss: minutes/seconds (0 <= mm/ss <= 59)
+ # ms: milliseconds (0 <= ms <= 999)
+ #
+ # Note: You can't position a sub before the previous with a negative
+ # value.
+ #
+ # You can also modify timestamps or delete a few subs you don't
+ # like. Just make sure they stay in increasing order.
+
+ # Settings
+
+ # Original frame size
+ size: 720x480
+
+ # Origin, relative to the upper-left corner, can be overloaded by
+ # alignment
+ org: 0, 0
+
+ # Image scaling (hor,ver), origin is at the upper-left corner or at
+ # the alignment coord (x, y)
+ scale: 100%, 100%
+
+ # Alpha blending
+ alpha: 100%
+
+ # Smoothing for very blocky images (use OLD for no filtering)
+ smooth: OFF
+
+ # In millisecs
+ fadein/out: 50, 50
+
+ # Force subtitle placement relative to (org.x, org.y)
+ align: OFF at LEFT TOP
+
+ # For correcting non-progressive desync. (in millisecs or
+ # hh:mm:ss:ms)
+ # Note: Not effective in DirectVobSub, use "delay: ... " instead.
+ time offset: 0
+
+ # ON: displays only forced subtitles, OFF: shows everything
+ forced subs: OFF
+
+ # The original palette of the DVD
+ palette: 000000, 7e7e7e, fbff8b, cb86f1, 7f74b8, e23f06, 0a48ea, \
+ b3d65a, 6b92f1, 87f087, c02081, f8d0f4, e3c411, 382201, e8840b, \
+ fdfdfd
+
+ # Custom colors (transp idxs and the four colors)
+ custom colors: OFF, tridx: 0000, colors: 000000, 000000, 000000, \
+ 000000
+
+ # Language index in use
+ langidx: 0
+
+ # English
+ id: en, index: 0
+ # Uncomment next line to activate alternative name in DirectVobSub /
+ # Windows Media Player 6.x
+ # alt: English
+ # Vob/Cell ID: 1, 1 (PTS: 0)
+ timestamp: 00:00:01:101, filepos: 000000000
+ timestamp: 00:00:08:708, filepos: 000001000
+
+ +

First, lines beginning with “#” are removed. These are comments to make text file editing easier, +and as this is not a text file, they aren’t needed.

+ +

Next remove the “langidx” and “id” lines. These are used to differentiate the subtitle +streams and define the language. As the streams will be stored separately anyway, +there is no need to differentiate them here. Also, the language setting will be stored +in the Matroska tags, so there is no need to store it here.

+ +

Finally, the “timestamp” will be used to set the Block’s timestamp. Once it is set there, +there is no need for it to be stored here. Also, as it may interfere if the file is edited, +it SHOULD NOT be stored here.

+ +

Once all of these items are removed, the data to store in the CodecPrivate SHOULD look like this:

+ +
 size: 720x480
+ org: 0, 0
+ scale: 100%, 100%
+ alpha: 100%
+ smooth: OFF
+ fadein/out: 50, 50
+ align: OFF at LEFT TOP
+ time offset: 0
+ forced subs: OFF
+ palette: 000000, 7e7e7e, fbff8b, cb86f1, 7f74b8, e23f06, 0a48ea, \
+ b3d65a, 6b92f1, 87f087, c02081, f8d0f4, e3c411, 382201, e8840b, \
+ fdfdfd
+ custom colors: OFF, tridx: 0000, colors: 000000, 000000, 000000, \
+ 000000
+
+ +

There SHOULD also be two Blocks containing one image each with the timestamps “00:00:01:101” and “00:00:08:708”.

+ +

SRT Subtitles

+ +

SRT is perhaps the most basic of all subtitle formats.

+ +

It consists of four parts, all in text:

+ +

1. A number indicating which subtitle it is in the sequence. +2. The time that the subtitle appears on the screen, and then disappears. +3. The subtitle itself. +4. A blank line indicating the start of a new subtitle.

+ +

When placing SRT in Matroska, part 3 is converted to UTF-8 (S_TEXT/UTF8) and placed +in the data portion of the Block. Part 2 is used to set the timestamp of the Block, +and BlockDuration element. Nothing else is used.

+ +

Here is an example SRT file:

+ +
1
+00:02:17,440 --> 00:02:20,375
+Senator, we're making
+our final approach into Coruscant.
+
+2
+00:02:20,476 --> 00:02:22,501
+Very good, Lieutenant.
+
+ +

In this example, the text “Senator, we’re making our final approach into Coruscant.” +would be converted into UTF-8 and placed in the Block. The timestamp of the block would +be set to “00:02:17,440”. And the BlockDuration element would be set to “00:00:02,935”.

+ +

The same is repeated for the next subtitle.

+ +

Because there are no general settings for SRT, the CodecPrivate is left blank.

+ +

SSA/ASS Subtitles

+ +

SSA stands for Sub Station Alpha. It’s the file format used by the popular subtitle editor, +SubStation Alpha. +This format is widely used by fansubbers.

+ +

It allows you to do some advanced display features, like positioning, karaoke, style managements…

+ +

For detailed information on SSA/ASS, see the SSA specs. +It includes an SSA specs description and the advanced features added by ASS format (standing for Advanced SSA). +Because SSA and ASS are so similar, they are treated the same here.

+ +

Like SRT, this format is text based with a particular syntax.

+ +

A file consists of 4 or 5 parts, declared ala INI file (but it’s not an INI !)

+ +

The first, “[Script Info]” contains some information about the subtitle file, +such as it’s title, who created it, type of script and a very important one: “PlayResY”. +Be careful of this value, everything in your script (font size, positioning) is scaled by it. +Sub Station Alpha uses your desktops Y resolution to write this value, so if a friend +with a large monitor and a high screen resolution gives you an edited script, +you can mess everything up by saving the script in SSA with your low-cost monitor.

+ +

The second, “[V4 Styles]”, is a list of style definitions. A style describe how will look +a text on the screen. It defines font, font size, primary/…/outile colour, position, alignment, etc.

+ +

For example this:

+ +
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, \
+TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, \
+Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
+Style: Wolf main,Wolf_Rain,56,15724527,15724527,15724527,4144959,0,\
+0,1,1,2,2,5,5,30,0,0
+
+ +

The third, “[Events]”, is the list of text you want to display at the right timing. +You can specify some attribute here. Like the style to use for this event +(MUSTbe defined in the list), the position of the text (Left, Right, Vertical Margin), +an effect. Name is mostly used by translator to know who said this sentence. +Timing is in h:mm:ss.cc (centisec).

+ +
Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, \
+Effect, Text
+Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,\
+0000,,Et les enregistrements de ses ondes delta ?
+Dialogue: Marked=0,0:02:42.42,0:02:44.15,Wolf main,autre,0000,0000,\
+0000,,Toujours rien.
+
+ +

“[Pictures]” or “[Fonts]” part can be found in some SSA file, they contains UUE-encoded +pictures/font but those features are only used by Sub Station Alpha – i.e. +no filter (Vobsub/Avery Lee Subtiler filter) use them.

+ +

Now, how are they stored in Matroska?

+ +
    +
  • All text is converted to UTF-8
  • +
  • All the headers are stored in CodecPrivate + (Script Info and the Styles list)
  • +
  • Start & End field are used to set TimeStamp + and the BlockDuration element. the data stored is:
  • +
  • Events are stored in the Block + in this order: ReadOrder, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, + Text (Layer comes from ASS specs … it’s empty for SSA.) “ReadOrder field is needed + for the decoder to be able to reorder the streamed samples as they were placed originally in the file.”
  • +
+ +

Here is an example of an SSA file.

+ +
[Script Info]
+; This is a Sub Station Alpha v4 script.
+; For Sub Station Alpha info and downloads,
+; go to \
+; [http://www.eswat.demon.co.uk/](http://www.eswat.demon.co.uk/)
+; or email \
+; [kotus@eswat.demon.co.uk](mailto:kotus@eswat.demon.co.uk)
+Title: Wolf's rain 2
+Original Script: Anime-spirit Ishin-francais
+Original Translation: Coolman
+Original Editing: Spikewolfwood
+Original Timing: Lord_alucard
+Original Script Checking: Spikewolfwood
+ScriptType: v4.00
+Collisions: Normal
+PlayResY: 1024
+PlayDepth: 0
+Wav: 0, 128697,D:\Alex\Anime\- Fansub -\- TAFF -\WR_-_02_Wav.wav
+Wav: 0, 120692,H:\team truc\WR_-_02.wav
+Wav: 0, 116504,E:\sub\wolf's_rain\WOLF'S RAIN 02.wav
+LastWav: 3
+Timer: 100,0000
+
+[V4 Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, \
+TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, \
+Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
+Style: Default,Arial,20,65535,65535,65535,-2147483640,-1,0,1,3,0,2,\
+30,30,30,0,0
+Style: Titre_episode,Akbar,140,15724527,65535,65535,986895,-1,0,1,1,\
+0,3,30,30,30,0,0
+Style: Wolf main,Wolf_Rain,56,15724527,15724527,15724527,4144959,0,\
+0,1,1,2,2,5,5,30,0,0
+
+[Events]
+Format: Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, \
+Effect, Text
+Dialogue: Marked=0,0:02:40.65,0:02:41.79,Wolf main,Cher,0000,0000,\
+0000,,Et les enregistrements de ses ondes delta ?
+Dialogue: Marked=0,0:02:42.42,0:02:44.15,Wolf main,autre,0000,0000,\
+0000,,Toujours rien.
+
+ +

Here is what would be placed into the CodecPrivate element.

+ +
[Script Info]
+; This is a Sub Station Alpha v4 script.
+; For Sub Station Alpha info and downloads,
+; go to \
+; [http://www.eswat.demon.co.uk/](http://www.eswat.demon.co.uk/)
+; or email \
+; [kotus@eswat.demon.co.uk](mailto:kotus@eswat.demon.co.uk)
+Title: Wolf's rain 2
+Original Script: Anime-spirit Ishin-francais
+Original Translation: Coolman
+Original Editing: Spikewolfwood
+Original Timing: Lord_alucard
+Original Script Checking: Spikewolfwood
+ScriptType: v4.00
+Collisions: Normal
+PlayResY: 1024
+PlayDepth: 0
+Wav: 0, 128697,D:\Alex\Anime\- Fansub -\- TAFF -\WR_-_02_Wav.wav
+Wav: 0, 120692,H:\team truc\WR_-_02.wav
+Wav: 0, 116504,E:\sub\wolf's_rain\WOLF'S RAIN 02.wav
+LastWav: 3
+Timer: 100,0000
+
+[V4 Styles]
+Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, \
+TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, \
+Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding
+Style: Default,Arial,20,65535,65535,65535,-2147483640,-1,0,1,3,0,2,\
+30,30,30,0,0
+Style: Titre_episode,Akbar,140,15724527,65535,65535,986895,-1,0,1,1,\
+0,3,30,30,30,0,0
+Style: Wolf main,Wolf_Rain,56,15724527,15724527,15724527,4144959,0,\
+0,1,1,2,2,5,5,30,0,0
+
+ +

And here are the two blocks that would be generated.

+ +

Block’s timestamp: 00:02:40.650 +BlockDuration: 00:00:01.140

+ +
1,,Wolf main,Cher,0000,0000,0000,,Et les enregistrements de ses \
+ondes delta ?
+
+ +

Block’s timestamp: 00:02:42.420 +BlockDuration: 00:00:01.730

+ +
2,,Wolf main,autre,0000,0000,0000,,Toujours rien.
+
+ +

WebVTT

+ +

The “Web Video Text Tracks Format” (short: WebVTT) is developed by the World Wide Web Consortium (W3C). +Its specifications are freely available.

+ +

The guiding principles for the storage of WebVTT in Matroska are:

+ +
    +
  • Consistency: store data in a similar way to other subtitle codecs
  • +
  • Simplicity: making decoding and remuxing as easy as possible for existing infrastructures
  • +
  • Completeness: keeping as much data as possible from the original WebVTT file
  • +
+ +

Storage of WebVTT in Matroska

+ +

CodecID: codec identification

+ +

The CodecID to use is S_TEXT/WEBVTT.

+ +

CodecPrivate: storage of global WebVTT blocks

+ +

This element contains all global blocks before the first subtitle entry. This starts at the “WEBVTT” +file identification marker but excludes the optional byte order mark.

+ +

Storage of non-global WebVTT blocks

+ +

Non-global WebVTT blocks (e.g., “NOTE”) before a WebVTT Cue Text are stored in Matroska’s BlockAddition +element together with the Matroska Block containing the WebVTT Cue Text these blocks precede +(see below for the actual format).

+ +

Storage of Cues in Matroska blocks

+ +

Each WebVTT Cue Text is stored directly in the Matroska Block.

+ +

A muxer MUST change all WebVTT Cue Timestamps present within the Cue Text to be relative +to the Matroska Block’s timestamp.

+ +

The Cue’s start timestamp is used as the Matroska Block’s timestamp.

+ +

The difference between the Cue’s end timestamp and its start timestamp is used as +the Matroska Block’s duration.

+ +

BlockAdditions: storing non-global WebVTT blocks, Cue Settings Lists and Cue identifiers

+ +

Each Matroska Block may be accompanied by one BlockAdditions element. Its format is as follows:

+ +
    +
  1. The first line contains the WebVTT Cue Text’s optional Cue Settings List followed by +one line feed character (U+0x000a). The Cue Settings List may be empty, in which case +the line consists of the line feed character only.
  2. +
  3. The second line contains the WebVTT Cue Text’s optional Cue Identifier followed by +one line feed character (U+0x000a). The line may be empty indicating that there was +no Cue Identifier in the source file, in which case the line consists of the line feed character only.
  4. +
  5. The third and all following lines contain all WebVTT Comment Blocks that precede +the current WebVTT Cue Block. These may be absent.
  6. +
+ +

If there is no Matroska BlockAddition element stored together with the Matroska Block, +then all three components (Cue Settings List, Cue Identifier, Cue Comments) MUST be assumed to be absent.

+ +

Examples of transformation

+ +

Here’s an example how a WebVTT is transformed.

+ +

Example WebVTT file

+ +

Let’s take the following example file:

+ +
WEBVTT with text after the signature
+
+STYLE
+::cue {
+  background-image: linear-gradient(to bottom, dimgray, lightgray);
+  color: papayawhip;
+}
+/* Style blocks cannot use blank lines nor "dash dash greater \
+than" */
+
+NOTE comment blocks can be used between style blocks.
+
+STYLE
+::cue(b) {
+  color: peachpuff;
+}
+
+REGION
+id:bill
+width:40%
+lines:3
+regionanchor:0%,100%
+viewportanchor:10%,90%
+scroll:up
+
+NOTE
+Notes always span a whole block and can cover multiple
+lines. Like this one.
+An empty line ends the block.
+
+hello
+00:00:00.000 --> 00:00:10.000
+Example entry 1: Hello <b>world</b>.
+
+NOTE style blocks cannot appear after the first cue.
+
+00:00:25.000 --> 00:00:35.000
+Example entry 2: Another entry.
+This one has multiple lines.
+
+00:01:03.000 --> 00:01:06.500 position:90% align:right size:35%
+Example entry 3: That stuff to the right of the timestamps are cue \
+settings.
+
+00:03:10.000 --> 00:03:20.000
+Example entry 4: Entries can even include timestamps.
+For example:<00:03:15.000>This becomes visible five seconds
+after the first part.
+
+ +

Example of CodecPrivate

+ +

The resulting CodecPrivate element will look like this:

+ +
WEBVTT with text after the signature
+
+STYLE
+::cue {
+  background-image: linear-gradient(to bottom, dimgray, lightgray);
+  color: papayawhip;
+}
+/* Style blocks cannot use blank lines nor "dash dash greater \
+than" */
+
+NOTE comment blocks can be used between style blocks.
+
+STYLE
+::cue(b) {
+  color: peachpuff;
+}
+
+REGION
+id:bill
+width:40%
+lines:3
+regionanchor:0%,100%
+viewportanchor:10%,90%
+scroll:up
+
+NOTE
+Notes always span a whole block and can cover multiple
+lines. Like this one.
+An empty line ends the block.
+
+ +

Storage of Cue 1

+ +

Example Cue 1: timestamp 00:00:00.000, duration 00:00:10.000, Block’s content:

+ +
Example entry 1: Hello <b>world</b>.
+
+ +

BlockAddition’s content starts with one empty line as there’s no Cue Settings List:

+ +

+hello
+
+ +

Storage of Cue 2

+ +

Example Cue 2: timestamp 00:00:25.000, duration 00:00:10.000, Block’s content:

+ +
Example entry 2: Another entry.
+This one has multiple lines.
+
+ +

BlockAddition’s content starts with two empty lines as there’s neither a Cue Settings List nor a Cue Identifier:

+ +

+NOTE style blocks cannot appear after the first cue.
+
+ +

Storage of Cue 3

+ +

Example Cue 3: timestamp 00:01:03.000, duration 00:00:03.500, Block’s content:

+ +
Example entry 3: That stuff to the right of the timestamps are cue \
+settings.
+
+ +

BlockAddition’s content ends with an empty line as there’s no Cue Identifier and +there were no WebVTT Comment blocks:

+ +
position:90% align:right size:35%
+
+
+ +

Storage of Cue 4

+ +

Example Cue 4: timestamp 00:03:10.000, duration 00:00:10.000, Block’s content:

+ +

Example entry 4: Entries can even include timestamps. For +example:<00:00:05.000>This becomes visible five seconds after the +first part.

+ +

This Block does not need a BlockAddition as the Cue did not contain an Identifier, +nor a Settings List, and it wasn’t preceded by Comment blocks.

+ +

Storage of WebVTT in Matroska vs. WebM

+ +

Note: the storage of WebVTT in Matroska is not the same as the design document for storage +of WebVTT in WebM. There are several reasons for this including but not limited to: +the WebM document is old (from February 2012) and was based on an earlier draft of WebVTT +and ignores several parts that were added to WebVTT later; WebM does still not support subtitles at all; +the proposal suggests splitting the information across multiple tracks making +demuxer’s and remuxer’s life very difficult.

+ +

HDMV presentation graphics subtitles

+ +

The specifications for the HDMV presentation graphics subtitle format (short: HDMV PGS) +can be found in the document “Blu-ray Disc Read-Only Format; Part 3 — Audio Visual Basic Specifications” +in section 9.14 “HDMV graphics streams”.

+ +

Storage of HDMV presentation graphics subtitles

+ +

The CodecID to use is S_HDMV/PGS. A CodecPrivate element is not used.

+ +

Storage of HDMV PGS Segments in Matroska Blocks

+ +

Each HDMV PGS Segment (short: Segment) will be stored in a Matroska Block. +A Segment is the data structure described in section 9.14.2.1 “Segment coding structure and parameters” +of the Blu-ray specifications.

+ +

Each Segment contains a presentation timestamp. This timestamp will be used as +the timestamp for the Matroska Block.

+ +

A Segment is normally shown until a subsequent Segment is encountered. Therefore the Matroska Block +MAY have no Duration. In that case, a player MUST display a Segment within a Matroska Block +until the next Segment is encountered.

+ +

A muxer MAY use a Duration, e.g., by calculating the distance between two subsequent Segments. +If a Matroska Block has a Duration, a player MUST display that Segment only for +the duration of the Block’s Duration.

+ +

HDMV text subtitles

+ +

The specifications for the HDMV text subtitle format (short: HDMV +TextST) can be found +in the document “Blu-ray Disc Read-Only Format; Part 3 — Audio Visual +Basic Specifications” in section 9.15 “HDMV text subtitle streams”.

+ +

Storage of HDMV text subtitles

+ +

The CodecID to use is S_HDMV/TEXTST.

+ +

A CodecPrivate Element is required. It MUST contain the stream’s Dialog Style Segment +as described in section 9.15.4.2 “Dialog Style Segment” of the Blu-ray specifications.

+ +

Storage of HDMV TextST Dialog Presentation Segments in Matroska Blocks

+ +

Each HDMV Dialog Presentation Segment (short: Segment) will be stored in a Matroska Block. +A Segment is the data structure described in section 9.15.4.3 “Dialog presentation segment” +of the Blu-ray specifications.

+ +

Each Segment contains a start and an end presentation timestamp (short: start PTS & end PTS). +The start PTS will be used as the timestamp for the Matroska Block. The Matroska Block MUST +have a Duration, and that Duration is the difference between the end PTS and the start PTS.

+ +

A player MUST use the Matroska Block’s timestamp and Duration instead of the Segment’s +start and end PTS for determining when and how long to show the Segment.

+ +

Character set

+ +

When TextST subtitles are stored inside Matroska, the only allowed character set is UTF-8.

+ +

Each HDMV text subtitle stream in a Blu-ray can use one of a handful of character sets. +This information is not stored in the MPEG2 Transport Stream itself but in the accompanying Clip Information file.

+ +

Therefore a muxer MUST parse the accompanying Clip Information file. If the information +indicates a character set other than UTF-8, it MUST re-encode all text Dialog Presentation Segments +from the indicated character set to UTF-8 prior to storing them in Matroska.

+ +

Digital Video Broadcasting (DVB) subtitles

+ +

The specifications for the Digital Video Broadcasting subtitle bitstream format (short: DVB subtitles) +can be found in the document “ETSI EN 300 743 - Digital Video Broadcasting (DVB); Subtitling systems”. +The storage of DVB subtitles in MPEG transport streams is specified in the document +“ETSI EN 300 468 - Digital Video Broadcasting (DVB); Specification for Service Information (SI) in DVB systems”.

+ +

Storage of DVB subtitles

+ +

CodecID

+ +

The CodecID to use is S_DVBSUB.

+ +

CodecPrivate

+ +

The CodecPrivate element is five bytes long and has the following structure:

+ +
    +
  • 2 bytes: composition page ID (bit string, left bit first)
  • +
  • 2 bytes: ancillary page ID (bit string, left bit first)
  • +
  • 1 byte: subtitling type (bit string, left bit first)
  • +
+ +

The semantics of these bytes are the same as the ones described in +section 6.2.41 “Subtitling descriptor” of ETSI EN 300 468.

+ +

Storage of DVB subtitles in Matroska Blocks

+ +

Each Matroska Block consists of one or more DVB Subtitle Segments as described +in segment 7.2 “Syntax and semantics of the subtitling segment” of ETSI EN 300 743.

+ +

Each Matroska Block SHOULD have a Duration indicating how long the DVB Subtitle Segments +in that Block SHOULD be displayed.

+ +

ARIB (ISDB) subtitles

+ +

The specifications for the ARIB B-24 subtitle bitstream format (short: ARIB subtitles) +and its storage in MPEG transport streams can be found in the documents +[@!ARIB.STD-B24], [@!ARIB.STD-B10], and [@!ARIB.TR-B14].

+ +

Storage of ARIB subtitles

+ +

CodecID

+ +

The CodecID to use is S_ARIBSUB.

+ +

CodecPrivate

+ +

The CodecPrivate element is three bytes long and has the following structure:

+ +
    +
  • 1 byte: component tag (bit string, left bit first)
  • +
  • 2 bytes: data component ID (bit string, left bit first)
  • +
+ +

The semantics of the component tag are the same as those described in + [@!ARIB.STD-B10], part 2, Annex J. +The semantics of the data component ID are the same as those described +in [@!ARIB.TR-B14], fascicle 2, Vol. 3, Section 2, 4.2.8.1.

+ +

Storage of ARIB subtitles in Matroska Blocks

+ +

Each Matroska Block consists of a single synchronized PES data structure as described in +chapter 5 “Independent PES transmission protocol” of [@!ARIB.STD-B24], volume 3, with a +Synchronized_PES_data_byte block containing one or more ISDB Caption Data Groups as described +in chapter 9 “Transmission of caption and superimpose” of [@!ARIB.STD-B24], volume 1, part 3. +All of the Caption Statement Data Groups in a given Matroska Track MUST use the same language index.

+ +

A Data Group is normally shown until a subsequent Group provides instructions to clear it. +Therefore the Matroska Block SHOULD NOT have a Duration. +A player SHOULD display a Data Group within a Matroska Block until its internal duration elapses, +or until a subsequent Data Group removes it.

+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/documentation/Subtitles_files/main.css b/documentation/Subtitles_files/main.css new file mode 100644 index 0000000..910c39c --- /dev/null +++ b/documentation/Subtitles_files/main.css @@ -0,0 +1,670 @@ +/** + * Reset some basic elements + */ +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; +} + +/** + * Basic styling + */ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; + color: #111; + background-color: #fdfdfd; + -webkit-text-size-adjust: 100%; +} + +/** + * Set `margin-bottom` to maintain vertical rhythm + */ +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +ul, ol, dl, figure, +.highlight { + margin-bottom: 15px; +} + +/** + * Images + */ +img { + max-width: 100%; + vertical-align: middle; +} + +/** + * Figures + */ +figure > img { + display: block; +} + +figcaption { + font-size: 14px; +} + +/** + * Lists + */ +ul, ol { + margin-left: 30px; +} + +li > ul, +li > ol { + margin-bottom: 0; +} + +/** + * Headings + */ +h1, h2, h3, h4, h5, h6 { + font-weight: 300; +} + +/** + * Links + */ +a { + color: #2a7ae2; + text-decoration: none; +} +a:visited { + color: #1756a9; +} +a:hover { + color: #111; + text-decoration: underline; +} + +/** + * Blockquotes + */ +blockquote { + color: #828282; + border-left: 4px solid #e8e8e8; + padding-left: 15px; + font-size: 18px; + letter-spacing: -1px; + font-style: italic; +} +blockquote > :last-child { + margin-bottom: 0; +} + +/** + * Code formatting + */ +pre, +code { + font-size: 15px; + border: 1px solid #e8e8e8; + border-radius: 3px; + background-color: #eef; +} + +code { + padding: 1px 5px; +} + +pre { + padding: 8px 12px; + overflow-x: scroll; +} +pre > code { + border: 0; + padding-right: 0; + padding-left: 0; +} + +/** + * Wrapper + */ +.wrapper { + max-width: -webkit-calc(1600px - (30px * 2)); + max-width: calc(1600px - (30px * 2)); + margin-right: auto; + margin-left: auto; + padding-right: 30px; + padding-left: 30px; +} +@media screen and (max-width: 800px) { + .wrapper { + max-width: -webkit-calc(1600px - (30px)); + max-width: calc(1600px - (30px)); + padding-right: 15px; + padding-left: 15px; + } +} + +/** + * Clearfix + */ +.footer-col-wrapper:after, .wrapper:after { + content: ""; + display: table; + clear: both; +} + +/** + * Icons + */ +.icon > svg { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; +} +.icon > svg path { + fill: #828282; +} + +/** + * Other general Matroska stuff + */ +table { + border: 1px solid black; + border-collapse: collapse; +} + +thead th { + border: 1px solid black; + padding: 1px 3px; +} + +tbody td { + border: 1px solid black; + padding: 1px 3px; +} + +/** + * Matroska specification table + */ +.techdef table { + font-family: sans-serif; + background: #fff; + margin: 0; + padding: 0; +} + +.techdef th { + font-size: larger; + border: 5px solid #ddd; + background: #eee; + padding: 0.5em 0 0.5em 0; + margin: 0; +} + +.techdef tr { + margin: 0; + padding: 0; +} + +.techdef td { + margin: 0; + border: 1px solid #eee; + padding: 2px; +} + +.techdef .level0 { + background: #fff; +} + +.techdef .level1 { + background: #eff; +} + +.techdef .level2 { + background: #eef; +} + +.techdef .level3 { + background: #dde; +} + +.techdef .level4 { + background: #ccd; +} + +.techdef .level5 { + background: #bbc; +} + +.techdef .level6 { + background: #aab; +} + +.techdef .level7 { + background: #99a; +} + +.techdef .level8 { + background: #889; +} + +.version2 { + color: #aaa; + background: #eee; +} + +/** + * Site header + */ +.site-header { + border-bottom: 2px solid #b4d7f0; + padding-bottom: 10px; + min-height: 56px; + position: relative; +} + +.site-title { + font-size: 26px; + line-height: 56px; + letter-spacing: -1px; + margin-bottom: 0; + float: left; +} +.site-title, .site-title:visited { + color: #424242; +} + +.site-nav { + float: right; + line-height: 56px; +} +.site-nav .menu-icon { + display: none; +} +.site-nav .page-link { + color: #111; + line-height: 1.5; +} +.site-nav .page-link:not(:first-child) { + margin-left: 20px; +} +@media screen and (max-width: 600px) { + .site-nav { + position: absolute; + top: 9px; + right: 30px; + background-color: #fdfdfd; + border: 1px solid #e8e8e8; + border-radius: 5px; + text-align: right; + } + .site-nav .menu-icon { + display: block; + float: right; + width: 36px; + height: 26px; + line-height: 0; + padding-top: 10px; + text-align: center; + } + .site-nav .menu-icon > svg { + width: 18px; + height: 15px; + } + .site-nav .menu-icon > svg path { + fill: #424242; + } + .site-nav .trigger { + clear: both; + display: none; + } + .site-nav:hover .trigger { + display: block; + padding-bottom: 5px; + } + .site-nav .page-link { + display: block; + padding: 5px 10px; + } +} + +/** + * Site footer + */ +.site-footer { + border-top: 2px solid #b4d7f0; + padding: 30px 0; + text-align: center; +} + +.footer-heading { + font-size: 18px; + margin-bottom: 15px; +} + +.contact-list, +.social-media-list { + list-style: none; + margin-left: 0; +} + +.footer-col-wrapper { + font-size: 15px; + color: #828282; + margin-left: -15px; +} + +.footer-col { + float: left; + margin-bottom: 15px; + padding-left: 15px; +} + +.footer-col-1 { + width: -webkit-calc(35% - (30px / 2)); + width: calc(35% - (30px / 2)); +} + +.footer-col-2 { + width: -webkit-calc(20% - (30px / 2)); + width: calc(20% - (30px / 2)); +} + +.footer-col-3 { + width: -webkit-calc(45% - (30px / 2)); + width: calc(45% - (30px / 2)); +} + +@media screen and (max-width: 800px) { + .footer-col-1, + .footer-col-2 { + width: -webkit-calc(50% - (30px / 2)); + width: calc(50% - (30px / 2)); + } + .footer-col-3 { + width: -webkit-calc(100% - (30px / 2)); + width: calc(100% - (30px / 2)); + } +} +@media screen and (max-width: 600px) { + .footer-col { + float: none; + width: -webkit-calc(100% - (30px / 2)); + width: calc(100% - (30px / 2)); + } +} +/** + * Page content + */ +.page-content { + padding: 30px 0; +} + +.page-heading { + font-size: 20px; +} + +.post-list { + margin-left: 0; + list-style: none; +} +.post-list > li { + margin-bottom: 30px; +} + +.post-meta { + font-size: 14px; + color: #828282; +} + +.post-link { + display: block; + font-size: 24px; +} + +/** + * Posts + */ +.post-header { + margin-bottom: 30px; +} + +.post-title { + font-size: 42px; + letter-spacing: -1px; + line-height: 1; +} +@media screen and (max-width: 800px) { + .post-title { + font-size: 36px; + } +} + +.post-content { + margin-bottom: 30px; +} +.post-content h2 { + font-size: 32px; +} +@media screen and (max-width: 800px) { + .post-content h2 { + font-size: 28px; + } +} +.post-content h3 { + font-size: 26px; +} +@media screen and (max-width: 800px) { + .post-content h3 { + font-size: 22px; + } +} +.post-content h4 { + font-size: 20px; +} +@media screen and (max-width: 800px) { + .post-content h4 { + font-size: 18px; + } +} + +/** + * Syntax highlighting styles + */ +.highlight { + background: #fff; +} +.highlight .c { + color: #998; + font-style: italic; +} +.highlight .err { + color: #a61717; + background-color: #e3d2d2; +} +.highlight .k { + font-weight: bold; +} +.highlight .o { + font-weight: bold; +} +.highlight .cm { + color: #998; + font-style: italic; +} +.highlight .cp { + color: #999; + font-weight: bold; +} +.highlight .c1 { + color: #998; + font-style: italic; +} +.highlight .cs { + color: #999; + font-weight: bold; + font-style: italic; +} +.highlight .gd { + color: #000; + background-color: #fdd; +} +.highlight .gd .x { + color: #000; + background-color: #faa; +} +.highlight .ge { + font-style: italic; +} +.highlight .gr { + color: #a00; +} +.highlight .gh { + color: #999; +} +.highlight .gi { + color: #000; + background-color: #dfd; +} +.highlight .gi .x { + color: #000; + background-color: #afa; +} +.highlight .go { + color: #888; +} +.highlight .gp { + color: #555; +} +.highlight .gs { + font-weight: bold; +} +.highlight .gu { + color: #aaa; +} +.highlight .gt { + color: #a00; +} +.highlight .kc { + font-weight: bold; +} +.highlight .kd { + font-weight: bold; +} +.highlight .kp { + font-weight: bold; +} +.highlight .kr { + font-weight: bold; +} +.highlight .kt { + color: #458; + font-weight: bold; +} +.highlight .m { + color: #099; +} +.highlight .s { + color: #d14; +} +.highlight .na { + color: #008080; +} +.highlight .nb { + color: #0086B3; +} +.highlight .nc { + color: #458; + font-weight: bold; +} +.highlight .no { + color: #008080; +} +.highlight .ni { + color: #800080; +} +.highlight .ne { + color: #900; + font-weight: bold; +} +.highlight .nf { + color: #900; + font-weight: bold; +} +.highlight .nn { + color: #555; +} +.highlight .nt { + color: #000080; +} +.highlight .nv { + color: #008080; +} +.highlight .ow { + font-weight: bold; +} +.highlight .w { + color: #bbb; +} +.highlight .mf { + color: #099; +} +.highlight .mh { + color: #099; +} +.highlight .mi { + color: #099; +} +.highlight .mo { + color: #099; +} +.highlight .sb { + color: #d14; +} +.highlight .sc { + color: #d14; +} +.highlight .sd { + color: #d14; +} +.highlight .s2 { + color: #d14; +} +.highlight .se { + color: #d14; +} +.highlight .sh { + color: #d14; +} +.highlight .si { + color: #d14; +} +.highlight .sx { + color: #d14; +} +.highlight .sr { + color: #009926; +} +.highlight .s1 { + color: #d14; +} +.highlight .ss { + color: #990073; +} +.highlight .bp { + color: #999; +} +.highlight .vc { + color: #008080; +} +.highlight .vg { + color: #008080; +} +.highlight .vi { + color: #008080; +} +.highlight .il { + color: #099; +} + +/*# sourceMappingURL=main.css.map */ \ No newline at end of file diff --git a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/parameters/fx/FXParametersModel.java b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/parameters/fx/FXParametersModel.java index 0036dd3..977141f 100644 --- a/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/parameters/fx/FXParametersModel.java +++ b/gui/fx/src/main/java/com/github/gtache/autosubtitle/gui/parameters/fx/FXParametersModel.java @@ -10,10 +10,6 @@ import com.github.gtache.autosubtitle.subtitle.OutputFormat; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModel; import com.github.gtache.autosubtitle.subtitle.extractor.ExtractionModelProvider; import com.github.gtache.autosubtitle.subtitle.impl.FontImpl; - -import javax.inject.Inject; -import javax.inject.Singleton; - import javafx.beans.binding.Bindings; import javafx.beans.property.IntegerProperty; import javafx.beans.property.ObjectProperty; @@ -27,6 +23,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.collections.ObservableMap; +import javax.inject.Inject; +import javax.inject.Singleton; import java.util.Map; /** @@ -55,7 +53,7 @@ public class FXParametersModel implements ParametersModel { this.extractionModelProvider = new SimpleObjectProperty<>(defaultProvider); this.availableExtractionModels = FXCollections.observableArrayList(defaultProvider.getAvailableExtractionModels()); this.extractionModel = new SimpleObjectProperty<>(defaultProvider.getDefaultExtractionModel()); - this.availableOutputFormats = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(OutputFormat.SRT)); + this.availableOutputFormats = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList(OutputFormat.values())); this.outputFormat = new SimpleObjectProperty<>(OutputFormat.SRT); this.availableFontFamilies = FXCollections.unmodifiableObservableList(FXCollections.observableArrayList("Arial")); this.fontName = new SimpleStringProperty(defaultFontFamily); diff --git a/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/setup/fx/setupView.fxml b/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/setup/fx/setupView.fxml index 4ef5af9..cbee879 100644 --- a/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/setup/fx/setupView.fxml +++ b/gui/fx/src/main/resources/com/github/gtache/autosubtitle/gui/setup/fx/setupView.fxml @@ -2,9 +2,7 @@ - - - + @@ -12,8 +10,9 @@ - + +