using System; using System.Collections.Generic; using System.Reflection; namespace DownKyi.Core.Danmaku2Ass { /// /// 显示方式 /// public class Display { public Config Config; public Danmaku Danmaku; public int LineIndex; public int FontSize; public bool IsScaled; public int MaxLength; public int Width; public int Height; public Tuple Horizontal; public Tuple Vertical; public int Duration; public int Leave; protected Display() { } public Display(Config config, Danmaku danmaku) { Config = config; Danmaku = danmaku; LineIndex = 0; IsScaled = SetIsScaled(); FontSize = SetFontSize(); MaxLength = SetMaxLength(); Width = SetWidth(); Height = SetHeight(); Horizontal = SetHorizontal(); Vertical = SetVertical(); Duration = SetDuration(); Leave = SetLeave(); } /// /// 根据弹幕样式自动创建对应的 Display 类 /// /// public static Display Factory(Config config, Danmaku danmaku) { Dictionary dict = new Dictionary { { "scroll", new ScrollDisplay(config, danmaku) }, { "top", new TopDisplay(config, danmaku) }, { "bottom", new BottomDisplay(config, danmaku) } }; return dict[danmaku.Style]; } /// /// 字体大小 /// 按用户自定义的字体大小来缩放 /// /// protected int SetFontSize() { if (IsScaled) { Console.WriteLine($"{Danmaku.SizeRatio}"); } return Utils.IntCeiling(Config.BaseFontSize * Danmaku.SizeRatio); } /// /// 字体是否被缩放过 /// /// protected bool SetIsScaled() { return !Math.Round(Danmaku.SizeRatio, 2).Equals(1.0); //return Danmaku.SizeRatio.Equals(1.0f); } /// /// 最长的行字符数 /// /// protected int SetMaxLength() { string[] lines = Danmaku.Content.Split('\n'); int maxLength = 0; foreach (string line in lines) { int length = Utils.DisplayLength(line); if (maxLength < length) { maxLength = length; } } return maxLength; } /// /// 整条字幕宽度 /// /// protected int SetWidth() { float charCount = MaxLength;// / 2; return Utils.IntCeiling(FontSize * charCount); } /// /// 整条字幕高度 /// /// protected int SetHeight() { int lineCount = Danmaku.Content.Split('\n').Length; return lineCount * FontSize; } /// /// 出现和消失的水平坐标位置 /// 默认在屏幕中间 /// /// protected virtual Tuple SetHorizontal() { int x = (int)Math.Floor(Config.ScreenWidth / 2.0); return Tuple.Create(x, x); } /// /// 出现和消失的垂直坐标位置 /// 默认在屏幕中间 /// /// protected virtual Tuple SetVertical() { int y = (int)Math.Floor(Config.ScreenHeight / 2.0); return Tuple.Create(y, y); } /// /// 整条字幕的显示时间 /// /// protected virtual int SetDuration() { int baseDuration = 3 + Config.TuneDuration; if (baseDuration <= 0) { baseDuration = 0; } float charCount = MaxLength / 2; int value; if (charCount < 6) { value = baseDuration + 1; } else if (charCount < 12) { value = baseDuration + 2; } else { value = baseDuration + 3; } return value; } /// /// 离开碰撞时间 /// /// protected virtual int SetLeave() { return (int)(Danmaku.Start + Duration); } /// /// 按照新的行号重新布局 /// /// public void Relayout(int lineIndex) { LineIndex = lineIndex; Horizontal = SetHorizontal(); Vertical = SetVertical(); } } /// /// 顶部 /// public class TopDisplay : Display { public TopDisplay(Config config, Danmaku danmaku) : base(config, danmaku) { Console.WriteLine("TopDisplay constructor."); } /// /// /// /// protected override Tuple SetVertical() { // 这里 y 坐标为 0 就是最顶行了 int y = LineIndex * Config.BaseFontSize; return Tuple.Create(y, y); } } /// /// 底部 /// public class BottomDisplay : Display { public BottomDisplay(Config config, Danmaku danmaku) : base(config, danmaku) { Console.WriteLine("BottomDisplay constructor."); } /// /// /// /// protected override Tuple SetVertical() { // 要让字幕不超出底部,减去高度 int y = Config.ScreenHeight - (LineIndex * Config.BaseFontSize) - Height; // 再减去自定义的底部边距 y -= Config.BottomMargin; return Tuple.Create(y, y); } } /// /// 滚动 /// public class ScrollDisplay : Display { public int Distance; public int Speed; public ScrollDisplay(Config config, Danmaku danmaku) : base() { Console.WriteLine("ScrollDisplay constructor."); Config = config; Danmaku = danmaku; LineIndex = 0; IsScaled = SetIsScaled(); FontSize = SetFontSize(); MaxLength = SetMaxLength(); Width = SetWidth(); Height = SetHeight(); Horizontal = SetHorizontal(); Vertical = SetVertical(); Distance = SetDistance(); Speed = SetSpeed(); Duration = SetDuration(); Leave = SetLeave(); } /// /// ASS 的水平位置参考点是整条字幕文本的中点 /// /// protected override Tuple SetHorizontal() { int x1 = Config.ScreenWidth + (int)Math.Floor(Width / 2.0); int x2 = 0 - (int)Math.Floor(Width / 2.0); return Tuple.Create(x1, x2); } protected override Tuple SetVertical() { int baseFontSize = Config.BaseFontSize; // 垂直位置,按基准字体大小算每一行的高度 int y = (LineIndex + 1) * baseFontSize; // 个别弹幕可能字体比基准要大,所以最上的一行还要避免挤出顶部屏幕 // 坐标不能小于字体大小 if (y < FontSize) { y = FontSize; } return Tuple.Create(y, y); } /// /// 字幕坐标点的移动距离 /// /// protected int SetDistance() { Tuple x = Horizontal; return x.Item1 - x.Item2; } /// /// 字幕每个字的移动的速度 /// /// protected int SetSpeed() { // 基准时间,就是每个字的移动时间 // 12 秒加上用户自定义的微调 int baseDuration = 12 + Config.TuneDuration; if (baseDuration <= 0) { baseDuration = 1; } return Utils.IntCeiling(Config.ScreenWidth / baseDuration); } /// /// 计算每条弹幕的显示时长,同步方式 /// 每个弹幕的滚动速度都一样,辨认度好,适合观看剧集类视频。 /// /// public int SyncDuration() { return Distance / Speed; } /// /// 计算每条弹幕的显示时长,异步方式 /// 每个弹幕的滚动速度都不一样,动态调整,辨认度低,适合观看 MTV 类视频。 /// /// public int AsyncDuration() { int baseDuration = 6 + Config.TuneDuration; if (baseDuration <= 0) { baseDuration = 0; } float charCount = MaxLength / 2; int value; if (charCount < 6) { value = (int)(baseDuration + charCount); } else if (charCount < 12) { value = baseDuration + (int)(charCount / 2); } else if (charCount < 24) { value = baseDuration + (int)(charCount / 3); } else { value = baseDuration + 10; } return value; } /// /// 整条字幕的移动时间 /// /// protected override int SetDuration() { string methodName = Config.LayoutAlgorithm.Substring(0, 1).ToUpper() + Config.LayoutAlgorithm.Substring(1); methodName += "Duration"; MethodInfo method = typeof(ScrollDisplay).GetMethod(methodName); if (method != null) { return (int)method.Invoke(this, null); } return 0; } /// /// 离开碰撞时间 /// /// protected override int SetLeave() { // 对于滚动样式弹幕来说,就是最后一个字符离开最右边缘的时间 // 坐标是字幕中点,在屏幕外和内各有半个字幕宽度 // 也就是跑过一个字幕宽度的路程 float duration = Width / Speed; return (int)(Danmaku.Start + duration); } } }