├── CustomRules.js ├── LICENSE ├── README.md ├── YTSubConverter.Shared ├── Animations │ ├── Animation.cs │ ├── Animator.cs │ ├── BackColorAnimation.cs │ ├── ColorAnimation.cs │ ├── FadeAnimation.cs │ ├── ForeColorAnimation.cs │ ├── GlitchingCharAnimation.cs │ ├── MoveAnimation.cs │ ├── ScaleAnimation.cs │ ├── SecondaryColorAnimation.cs │ ├── ShadowColorAnimation.cs │ └── ShakeAnimation.cs ├── AssStyleOptions.cs ├── AssStyleOptionsList.cs ├── CommandLineHandler.cs ├── Enumerations.cs ├── Formats │ ├── Ass │ │ ├── AssDialogue.cs │ │ ├── AssDocument.cs │ │ ├── AssDocumentItem.cs │ │ ├── AssFileSection.cs │ │ ├── AssKaraokeStepContext.cs │ │ ├── AssLine.cs │ │ ├── AssLineContentBuilder.cs │ │ ├── AssSection.cs │ │ ├── AssStyle.cs │ │ ├── AssTagContext.cs │ │ ├── KaraokeTypes │ │ │ ├── CursorKaraokeType.cs │ │ │ ├── FadeKaraokeType.cs │ │ │ ├── GlitchKaraokeType.cs │ │ │ ├── IKaraokeType.cs │ │ │ └── SimpleKaraokeType.cs │ │ ├── Tags │ │ │ ├── AssAlignmentTagHandler.cs │ │ │ ├── AssAlphaTagHandler.cs │ │ │ ├── AssBoldTagHandler.cs │ │ │ ├── AssChromaTagHandler.cs │ │ │ ├── AssComplexFadeTagHandler.cs │ │ │ ├── AssFontSizeTagHandler.cs │ │ │ ├── AssFontTagHandler.cs │ │ │ ├── AssForeAlphaTagHandler.cs │ │ │ ├── AssForeColorTagHandler.cs │ │ │ ├── AssHorizontalTextDirectionTag.cs │ │ │ ├── AssItalicTagHandler.cs │ │ │ ├── AssKaraokeTagHandler.cs │ │ │ ├── AssKaraokeTypeTagHandler.cs │ │ │ ├── AssMoveTagHandler.cs │ │ │ ├── AssOutlineAlphaTagHandler.cs │ │ │ ├── AssOutlineColorTagHandler.cs │ │ │ ├── AssPackTagHandler.cs │ │ │ ├── AssPositionTagHandler.cs │ │ │ ├── AssRegularScriptTagHandler.cs │ │ │ ├── AssResetTagHandler.cs │ │ │ ├── AssRubyTagHandler.cs │ │ │ ├── AssSecondaryAlphaTagHandler.cs │ │ │ ├── AssSecondaryColorTagHandler.cs │ │ │ ├── AssShadowAlphaTagHandler.cs │ │ │ ├── AssShadowColorTagHandler.cs │ │ │ ├── AssShakeTagHandler.cs │ │ │ ├── AssSimpleFadeTagHandler.cs │ │ │ ├── AssSubscriptTagHandler.cs │ │ │ ├── AssSuperscriptTagHandler.cs │ │ │ ├── AssTagHandlerBase.cs │ │ │ ├── AssTransformTagHandler.cs │ │ │ ├── AssUnderlineTagHandler.cs │ │ │ └── AssVerticalTypeTagHandler.cs │ │ └── VisualizingAssDocument.cs │ ├── SbvDocument.cs │ ├── SrtDocument.cs │ ├── SubtitleDocument.cs │ ├── Ttml │ │ ├── Enumerations.cs │ │ ├── TtmlBody.cs │ │ ├── TtmlColor.cs │ │ ├── TtmlContent.cs │ │ ├── TtmlDiv.cs │ │ ├── TtmlDocument.cs │ │ ├── TtmlLength.cs │ │ ├── TtmlMultipartAttributeReader.cs │ │ ├── TtmlOutline.cs │ │ ├── TtmlParagraph.cs │ │ ├── TtmlPosition.cs │ │ ├── TtmlRegion.cs │ │ ├── TtmlResolutionContext.cs │ │ ├── TtmlShadow.cs │ │ ├── TtmlSize.cs │ │ ├── TtmlSpan.cs │ │ ├── TtmlStyle.cs │ │ ├── TtmlTime.cs │ │ └── TtmlTimeRange.cs │ └── YttDocument.cs ├── HtmlStylePreviewGenerator.cs ├── Line.cs ├── Resources.Designer.cs ├── Resources.ja.resx ├── Resources.ko.resx ├── Resources.ms.resx ├── Resources.pl.resx ├── Resources.resx ├── Resources.ru.resx ├── Resources.zh-Hans.resx ├── Resources.zh-Hant.resx ├── Resources │ ├── StyleOptions.xml │ ├── checkers.png_ │ └── defaultstyles.ass ├── Section.cs ├── SectionFormatComparer.cs ├── StylePreviewGenerator.cs ├── Util │ ├── AnchorPointUtil.cs │ ├── CharacterRange.cs │ ├── CollectionExtensions.cs │ ├── ColorUtil.cs │ ├── FontSizeMapper.cs │ ├── Range.cs │ ├── StringExtensions.cs │ ├── TimeRange.cs │ ├── TimeUtil.cs │ └── XmlExtensions.cs └── YTSubConverter.Shared.csproj ├── YTSubConverter.Tests ├── Ass │ ├── AssIntegrationTests.cs │ └── Files │ │ ├── Alignment.ass │ │ ├── Alignment.ytt │ │ ├── BoldItalicUnderline.ass │ │ ├── BoldItalicUnderline.ytt │ │ ├── Chroma.ass │ │ ├── Chroma.ytt │ │ ├── Colors.ass │ │ ├── Colors.ytt │ │ ├── Fade.ass │ │ ├── Fade.ytt │ │ ├── FaultTolerance.ass │ │ ├── FaultTolerance.ytt │ │ ├── Fonts.ass │ │ ├── Fonts.ytt │ │ ├── Karaoke.ass │ │ ├── Karaoke.ytt │ │ ├── Move.ass │ │ ├── Move.ytt │ │ ├── NoDefaultScale.ass │ │ ├── NoDefaultScale.ytt │ │ ├── Offset.ass │ │ ├── Offset.ytt │ │ ├── Ruby.ass │ │ ├── Ruby.ytt │ │ ├── Shadows.ass │ │ ├── Shadows.ytt │ │ ├── Shake.ass │ │ ├── Shake.ytt │ │ ├── Simultaneous.ass │ │ ├── Simultaneous.ytt │ │ ├── SimultaneousReverse.ass │ │ ├── SimultaneousReverse.ytt │ │ ├── TextDirection.ass │ │ ├── TextDirection.ytt │ │ ├── Transform.ass │ │ └── Transform.ytt ├── IntegrationTestsBase.cs ├── Properties │ └── AssemblyInfo.cs ├── StyleOptions.xml ├── Ttml │ ├── Files │ │ ├── Alignment.xml │ │ ├── Alignment.ytt │ │ ├── BoldItalicUnderline.xml │ │ ├── BoldItalicUnderline.ytt │ │ ├── Colors.xml │ │ ├── Colors.ytt │ │ ├── Fonts.xml │ │ ├── Fonts.ytt │ │ ├── Positioning.xml │ │ ├── Positioning.ytt │ │ ├── Ruby.xml │ │ ├── Ruby.ytt │ │ ├── Shadows.xml │ │ ├── Shadows.ytt │ │ ├── StyleDerivation.xml │ │ ├── StyleDerivation.ytt │ │ ├── TextDirection.xml │ │ ├── TextDirection.ytt │ │ ├── Visibility.xml │ │ └── Visibility.ytt │ ├── TtmlColorTests.cs │ ├── TtmlIntegrationTests.cs │ ├── TtmlLengthTests.cs │ ├── TtmlOutlineTests.cs │ ├── TtmlPositionTests.cs │ ├── TtmlShadowTests.cs │ ├── TtmlSizeTests.cs │ └── TtmlTimeTests.cs ├── YTSubConverter.Tests.csproj └── packages.config ├── YTSubConverter.UI.Linux ├── GtkExtensions.cs ├── GtkStylePreviewGenerator.cs ├── MainWindow.cs ├── MainWindow.glade ├── MessageDialogHelper.cs ├── MultiStyleLabel.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── YTSubConverter.UI.Linux.csproj ├── checkers.png ├── icon32.png └── packages.config ├── YTSubConverter.UI.Mac ├── Alert.cs ├── AppDelegate.cs ├── AppDelegate.designer.cs ├── Assets.xcassets │ ├── .DS_Store │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon1024.png │ │ ├── icon128.png │ │ ├── icon16.png │ │ ├── icon256-1.png │ │ ├── icon256.png │ │ ├── icon32-1.png │ │ ├── icon32.png │ │ ├── icon512-1.png │ │ ├── icon512.png │ │ └── icon64.png │ └── Contents.json ├── CocoaExtensions.cs ├── Entitlements.plist ├── Info.plist ├── Main.cs ├── Main.storyboard ├── Properties │ └── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.ja.resx ├── Resources.ko.resx ├── Resources.ms.resx ├── Resources.pl.resx ├── Resources.resx ├── Resources.ru.resx ├── Resources.zh-Hans.resx ├── Resources.zh-Hant.resx ├── SimpleTableViewDataSource.cs ├── View.cs ├── ViewController.cs ├── ViewController.designer.cs └── YTSubConverter.UI.Mac.csproj ├── YTSubConverter.UI.Win ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── YTSubConverter.UI.Win.csproj ├── app.manifest ├── icon.ico └── packages.config ├── YTSubConverter.sln ├── images ├── download.png ├── fonts.png ├── mobile.png ├── outlines.png ├── sample.png └── style-options.png ├── replace markers.lua ├── sample1.ass ├── sample2.ass └── ytt.ytt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 arcusmaximus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/Animation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using YTSubConverter.Shared.Formats.Ass; 4 | 5 | namespace YTSubConverter.Shared.Animations 6 | { 7 | public abstract class Animation : ICloneable 8 | { 9 | protected Animation(DateTime startTime, DateTime endTime, float acceleration) 10 | { 11 | StartTime = startTime; 12 | EndTime = endTime; 13 | Acceleration = acceleration; 14 | } 15 | 16 | public DateTime StartTime 17 | { 18 | get; 19 | } 20 | 21 | public DateTime EndTime 22 | { 23 | get; 24 | } 25 | 26 | public float Acceleration 27 | { 28 | get; 29 | } 30 | 31 | public abstract bool AffectsPast 32 | { 33 | get; 34 | } 35 | 36 | public virtual bool AffectsText => false; 37 | 38 | public abstract void Apply(AssLine line, AssSection section, float t); 39 | 40 | protected int Interpolate(int from, int to, float t) 41 | { 42 | return from + (int)((to - from) * Math.Pow(t, Acceleration)); 43 | } 44 | 45 | protected float Interpolate(float from, float to, float t) 46 | { 47 | return from + (to - from) * (float)Math.Pow(t, Acceleration); 48 | } 49 | 50 | protected Color Interpolate(Color from, Color to, float t) 51 | { 52 | return Color.FromArgb( 53 | Interpolate(from.A, to.A, t), 54 | Interpolate(from.R, to.R, t), 55 | Interpolate(from.G, to.G, t), 56 | Interpolate(from.B, to.B, t) 57 | ); 58 | } 59 | 60 | public abstract object Clone(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/BackColorAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using YTSubConverter.Shared.Formats.Ass; 4 | 5 | namespace YTSubConverter.Shared.Animations 6 | { 7 | public class BackColorAnimation : ColorAnimation 8 | { 9 | public BackColorAnimation(DateTime startTime, Color startColor, DateTime endTime, Color endColor, float acceleration) 10 | : base(startTime, startColor, endTime, endColor, acceleration) 11 | { 12 | } 13 | 14 | public override void Apply(AssLine line, AssSection section, float t) 15 | { 16 | section.BackColor = GetColor(t); 17 | } 18 | 19 | public override object Clone() 20 | { 21 | return new BackColorAnimation(StartTime, StartColor, EndTime, EndColor, Acceleration); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/ColorAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace YTSubConverter.Shared.Animations 5 | { 6 | public abstract class ColorAnimation : Animation 7 | { 8 | protected ColorAnimation(DateTime startTime, Color startColor, DateTime endTime, Color endColor, float acceleration) 9 | : base(startTime, endTime, acceleration) 10 | { 11 | StartColor = startColor; 12 | EndColor = endColor; 13 | } 14 | 15 | public Color StartColor 16 | { 17 | get; 18 | set; 19 | } 20 | 21 | public Color EndColor 22 | { 23 | get; 24 | set; 25 | } 26 | 27 | public override bool AffectsPast => false; 28 | 29 | protected Color GetColor(float t) 30 | { 31 | return Interpolate(StartColor, EndColor, t); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/FadeAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using YTSubConverter.Shared.Formats.Ass; 3 | 4 | namespace YTSubConverter.Shared.Animations 5 | { 6 | public class FadeAnimation : Animation 7 | { 8 | public FadeAnimation(DateTime startTime, int startAlpha, DateTime endTime, int endAlpha) 9 | : base(startTime, endTime, 1) 10 | { 11 | StartAlpha = startAlpha; 12 | EndAlpha = endAlpha; 13 | } 14 | 15 | public int StartAlpha 16 | { 17 | get; 18 | } 19 | 20 | public int EndAlpha 21 | { 22 | get; 23 | } 24 | 25 | public override bool AffectsPast => true; 26 | 27 | public override void Apply(AssLine line, AssSection section, float t) 28 | { 29 | line.Alpha = Interpolate(StartAlpha, EndAlpha, t); 30 | } 31 | 32 | public override object Clone() 33 | { 34 | return new FadeAnimation(StartTime, StartAlpha, EndTime, EndAlpha); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/ForeColorAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using YTSubConverter.Shared.Formats.Ass; 4 | 5 | namespace YTSubConverter.Shared.Animations 6 | { 7 | public class ForeColorAnimation : ColorAnimation 8 | { 9 | public ForeColorAnimation(DateTime startTime, Color startColor, DateTime endTime, Color endColor, float acceleration) 10 | : base(startTime, startColor, endTime, endColor, acceleration) 11 | { 12 | } 13 | 14 | public override void Apply(AssLine line, AssSection section, float t) 15 | { 16 | section.ForeColor = GetColor(t); 17 | } 18 | 19 | public override object Clone() 20 | { 21 | return new ForeColorAnimation(StartTime, StartColor, EndTime, EndColor, Acceleration); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/GlitchingCharAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using YTSubConverter.Shared.Formats; 3 | using YTSubConverter.Shared.Formats.Ass; 4 | using YTSubConverter.Shared.Util; 5 | 6 | namespace YTSubConverter.Shared.Animations 7 | { 8 | public class GlitchingCharAnimation : Animation 9 | { 10 | private readonly Random _random; 11 | private readonly CharacterRange[] _charRanges; 12 | 13 | public GlitchingCharAnimation(DateTime startTime, DateTime endTime, params CharacterRange[] charRanges) 14 | : base(startTime, endTime, 1) 15 | { 16 | _random = new Random((int)(startTime - SubtitleDocument.TimeBase).TotalMilliseconds); 17 | _charRanges = charRanges; 18 | } 19 | 20 | public override bool AffectsPast => false; 21 | 22 | public override bool AffectsText => true; 23 | 24 | public override void Apply(AssLine line, AssSection section, float t) 25 | { 26 | if (t > 0 && t < 1) 27 | section.Text = GetRandomChar().ToString(); 28 | } 29 | 30 | private char GetRandomChar() 31 | { 32 | CharacterRange range = _charRanges[_random.Next(_charRanges.Length)]; 33 | return (char)(range.Start + _random.Next(range.End - range.Start)); 34 | } 35 | 36 | public override object Clone() 37 | { 38 | return new GlitchingCharAnimation(StartTime, EndTime, _charRanges); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/MoveAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using YTSubConverter.Shared.Formats.Ass; 4 | 5 | namespace YTSubConverter.Shared.Animations 6 | { 7 | public class MoveAnimation : Animation 8 | { 9 | public MoveAnimation(DateTime startTime, PointF startPos, DateTime endTime, PointF endPos) 10 | : base(startTime, endTime, 1) 11 | { 12 | StartPosition = startPos; 13 | EndPosition = endPos; 14 | } 15 | 16 | public PointF StartPosition 17 | { 18 | get; 19 | } 20 | 21 | public PointF EndPosition 22 | { 23 | get; 24 | } 25 | 26 | public override bool AffectsPast => true; 27 | 28 | public override void Apply(AssLine line, AssSection section, float t) 29 | { 30 | float x = Interpolate(StartPosition.X, EndPosition.X, t); 31 | float y = Interpolate(StartPosition.Y, EndPosition.Y, t); 32 | line.Position = new PointF(x, y); 33 | } 34 | 35 | public override object Clone() 36 | { 37 | return new MoveAnimation(StartTime, StartPosition, EndTime, EndPosition); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/ScaleAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using YTSubConverter.Shared.Formats.Ass; 3 | 4 | namespace YTSubConverter.Shared.Animations 5 | { 6 | public class ScaleAnimation : Animation 7 | { 8 | public ScaleAnimation(DateTime startTime, float startScale, DateTime endTime, float endScale, float acceleration) 9 | : base(startTime, endTime, acceleration) 10 | { 11 | StartScale = startScale; 12 | EndScale = endScale; 13 | } 14 | 15 | public override bool AffectsPast => false; 16 | 17 | public float StartScale 18 | { 19 | get; 20 | } 21 | 22 | public float EndScale 23 | { 24 | get; 25 | } 26 | 27 | public override void Apply(AssLine line, AssSection section, float t) 28 | { 29 | section.Scale = Interpolate(StartScale, EndScale, t); 30 | } 31 | 32 | public override object Clone() 33 | { 34 | return new ScaleAnimation(StartTime, StartScale, EndTime, EndScale, Acceleration); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/SecondaryColorAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using YTSubConverter.Shared.Formats.Ass; 4 | 5 | namespace YTSubConverter.Shared.Animations 6 | { 7 | public class SecondaryColorAnimation : ColorAnimation 8 | { 9 | public SecondaryColorAnimation(DateTime startTime, Color startColor, DateTime endTime, Color endColor, float acceleration) 10 | : base(startTime, startColor, endTime, endColor, acceleration) 11 | { 12 | } 13 | 14 | public override void Apply(AssLine line, AssSection section, float t) 15 | { 16 | section.SecondaryColor = GetColor(t); 17 | } 18 | 19 | public override object Clone() 20 | { 21 | return new SecondaryColorAnimation(StartTime, StartColor, EndTime, EndColor, Acceleration); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/ShadowColorAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using YTSubConverter.Shared.Formats.Ass; 4 | 5 | namespace YTSubConverter.Shared.Animations 6 | { 7 | public class ShadowColorAnimation : ColorAnimation 8 | { 9 | public ShadowColorAnimation(ShadowType shadowType, DateTime startTime, Color startColor, DateTime endTime, Color endColor, float acceleration) 10 | : base(startTime, startColor, endTime, endColor, acceleration) 11 | { 12 | ShadowType = shadowType; 13 | } 14 | 15 | public ShadowType ShadowType 16 | { 17 | get; 18 | } 19 | 20 | public override void Apply(AssLine line, AssSection section, float t) 21 | { 22 | section.ShadowColors[ShadowType] = GetColor(t); 23 | } 24 | 25 | public override object Clone() 26 | { 27 | return new ShadowColorAnimation(ShadowType, StartTime, StartColor, EndTime, EndColor, Acceleration); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Animations/ShakeAnimation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using YTSubConverter.Shared.Formats; 4 | using YTSubConverter.Shared.Formats.Ass; 5 | 6 | namespace YTSubConverter.Shared.Animations 7 | { 8 | public class ShakeAnimation : Animation 9 | { 10 | private readonly Random _random; 11 | 12 | public ShakeAnimation(DateTime startTime, DateTime endTime, SizeF radius) 13 | : base(startTime, endTime, 1) 14 | { 15 | Radius = radius; 16 | _random = new Random((int)(startTime - SubtitleDocument.TimeBase).TotalMilliseconds); 17 | } 18 | 19 | public SizeF Radius 20 | { 21 | get; 22 | } 23 | 24 | public override bool AffectsPast => false; 25 | 26 | public override void Apply(AssLine line, AssSection section, float t) 27 | { 28 | if (t <= 0 || t >= 1 || line.Position == null) 29 | return; 30 | 31 | line.Position = new PointF( 32 | line.Position.Value.X + Radius.Width * ((float)_random.NextDouble() * 2 - 1.0f), 33 | line.Position.Value.Y + Radius.Height * ((float)_random.NextDouble() * 2 - 1.0f) 34 | ); 35 | } 36 | 37 | public override object Clone() 38 | { 39 | return new ShakeAnimation(StartTime, EndTime, Radius); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/AssStyleOptionsList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Xml.Serialization; 6 | 7 | namespace YTSubConverter.Shared 8 | { 9 | [XmlRoot("StyleOptions")] 10 | public class AssStyleOptionsList 11 | { 12 | public const string FileName = "StyleOptions.xml"; 13 | 14 | public AssStyleOptionsList() 15 | { 16 | } 17 | 18 | public AssStyleOptionsList(IEnumerable options) 19 | { 20 | Options = options.ToList(); 21 | } 22 | 23 | [XmlElement("Style")] 24 | public List Options 25 | { 26 | get; 27 | set; 28 | } 29 | 30 | public static List LoadFromFile(string filePath = null) 31 | { 32 | filePath ??= GetDefaultFilePath(); 33 | if (!File.Exists(filePath)) 34 | return new List(); 35 | 36 | try 37 | { 38 | using Stream stream = File.Open(filePath, FileMode.Open, FileAccess.Read); 39 | XmlSerializer serializer = new XmlSerializer(typeof(AssStyleOptionsList)); 40 | AssStyleOptionsList options = (AssStyleOptionsList)serializer.Deserialize(stream); 41 | return options.Options; 42 | } 43 | catch 44 | { 45 | return new List(); 46 | } 47 | } 48 | 49 | public static List LoadFromString(string data) 50 | { 51 | try 52 | { 53 | using StringReader reader = new StringReader(data); 54 | XmlSerializer serializer = new XmlSerializer(typeof(AssStyleOptionsList)); 55 | AssStyleOptionsList options = (AssStyleOptionsList)serializer.Deserialize(reader); 56 | return options.Options; 57 | } 58 | catch 59 | { 60 | return new List(); 61 | } 62 | } 63 | 64 | public static void SaveToFile(IEnumerable options, string filePath = null) 65 | { 66 | using Stream stream = File.Open(filePath ?? GetDefaultFilePath(), FileMode.Create, FileAccess.Write); 67 | XmlSerializer serializer = new XmlSerializer(typeof(AssStyleOptionsList)); 68 | AssStyleOptionsList list = new AssStyleOptionsList(options); 69 | serializer.Serialize(stream, list); 70 | } 71 | 72 | public static string GetDefaultFilePath() 73 | { 74 | return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), FileName); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Enumerations.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared 2 | { 3 | public enum LineMergeType 4 | { 5 | MoveNew, 6 | MoveExisting 7 | } 8 | 9 | public enum AnchorPoint 10 | { 11 | TopLeft, 12 | TopCenter, 13 | TopRight, 14 | MiddleLeft, 15 | Center, 16 | MiddleRight, 17 | BottomLeft, 18 | BottomCenter, 19 | BottomRight 20 | } 21 | 22 | public enum ShadowType 23 | { 24 | Glow, 25 | Bevel, 26 | HardShadow, 27 | SoftShadow 28 | } 29 | 30 | public enum OffsetType 31 | { 32 | Regular, 33 | Subscript, 34 | Superscript 35 | } 36 | 37 | public enum RubyPart 38 | { 39 | None, 40 | Base, 41 | Parenthesis, 42 | TextBefore, 43 | TextAfter 44 | } 45 | 46 | public enum HorizontalTextDirection 47 | { 48 | LeftToRight, 49 | RightToLeft 50 | } 51 | 52 | public enum VerticalTextType 53 | { 54 | None, 55 | Positioned, 56 | Rotated 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssDialogue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass 4 | { 5 | internal class AssDialogue 6 | { 7 | public AssDialogue(AssDocumentItem item) 8 | { 9 | Layer = item.GetInt("Layer"); 10 | Start = item.GetTimestamp("Start"); 11 | End = item.GetTimestamp("End"); 12 | Style = item.GetString("Style"); 13 | Effect = item.GetString("Effect"); 14 | Text = item.GetString("Text"); 15 | } 16 | 17 | public int Layer 18 | { 19 | get; 20 | } 21 | 22 | public DateTime Start 23 | { 24 | get; 25 | } 26 | 27 | public DateTime End 28 | { 29 | get; 30 | } 31 | 32 | public string Style 33 | { 34 | get; 35 | } 36 | 37 | public string Effect 38 | { 39 | get; 40 | } 41 | 42 | public string Text 43 | { 44 | get; 45 | } 46 | 47 | public override string ToString() 48 | { 49 | return Text; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssDocumentItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Globalization; 5 | using System.Text.RegularExpressions; 6 | 7 | namespace YTSubConverter.Shared.Formats.Ass 8 | { 9 | internal struct AssDocumentItem 10 | { 11 | public AssDocumentItem(AssDocumentSection section, string type, List values) 12 | { 13 | Section = section; 14 | Type = type; 15 | Values = values; 16 | } 17 | 18 | public AssDocumentSection Section 19 | { 20 | get; 21 | } 22 | 23 | public string Type 24 | { 25 | get; 26 | } 27 | 28 | public List Values 29 | { 30 | get; 31 | } 32 | 33 | public string GetString(string field) 34 | { 35 | return Values[Section.Format[field]]; 36 | } 37 | 38 | public int GetInt(string field, int defaultValue = 0) 39 | { 40 | return int.TryParse(GetString(field), out int result) ? result : defaultValue; 41 | } 42 | 43 | public float GetFloat(string field, float defaultValue = 0) 44 | { 45 | return float.TryParse(GetString(field), NumberStyles.Any, CultureInfo.InvariantCulture, out float result) ? result : defaultValue; 46 | } 47 | 48 | public bool GetBool(string field, bool defaultValue = false) 49 | { 50 | return Convert.ToBoolean(GetInt(field, Convert.ToInt32(defaultValue))); 51 | } 52 | 53 | public Color GetColor(string field) 54 | { 55 | string value = GetString(field); 56 | if (value.Length != 10 || !value.StartsWith("&H")) 57 | throw new FormatException(string.Format(Resources.IsNotAValidColor, value)); 58 | 59 | if (!uint.TryParse(value.Substring(2), NumberStyles.AllowHexSpecifier, null, out uint abgr)) 60 | throw new FormatException(string.Format(Resources.IsNotAValidColor, value)); 61 | 62 | byte a = (byte)(255 - (abgr >> 24)); 63 | byte r = (byte)abgr; 64 | byte g = (byte)(abgr >> 8); 65 | byte b = (byte)(abgr >> 16); 66 | return Color.FromArgb(a, r, g, b); 67 | } 68 | 69 | public DateTime GetTimestamp(string field) 70 | { 71 | string value = GetString(field); 72 | Match match = Regex.Match(value, @"^(\d+):(\d\d):(\d\d)\.(\d\d)$"); 73 | if (!match.Success) 74 | throw new FormatException(string.Format(Resources.IsNotAValidTimestamp, value)); 75 | 76 | return new DateTime( 77 | SubtitleDocument.TimeBase.Year, 78 | SubtitleDocument.TimeBase.Month, 79 | SubtitleDocument.TimeBase.Day, 80 | int.Parse(match.Groups[1].Value), 81 | int.Parse(match.Groups[2].Value), 82 | int.Parse(match.Groups[3].Value), 83 | int.Parse(match.Groups[4].Value) * 10 84 | ); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssFileSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | 6 | namespace YTSubConverter.Shared.Formats.Ass 7 | { 8 | internal class AssDocumentSection 9 | { 10 | public Dictionary Format 11 | { 12 | get; 13 | set; 14 | } 15 | 16 | public List Items { get; } = new List(); 17 | 18 | public void SetFormat(List format) 19 | { 20 | Format = new Dictionary(); 21 | for (int i = 0; i < format.Count; i++) 22 | { 23 | Format.Add(format[i], i); 24 | } 25 | } 26 | 27 | public void AddItem(string type, List values) 28 | { 29 | Items.Add(new AssDocumentItem(this, type, values)); 30 | } 31 | 32 | public string GetItemString(string type) 33 | { 34 | return Items.FirstOrDefault(i => i.Type == type).Values?.FirstOrDefault(); 35 | } 36 | 37 | public int GetItemInt(string type, int defaultValue = 0) 38 | { 39 | return int.TryParse(GetItemString(type), out int result) ? result : defaultValue; 40 | } 41 | 42 | public float GetItemFloat(string type, float defaultValue = 0) 43 | { 44 | return float.TryParse(GetItemString(type), NumberStyles.Any, CultureInfo.InvariantCulture, out float result) ? result : defaultValue; 45 | } 46 | 47 | public bool GetItemBool(string type, bool defaultValue = false) 48 | { 49 | return Convert.ToBoolean(GetItemInt(type, Convert.ToInt32(defaultValue))); 50 | } 51 | 52 | public IEnumerable MapItems(string type, Func selector) 53 | { 54 | return Items.Where(i => i.Type == type).Select(selector); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssKaraokeStepContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace YTSubConverter.Shared.Formats.Ass 5 | { 6 | public class AssKaraokeStepContext 7 | { 8 | public AssDocument Document; 9 | public AssLine OriginalLine; 10 | public SortedList ActiveSectionsPerStep; 11 | 12 | public AssLine StepLine; 13 | public int StepIndex; 14 | public int NumActiveSections; 15 | public List SingingSections; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssLine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using YTSubConverter.Shared.Animations; 6 | using YTSubConverter.Shared.Formats.Ass.KaraokeTypes; 7 | using YTSubConverter.Shared.Util; 8 | 9 | namespace YTSubConverter.Shared.Formats.Ass 10 | { 11 | public class AssLine : Line 12 | { 13 | public AssLine(DateTime start, DateTime end) 14 | : base(start, end) 15 | { 16 | } 17 | 18 | public AssLine(Line line) 19 | : base(line) 20 | { 21 | if (line is AssLine || Sections.All(s => s.StartOffset == TimeSpan.Zero)) 22 | return; 23 | 24 | for (int i = 0; i < Sections.Count - 1; i++) 25 | { 26 | ((AssSection)Sections[i]).Duration = Sections[i + 1].StartOffset - Sections[i].StartOffset; 27 | } 28 | 29 | AssSection lastSection = (AssSection)Sections.Last(); 30 | lastSection.Duration = End - Start - lastSection.StartOffset; 31 | } 32 | 33 | public int Layer { get; set; } 34 | 35 | public int Alpha { get; set; } = 255; 36 | 37 | public List Animations { get; } = new List(); 38 | 39 | public IKaraokeType KaraokeType { get; set; } = SimpleKaraokeType.Instance; 40 | 41 | public void NormalizeAlpha() 42 | { 43 | if (Alpha == 255) 44 | return; 45 | 46 | float factor = Alpha / 255.0f; 47 | foreach (Section section in Sections) 48 | { 49 | section.ForeColor = MultiplyColorAlpha(section.ForeColor, factor); 50 | section.BackColor = MultiplyColorAlpha(section.BackColor, factor); 51 | foreach (KeyValuePair shadowColor in section.ShadowColors.ToList()) 52 | { 53 | section.ShadowColors[shadowColor.Key] = MultiplyColorAlpha(shadowColor.Value, factor); 54 | } 55 | } 56 | Alpha = 255; 57 | } 58 | 59 | private static Color MultiplyColorAlpha(Color color, float factor) 60 | { 61 | if (color.IsEmpty) 62 | return color; 63 | 64 | return ColorUtil.ChangeAlpha(color, (int)(color.A * factor)); 65 | } 66 | 67 | public override object Clone() 68 | { 69 | return new AssLine(this); 70 | } 71 | 72 | protected override void Assign(Line line) 73 | { 74 | base.Assign(line); 75 | if (!(line is AssLine assLine)) 76 | return; 77 | 78 | Layer = assLine.Layer; 79 | Alpha = assLine.Alpha; 80 | Animations.Clear(); 81 | Animations.AddRange(assLine.Animations.Select(a => (Animation)a.Clone())); 82 | KaraokeType = assLine.KaraokeType; 83 | } 84 | 85 | protected override Section CreateSection(Section section) 86 | { 87 | return new AssSection(section); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssSection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using YTSubConverter.Shared.Animations; 6 | 7 | namespace YTSubConverter.Shared.Formats.Ass 8 | { 9 | public class AssSection : Section 10 | { 11 | public AssSection() 12 | { 13 | } 14 | 15 | public AssSection(string text) 16 | : base(text) 17 | { 18 | } 19 | 20 | public AssSection(Section section) 21 | : base(section) 22 | { 23 | } 24 | 25 | public Color SecondaryColor 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | public float Blur 32 | { 33 | get; 34 | set; 35 | } 36 | 37 | public Color CurrentWordForeColor 38 | { 39 | get; 40 | set; 41 | } 42 | 43 | public Color CurrentWordOutlineColor 44 | { 45 | get; 46 | set; 47 | } 48 | 49 | public Color CurrentWordShadowColor 50 | { 51 | get; 52 | set; 53 | } 54 | 55 | public TimeSpan Duration 56 | { 57 | get; 58 | set; 59 | } 60 | 61 | public List Animations { get; } = new List(); 62 | 63 | public override object Clone() 64 | { 65 | return new AssSection(this); 66 | } 67 | 68 | protected override void Assign(Section section) 69 | { 70 | base.Assign(section); 71 | if (!(section is AssSection assSection)) 72 | return; 73 | 74 | SecondaryColor = assSection.SecondaryColor; 75 | Blur = assSection.Blur; 76 | CurrentWordForeColor = assSection.CurrentWordForeColor; 77 | CurrentWordOutlineColor = assSection.CurrentWordOutlineColor; 78 | CurrentWordShadowColor = assSection.CurrentWordShadowColor; 79 | Duration = assSection.Duration; 80 | Animations.Clear(); 81 | Animations.AddRange(assSection.Animations.Select(a => (Animation)a.Clone())); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssStyle.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass 4 | { 5 | public class AssStyle 6 | { 7 | internal AssStyle(AssDocumentItem item) 8 | { 9 | Name = item.GetString("Name"); 10 | Font = item.GetString("Fontname"); 11 | LineHeight = item.GetFloat("Fontsize"); 12 | Bold = item.GetBool("Bold"); 13 | Italic = item.GetBool("Italic"); 14 | Underline = item.GetBool("Underline"); 15 | PrimaryColor = item.GetColor("PrimaryColour"); 16 | SecondaryColor = item.GetColor("SecondaryColour"); 17 | OutlineColor = item.GetColor("OutlineColour"); 18 | OutlineThickness = item.GetFloat("Outline"); 19 | OutlineIsBox = item.GetInt("BorderStyle") == 3; 20 | ShadowColor = item.GetColor("BackColour"); 21 | ShadowDistance = item.GetFloat("Shadow"); 22 | AnchorPoint = AssDocument.GetAnchorPoint(item.GetInt("Alignment")); 23 | } 24 | 25 | public string Name 26 | { 27 | get; 28 | } 29 | 30 | public string Font 31 | { 32 | get; 33 | } 34 | 35 | public float LineHeight 36 | { 37 | get; 38 | set; 39 | } 40 | 41 | public bool Bold 42 | { 43 | get; 44 | } 45 | 46 | public bool Italic 47 | { 48 | get; 49 | } 50 | 51 | public bool Underline 52 | { 53 | get; 54 | } 55 | 56 | public Color PrimaryColor 57 | { 58 | get; 59 | } 60 | 61 | public Color SecondaryColor 62 | { 63 | get; 64 | } 65 | 66 | public Color OutlineColor 67 | { 68 | get; 69 | } 70 | 71 | public float OutlineThickness 72 | { 73 | get; 74 | } 75 | 76 | public bool OutlineIsBox 77 | { 78 | get; 79 | } 80 | 81 | public bool HasOutline => OutlineThickness > 0; 82 | 83 | public bool HasOutlineBox => HasOutline && OutlineIsBox; 84 | 85 | public Color ShadowColor 86 | { 87 | get; 88 | } 89 | 90 | public float ShadowDistance 91 | { 92 | get; 93 | } 94 | 95 | public bool HasShadow => ShadowDistance > 0; 96 | 97 | public AnchorPoint AnchorPoint 98 | { 99 | get; 100 | } 101 | 102 | public override string ToString() 103 | { 104 | return Name; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/AssTagContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass 4 | { 5 | internal class AssTagContext 6 | { 7 | public delegate List PostProcessor(); 8 | 9 | public AssDocument Document; 10 | public AssStyle InitialStyle; 11 | public AssStyleOptions InitialStyleOptions; 12 | public AssStyle Style; 13 | public AssStyleOptions StyleOptions; 14 | public AssLine Line; 15 | public AssSection Section; 16 | public readonly List PostProcessors = new List(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/KaraokeTypes/GlitchKaraokeType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using YTSubConverter.Shared.Animations; 5 | using YTSubConverter.Shared.Util; 6 | 7 | namespace YTSubConverter.Shared.Formats.Ass.KaraokeTypes 8 | { 9 | public class GlitchKaraokeType : SimpleKaraokeType 10 | { 11 | private static readonly CharacterRange[][] LanguageCharacterRanges = 12 | { 13 | new[] { new CharacterRange('A', 'Z'), new CharacterRange('a', 'z') }, 14 | new[] { CharacterRange.IdeographRange, CharacterRange.IdeographExtensionRange, CharacterRange.IdeographCompatibilityRange }, 15 | new[] { CharacterRange.HiraganaRange }, 16 | new[] { CharacterRange.KatakanaRange }, 17 | new[] { CharacterRange.HangulRange } 18 | }; 19 | 20 | private static readonly CharacterRange[] RandomCharacterRanges = 21 | { 22 | new CharacterRange('\x2300', '\x231A'), 23 | new CharacterRange('\x231C', '\x23E1') 24 | }; 25 | 26 | public override IEnumerable Apply(AssKaraokeStepContext context) 27 | { 28 | AssSection singingSection = context.SingingSections.LastOrDefault(s => s.RubyPart == RubyPart.None || s.RubyPart == RubyPart.Base); 29 | if (singingSection == null || singingSection.Text.Length == 0) 30 | return new List { context.StepLine }; 31 | 32 | base.Apply(context); 33 | DateTime glitchEndTime = TimeUtil.FrameToEndTime(TimeUtil.StartTimeToFrame(context.StepLine.Start) + 1); 34 | CharacterRange[] charRanges = GetGlitchKaraokeCharacterRanges(singingSection.Text[0]); 35 | singingSection.Animations.Add(new GlitchingCharAnimation(context.StepLine.Start, glitchEndTime, charRanges)); 36 | return new[] { context.StepLine }; 37 | } 38 | 39 | private static CharacterRange[] GetGlitchKaraokeCharacterRanges(char c) 40 | { 41 | foreach (CharacterRange[] ranges in LanguageCharacterRanges) 42 | { 43 | if (ranges.Any(r => r.Contains(c))) 44 | return ranges; 45 | } 46 | 47 | return RandomCharacterRanges; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/KaraokeTypes/IKaraokeType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass.KaraokeTypes 4 | { 5 | public interface IKaraokeType 6 | { 7 | IEnumerable Apply(AssKaraokeStepContext context); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/KaraokeTypes/SimpleKaraokeType.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using YTSubConverter.Shared.Animations; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ass.KaraokeTypes 6 | { 7 | public class SimpleKaraokeType : IKaraokeType 8 | { 9 | public static readonly SimpleKaraokeType Instance = new SimpleKaraokeType(); 10 | 11 | public virtual IEnumerable Apply(AssKaraokeStepContext context) 12 | { 13 | foreach (AssSection singingSection in context.SingingSections) 14 | { 15 | if (!singingSection.CurrentWordForeColor.IsEmpty) 16 | { 17 | singingSection.ForeColor = singingSection.CurrentWordForeColor; 18 | singingSection.Animations.RemoveAll(a => a is ForeColorAnimation); 19 | } 20 | 21 | if (!singingSection.CurrentWordShadowColor.IsEmpty) 22 | { 23 | foreach (ShadowType shadowType in singingSection.ShadowColors.Keys.ToList()) 24 | { 25 | singingSection.ShadowColors[shadowType] = singingSection.CurrentWordShadowColor; 26 | singingSection.Animations.RemoveAll(a => a is ShadowColorAnimation shadowAnim && shadowAnim.ShadowType == shadowType); 27 | } 28 | } 29 | 30 | if (!singingSection.CurrentWordOutlineColor.IsEmpty && singingSection.ShadowColors.ContainsKey(ShadowType.Glow)) 31 | { 32 | singingSection.ShadowColors[ShadowType.Glow] = singingSection.CurrentWordOutlineColor; 33 | singingSection.Animations.RemoveAll(a => a is ShadowColorAnimation shadowAnim && shadowAnim.ShadowType == ShadowType.Glow); 34 | } 35 | } 36 | 37 | return new[] { context.StepLine }; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssAlignmentTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssAlignmentTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "an"; 6 | 7 | public override bool AffectsWholeLine => true; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | int alignment = ParseInt(arg); 12 | if (alignment >= 1 && alignment <= 9) 13 | context.Line.AnchorPoint = AssDocument.GetAnchorPoint(alignment); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssAlphaTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using YTSubConverter.Shared.Animations; 3 | using YTSubConverter.Shared.Util; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ass.Tags 6 | { 7 | internal class AssAlphaTagHandler : AssTagHandlerBase 8 | { 9 | public override string Tag => "alpha"; 10 | 11 | public override bool AffectsWholeLine => false; 12 | 13 | public override void Handle(AssTagContext context, string arg) 14 | { 15 | int alpha = 255 - (ParseHex(arg) & 255); 16 | context.Section.ForeColor = ColorUtil.ChangeAlpha(context.Section.ForeColor, alpha); 17 | context.Section.SecondaryColor = ColorUtil.ChangeAlpha(context.Section.SecondaryColor, alpha); 18 | 19 | if (context.Style.HasOutlineBox) 20 | context.Section.BackColor = ColorUtil.ChangeAlpha(context.Section.BackColor, alpha); 21 | 22 | foreach (ShadowType shadowType in context.Section.ShadowColors.Keys.ToList()) 23 | { 24 | context.Section.ShadowColors[shadowType] = ColorUtil.ChangeAlpha(context.Section.ShadowColors[shadowType], alpha); 25 | } 26 | 27 | context.Section.Animations.RemoveAll(a => a is ColorAnimation); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssBoldTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssBoldTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "b"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | if (string.IsNullOrEmpty(arg)) 12 | { 13 | context.Section.Bold = context.InitialStyle.Bold; 14 | return; 15 | } 16 | 17 | if (!TryParseInt(arg, out int value)) 18 | { 19 | context.Section.Bold = false; 20 | return; 21 | } 22 | 23 | if (value == 0) 24 | context.Section.Bold = false; 25 | else if (value == 1) 26 | context.Section.Bold = true; 27 | else 28 | context.Section.Bold = context.InitialStyle.Bold; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssComplexFadeTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using YTSubConverter.Shared.Animations; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ass.Tags 6 | { 7 | internal class AssComplexFadeTagHandler : AssTagHandlerBase 8 | { 9 | public override string Tag => "fade"; 10 | 11 | public override bool AffectsWholeLine => true; 12 | 13 | public override void Handle(AssTagContext context, string arg) 14 | { 15 | List args = ParseFloatList(arg); 16 | if (args == null || args.Count != 7) 17 | return; 18 | 19 | AssLine line = context.Line; 20 | 21 | int initialAlpha = 255 - (int)args[0]; 22 | int midAlpha = 255 - (int)args[1]; 23 | int finalAlpha = 255 - (int)args[2]; 24 | 25 | DateTime fadeInStartTime = line.Start.AddMilliseconds(args[3]); 26 | DateTime fadeInEndTime = line.Start.AddMilliseconds(args[4]); 27 | DateTime fadeOutStartTime = line.Start.AddMilliseconds(args[5]); 28 | DateTime fadeOutEndTime = line.Start.AddMilliseconds(args[6]); 29 | 30 | if (fadeInEndTime > fadeInStartTime) 31 | line.Animations.Add(new FadeAnimation(fadeInStartTime, initialAlpha, fadeInEndTime, midAlpha)); 32 | 33 | if (fadeOutEndTime > fadeOutStartTime) 34 | line.Animations.Add(new FadeAnimation(fadeOutStartTime, midAlpha, fadeOutEndTime, finalAlpha)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssFontSizeTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssFontSizeTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "fs"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | if (!TryParseFloat(arg, out float lineHeight)) 12 | lineHeight = context.Style.LineHeight; 13 | 14 | context.Section.Scale = lineHeight / context.Document.DefaultStyle.LineHeight; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssFontTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssFontTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "fn"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | context.Section.Font = !string.IsNullOrEmpty(arg) ? arg : context.Style.Font; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssForeAlphaTagHandler.cs: -------------------------------------------------------------------------------- 1 | using YTSubConverter.Shared.Animations; 2 | using YTSubConverter.Shared.Util; 3 | 4 | namespace YTSubConverter.Shared.Formats.Ass.Tags 5 | { 6 | internal class AssForeAlphaTagHandler : AssTagHandlerBase 7 | { 8 | public override string Tag => "1a"; 9 | 10 | public override bool AffectsWholeLine => false; 11 | 12 | public override void Handle(AssTagContext context, string arg) 13 | { 14 | int alpha = !string.IsNullOrEmpty(arg) ? 255 - (ParseHex(arg) & 255) : context.Style.PrimaryColor.A; 15 | context.Section.ForeColor = ColorUtil.ChangeAlpha(context.Section.ForeColor, alpha); 16 | context.Section.Animations.RemoveAll(a => a is ForeColorAnimation); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssForeColorTagHandler.cs: -------------------------------------------------------------------------------- 1 | using YTSubConverter.Shared.Animations; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass.Tags 4 | { 5 | internal class AssForeColorTagHandler : AssTagHandlerBase 6 | { 7 | public AssForeColorTagHandler(string tag) 8 | { 9 | Tag = tag; 10 | } 11 | 12 | public override string Tag 13 | { 14 | get; 15 | } 16 | 17 | public override bool AffectsWholeLine => false; 18 | 19 | public override void Handle(AssTagContext context, string arg) 20 | { 21 | context.Section.ForeColor = !string.IsNullOrEmpty(arg) ? ParseColor(arg, context.Section.ForeColor.A) : context.Style.PrimaryColor; 22 | context.Section.Animations.RemoveAll(a => a is ForeColorAnimation); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssHorizontalTextDirectionTag.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssHorizontalTextDirectionTag : AssTagHandlerBase 4 | { 5 | public override string Tag => "ytdir"; 6 | 7 | public override bool AffectsWholeLine => true; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | if (!int.TryParse(arg, out int direction)) 12 | direction = 6; 13 | 14 | context.Line.HorizontalTextDirection = GetHorizontalTextDirection(direction); 15 | } 16 | 17 | public static HorizontalTextDirection GetHorizontalTextDirection(int directionId) 18 | { 19 | return directionId switch 20 | { 21 | 4 => HorizontalTextDirection.RightToLeft, 22 | _ => HorizontalTextDirection.LeftToRight 23 | }; 24 | } 25 | 26 | public static int GetHorizontalTextDirectionId(HorizontalTextDirection direction) 27 | { 28 | return direction switch 29 | { 30 | HorizontalTextDirection.RightToLeft => 4, 31 | _ => 6 32 | }; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssItalicTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssItalicTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "i"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | if (string.IsNullOrEmpty(arg)) 12 | { 13 | context.Section.Italic = context.InitialStyle.Italic; 14 | return; 15 | } 16 | 17 | if (!TryParseInt(arg, out int value)) 18 | { 19 | context.Section.Italic = false; 20 | return; 21 | } 22 | 23 | if (value == 0) 24 | context.Section.Italic = false; 25 | else if (value == 1) 26 | context.Section.Italic = true; 27 | else 28 | context.Section.Italic = context.InitialStyle.Italic; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssKaraokeTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass.Tags 4 | { 5 | internal class AssKaraokeTagHandler : AssTagHandlerBase 6 | { 7 | public override string Tag => "k"; 8 | 9 | public override bool AffectsWholeLine => false; 10 | 11 | public override void Handle(AssTagContext context, string arg) 12 | { 13 | int.TryParse(arg, out int centiSeconds); 14 | context.Section.Duration = TimeSpan.FromMilliseconds(centiSeconds * 10); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssMoveTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using YTSubConverter.Shared.Animations; 5 | 6 | namespace YTSubConverter.Shared.Formats.Ass.Tags 7 | { 8 | internal class AssMoveTagHandler : AssTagHandlerBase 9 | { 10 | public override string Tag => "move"; 11 | 12 | public override bool AffectsWholeLine => true; 13 | 14 | public override void Handle(AssTagContext context, string arg) 15 | { 16 | List args = ParseFloatList(arg); 17 | if (args == null || args.Count < 4) 18 | return; 19 | 20 | AssLine line = context.Line; 21 | PointF startPos = new PointF(args[0], args[1]); 22 | PointF endPos = new PointF(args[2], args[3]); 23 | 24 | DateTime startTime = line.Start; 25 | DateTime endTime = line.End; 26 | if (args.Count >= 6) 27 | { 28 | startTime = line.Start.AddMilliseconds(args[4]); 29 | endTime = line.Start.AddMilliseconds(args[5]); 30 | } 31 | 32 | if (endTime > startTime) 33 | line.Animations.Add(new MoveAnimation(startTime, startPos, endTime, endPos)); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssOutlineAlphaTagHandler.cs: -------------------------------------------------------------------------------- 1 | using YTSubConverter.Shared.Animations; 2 | using YTSubConverter.Shared.Util; 3 | 4 | namespace YTSubConverter.Shared.Formats.Ass.Tags 5 | { 6 | internal class AssOutlineAlphaTagHandler : AssTagHandlerBase 7 | { 8 | public override string Tag => "3a"; 9 | 10 | public override bool AffectsWholeLine => false; 11 | 12 | public override void Handle(AssTagContext context, string arg) 13 | { 14 | if (!context.Style.HasOutline) 15 | return; 16 | 17 | int alpha = !string.IsNullOrEmpty(arg) ? 255 - (ParseHex(arg) & 255) : context.Style.OutlineColor.A; 18 | 19 | if (context.Style.OutlineIsBox) 20 | { 21 | context.Section.BackColor = ColorUtil.ChangeAlpha(context.Section.BackColor, alpha); 22 | context.Section.Animations.RemoveAll(a => a is BackColorAnimation); 23 | } 24 | else 25 | { 26 | context.Section.ShadowColors[ShadowType.Glow] = ColorUtil.ChangeAlpha(context.Section.ShadowColors[ShadowType.Glow], alpha); 27 | context.Section.Animations.RemoveAll(a => a is ShadowColorAnimation); 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssOutlineColorTagHandler.cs: -------------------------------------------------------------------------------- 1 | using YTSubConverter.Shared.Animations; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass.Tags 4 | { 5 | internal class AssOutlineColorTagHandler : AssTagHandlerBase 6 | { 7 | public override string Tag => "3c"; 8 | 9 | public override bool AffectsWholeLine => false; 10 | 11 | public override void Handle(AssTagContext context, string arg) 12 | { 13 | if (!context.Style.HasOutline) 14 | return; 15 | 16 | if (context.Style.OutlineIsBox) 17 | { 18 | context.Section.BackColor = !string.IsNullOrEmpty(arg) ? ParseColor(arg, context.Section.BackColor.A) : context.Style.OutlineColor; 19 | context.Section.Animations.RemoveAll(a => a is BackColorAnimation); 20 | } 21 | else 22 | { 23 | context.Section.ShadowColors[ShadowType.Glow] = !string.IsNullOrEmpty(arg) ? ParseColor(arg, context.Section.ShadowColors[ShadowType.Glow].A) : context.Style.OutlineColor; 24 | context.Section.Animations.RemoveAll(a => a is ShadowColorAnimation); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssPackTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssPackTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "ytpack"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | context.Section.Packed = arg != "0"; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssPositionTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | 4 | namespace YTSubConverter.Shared.Formats.Ass.Tags 5 | { 6 | internal class AssPositionTagHandler : AssTagHandlerBase 7 | { 8 | public override string Tag => "pos"; 9 | 10 | public override bool AffectsWholeLine => true; 11 | 12 | public override void Handle(AssTagContext context, string arg) 13 | { 14 | List coords = ParseFloatList(arg); 15 | if (coords == null || coords.Count != 2 || context.Line.Position != null) 16 | return; 17 | 18 | context.Line.Position = new PointF(coords[0], coords[1]); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssRegularScriptTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssRegularScriptTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "ytsur"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | context.Section.Offset = OffsetType.Regular; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssResetTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssResetTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "r"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | context.Style = context.Document.GetStyle(arg) ?? context.InitialStyle; 12 | context.StyleOptions = context.Document.GetStyleOptions(arg) ?? context.InitialStyleOptions; 13 | context.Document.ApplyStyle(context.Section, context.Style, context.StyleOptions); 14 | context.Section.Offset = OffsetType.Regular; 15 | context.Section.RubyPart = RubyPart.None; 16 | context.Section.Animations.Clear(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssRubyTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssRubyTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "ytruby"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | int.TryParse(arg, out int rubyPos); 12 | context.Section.RubyPart = rubyPos == 2 ? RubyPart.TextAfter : RubyPart.TextBefore; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssSecondaryAlphaTagHandler.cs: -------------------------------------------------------------------------------- 1 | using YTSubConverter.Shared.Animations; 2 | using YTSubConverter.Shared.Util; 3 | 4 | namespace YTSubConverter.Shared.Formats.Ass.Tags 5 | { 6 | internal class AssSecondaryAlphaTagHandler : AssTagHandlerBase 7 | { 8 | public override string Tag => "2a"; 9 | 10 | public override bool AffectsWholeLine => false; 11 | 12 | public override void Handle(AssTagContext context, string arg) 13 | { 14 | int alpha = !string.IsNullOrEmpty(arg) ? 255 - (ParseHex(arg) & 255) : context.Style.SecondaryColor.A; 15 | context.Section.SecondaryColor = ColorUtil.ChangeAlpha(context.Section.SecondaryColor, alpha); 16 | context.Section.Animations.RemoveAll(a => a is SecondaryColorAnimation); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssSecondaryColorTagHandler.cs: -------------------------------------------------------------------------------- 1 | using YTSubConverter.Shared.Animations; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ass.Tags 4 | { 5 | internal class AssSecondaryColorTagHandler : AssTagHandlerBase 6 | { 7 | public override string Tag => "2c"; 8 | 9 | public override bool AffectsWholeLine => false; 10 | 11 | public override void Handle(AssTagContext context, string arg) 12 | { 13 | context.Section.SecondaryColor = !string.IsNullOrEmpty(arg) ? ParseColor(arg, context.Section.SecondaryColor.A) : context.Style.SecondaryColor; 14 | context.Section.Animations.RemoveAll(a => a is SecondaryColorAnimation); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssShadowAlphaTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | using System.Linq; 4 | using YTSubConverter.Shared.Animations; 5 | using YTSubConverter.Shared.Util; 6 | 7 | namespace YTSubConverter.Shared.Formats.Ass.Tags 8 | { 9 | internal class AssShadowAlphaTagHandler : AssTagHandlerBase 10 | { 11 | public override string Tag => "4a"; 12 | 13 | public override bool AffectsWholeLine => false; 14 | 15 | public override void Handle(AssTagContext context, string arg) 16 | { 17 | if (!context.Style.HasShadow) 18 | return; 19 | 20 | int alpha = !string.IsNullOrEmpty(arg) ? 255 - (ParseHex(arg) & 255) : context.Style.ShadowColor.A; 21 | foreach (KeyValuePair shadowColor in context.Section.ShadowColors.ToList()) 22 | { 23 | if (shadowColor.Key != ShadowType.Glow || !context.Style.HasOutline || context.Style.HasOutlineBox) 24 | context.Section.ShadowColors[shadowColor.Key] = ColorUtil.ChangeAlpha(shadowColor.Value, alpha); 25 | } 26 | context.Section.Animations.RemoveAll(a => a is ShadowColorAnimation); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssShadowColorTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | using System.Linq; 4 | using YTSubConverter.Shared.Animations; 5 | 6 | namespace YTSubConverter.Shared.Formats.Ass.Tags 7 | { 8 | internal class AssShadowColorTagHandler : AssTagHandlerBase 9 | { 10 | public override string Tag => "4c"; 11 | 12 | public override bool AffectsWholeLine => false; 13 | 14 | public override void Handle(AssTagContext context, string arg) 15 | { 16 | if (!context.Style.HasShadow) 17 | return; 18 | 19 | foreach (KeyValuePair shadowColor in context.Section.ShadowColors.ToList()) 20 | { 21 | if (shadowColor.Key != ShadowType.Glow || !context.Style.HasOutline || context.Style.HasOutlineBox) 22 | context.Section.ShadowColors[shadowColor.Key] = !string.IsNullOrEmpty(arg) ? ParseColor(arg, shadowColor.Value.A) : context.Style.ShadowColor; 23 | } 24 | 25 | context.Section.Animations.RemoveAll(a => a is ShadowColorAnimation); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssShakeTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using YTSubConverter.Shared.Animations; 5 | 6 | namespace YTSubConverter.Shared.Formats.Ass.Tags 7 | { 8 | /// 9 | /// Nonstandard tag: \ytshake, \ytshake(radius), \ytshake(radiusX, radiusY), \ytshake(radius, t1, t2), \ytshake(radiusX, radiusY, t1, t2) 10 | /// 11 | internal class AssShakeTagHandler : AssTagHandlerBase 12 | { 13 | public override string Tag => "ytshake"; 14 | 15 | public override bool AffectsWholeLine => true; 16 | 17 | public override void Handle(AssTagContext context, string arg) 18 | { 19 | if (!TryParseArgs(context, arg, out SizeF radius, out DateTime startTime, out DateTime endTime)) 20 | return; 21 | 22 | context.PostProcessors.Add( 23 | () => 24 | { 25 | context.Line.Animations.Add(new ShakeAnimation(startTime, endTime, radius)); 26 | return null; 27 | } 28 | ); 29 | } 30 | 31 | private static bool TryParseArgs(AssTagContext context, string arg, out SizeF radius, out DateTime startTime, out DateTime endTime) 32 | { 33 | int defaultRadius = 20; 34 | radius = new SizeF(defaultRadius, defaultRadius); 35 | startTime = context.Line.Start; 36 | endTime = context.Line.End; 37 | 38 | if (string.IsNullOrWhiteSpace(arg)) 39 | return true; 40 | 41 | List args = ParseFloatList(arg); 42 | if (args == null) 43 | return false; 44 | 45 | switch (args.Count) 46 | { 47 | case 0: 48 | return true; 49 | 50 | case 1: 51 | radius = new SizeF(args[0], args[0]); 52 | return true; 53 | 54 | case 2: 55 | radius = new SizeF(args[0], args[1]); 56 | return true; 57 | 58 | case 3: 59 | radius = new SizeF(args[0], args[0]); 60 | startTime = context.Line.Start.AddMilliseconds(args[1]); 61 | endTime = context.Line.Start.AddMilliseconds(args[2]); 62 | return true; 63 | 64 | case 4: 65 | radius = new SizeF(args[0], args[1]); 66 | startTime = context.Line.Start.AddMilliseconds(args[2]); 67 | endTime = context.Line.Start.AddMilliseconds(args[3]); 68 | return true; 69 | 70 | default: 71 | return false; 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssSimpleFadeTagHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using YTSubConverter.Shared.Animations; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ass.Tags 6 | { 7 | internal class AssSimpleFadeTagHandler : AssTagHandlerBase 8 | { 9 | public override string Tag => "fad"; 10 | 11 | public override bool AffectsWholeLine => true; 12 | 13 | public override void Handle(AssTagContext context, string arg) 14 | { 15 | List times = ParseFloatList(arg); 16 | if (times == null || times.Count != 2) 17 | return; 18 | 19 | AssLine line = context.Line; 20 | DateTime fadeInStartTime = line.Start; 21 | DateTime fadeInEndTime = line.Start.AddMilliseconds(times[0]); 22 | DateTime fadeOutStartTime = line.End.AddMilliseconds(-times[1]); 23 | DateTime fadeOutEndTime = line.End; 24 | 25 | if (fadeInEndTime > fadeInStartTime) 26 | line.Animations.Add(new FadeAnimation(fadeInStartTime, 0, fadeInEndTime, 255)); 27 | 28 | if (fadeOutEndTime > fadeOutStartTime) 29 | line.Animations.Add(new FadeAnimation(fadeOutStartTime, 255, fadeOutEndTime, 0)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssSubscriptTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssSubscriptTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "ytsub"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | context.Section.Offset = OffsetType.Subscript; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssSuperscriptTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssSuperscriptTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "ytsup"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | context.Section.Offset = OffsetType.Superscript; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssUnderlineTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssUnderlineTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "u"; 6 | 7 | public override bool AffectsWholeLine => false; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | if (string.IsNullOrEmpty(arg)) 12 | { 13 | context.Section.Underline = context.InitialStyle.Underline; 14 | return; 15 | } 16 | 17 | if (!TryParseInt(arg, out int value)) 18 | { 19 | context.Section.Underline = false; 20 | return; 21 | } 22 | 23 | if (value == 0) 24 | context.Section.Underline = false; 25 | else if (value == 1) 26 | context.Section.Underline = true; 27 | else 28 | context.Section.Underline = context.InitialStyle.Underline; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ass/Tags/AssVerticalTypeTagHandler.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Formats.Ass.Tags 2 | { 3 | internal class AssVerticalTypeTagHandler : AssTagHandlerBase 4 | { 5 | public override string Tag => "ytvert"; 6 | 7 | public override bool AffectsWholeLine => true; 8 | 9 | public override void Handle(AssTagContext context, string arg) 10 | { 11 | if (!int.TryParse(arg, out int vertType)) 12 | vertType = 9; 13 | 14 | (context.Line.HorizontalTextDirection, context.Line.VerticalTextType) = GetVerticalTextType(vertType); 15 | } 16 | 17 | public static (HorizontalTextDirection, VerticalTextType) GetVerticalTextType(int id) 18 | { 19 | return id switch 20 | { 21 | 9 => (HorizontalTextDirection.RightToLeft, VerticalTextType.Positioned), 22 | 7 => (HorizontalTextDirection.LeftToRight, VerticalTextType.Positioned), 23 | 1 => (HorizontalTextDirection.LeftToRight, VerticalTextType.Rotated), 24 | 3 => (HorizontalTextDirection.RightToLeft, VerticalTextType.Rotated), 25 | _ => (HorizontalTextDirection.LeftToRight, VerticalTextType.None) 26 | }; 27 | } 28 | 29 | public static int GetVerticalTextTypeId(HorizontalTextDirection hor, VerticalTextType ver) 30 | { 31 | if (hor == HorizontalTextDirection.LeftToRight) 32 | { 33 | return ver switch 34 | { 35 | VerticalTextType.Positioned => 7, 36 | VerticalTextType.Rotated => 1, 37 | _ => 0 38 | }; 39 | } 40 | else 41 | { 42 | return ver switch 43 | { 44 | VerticalTextType.Positioned => 9, 45 | VerticalTextType.Rotated => 3, 46 | _ => 0 47 | }; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/Enumerations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ttml 4 | { 5 | public enum TtmlDisplayMode 6 | { 7 | Auto, 8 | None, 9 | InlineBlock 10 | } 11 | 12 | public enum TtmlVisibility 13 | { 14 | Visible, 15 | Hidden 16 | } 17 | 18 | public enum TtmlFontWeight 19 | { 20 | Normal, 21 | Bold 22 | } 23 | 24 | public enum TtmlFontStyle 25 | { 26 | Normal, 27 | Italic, 28 | Oblique 29 | } 30 | 31 | [Flags] 32 | public enum TtmlFontVariant 33 | { 34 | Normal = 0, 35 | Super = 1, 36 | Sub = 2, 37 | Full = 4, 38 | Half = 8, 39 | Ruby = 16 40 | } 41 | 42 | [Flags] 43 | public enum TtmlTextDecoration 44 | { 45 | None = 0, 46 | Underline = 1, 47 | LineThrough = 2, 48 | Overline = 4 49 | } 50 | 51 | public enum TtmlTextAlign 52 | { 53 | Left, 54 | Center, 55 | Right, 56 | Start, 57 | End, 58 | Justify 59 | } 60 | 61 | public enum TtmlDisplayAlign 62 | { 63 | Before, 64 | Center, 65 | After, 66 | Justify 67 | } 68 | 69 | public enum TtmlTextCombine 70 | { 71 | None, 72 | All 73 | } 74 | 75 | public enum TtmlDirection 76 | { 77 | Ltr, 78 | Rtl 79 | } 80 | 81 | public enum TtmlWritingMode 82 | { 83 | Lrtb, 84 | Rltb, 85 | Tbrl, 86 | Tblr, 87 | Lr, 88 | Rl, 89 | Tb 90 | } 91 | 92 | public enum TtmlTextOrientation 93 | { 94 | Mixed, 95 | Sideways, 96 | Upright 97 | } 98 | 99 | public enum TtmlRubyMode 100 | { 101 | None, 102 | Container, 103 | Base, 104 | BaseContainer, 105 | Text, 106 | TextContainer, 107 | Delimiter 108 | } 109 | 110 | public enum TtmlRubyPosition 111 | { 112 | Before, 113 | After, 114 | Outside 115 | } 116 | 117 | public enum TtmlPosHBase 118 | { 119 | Left, 120 | Center, 121 | Right 122 | } 123 | 124 | public enum TtmlPosVBase 125 | { 126 | Top, 127 | Center, 128 | Bottom 129 | } 130 | 131 | public enum TtmlUnit 132 | { 133 | Pixels, 134 | Percent, 135 | Em, 136 | Cell, 137 | RootWidth, 138 | RootHeight 139 | } 140 | 141 | public enum TtmlTimeContainer 142 | { 143 | Par, 144 | Seq 145 | } 146 | 147 | public enum TtmlProgression 148 | { 149 | Inline, 150 | Block 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlBody.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ttml 4 | { 5 | public class TtmlBody : TtmlContent 6 | { 7 | public TtmlBody(XmlElement elem, XmlNamespaceManager nsmgr, TtmlDocument doc) 8 | : base(elem, nsmgr, doc, null) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlContent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Xml; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ttml 6 | { 7 | public abstract class TtmlContent 8 | { 9 | protected TtmlContent(XmlElement elem, XmlNamespaceManager nsmgr, TtmlDocument doc, TtmlContent parent) 10 | { 11 | TimeRange = TtmlTimeRange.Get(elem, doc); 12 | Style = TtmlStyle.CreateAggregateStyle(elem, nsmgr, false, doc); 13 | Region = TtmlRegion.Get(elem, nsmgr, doc); 14 | 15 | Parent = parent; 16 | Children = elem.ChildNodes 17 | .Cast() 18 | .Select(n => Create(n, nsmgr, doc, this)) 19 | .Where(c => c != null) 20 | .ToList(); 21 | } 22 | 23 | protected TtmlContent(string text, TtmlContent parent) 24 | { 25 | Parent = parent; 26 | Children = new List(); 27 | Text = text; 28 | } 29 | 30 | public static TtmlContent Create(XmlNode node, XmlNamespaceManager nsmgr, TtmlDocument doc, TtmlContent parent) 31 | { 32 | switch (node) 33 | { 34 | case XmlElement elem: 35 | return elem.LocalName switch 36 | { 37 | "body" => new TtmlBody(elem, nsmgr, doc), 38 | "div" => new TtmlDiv(elem, nsmgr, doc, parent), 39 | "p" => new TtmlParagraph(elem, nsmgr, doc, parent), 40 | "span" => new TtmlSpan(elem, nsmgr, doc, parent), 41 | "br" => new TtmlSpan("\r\n", parent), 42 | _ => null 43 | }; 44 | 45 | case XmlText _: 46 | case XmlWhitespace _ when parent is TtmlParagraph || parent is TtmlSpan: 47 | return new TtmlSpan(node.Value.Replace("\r\n", " "), parent); 48 | 49 | default: 50 | return null; 51 | } 52 | } 53 | 54 | public TtmlTimeRange TimeRange 55 | { 56 | get; 57 | } 58 | 59 | public TtmlStyle Style 60 | { 61 | get; 62 | } 63 | 64 | public TtmlRegion Region 65 | { 66 | get; 67 | } 68 | 69 | public TtmlContent Parent 70 | { 71 | get; 72 | } 73 | 74 | public List Children 75 | { 76 | get; 77 | } 78 | 79 | public string Text 80 | { 81 | get; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlDiv.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ttml 4 | { 5 | public class TtmlDiv : TtmlContent 6 | { 7 | public TtmlDiv(XmlElement elem, XmlNamespaceManager nsmgr, TtmlDocument doc, TtmlContent parent) 8 | : base(elem, nsmgr, doc, parent) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlMultipartAttributeReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ttml 6 | { 7 | internal class TtmlMultipartAttributeReader 8 | { 9 | private readonly string[] _parts; 10 | private int _index; 11 | 12 | public TtmlMultipartAttributeReader(string text) 13 | { 14 | _parts = Regex.Matches(text, @"(?:\(.+?\)|\S)+") 15 | .Cast() 16 | .Select(m => m.Value) 17 | .ToArray(); 18 | _index = 0; 19 | } 20 | 21 | public int Count => _parts.Length; 22 | 23 | public delegate bool Parser(string part, out T value); 24 | 25 | public bool TryRead(Parser tryParse, out T value) 26 | { 27 | if (IsAtEnd) 28 | { 29 | value = default; 30 | return false; 31 | } 32 | 33 | if (!tryParse(_parts[_index], out value)) 34 | return false; 35 | 36 | _index++; 37 | return true; 38 | } 39 | 40 | public bool TryReadEnum(out T value) 41 | where T : struct 42 | { 43 | return TryRead(TryParseEnum, out value); 44 | } 45 | 46 | private static bool TryParseEnum(string part, out T value) 47 | where T : struct 48 | { 49 | return Enum.TryParse(part, true, out value); 50 | } 51 | 52 | public void Reset() 53 | { 54 | _index = 0; 55 | } 56 | 57 | public bool IsAtEnd => _index == _parts.Length; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlOutline.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Text; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ttml 6 | { 7 | public class TtmlOutline 8 | { 9 | public TtmlOutline() 10 | { 11 | } 12 | 13 | public TtmlOutline(Color color, TtmlLength thickness, TtmlLength blurRadius) 14 | { 15 | Color = color; 16 | Thickness = thickness; 17 | BlurRadius = blurRadius; 18 | } 19 | 20 | public Color Color 21 | { 22 | get; 23 | set; 24 | } 25 | 26 | public TtmlLength Thickness 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | public TtmlLength BlurRadius 33 | { 34 | get; 35 | set; 36 | } 37 | 38 | public static bool TryParse(string text, out TtmlOutline outline) 39 | { 40 | if (string.IsNullOrEmpty(text)) 41 | { 42 | outline = new TtmlOutline(); 43 | return false; 44 | } 45 | 46 | if (text == "none") 47 | { 48 | outline = new TtmlOutline(); 49 | return true; 50 | } 51 | 52 | outline = null; 53 | 54 | TtmlMultipartAttributeReader reader = new TtmlMultipartAttributeReader(text); 55 | 56 | reader.TryRead(TtmlColor.TryParse, out Color color); 57 | 58 | if (!reader.TryRead(TtmlLength.TryParse, out TtmlLength thickness)) 59 | return false; 60 | 61 | reader.TryRead(TtmlLength.TryParse, out TtmlLength blurRadius); 62 | 63 | if (!reader.IsAtEnd) 64 | return false; 65 | 66 | outline = new TtmlOutline(color, thickness, blurRadius); 67 | return true; 68 | } 69 | 70 | public static TtmlOutline Parse(string text) 71 | { 72 | if (text == null) 73 | throw new ArgumentNullException(nameof(text)); 74 | 75 | if (!TryParse(text, out TtmlOutline outline)) 76 | throw new FormatException(); 77 | 78 | return outline; 79 | } 80 | 81 | public override string ToString() 82 | { 83 | StringBuilder result = new StringBuilder(); 84 | 85 | if (!Color.IsEmpty) 86 | { 87 | result.Append(TtmlColor.ToString(Color)); 88 | result.Append(" "); 89 | } 90 | 91 | result.Append(Thickness); 92 | 93 | if (BlurRadius.Value > 0) 94 | { 95 | result.Append(" "); 96 | result.Append(BlurRadius); 97 | } 98 | 99 | return result.ToString(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlParagraph.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ttml 4 | { 5 | public class TtmlParagraph : TtmlContent 6 | { 7 | public TtmlParagraph(XmlElement elem, XmlNamespaceManager nsmgr, TtmlDocument doc, TtmlContent parent) 8 | : base(elem, nsmgr, doc, parent) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlRegion.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | using YTSubConverter.Shared.Util; 3 | 4 | namespace YTSubConverter.Shared.Formats.Ttml 5 | { 6 | public class TtmlRegion 7 | { 8 | public TtmlRegion(XmlElement elem, XmlNamespaceManager nsmgr, TtmlDocument doc) 9 | { 10 | string xml = nsmgr.LookupNamespace("xml"); 11 | 12 | Id = elem.GetAttributeNode("id", xml)?.Value; 13 | TimeRange = TtmlTimeRange.Get(elem, doc); 14 | Style = TtmlStyle.CreateAggregateStyle(elem, nsmgr, true, doc); 15 | } 16 | 17 | public string Id 18 | { 19 | get; 20 | } 21 | 22 | public TtmlTimeRange TimeRange 23 | { 24 | get; 25 | } 26 | 27 | public TtmlStyle Style 28 | { 29 | get; 30 | } 31 | 32 | public static TtmlRegion Get(XmlElement elem, XmlNamespaceManager nsmgr, TtmlDocument doc) 33 | { 34 | // Child element 35 | XmlElement regionElem = (XmlElement)elem.SelectSingleNode("tt:region", nsmgr); 36 | if (regionElem != null) 37 | return new TtmlRegion(regionElem, nsmgr, doc); 38 | 39 | // region="" attribute 40 | string regionId = elem.GetAttribute("region"); 41 | if (!string.IsNullOrEmpty(regionId)) 42 | { 43 | TtmlRegion referencedRegion = doc.Regions.GetOrDefault(regionId); 44 | if (referencedRegion != null) 45 | return referencedRegion; 46 | } 47 | 48 | return null; 49 | } 50 | 51 | public override string ToString() 52 | { 53 | return Id; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlResolutionContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ttml 4 | { 5 | internal class TtmlResolutionContext 6 | { 7 | public TtmlResolutionContext(TtmlDocument doc) 8 | { 9 | Document = doc; 10 | } 11 | 12 | public TtmlDocument Document 13 | { 14 | get; 15 | } 16 | 17 | public DateTime BeginTime 18 | { 19 | get; 20 | set; 21 | } 22 | 23 | public DateTime EndTime 24 | { 25 | get; 26 | set; 27 | } 28 | 29 | public TtmlStyle StylingStyle 30 | { 31 | get; 32 | set; 33 | } 34 | 35 | public TtmlStyle RegionStyle 36 | { 37 | get; 38 | set; 39 | } 40 | 41 | public TtmlStyle Style 42 | { 43 | get; 44 | set; 45 | } 46 | 47 | public static TtmlResolutionContext CreateInitialContext(TtmlDocument doc) 48 | { 49 | return new TtmlResolutionContext(doc) 50 | { 51 | BeginTime = SubtitleDocument.TimeBase, 52 | EndTime = DateTime.MaxValue, 53 | RegionStyle = doc.InitialStyle, 54 | StylingStyle = doc.InitialStyle, 55 | Style = doc.InitialStyle 56 | }; 57 | } 58 | 59 | public static TtmlResolutionContext Extend(TtmlResolutionContext parentContext, TtmlResolutionContext prevSiblingContext, TtmlContent content) 60 | { 61 | TtmlResolutionContext context = parentContext.Clone(); 62 | 63 | if (content.TimeRange != null) 64 | (context.BeginTime, context.EndTime) = content.TimeRange.Resolve(parentContext, prevSiblingContext); 65 | 66 | if (content.Region?.Style != null) 67 | context.RegionStyle = content.Region.Style.CloneUsingNewInitialStyle(parentContext.RegionStyle); 68 | 69 | if (content.Style != null) 70 | context.StylingStyle = content.Style.CloneUsingNewInitialStyle(parentContext.StylingStyle); 71 | 72 | context.Style = context.StylingStyle.CloneUsingNewInitialStyle(context.RegionStyle); 73 | 74 | return context; 75 | } 76 | 77 | public TtmlResolutionContext Clone() 78 | { 79 | return new TtmlResolutionContext(Document) 80 | { 81 | BeginTime = BeginTime, 82 | EndTime = EndTime, 83 | StylingStyle = StylingStyle, 84 | RegionStyle = RegionStyle, 85 | Style = Style 86 | }; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlShadow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Text; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ttml 6 | { 7 | public class TtmlShadow 8 | { 9 | public TtmlShadow() 10 | { 11 | } 12 | 13 | public TtmlShadow(TtmlSize offset, TtmlLength blurRadius, Color color) 14 | { 15 | Offset = offset; 16 | BlurRadius = blurRadius; 17 | Color = color; 18 | } 19 | 20 | public TtmlSize Offset 21 | { 22 | get; 23 | set; 24 | } 25 | 26 | public TtmlLength BlurRadius 27 | { 28 | get; 29 | set; 30 | } 31 | 32 | public Color Color 33 | { 34 | get; 35 | set; 36 | } 37 | 38 | public static bool TryParse(string text, out TtmlShadow shadow) 39 | { 40 | shadow = null; 41 | 42 | if (string.IsNullOrEmpty(text)) 43 | return false; 44 | 45 | TtmlMultipartAttributeReader reader = new TtmlMultipartAttributeReader(text); 46 | if (!reader.TryRead(TtmlLength.TryParse, out TtmlLength xOffset)) 47 | return false; 48 | 49 | if (!reader.TryRead(TtmlLength.TryParse, out TtmlLength yOffset)) 50 | return false; 51 | 52 | reader.TryRead(TtmlLength.TryParse, out TtmlLength blurRadius); 53 | reader.TryRead(TtmlColor.TryParse, out Color color); 54 | 55 | if (!reader.IsAtEnd) 56 | return false; 57 | 58 | shadow = new TtmlShadow(new TtmlSize(xOffset, yOffset), blurRadius, color); 59 | return true; 60 | } 61 | 62 | public static TtmlShadow Parse(string text) 63 | { 64 | if (text == null) 65 | throw new ArgumentNullException(nameof(text)); 66 | 67 | if (!TryParse(text, out TtmlShadow shadow)) 68 | throw new FormatException(); 69 | 70 | return shadow; 71 | } 72 | 73 | public override string ToString() 74 | { 75 | StringBuilder result = new StringBuilder(); 76 | result.Append(Offset); 77 | 78 | if (BlurRadius.Value > 0) 79 | { 80 | result.Append(" "); 81 | result.Append(BlurRadius); 82 | } 83 | 84 | if (!Color.IsEmpty) 85 | { 86 | result.Append(" "); 87 | result.Append(TtmlColor.ToString(Color)); 88 | } 89 | 90 | return result.ToString(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlSize.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace YTSubConverter.Shared.Formats.Ttml 5 | { 6 | public struct TtmlSize 7 | { 8 | public TtmlSize(TtmlLength width, TtmlLength height) 9 | { 10 | Width = width; 11 | Height = height; 12 | } 13 | 14 | public TtmlSize(float widthValue, TtmlUnit widthUnit, float heightValue, TtmlUnit heightUnit) 15 | { 16 | Width = new TtmlLength(widthValue, widthUnit); 17 | Height = new TtmlLength(heightValue, heightUnit); 18 | } 19 | 20 | public TtmlLength Width; 21 | public TtmlLength Height; 22 | 23 | public static bool TryParse(string text, out TtmlSize size) 24 | { 25 | if (string.IsNullOrEmpty(text)) 26 | { 27 | size = new TtmlSize(); 28 | return false; 29 | } 30 | 31 | TtmlMultipartAttributeReader reader = new TtmlMultipartAttributeReader(text); 32 | if (!reader.TryRead(TtmlLength.TryParse, out TtmlLength width) || 33 | !reader.TryRead(TtmlLength.TryParse, out TtmlLength height) || 34 | !reader.IsAtEnd) 35 | { 36 | size = new TtmlSize(); 37 | return false; 38 | } 39 | 40 | size = new TtmlSize(width, height); 41 | return true; 42 | } 43 | 44 | public static TtmlSize Parse(string text) 45 | { 46 | if (text == null) 47 | throw new ArgumentNullException(nameof(text)); 48 | 49 | if (!TryParse(text, out TtmlSize size)) 50 | throw new FormatException(); 51 | 52 | return size; 53 | } 54 | 55 | internal SizeF Resolve(TtmlResolutionContext context) 56 | { 57 | return new SizeF( 58 | Width.Resolve(context, TtmlProgression.Inline), 59 | Height.Resolve(context, TtmlProgression.Block) 60 | ); 61 | } 62 | 63 | public override string ToString() 64 | { 65 | return Width + " " + Height; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlSpan.cs: -------------------------------------------------------------------------------- 1 | using System.Xml; 2 | 3 | namespace YTSubConverter.Shared.Formats.Ttml 4 | { 5 | public class TtmlSpan : TtmlContent 6 | { 7 | public TtmlSpan(XmlElement elem, XmlNamespaceManager nsmgr, TtmlDocument doc, TtmlContent parent) 8 | : base(elem, nsmgr, doc, parent) 9 | { 10 | } 11 | 12 | public TtmlSpan(string text, TtmlContent parent) 13 | : base(text, parent) 14 | { 15 | } 16 | 17 | public TtmlParagraph Paragraph 18 | { 19 | get 20 | { 21 | TtmlContent parent = Parent; 22 | while (parent != null) 23 | { 24 | if (parent is TtmlParagraph paragraph) 25 | return paragraph; 26 | 27 | parent = parent.Parent; 28 | } 29 | return null; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Formats/Ttml/TtmlTimeRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml; 3 | using YTSubConverter.Shared.Util; 4 | 5 | namespace YTSubConverter.Shared.Formats.Ttml 6 | { 7 | public class TtmlTimeRange 8 | { 9 | public static TtmlTimeRange Get(XmlElement elem, TtmlDocument doc) 10 | { 11 | DateTime? begin = GetTime(elem, "begin", doc); 12 | TimeSpan? duration = GetTime(elem, "dur", doc) - SubtitleDocument.TimeBase; 13 | DateTime? end = GetTime(elem, "end", doc); 14 | TtmlTimeContainer timeContainer = elem.GetEnumAttribute("timeContainer") ?? TtmlTimeContainer.Par; 15 | 16 | if (begin == null && duration == null && end == null) 17 | return null; 18 | 19 | begin ??= SubtitleDocument.TimeBase; 20 | 21 | if (duration != null && (end == null || begin.Value + duration.Value < end.Value)) 22 | end = begin + duration; 23 | 24 | end ??= DateTime.MaxValue; 25 | 26 | return new TtmlTimeRange 27 | { 28 | Begin = begin.Value, 29 | End = end.Value, 30 | TimeContainer = timeContainer 31 | }; 32 | } 33 | 34 | public DateTime Begin 35 | { 36 | get; 37 | set; 38 | } 39 | 40 | public DateTime End 41 | { 42 | get; 43 | set; 44 | } 45 | 46 | public TtmlTimeContainer TimeContainer 47 | { 48 | get; 49 | set; 50 | } 51 | 52 | internal (DateTime, DateTime) Resolve(TtmlResolutionContext parentContext, TtmlResolutionContext prevSiblingContext) 53 | { 54 | DateTime reference = TimeContainer == TtmlTimeContainer.Par ? parentContext.BeginTime : prevSiblingContext?.EndTime ?? parentContext.BeginTime; 55 | DateTime begin = reference + (Begin - SubtitleDocument.TimeBase); 56 | DateTime end = End < DateTime.MaxValue ? reference + (End - SubtitleDocument.TimeBase) : DateTime.MaxValue; 57 | if (end > parentContext.EndTime) 58 | end = parentContext.EndTime; 59 | 60 | return (begin, end); 61 | } 62 | 63 | private static DateTime? GetTime(XmlElement elem, string attr, TtmlDocument doc) 64 | { 65 | return elem.GetTypedAttribute( 66 | attr, 67 | string.Empty, 68 | (string textValue, out DateTime parsedValue) => TtmlTime.TryParse(textValue, doc.FrameRate, doc.SubFrameRate, doc.TickRate, out parsedValue) 69 | ); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Line.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | 6 | namespace YTSubConverter.Shared 7 | { 8 | public class Line : ICloneable 9 | { 10 | public Line(DateTime start, DateTime end) 11 | { 12 | Start = start; 13 | End = end; 14 | } 15 | 16 | public Line(DateTime start, DateTime end, string content) 17 | { 18 | Start = start; 19 | End = end; 20 | Sections.Add(new Section(content)); 21 | } 22 | 23 | public Line(DateTime start, DateTime end, IEnumerable
sections) 24 | { 25 | Start = start; 26 | End = end; 27 | Sections.AddRange(sections); 28 | } 29 | 30 | public Line(Line line) 31 | { 32 | Assign(line); 33 | } 34 | 35 | public DateTime Start 36 | { 37 | get; 38 | set; 39 | } 40 | 41 | public DateTime End 42 | { 43 | get; 44 | set; 45 | } 46 | 47 | public List
Sections { get; } = new List
(); 48 | 49 | public string Text 50 | { 51 | get { return string.Join("", Sections.Select(s => s.Text)); } 52 | } 53 | 54 | public AnchorPoint AnchorPoint 55 | { 56 | get; 57 | set; 58 | } = AnchorPoint.BottomCenter; 59 | 60 | public PointF? Position 61 | { 62 | get; 63 | set; 64 | } 65 | 66 | public HorizontalTextDirection HorizontalTextDirection 67 | { 68 | get; 69 | set; 70 | } 71 | 72 | public VerticalTextType VerticalTextType 73 | { 74 | get; 75 | set; 76 | } 77 | 78 | public bool AndroidDarkTextHackAllowed 79 | { 80 | get; 81 | set; 82 | } = true; 83 | 84 | public override string ToString() 85 | { 86 | return Text; 87 | } 88 | 89 | public virtual object Clone() 90 | { 91 | return new Line(this); 92 | } 93 | 94 | protected virtual void Assign(Line line) 95 | { 96 | Start = line.Start; 97 | End = line.End; 98 | AnchorPoint = line.AnchorPoint; 99 | Position = line.Position; 100 | HorizontalTextDirection = line.HorizontalTextDirection; 101 | VerticalTextType = line.VerticalTextType; 102 | AndroidDarkTextHackAllowed = line.AndroidDarkTextHackAllowed; 103 | 104 | Sections.Clear(); 105 | Sections.AddRange(line.Sections.Select(CreateSection)); 106 | } 107 | 108 | protected virtual Section CreateSection(Section section) 109 | { 110 | return new Section(section); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Resources/checkers.png_: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcusmaximus/YTSubConverter/38fb2ab469f37e8f3a5a6a27adf91d9d0e81ea4f/YTSubConverter.Shared/Resources/checkers.png_ -------------------------------------------------------------------------------- /YTSubConverter.Shared/Resources/defaultstyles.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.3.0 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 1280 9 | PlayResY: 720 10 | YCbCr Matrix: None 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:23.976000:40000:1280:720:150:150:150: 15 | Video AR Value: 1.777778 16 | Video Zoom Percent: 1.000000 17 | 18 | [V4+ Styles] 19 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 20 | Style: YTPlain,Roboto,38,&H01FEFEFE,&HFF000000,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,0,2,25,25,15,1 21 | Style: YTPlainBox,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H00000000,0,0,0,0,100,100,0,0,3,0.01,0,2,25,25,15,1 22 | Style: YTGlow,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H01000000,0,0,0,0,100,100,0,0,1,2,0,2,25,25,15,1 23 | Style: YTGlowBox,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H01000000,0,0,0,0,100,100,0,0,3,0.01,4,2,25,25,15,1 24 | Style: YTSoftShadow,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H01000000,0,0,0,0,100,100,0,0,1,0,4,2,25,25,15,1 25 | Style: YTSoftShadowBox,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H01000000,0,0,0,0,100,100,0,0,3,0.01,4,2,25,25,15,1 26 | Style: YTHardShadow,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H01000000,0,0,0,0,100,100,0,0,1,0,4,2,25,25,15,1 27 | Style: YTHardShadowBox,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H01000000,0,0,0,0,100,100,0,0,3,0.01,4,2,25,25,15,1 28 | Style: YTBevel,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H01000000,0,0,0,0,100,100,0,0,1,0,4,2,25,25,15,1 29 | Style: YTBevelBox,Roboto,38,&H01FEFEFE,&HFF000000,&H01000000,&H00000000,0,0,0,0,100,100,0,0,3,0.01,4,2,25,25,15,1 30 | 31 | [Events] 32 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 33 | Dialogue: 0,0:00:00.00,0:00:05.00,YTPlain,,0,0,0,, 34 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Section.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using YTSubConverter.Shared.Util; 5 | 6 | namespace YTSubConverter.Shared 7 | { 8 | public class Section : ICloneable 9 | { 10 | public Section() 11 | { 12 | } 13 | 14 | public Section(string text) 15 | { 16 | Text = text; 17 | } 18 | 19 | public Section(Section other) 20 | { 21 | Assign(other); 22 | } 23 | 24 | public string Text 25 | { 26 | get; 27 | set; 28 | } 29 | 30 | public string Font 31 | { 32 | get; 33 | set; 34 | } 35 | 36 | public float Scale 37 | { 38 | get; 39 | set; 40 | } = 1; 41 | 42 | public OffsetType Offset 43 | { 44 | get; 45 | set; 46 | } 47 | 48 | public bool Bold 49 | { 50 | get; 51 | set; 52 | } 53 | 54 | public bool Italic 55 | { 56 | get; 57 | set; 58 | } 59 | 60 | public bool Underline 61 | { 62 | get; 63 | set; 64 | } 65 | 66 | public Color ForeColor 67 | { 68 | get; 69 | set; 70 | } 71 | 72 | public Color BackColor 73 | { 74 | get; 75 | set; 76 | } 77 | 78 | public Dictionary ShadowColors { get; } = new Dictionary(); 79 | 80 | public RubyPart RubyPart 81 | { 82 | get; 83 | set; 84 | } 85 | 86 | public bool Packed 87 | { 88 | get; 89 | set; 90 | } 91 | 92 | public TimeSpan StartOffset 93 | { 94 | get; 95 | set; 96 | } 97 | 98 | public override string ToString() 99 | { 100 | return Text; 101 | } 102 | 103 | public virtual object Clone() 104 | { 105 | return new Section(this); 106 | } 107 | 108 | protected virtual void Assign(Section section) 109 | { 110 | Text = section.Text; 111 | Font = section.Font; 112 | Scale = section.Scale; 113 | Offset = section.Offset; 114 | Bold = section.Bold; 115 | Italic = section.Italic; 116 | Underline = section.Underline; 117 | ForeColor = section.ForeColor; 118 | BackColor = section.BackColor; 119 | ShadowColors.Clear(); 120 | ShadowColors.AddRange(section.ShadowColors); 121 | RubyPart = section.RubyPart; 122 | Packed = section.Packed; 123 | StartOffset = section.StartOffset; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/SectionFormatComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | 5 | namespace YTSubConverter.Shared 6 | { 7 | internal class SectionFormatComparer : IEqualityComparer
8 | { 9 | public bool Equals(Section x, Section y) 10 | { 11 | if (x.Bold != y.Bold || 12 | x.Italic != y.Italic || 13 | x.Underline != y.Underline || 14 | NormalizeFont(x.Font) != NormalizeFont(y.Font) || 15 | Math.Abs(NormalizeScale(x.Scale) - NormalizeScale(y.Scale)) > 0.001 || 16 | x.Offset != y.Offset || 17 | x.ForeColor.ToArgb() != y.ForeColor.ToArgb() || 18 | x.BackColor.ToArgb() != y.BackColor.ToArgb() || 19 | x.RubyPart != y.RubyPart || 20 | x.Packed != y.Packed || 21 | x.ShadowColors.Count != y.ShadowColors.Count) 22 | { 23 | return false; 24 | } 25 | 26 | foreach (KeyValuePair xShadowColor in x.ShadowColors) 27 | { 28 | if (!y.ShadowColors.TryGetValue(xShadowColor.Key, out Color yShadowColor) || xShadowColor.Value.ToArgb() != yShadowColor.ToArgb()) 29 | return false; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | public int GetHashCode(Section section) 36 | { 37 | return section.Bold.GetHashCode() ^ 38 | section.Italic.GetHashCode() ^ 39 | section.Underline.GetHashCode() ^ 40 | (NormalizeFont(section.Font)?.GetHashCode() ?? 0) ^ 41 | section.Offset.GetHashCode() ^ 42 | section.ForeColor.ToArgb() ^ 43 | section.BackColor.ToArgb() ^ 44 | section.RubyPart.GetHashCode() ^ 45 | section.Packed.GetHashCode(); 46 | } 47 | 48 | protected virtual string NormalizeFont(string font) 49 | { 50 | return font; 51 | } 52 | 53 | protected virtual float NormalizeScale(float scale) 54 | { 55 | return scale; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/CharacterRange.cs: -------------------------------------------------------------------------------- 1 | namespace YTSubConverter.Shared.Util 2 | { 3 | public class CharacterRange : Range 4 | { 5 | public static readonly CharacterRange ArabicRange = new CharacterRange((char)0x600, (char)0x6FF); 6 | public static readonly CharacterRange HebrewRange = new CharacterRange((char)0x590, (char)0x5FF); 7 | 8 | public static readonly CharacterRange HiraganaRange = new CharacterRange((char)0x3041, (char)0x3097); 9 | public static readonly CharacterRange KatakanaRange = new CharacterRange((char)0x30A0, (char)0x3100); 10 | public static readonly CharacterRange IdeographExtensionRange = new CharacterRange((char)0x3400, (char)0x4DB6); 11 | public static readonly CharacterRange IdeographRange = new CharacterRange((char)0x4E00, (char)0x9FCC); 12 | public static readonly CharacterRange IdeographCompatibilityRange = new CharacterRange((char)0xF900, (char)0xFA6B); 13 | public static readonly CharacterRange HangulRange = new CharacterRange((char)0xAC00, (char)0xD7A4); 14 | 15 | public CharacterRange(char start, char end) 16 | : base(start, end) 17 | { 18 | } 19 | 20 | public static bool IsRightToLeft(char c) 21 | { 22 | return ArabicRange.Contains(c) || HebrewRange.Contains(c); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/ColorUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Globalization; 4 | 5 | namespace YTSubConverter.Shared.Util 6 | { 7 | public static class ColorUtil 8 | { 9 | public static Color ChangeAlpha(Color color, int alpha) 10 | { 11 | return Color.FromArgb((alpha << 24) | (color.ToArgb() & 0xFFFFFF)); 12 | } 13 | 14 | public static bool IsDark(Color color) 15 | { 16 | return Math.Max(Math.Max(color.R, color.G), color.B) < 128; 17 | } 18 | 19 | public static Color Brighten(Color color) 20 | { 21 | int r = Math.Max(color.R, (byte)1); 22 | int g = Math.Max(color.G, (byte)1); 23 | int b = Math.Max(color.B, (byte)1); 24 | 25 | int highestComponent = Math.Max(Math.Max(r, g), b); 26 | float factor = 255f / highestComponent; 27 | return Color.FromArgb( 28 | color.A, 29 | (byte)Math.Round(r * factor), 30 | (byte)Math.Round(g * factor), 31 | (byte)Math.Round(b * factor) 32 | ); 33 | } 34 | 35 | public static Color FromHtml(string html) 36 | { 37 | if (string.IsNullOrEmpty(html)) 38 | return Color.Empty; 39 | 40 | if (html.Length != 7 || !html.StartsWith("#")) 41 | throw new FormatException(); 42 | 43 | if (!int.TryParse(html.Substring(1), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int rgb)) 44 | throw new FormatException(); 45 | 46 | return Color.FromArgb((0xFF << 24) | rgb); 47 | } 48 | 49 | public static string ToHtml(Color color) 50 | { 51 | if (color.IsEmpty) 52 | return string.Empty; 53 | 54 | return $"#{color.ToArgb() & 0xFFFFFF:X06}"; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/FontSizeMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace YTSubConverter.Shared.Util 5 | { 6 | /// 7 | /// Converts between Aegisub line heights and real font sizes, as these 8 | /// are indeed different depending on the font. (For example, YouTube uses 9 | /// a default size of 32px for all fonts at 720p, but to get the same size 10 | /// in Aegisub, you need to specify 38px for Roboto, 45px for Comic Sans etc.) 11 | /// 12 | public static class FontSizeMapper 13 | { 14 | private static readonly Dictionary LineHeightToFontSizeFactors = 15 | new Dictionary 16 | { 17 | { "carrois gothic sc", 32f / 38f }, 18 | { "comic sans ms", 32f / 45f }, 19 | { "courier new", 32f / 36f }, 20 | { "lucida console", 32f / 32f }, 21 | { "monotype corsiva", 32f / 35f }, 22 | { "roboto", 32f / 38f }, 23 | { "times new roman", 32f / 35f } 24 | }; 25 | 26 | public static float LineHeightToFontSize(string fontName, float lineHeight) 27 | { 28 | return lineHeight * GetSizeFactor(fontName); 29 | } 30 | 31 | public static float FontSizeToLineHeight(string fontName, float fontSize) 32 | { 33 | return fontSize / GetSizeFactor(fontName); 34 | } 35 | 36 | private static float GetSizeFactor(string fontName) 37 | { 38 | if (!LineHeightToFontSizeFactors.TryGetValue(fontName.ToLower(), out float factor)) 39 | throw new NotSupportedException($"No size factor defined for font {fontName}"); 40 | 41 | return factor; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/Range.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace YTSubConverter.Shared.Util 4 | { 5 | public class Range : IComparable> 6 | where T : IComparable 7 | { 8 | public Range(T start, T end) 9 | { 10 | Start = start; 11 | End = end; 12 | } 13 | 14 | public T Start 15 | { 16 | get; 17 | set; 18 | } 19 | 20 | public T End 21 | { 22 | get; 23 | set; 24 | } 25 | 26 | public bool Contains(T point) 27 | { 28 | return point.CompareTo(Start) >= 0 && point.CompareTo(End) < 0; 29 | } 30 | 31 | public bool Overlaps(Range other) 32 | { 33 | return Start.CompareTo(other.End) < 0 && End.CompareTo(other.Start) > 0; 34 | } 35 | 36 | public void IntersectWith(Range other) 37 | { 38 | if (!Overlaps(other)) 39 | throw new InvalidOperationException("Can't intersect with a non-overlapping time range"); 40 | 41 | Start = Max(Start, other.Start); 42 | End = Min(End, other.End); 43 | } 44 | 45 | public void UnionWith(Range other) 46 | { 47 | if (!Overlaps(other)) 48 | throw new InvalidOperationException("Can't union with a non-overlapping time range"); 49 | 50 | Start = Min(Start, other.Start); 51 | End = Max(End, other.End); 52 | } 53 | 54 | public int CompareTo(Range other) 55 | { 56 | return Start.CompareTo(other.Start); 57 | } 58 | 59 | private static T Min(T x, T y) 60 | { 61 | return x.CompareTo(y) < 0 ? x : y; 62 | } 63 | 64 | private static T Max(T x, T y) 65 | { 66 | return x.CompareTo(y) > 0 ? x : y; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text.RegularExpressions; 3 | 4 | namespace YTSubConverter.Shared.Util 5 | { 6 | public static class StringExtensions 7 | { 8 | public static string ToCrlf(this string str) 9 | { 10 | str = Regex.Replace(str, @"(? Split(this string str, string separator, int? maxItems = null) 16 | { 17 | List result = new List(); 18 | int start = 0; 19 | int end; 20 | while (start <= str.Length) 21 | { 22 | if (start < str.Length && (maxItems == null || result.Count < maxItems.Value - 1)) 23 | { 24 | end = str.IndexOf(separator, start); 25 | if (end < 0) 26 | end = str.Length; 27 | } 28 | else 29 | { 30 | end = str.Length; 31 | } 32 | 33 | result.Add(str.Substring(start, end - start)); 34 | start = end + separator.Length; 35 | } 36 | return result; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/TimeRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace YTSubConverter.Shared.Util 4 | { 5 | public class TimeRange : Range 6 | { 7 | public TimeRange(DateTime start, DateTime end) 8 | : base(start, end) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/TimeUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using YTSubConverter.Shared.Formats; 3 | 4 | namespace YTSubConverter.Shared.Util 5 | { 6 | public static class TimeUtil 7 | { 8 | public static DateTime Min(DateTime date1, DateTime date2) 9 | { 10 | return date1 < date2 ? date1 : date2; 11 | } 12 | 13 | public static DateTime Max(DateTime date1, DateTime date2) 14 | { 15 | return date1 > date2 ? date1 : date2; 16 | } 17 | 18 | public static int StartTimeToFrame(DateTime time) 19 | { 20 | if (time <= SubtitleDocument.TimeBase) 21 | return 0; 22 | 23 | return EndTimeToFrame(time) + 1; 24 | } 25 | 26 | public static int EndTimeToFrame(DateTime time) 27 | { 28 | return (int)((time.TimeOfDay.TotalMilliseconds + 1) / 33.36666666666667); 29 | } 30 | 31 | public static DateTime FrameToStartTime(int frame) 32 | { 33 | if (frame <= 0) 34 | return SubtitleDocument.TimeBase; 35 | 36 | return FrameToTime(frame).AddMilliseconds(-16); 37 | } 38 | 39 | public static DateTime FrameToEndTime(int frame) 40 | { 41 | return FrameToTime(frame).AddMilliseconds(16); 42 | } 43 | 44 | private static DateTime FrameToTime(int frame) 45 | { 46 | if (frame == 0) 47 | return SubtitleDocument.TimeBase; 48 | 49 | int ms = (int)(frame * 33.36666666666667); 50 | return SubtitleDocument.TimeBase + TimeSpan.FromMilliseconds(ms); 51 | } 52 | 53 | public static DateTime RoundTimeToFrameCenter(DateTime time) 54 | { 55 | if (time <= SubtitleDocument.TimeBase) 56 | return SubtitleDocument.TimeBase; 57 | 58 | return FrameToStartTime(StartTimeToFrame(time)); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/Util/XmlExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Xml; 4 | 5 | namespace YTSubConverter.Shared.Util 6 | { 7 | internal static class XmlExtensions 8 | { 9 | public static int? GetIntAttribute(this XmlElement elem, string attr, string ns = "") 10 | { 11 | return elem.GetTypedAttribute(attr, ns, int.TryParse); 12 | } 13 | 14 | public static float? GetFloatAttribute(this XmlElement elem, string attr, string ns = "") 15 | { 16 | return elem.GetTypedAttribute( 17 | attr, 18 | ns, 19 | (string textValue, out float parsedValue) => float.TryParse(textValue, NumberStyles.Float, CultureInfo.InvariantCulture, out parsedValue) 20 | ); 21 | } 22 | 23 | public static T? GetEnumAttribute(this XmlElement elem, string attr, string ns = "") 24 | where T : struct 25 | { 26 | return elem.GetTypedAttribute(attr, ns, TryParseEnum); 27 | } 28 | 29 | private static bool TryParseEnum(string part, out T value) 30 | where T : struct 31 | { 32 | return Enum.TryParse(part, true, out value); 33 | } 34 | 35 | public delegate bool Parser(string text, out T value); 36 | 37 | public static T? GetTypedAttribute(this XmlElement elem, string attr, string ns, Parser tryParse) 38 | where T : struct 39 | { 40 | string textValue = elem.GetAttribute(attr, ns); 41 | if (string.IsNullOrEmpty(textValue)) 42 | return null; 43 | 44 | if (!tryParse(textValue, out T parsedValue)) 45 | return null; 46 | 47 | return parsedValue; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /YTSubConverter.Shared/YTSubConverter.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 8 6 | YTSubConverter.Shared 7 | YTSubConverter.Shared 8 | 9 | 10 | 11 | 12 | True 13 | True 14 | Resources.resx 15 | 16 | 17 | 18 | 19 | 20 | PublicResXFileCodeGenerator 21 | Resources.Designer.cs 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Alignment.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Top left​ ​

​ ​Top center​ ​

​ ​Top right​ ​

​ ​Middle left​ ​

​ ​Middle center​ ​

​ ​Middle right​ ​

​ ​Bottom left​ ​

​ ​Bottom center​ ​

​ ​Bottom right​ ​

​ ​\\an ( 7 )\an1​ ​

​ ​\an8​ ​

​ ​\an9​ ​

​ ​\an4​ ​

​ ​\an5​ ​

​ ​\an6​ ​

​ ​\an1​ ​

​ ​\an2​ ​

​ ​\an3​ ​

​ ​\anX​ ​​ 2 | ​​ ​\an10​ ​​ 3 | ​​ ​\an0​ ​​ 4 | ​​ ​\an​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/BoldItalicUnderline.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.3.0 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: None 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:23.976000:40000:1280:720:205:201:202: 15 | Video AR Value: 1.777778 16 | Video Zoom Percent: 0.750000 17 | Active Line: 5 18 | 19 | [V4+ Styles] 20 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 21 | Style: Default,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H00000000,&H00FFFFFF,0,0,0,0,100,100,0,0,3,2,0,8,10,10,10,1 22 | Style: Bold,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H00000000,&H00FFFFFF,-1,0,0,0,100,100,0,0,3,2,0,8,10,10,10,1 23 | Style: Italic,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H00000000,&H00FFFFFF,0,-1,0,0,100,100,0,0,3,2,0,8,10,10,10,1 24 | Style: Underline,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H00000000,&H00FFFFFF,0,0,-1,0,100,100,0,0,3,2,0,8,10,10,10,1 25 | Style: Soft shadow box,Arial,15,&H00FFFFFF,&HFFFFFFFF,&H00000000,&H00828282,0,0,0,0,100,100,0,0,3,2,2,5,10,10,10,1 26 | 27 | [Events] 28 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 29 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,Default {\\b ( 1 ) }\\b ( 1 ) {\b}\b {\b1}\b1 {\b0}\b0 {\b1}\b1 {\b2}\b2 {\b1}\b1 {\bX}\bX {\r}\r 30 | Dialogue: 0,0:00:00.00,0:00:05.00,Bold,,0,0,0,,Bold {\rDefault}\rDefault {\b}\b {\b0}\b0 {\b1}\b1 {\b0}\b0 {\b2}\b2 {\b0}\b0 {\bX}\bX {\r}\r 31 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,Default {\\i ( 1 ) }\\i ( 1 ) {\i}\i {\i1}\i1 {\i0}\i0 {\i1}\i1 {\i2}\i2 {\i1}\i1 {\iX}\iX {\r}\r 32 | Dialogue: 0,0:00:00.00,0:00:05.00,Italic,,0,0,0,,Italic {\rDefault}\rDefault {\i}\i {\i0}\i0 {\i1}\i1 {\i0}\i0 {\i2}\i2 {\i0}\i0 {\iX}\iX {\r}\r 33 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,Default {\\u ( 1 )}\\u ( 1 ) {\u}\u {\u1}\u1 {\u0}\u0 {\u1}\u1 {\u2}\u2 {\u1}\u1 {\uX}\uX {\r}\r 34 | Dialogue: 0,0:00:00.00,0:00:05.00,Underline,,0,0,0,,Underline {\rDefault}\rDefault {\u}\u {\u0}\u0 {\u1}\u1 {\u0}\u0 {\u2}\u2 {\u0}\u0 {\uX}\uX {\r}\r 35 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/BoldItalicUnderline.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Default \\b ( 1 ) \b \b1 \b0 \b1 \b2 \b1 \bX \r​ ​​ 2 | ​​ ​Bold \rDefault \b \b0 \b1 \b0 \b2 \b0 \bX \r​ ​​ 3 | ​​ ​Default \\i ( 1 ) \i \i1 \i0 \i1 \i2 \i1 \iX \r​ ​​ 4 | ​​ ​Italic \rDefault \i \i0 \i1 \i0 \i2 \i0 \iX \r​ ​​ 5 | ​​ ​Default \\u ( 1 ) \u \u1 \u0 \u1 \u2 \u1 \uX \r​ ​​ 6 | ​​ ​Underline \rDefault \u \u0 \u1 \u0 \u2 \u0 \uX \r​ ​

​ ​​​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Fade.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.2.2 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: TV.601 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:30.000000:40000:640:480:204:202:200: 15 | Video AR Value: 1.333333 16 | Video Position: 50 17 | 18 | [V4+ Styles] 19 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 20 | Style: Soft shadow box,Roboto,15,&H00FFFFFF,&H000000FF,&H85000000,&H00222222,0,0,0,0,100,100,0,0,3,2,2,5,10,10,10,1 21 | 22 | [Events] 23 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 24 | Dialogue: 0,0:00:00.00,0:00:05.01,Soft shadow box,,0,0,0,,{\pos(192,52)\fad(1000,500)}Simple fade 25 | Dialogue: 0,0:00:00.00,0:00:05.01,Soft shadow box,,0,0,0,,{\pos(192,96.4)\fade(100,0,200,1000,2000,3000,4000)}Complex fade 26 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/FaultTolerance.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.3.0 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: None 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:30.000000:40000:640:480:204:202:200: 15 | Video AR Value: 1.333333 16 | 17 | [V4+ Styles] 18 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 19 | Style: Default,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H3C000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,2,10,10,10,1 20 | 21 | [Events] 22 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 23 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{ {random stuff \\\b1 \an7\an3 \cFF \pos ( 90 . 1, 200 }Te{}xt 24 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\pos(158.7,240)}\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z 25 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\pos(159.3,278.4)}\A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z 26 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/FaultTolerance.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Text​ ​

​ ​\a\b\c\d\e\f\g \i\j\k\l\m \o\p\q\r\s\t\u\v\w\x\y\z​ ​

​ ​\A\B\C\D\E\F\G\H\I\J\K\L\M​ ​​ 2 | ​​ ​\O\P\Q\R\S\T\U\V\W\X\Y\Z​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Move.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.2.2 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: TV.601 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:30.000000:40000:640:480:218:216:214: 15 | Video AR Value: 1.333333 16 | Active Line: 1 17 | Video Position: 150 18 | 19 | [V4+ Styles] 20 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 21 | Style: Default,Arial,20,&H00FFFFFF,&HFFFFFFFF,&H64000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,2,10,10,10,1 22 | 23 | [Events] 24 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 25 | Dialogue: 0,0:00:00.00,0:00:05.01,Default,,0,0,0,,{\an8\move(72,67.8,335.2,87.4)}Full duration 26 | Dialogue: 0,0:00:00.00,0:00:05.01,Default,,0,0,0,,{\an8\move(55.8,118.8,331.6,152.8,1000,3000)}Sub duration 27 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/NoDefaultScale.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.3.0 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: None 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Active Line: 2 15 | 16 | [V4+ Styles] 17 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 18 | Style: Initial,Arial,15,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,5,10,10,10,1 19 | Style: Big,Arial,30,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,5,10,10,10,1 20 | Style: Small,Arial,7.5,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,5,10,10,10,1 21 | 22 | [Events] 23 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 24 | Dialogue: 0,0:00:00.00,0:00:05.00,Initial,,0,0,0,,{\pos(192,98.4)}Initial 25 | Dialogue: 0,0:00:00.00,0:00:05.00,Big,,0,0,0,,{\pos(192,144)}Big 26 | Dialogue: 0,0:00:00.00,0:00:05.00,Small,,0,0,0,,{\pos(192,187.2)}Small 27 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/NoDefaultScale.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Initial​ ​

​ ​Big​ ​

​ ​Small​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Offset.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.2.2 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: TV.601 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:30.000000:40000:640:480:218:216:214: 15 | Video AR Value: 1.333333 16 | Video Position: 150 17 | 18 | [V4+ Styles] 19 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 20 | Style: Default,Arial,15,&H00FFFFFF,&HFFFFFFFF,&H64000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,2,10,10,10,1 21 | 22 | [Events] 23 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 24 | Dialogue: 0,0:00:00.00,0:00:05.01,Default,,0,0,0,,{\an5\pos(192,94.6)\ytsub}Subscript{\ytsur} Regular {\ytsup}Superscript {\r}Reset 25 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Offset.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Subscript Regular Superscript Reset​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Ruby.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.2.2 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: None 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:30.000000:40000:640:480:204:202:200: 15 | Video AR Value: 1.333333 16 | Active Line: 1 17 | 18 | [V4+ Styles] 19 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 20 | Style: Default,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H3C000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,5,10,10,10,1 21 | Style: Soft shadow,Roboto,15,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0,2,5,10,10,10,1 22 | 23 | [Events] 24 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 25 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\pos(77.4,39.8)}No [ru/ル][by/ビ] 26 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytruby\pos(249,39.8)}[Ru/ル][by/ビ] 27 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytruby\pos(77.4,77.6)} [Ru/ル][by/ビ] 28 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytruby\pos(249.6,77.6)} [Ru/ル][by/ビ] 29 | Dialogue: 0,0:00:00.00,0:00:05.00,Soft shadow,,0,0,0,,{\ytruby\pos(168.6,123.8)}[Default/ルビ] {\ytruby2}[\ytruby2/ルビ] {\ytruby8}[\ytruby8/ルビ] {\r}[\r/ルビ] 30 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Ruby.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​No [ru/ル][by/ビ]​ ​

​ ​Ru()by() <Suffix>​ ​

​ ​<Prefix> Ru()by()​ ​

​ ​<Prefix> Ru()by() <Suffix>​ ​

​ ​Default(ルビ) \ytruby2(ルビ) \ytruby8(ルビ) [\r/ルビ]​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/Shadows.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.3.0 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: TV.601 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Video File: ?dummy:23.976000:40000:1280:720:205:201:202: 15 | Video AR Value: 1.777778 16 | Active Line: 5 17 | 18 | [V4+ Styles] 19 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 20 | Style: Default,Arial,15,&H00FFFFFF,&HFFFFFFFF,&H64000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,2,10,10,10,1 21 | Style: Outline,Arial,15,&H00FFFFFF,&H000000FF,&H00FF0000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1 22 | Style: Glow shadow box,Arial,15,&H00FFFFFF,&H000000FF,&H64000000,&H00000000,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 23 | Style: Bevel shadow box,Arial,15,&H00FFFFFF,&H000000FF,&H64000000,&H00000000,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 24 | Style: Soft shadow box,Arial,15,&H00FFFFFF,&HFF0000FF,&H64000000,&H00828282,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 25 | Style: Hard shadow box,Arial,15,&H00FFFFFF,&H000000FF,&H64000000,&H00828282,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 26 | Style: Glow + soft shadow box,Arial,15,&H00FFFFFF,&H000000FF,&H64000000,&H00000000,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 27 | Style: Glow + hard shadow box,Arial,15,&H00FFFFFF,&H000000FF,&H64000000,&H00828282,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 28 | 29 | [Events] 30 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 31 | Dialogue: 0,0:00:00.00,0:00:05.01,Glow shadow box,,0,0,0,,{\an5\pos(81,70.6)\4c&HFFFF00&}Glow 32 | Dialogue: 0,0:00:00.00,0:00:05.01,Bevel shadow box,,0,0,0,,{\an5\pos(81,117.4)\4c&HFFFF00&}Bevel 33 | Dialogue: 0,0:00:00.00,0:00:05.01,Soft shadow box,,0,0,0,,{\an5\pos(81,157)\4c&HFFFF00&}Soft shadow 34 | Dialogue: 0,0:00:00.00,0:00:05.01,Hard shadow box,,0,0,0,,{\an5\pos(81,195.4)\4c&HFFFF00&}Hard shadow 35 | Dialogue: 0,0:00:00.00,0:00:05.01,Glow + soft shadow box,,0,0,0,,{\an5\4c&HFFFF00&\pos(259.8,70.6)}Glow + soft shadow 36 | Dialogue: 0,0:00:00.00,0:00:05.01,Glow + soft shadow box,,0,0,0,,{\4c&HFFFF00&\an3}Hello, Darling ♡ 37 | Dialogue: 0,0:00:00.00,0:00:05.01,Default,,0,0,0,,{\an5\4c&HFF0000&\pos(259.8,132.4)}None, {\rGlow shadow box\4c&H0000FF&}Glow, {\rHard shadow box\4c&H00FF00&}hard shadow,\N{\rGlow + hard shadow box\4c&HFF0000}glow + hard shadow, {\r}None 38 | Dialogue: 0,0:00:05.03,0:00:09.66,Soft shadow box,,0,0,0,,{\pos(192,163.2)\4c&H1723E6&}Shadow {\i1}clipping{\i0} prevention 39 | Dialogue: 0,0:00:05.03,0:00:09.66,Soft shadow box,,0,0,0,,{\pos(192,192.4)\4c&H1723E6&}Shadow {\i1}clipping {\i0}prevention 40 | Dialogue: 0,0:00:05.03,0:00:09.66,Soft shadow box,,0,0,0,,{\pos(192,219.2)\ytdir4\4c&H1723E6&}منع {\i1}قص{\i0} الظل 41 | Dialogue: 0,0:00:05.03,0:00:09.66,Soft shadow box,,0,0,0,,{\pos(192,250.4)\ytdir4\4c&H1723E6&}منع{\i1} قص{\i0} الظل 42 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/SimultaneousReverse.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.3.0 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: None 11 | Collisions: Reverse 12 | 13 | [Aegisub Project Garbage] 14 | Last Style Storage: Default 15 | Video AR Value: 1.333333 16 | Video Position: 129 17 | 18 | [V4+ Styles] 19 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 20 | Style: Default,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H3C000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,2,10,10,10,1 21 | Style: Soft shadow box,Roboto,15,&H00FFFFFF,&HFF0000FF,&H3C000000,&H00828282,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 22 | 23 | [Events] 24 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 25 | Dialogue: 0,0:00:00.00,0:00:04.37,Default,,0,0,0,,Line 1 26 | Dialogue: 0,0:00:00.60,0:00:03.70,Default,,0,0,0,,Line 2 27 | Dialogue: 0,0:00:01.40,0:00:02.37,Default,,0,0,0,,Line 3 28 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/SimultaneousReverse.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Line 1​ ​

​ ​Line 1​ ​​ 2 | ​​ ​Line 2​ ​

​ ​Line 1​ ​​ 3 | ​​ ​Line 2​ ​​ 4 | ​​ ​Line 3​ ​

​ ​Line 1​ ​​ 5 | ​​ ​Line 2​ ​

​ ​Line 1​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ass/Files/TextDirection.ass: -------------------------------------------------------------------------------- 1 | [Script Info] 2 | ; Script generated by Aegisub 3.3.0 3 | ; http://www.aegisub.org/ 4 | Title: Default Aegisub file 5 | ScriptType: v4.00+ 6 | WrapStyle: 0 7 | ScaledBorderAndShadow: yes 8 | PlayResX: 384 9 | PlayResY: 288 10 | YCbCr Matrix: None 11 | 12 | [Aegisub Project Garbage] 13 | Last Style Storage: Default 14 | Active Line: 13 15 | Video Position: 230 16 | 17 | [V4+ Styles] 18 | Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding 19 | Style: Default,Roboto,15,&H00FFFFFF,&HFFFFFFFF,&H3C000000,&H00000000,0,0,0,0,100,100,0,0,3,2,0,2,10,10,10,1 20 | Style: Glow + soft shadow box,Arial,15,&H00FFFFFF,&H000000FF,&H64000000,&H004343FF,0,0,0,0,100,100,0,0,3,2,2,2,10,10,10,1 21 | 22 | [Events] 23 | Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text 24 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytvert3\an3\pos(123.6,119)}\ytve\Nrt3 25 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytvert1\an1\pos(231,121.4)}\ytve\Nrt1 26 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytvert9\an9\pos(120.6,178.8)}\ytve\Nrt9 27 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytvert7\an7\pos(235.2,181.4)}\ytve\Nrt7 28 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytvert9\pos(334.8,138.2)}{\ytpack1}2019{\ytpack0}年{\ytpack1}08{\ytpack0}月{\ytpack1}25{\ytpack0}日 29 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytdir6\pos(179.1,241.4)}Text with \ytdir6. 30 | Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,{\ytdir4\pos(179.1,269.8)}Text with \ytdir4. 31 | Dialogue: 0,0:00:04.98,0:00:09.61,Glow + soft shadow box,,0,0,0,,{\ytvert3\an3\pos(123.6,119)}\ytve\Nrt3 32 | Dialogue: 0,0:00:04.98,0:00:09.61,Glow + soft shadow box,,0,0,0,,{\ytvert1\an1\pos(231,121.4)}\ytve\Nrt1 33 | Dialogue: 0,0:00:04.98,0:00:09.61,Glow + soft shadow box,,0,0,0,,{\ytvert9\an9\pos(120.6,178.8)}\ytve\Nrt9 34 | Dialogue: 0,0:00:04.98,0:00:09.61,Glow + soft shadow box,,0,0,0,,{\ytvert7\an7\pos(235.2,181.4)}\ytve\Nrt7 35 | Dialogue: 0,0:00:04.98,0:00:09.61,Glow + soft shadow box,,0,0,0,,{\ytvert9\pos(334.8,138.2)}{\ytpack1}2019{\ytpack0}年{\ytpack1}08{\ytpack0}月{\ytpack1}25{\ytpack0}日 36 | Dialogue: 0,0:00:04.98,0:00:09.61,Glow + soft shadow box,,0,0,0,,{\ytdir6\pos(179.1,241.4)}Text with \ytdir6. 37 | Dialogue: 0,0:00:04.98,0:00:09.61,Glow + soft shadow box,,0,0,0,,{\ytdir4\pos(179.1,269.8)}Text with \ytdir4. 38 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/IntegrationTestsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Threading; 6 | using NUnit.Framework; 7 | 8 | namespace YTSubConverter.Tests 9 | { 10 | public abstract class IntegrationTestsBase 11 | { 12 | protected static string DllFolderPath => Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath); 13 | 14 | [SetUp] 15 | public void Setup() 16 | { 17 | // Verify that time and number separators are culture-independent 18 | Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("fi"); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("YTSubConverter.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("YTSubConverter.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2020")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから 18 | // 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("1a6d0829-801d-428b-b04b-65dd35b9f4f0")] 24 | 25 | // アセンブリのバージョン情報は、以下の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // リビジョン 31 | // 32 | // すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます 33 | // 既定値にすることができます: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Alignment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Before left

11 |

Before center

12 |

Before right

13 |

Center left

14 |

Center center

15 |

Center right

16 |

After left

17 |

After center

18 |

After right

19 | 20 |

Justify start

21 |

Justify end

22 | 23 |

Justify justify

24 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Alignment.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Before left​ ​

​ ​Before center​ ​

​ ​Before right​ ​

​ ​Center left​ ​

​ ​Center center​ ​

​ ​Center right​ ​

​ ​After left​ ​

​ ​After center​ ​

​ ​After right​ ​

​ ​Justify start​ ​

​ ​Justify end​ ​

​ ​Justify justify​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/BoldItalicUnderline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Bold Normal

6 |

Italic Normal Oblique

7 |

Underline None NoUnderline

8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/BoldItalicUnderline.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Bold Normal​ ​

​ ​Italic Normal Oblique​ ​

​ ​Underline None NoUnderline​ ​

​ ​​​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Red Green Blue

6 |
7 | 8 |
9 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Colors.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Red Green Blue​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Fonts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

Default

11 |

Monospace

12 |

Sans serif

13 |

Serif

14 |

Monospace sans serif

15 |

Monospace serif

16 |

Proportional sans serif

17 |

Proportional serif

18 |

Comic Sans

19 |

Carrois Gothic SC

20 |

Monotype Corsiva

21 |

Large Small

22 |

Normal Superscript Subscript

23 |
24 | 25 |
26 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Fonts.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Default​ ​

​ ​Monospace​ ​

​ ​Sans serif​ ​

​ ​Serif​ ​

​ ​Monospace sans serif​ ​

​ ​Monospace serif​ ​

​ ​Proportional sans serif​ ​

​ ​Proportional serif​ ​

​ ​Comic Sans​ ​

​ ​Carrois Gothic SC​ ​

​ ​Monotype Corsiva​ ​

​ ​Large Small​ ​

​ ​Normal Superscript Subscript​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Positioning.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |

origin=400px 200px

22 |

origin=20% 50%

23 | 24 |

Left top

25 |

Center top

26 |

Right top

27 |

Left center

28 |

Center

29 |

Right center

30 |

Left bottom

31 |

Center bottom

32 |

Right bottom

33 |
34 | 35 |
36 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Positioning.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​origin=400px 200px​ ​

​ ​origin=20% 50%​ ​

​ ​Left top​ ​

​ ​Center top​ ​

​ ​Right top​ ​

​ ​Left center​ ​

​ ​Center​ ​

​ ​Right center​ ​

​ ​Left bottom​ ​

​ ​Center bottom​ ​

​ ​Right bottom​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Ruby.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​(かん) ()​ ​

​ ​[かん] []​ ​

​ ​漢字(かん)​ ​

​ ​(かんじ)​ ​

​ ​<漢>(<かん>)<字>(<じ>)​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Shadows.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |

Outline: 0px|4px|4px 2px|blue 4px|none

6 |

Shadow: 0px 0px|0px 0px 2px|4px 4px|4px 4px blue|4px 4px 2px blue|none

7 |

Outline + shadow

8 |

Multishadow

9 |
10 | 11 |
12 | -------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/Shadows.ytt: -------------------------------------------------------------------------------- 1 | 

​ ​Outline: 0px|4px|4px 2px|blue 4px|none​ ​

​ ​Shadow: 0px 0px|0px 0px 2px|4px 4px|4px 4px blue|4px 4px 2px blue|none​ ​

​ ​Outline + shadow​ ​

​ ​Outline + shadow​ ​

​ ​Multishadow​ ​

​ ​Multishadow​ ​

​ ​Multishadow​ ​

-------------------------------------------------------------------------------- /YTSubConverter.Tests/Ttml/Files/StyleDerivation.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |