/// <summary> /// Reads WebVTT block from the reader. /// </summary> /// <param name="reader">Text reader to read the region block.</param> /// <returns>A task that represents the asynchronous read operation.</returns> private static async Task <BaseBlock> ReadBlockAsync( TextReader reader) { var line = await reader.ReadLineAsync() .ConfigureAwait(false); if (string.IsNullOrEmpty(line)) { return(null); } if (line.StartsWith(Constants.RegionToken)) { return(await WebVttParser.ReadRegionAsync(line, reader) .ConfigureAwait(false)); } if (line.StartsWith(Constants.StyleToken)) { return(await WebVttParser.ReadStyleAsync(line, reader) .ConfigureAwait(false)); } if (line.StartsWith(Constants.CommentToken)) { return(await WebVttParser.ReadCommentAsync(line, reader) .ConfigureAwait(false)); } return(await ReadCueAsync(line, reader) .ConfigureAwait(false)); }
/// <summary> /// Utility method to get anchor setting value. /// </summary> /// <param name="name">Setting name.</param> /// <param name="settings">Setting property bag.</param> /// <param name="value">If successful, contains setting value; otherwise default value.</param> /// <returns>True if the setting exists and is valid; otherwise false.</returns> private static bool TryGetAnchorSetting( string name, Dictionary <string, string> settings, out Anchor value) { value = default(Anchor); string stringValue; if (false == settings.TryGetValue(name, out stringValue) || string.IsNullOrEmpty(stringValue)) { return(false); } int index = stringValue.IndexOf(','); if (index < 2 || stringValue.Length - index < 3) { return(false); } if (false == WebVttParser.TryParsePercent(stringValue.Substring(0, index), out value.XPercent) || false == WebVttParser.TryParsePercent(stringValue.Substring(index + 1, stringValue.Length - index - 1), out value.YPercent)) { value = default(Anchor); return(false); } return(true); }
/// <summary> /// Utility method to get position setting value. /// </summary> /// <param name="name">Setting name.</param> /// <param name="settings">Setting property bag.</param> /// <param name="value">If successful, contains setting value; otherwise default value.</param> /// <returns>True if the setting exists and is valid; otherwise false.</returns> private static bool TryGetPositionSetting( string name, Dictionary <string, string> settings, out PositionSettings value) { value = default(PositionSettings); string stringValue; if (false == settings.TryGetValue(name, out stringValue) || string.IsNullOrEmpty(stringValue)) { return(false); } string positionValue = stringValue; int commaIndex = stringValue.IndexOf(','); if (0 <= commaIndex) { if (stringValue.Length - commaIndex < 2) { return(false); } string alignment = stringValue.Substring(commaIndex + 1, stringValue.Length - commaIndex - 1); if (string.Equals(Constants.LineLeftValue, alignment, StringComparison.OrdinalIgnoreCase)) { value.Alignment = PositionAlignment.LineLeft; } else if (string.Equals(Constants.CenterValue, alignment, StringComparison.OrdinalIgnoreCase)) { value.Alignment = PositionAlignment.Center; } else if (string.Equals(Constants.LineRightValue, alignment, StringComparison.OrdinalIgnoreCase)) { value.Alignment = PositionAlignment.LineRight; } else { return(false); } positionValue = stringValue.Substring(0, commaIndex); } double percent; if (WebVttParser.TryParsePercent(positionValue, out percent)) { value.PositionPercent = percent; return(true); } value = default(PositionSettings); return(false); }
/// <summary> /// Utility method to parse single span from text input. /// </summary> /// <param name="input">Input string to process.</param> /// <param name="position">Position in the string to begin parsing.</param> /// <param name="span">If successful, contains span parsed from the input.</param> /// <returns>True if span was successfully parsed from the input; otherwise false.</returns> private static bool TryParseSpan( string input, ref int position, out Span span) { span = null; if (position >= input.Length || input[position] != '<') { return(false); } string tagName; string endTagName; string annotation; List <string> classes; List <Span> innerSpans; if (false == WebVttParser.TryParseTagStart(input, ref position, out tagName, out classes, out annotation)) { return(false); } if (false == WebVttParser.TryParseSpans(input, ref position, out innerSpans)) { return(false); } if (false == WebVttParser.TryParseTagEnd(input, ref position, out endTagName)) { return(false); } if (endTagName != null && false == string.Equals(tagName, endTagName, StringComparison.Ordinal)) { return(false); } SpanType spanType; if (false == WebVttParser.TryGetSpanTypeFromName(tagName, out spanType)) { return(false); } span = new Span() { Type = spanType, Children = innerSpans != null && innerSpans.Count > 0 ? innerSpans.ToArray() : null, Annotation = annotation, Classes = classes != null && classes.Count > 0 ? classes.ToArray() : null }; return(true); }
/// <summary> /// Utility method to get percent setting value. /// </summary> /// <param name="name">Setting name.</param> /// <param name="settings">Setting property bag.</param> /// <param name="value">If successful, contains setting value; otherwise default value.</param> /// <returns>True if the setting exists and is valid; otherwise false.</returns> private static bool TryGetPercentSetting( string name, Dictionary <string, string> settings, out double value) { string stringValue; if (settings.TryGetValue(name, out stringValue) && false == string.IsNullOrEmpty(stringValue)) { return(WebVttParser.TryParsePercent(stringValue, out value)); } value = default(double); return(false); }
/// <summary> /// Utility method to parse region or cue setting values. /// </summary> /// <param name="rawSettings">Raw settings string to parse.</param> /// <returns>Collection of setting values parsed from the input.</returns> private static Dictionary <string, string> ParseSettings( string rawSettings) { Dictionary <string, string> result = null; if (false == string.IsNullOrWhiteSpace(rawSettings)) { int start = 0; int current = 0; foreach (char c in rawSettings) { bool last = rawSettings.Length - current == 1; if (char.IsWhiteSpace(c) || last) { if (start < current || last) { KeyValuePair <string, string> setting; if (WebVttParser.TryParseSettingValue(rawSettings.Substring(start, current - start + (last ? 1 : 0)), out setting)) { if (result == null) { result = new Dictionary <string, string>(5, StringComparer.OrdinalIgnoreCase); } result[setting.Key] = setting.Value; } else { throw new InvalidDataException(string.Format("Invalid setting value in '{0}'.", rawSettings)); } } current++; start = current; } else { current++; } } } return(result); }
/// <summary> /// Utility method to get line setting value. /// </summary> /// <param name="name">Setting name.</param> /// <param name="settings">Setting property bag.</param> /// <param name="value">If successful, contains setting value; otherwise default value.</param> /// <returns>True if the setting exists and is valid; otherwise false.</returns> private static bool TryGetLineSetting( string name, Dictionary <string, string> settings, out LineSettings value) { value = default(LineSettings); string stringValue; if (false == settings.TryGetValue(name, out stringValue) || string.IsNullOrEmpty(stringValue)) { return(false); } string offsetValue = stringValue; int commaIndex = stringValue.IndexOf(','); if (0 <= commaIndex) { if (stringValue.Length - commaIndex < 2) { return(false); } string alignment = stringValue.Substring(commaIndex + 1, stringValue.Length - commaIndex - 1); if (string.Equals(Constants.StartValue, alignment, StringComparison.OrdinalIgnoreCase)) { value.Alignment = LineAlignment.Start; } else if (string.Equals(Constants.CenterValue, alignment, StringComparison.OrdinalIgnoreCase)) { value.Alignment = LineAlignment.Center; } else if (string.Equals(Constants.EndValue, alignment, StringComparison.OrdinalIgnoreCase)) { value.Alignment = LineAlignment.End; } else { return(false); } offsetValue = stringValue.Substring(0, commaIndex); } double percent; if (WebVttParser.TryParsePercent(offsetValue, out percent)) { value.Percent = percent; return(true); } int number; if (int.TryParse(offsetValue, out number)) { value.LineNumber = number; return(true); } value = default(LineSettings); return(false); }
/// <summary> /// Reads WebVTT media captions from a given <see cref="TextReader"/>. /// </summary> /// <param name="reader">Text reader to read the captions.</param> /// <returns>A task that represents the asynchronous read operation.</returns> public static async Task <MediaCaptions> ReadMediaCaptionsAsync( TextReader reader) { if (reader == null) { throw new ArgumentNullException("reader"); } // Process stream header line var line = await reader.ReadLineAsync() .ConfigureAwait(false); if (string.IsNullOrWhiteSpace(line) || line.Length < 6 || false == line.StartsWith(Constants.WebVttHeaderToken) || line.Length >= 7 && line[6] != '\t' && line[6] != ' ') { throw new InvalidDataException("The stream does not start with the correct WebVTT file signature."); } var result = new MediaCaptions(); // Process (skip over) optional headers from the stream while (false == string.IsNullOrEmpty(line)) { line = await reader.ReadLineAsync() .ConfigureAwait(false); } // Process media caption blocks. List <RegionDefinition> regions = new List <RegionDefinition>(); List <Style> styles = new List <Style>(); List <Cue> cues = new List <Cue>(); BaseBlock block; do { block = await WebVttParser.ReadBlockAsync(reader) .ConfigureAwait(false); RegionDefinition region = block as RegionDefinition; Style style = block as Style; Cue cue = block as Cue; if (cues.Count == 0) { if (region != null) { regions.Add(region); } else if (style != null) { styles.Add(style); } } else if (region != null || style != null) { throw new InvalidDataException("Region or style blocks cannot be mixed with cue blocks."); } if (cue != null) { cues.Add(cue); } }while (block != null); if (regions.Count > 0) { result.Regions = regions.ToArray(); } if (styles.Count > 0) { result.Styles = styles.ToArray(); } if (cues.Count > 0) { result.Cues = cues.ToArray(); } return(result); }
/// <summary> /// Reads WebVTT Cue block from the reader. /// </summary> /// <param name="firstLine">First line of the block read from the reader.</param> /// <param name="reader">Text reader to read the style block.</param> /// <returns>A task that represents the asynchronous read operation.</returns> private static async Task <Cue> ReadCueAsync( string firstLine, TextReader reader) { string line = firstLine; var content = new StringBuilder(100); Cue result = new Cue(); if (false == line.Contains(Constants.ArrowToken)) { result.Id = line; line = await reader.ReadLineAsync() .ConfigureAwait(false); } int position = 0; result.Start = ParseTimeSpan(line, ref position); while (position < line.Length && char.IsWhiteSpace(line, position)) { position++; } if (0 != string.CompareOrdinal(line, position, Constants.ArrowToken, 0, Constants.ArrowToken.Length)) { throw new InvalidDataException(string.Format("Invalid characters found in cue timings '{0}' at position {1}.", line, position)); } position += Constants.ArrowToken.Length; while (position < line.Length && char.IsWhiteSpace(line, position)) { position++; } result.End = ParseTimeSpan(line, ref position); if (result.End < result.Start) { throw new InvalidDataException(string.Format("Cue start time is greater than end time in '{0}'.", line)); } if (position < line.Length) { var settings = line.Substring(position).TrimStart('\t', ' ').TrimEnd('\t', ' '); if (false == string.IsNullOrWhiteSpace(settings)) { result.RawSettings = settings; var parsedSettings = WebVttParser.ParseSettings(settings); if (parsedSettings != null) { VerticalTextLayout vertical; if (WebVttParser.TryGetVerticalTextLayoutSetting(Constants.VerticalName, parsedSettings, out vertical)) { result.Vertical = vertical; } LineSettings lineSetting; if (WebVttParser.TryGetLineSetting(Constants.LineName, parsedSettings, out lineSetting)) { result.Line = lineSetting; } PositionSettings positionSetting; if (WebVttParser.TryGetPositionSetting(Constants.PositionName, parsedSettings, out positionSetting)) { result.Position = positionSetting; } double percent; if (WebVttParser.TryGetPercentSetting(Constants.SizeName, parsedSettings, out percent)) { result.SizePercent = percent; } TextAlignment alignment; if (WebVttParser.TryGetAlignmentSetting(Constants.AlignName, parsedSettings, out alignment)) { result.Alignment = alignment; } string name; if (WebVttParser.TryGetStringSetting(Constants.RegionName, parsedSettings, out name)) { result.Region = name; } } } } while (false == string.IsNullOrEmpty(line)) { line = await reader.ReadLineAsync() .ConfigureAwait(false); if (false == string.IsNullOrEmpty(line)) { if (line.Contains(Constants.ArrowToken)) { throw new InvalidDataException(string.Format("Cue must not contain '{0}'.", Constants.ArrowToken)); } content.SafeAppendLine(line); } } if (content.Length > 0) { result.RawContent = content.ToString(); List <Span> spans; position = 0; if (WebVttParser.TryParseSpans(result.RawContent, ref position, out spans) && spans != null && spans.Count > 0) { result.Content = spans.ToArray(); } } return(result); }
/// <summary> /// Reads WebVTT Region definition block from the stream. /// </summary> /// <param name="firstLine">First line of the block read from the reader.</param> /// <param name="reader">Text reader to read the region block.</param> /// <returns>A task that represents the asynchronous read operation.</returns> private static async Task <RegionDefinition> ReadRegionAsync( string firstLine, TextReader reader) { string line = firstLine; if (line.Length > Constants.RegionToken.Length && false == string.IsNullOrWhiteSpace(line.Substring(Constants.RegionToken.Length))) { throw new InvalidDataException(string.Format("Invalid characters found after region definition header: {0}", line)); } var content = new StringBuilder(100); while (false == string.IsNullOrEmpty(line)) { line = await reader.ReadLineAsync() .ConfigureAwait(false); if (false == string.IsNullOrEmpty(line)) { if (line.Contains(Constants.ArrowToken)) { throw new InvalidDataException(string.Format("Region definition must not contain '{0}'.", Constants.ArrowToken)); } content.SafeAppendLine(line); } } var result = new RegionDefinition() { RawContent = content.Length > 0 ? content.ToString() : null }; if (result.RawContent != null) { var settings = WebVttParser.ParseSettings(result.RawContent); if (settings != null) { string value; if (WebVttParser.TryGetStringSetting(Constants.RegionIdName, settings, out value)) { result.Id = value; } double percent; if (WebVttParser.TryGetPercentSetting(Constants.WidthName, settings, out percent)) { result.WidthPercent = percent; } Anchor anchor; if (WebVttParser.TryGetAnchorSetting(Constants.RegionAnchorName, settings, out anchor)) { result.RegionAnchor = anchor; } if (WebVttParser.TryGetAnchorSetting(Constants.ViewPortAnchorName, settings, out anchor)) { result.ViewPortAnchor = anchor; } if (WebVttParser.TryGetStringSetting(Constants.ScrollName, settings, out value)) { result.Scroll = string.Equals(value, Constants.ScrollUpValue, StringComparison.OrdinalIgnoreCase); } int lines; if (WebVttParser.TryGetIntSetting(Constants.LinesName, settings, out lines)) { result.Lines = lines; } } } return(result); }
/// <summary> /// Utility method to parse cue content and create a span syntax tree. /// </summary> /// <param name="input">Input string to process.</param> /// <param name="position">Position in the string to begin parsing.</param> /// <param name="spans">If successful, contains the list of spans parsed from the input.</param> /// <returns>True if successfully parsed spans from the input; otherwise false.</returns> private static bool TryParseSpans( string input, ref int position, out List <Span> spans) { spans = null; if (string.IsNullOrEmpty(input)) { return(false); } List <Span> result = null; int textStart = position; for (; position < input.Length; position++) { char c = input[position]; bool last = position == input.Length - 1; if (c == '<' || last || c == '\r' || c == '\n') { if (textStart < position || last && c != '<') { WebVttParser.SafeAddSpan( new Span() { Type = SpanType.Text, Text = input.Substring(textStart, position - textStart + (last ? 1 : 0)) }, ref result); } if (false == last) { if (c == '<' && input[position + 1] == '/') { break; } if (c == '<') { Span span; if (false == WebVttParser.TryParseSpan(input, ref position, out span)) { return(false); } WebVttParser.SafeAddSpan(span, ref result); } textStart = position + 1; } else if (c == '<') { return(false); } } } spans = result; return(true); }