清理硬盘发现以前写过一个进行一半的代码,这次补全并从硬盘删掉。
格式说明来自 https://shansing.com/read/392/
krc解码并解压缩后得到一个字符串,例子:
[id:$00000000]
[ar:信乐团]
[ti:北京一夜]
[by:韩佯Τé]
[hash:766fe295bf2722a9ede2abdd61d580c1]
[total:278438]
[sign:大家去北京玩一夜吧!!!!]
[53883,3092]<0,632,0>One <632,784,0>Night <1416,372,0>in <1788,548,0>北<2336,755,0>京
[56675,3539]<0,560,0>我<560,416,0>留<976,392,0>下<1368,412,0>许<1780,392,0>多<2172,1366,0>情
[59914,2577]<0,549,0>不<549,276,0>管<825,252,0>你<1077,214,0>爱<1291,182,0>与<1473,212,0>不 <1685,887,0>爱
[62191,3344]<0,560,0>都<560,210,0>是<770,210,0>历<980,204,0>史<1184,202,0>的<1386,564,0>尘<1950,1387,0>埃
开头的几行就不用解释了,lrc也有。
其中快速匹配歌词的可能方式是靠计算歌曲文件的hash,以及匹配歌词与歌曲的total
歌词开始的行格式:
[此行开始时刻距0时刻的毫秒数,此行持续的毫秒数]<0,此字持续的毫秒数,0>歌<此字开始的时刻距此行开始时刻的毫秒数,此字持续的毫秒数,0>词<此字开始的时刻距此行开始时刻的毫秒数,此字持续的毫秒数,0>正<此字开始的时刻距此行开始时刻的毫秒数,此字持续的毫秒数,0>文
具体代码如下:
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.IO;
6 using System.IO.Compression;
7 using ICSharpCode.SharpZipLib.Zip.Compression;
8 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
9 using System.Diagnostics;
10
11 namespace KRC.KRCLib
12 {
13 public static class KRCFile
14 {
15 /// <summary>
16 /// 异或加密 密钥
17 /// </summary>
18 public static readonly char[] KRCFileXorKey = { '@', 'G', 'a', 'w', '^', '2', 't', 'G', 'Q', '6', '1', '-', 'Î', 'Ò', 'n', 'i' };
19
20 /// <summary>
21 /// KRC 文件头
22 /// </summary>
23 public static readonly char[] KRCFileHead = { 'k', 'r', 'c', '1' };
24
25 /// <summary>
26 /// KRC 文件头的字节
27 /// </summary>
28 public static readonly byte[] KRCFileHeadBytes = { 0x6B, 0x72, 0x63, 0x31 };
29
30
31 /// <summary>
32 /// 解码
33 /// </summary>
34 public static string DecodeFileToString(string krcFilePath)
35 {
36 //krc1
37 var headBytes = new byte[4];
38 byte[] encodedBytes;
39 byte[] zipedBytes;
40
41 using (var krcfs = new FileStream(krcFilePath, FileMode.Open))
42 {
43 encodedBytes = new byte[krcfs.Length - headBytes.Length];
44 zipedBytes = new byte[krcfs.Length - headBytes.Length];
45
46 //读文件头标记
47 krcfs.Read(headBytes, 0, headBytes.Length);
48
49 //读XOR加密的内容
50 krcfs.Read(encodedBytes, 0, encodedBytes.Length);
51
52 //关闭文件
53 krcfs.Close();
54 }
55
56 for (var i = 0; i < encodedBytes.Length; i++)
57 {
58 zipedBytes[i] = (byte)(encodedBytes[i] ^ KRCFileXorKey[i % 16]);
59 }
60
61 //前面3字节是 UTF-8 的 BOM
62 var unzipedBytes = Decompress(zipedBytes);
63
64 //编码器带有BOM输出时多了3字节,所以跳过开头的3字节bom
65 var text = RemoveBom(Encoding.UTF8.GetString(unzipedBytes));
66
67 return text;
68 }
69
70 /// <summary>
71 /// 编码到字节数组
72 /// </summary>
73 /// <param name="inText"></param>
74 /// <returns></returns>
75 public static byte[] EncodeStringToBytes(string inText)
76 {
77 //用默认的,编码时带有UTF-8的BOM
78 byte[] inbytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(inText)).ToArray();
79
80 byte[] zipedBytes = Compress(inbytes);
81
82 int encodedBytesLength = zipedBytes.Length;
83
84 var encodedBytes = new byte[zipedBytes.Length];
85
86
87 for (int i = 0; i < encodedBytesLength; i++)
88 {
89 int l = i % 16;
90
91 encodedBytes[i] = (byte)(zipedBytes[i] ^ KRCFileXorKey[l]);
92 }
93
94 byte[] byets = null;
95
96 using (var ms = new MemoryStream())
97 {
98 ms.Write(KRCFileHeadBytes, 0, KRCFileHeadBytes.Length);
99 ms.Write(encodedBytes, 0, encodedBytes.Length);
100 ms.Flush();
101 byets = ms.ToArray();
102 }
103
104 return byets;
105 }
106
107 /// <summary>
108 /// 移除UTF-8 BOM
109 /// </summary>
110 /// <param name="p"></param>
111 /// <returns></returns>
112 private static string RemoveBom(string p)
113 {
114 string bomMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
115 if (p.StartsWith(bomMarkUtf8))
116 p = p.Remove(0, bomMarkUtf8.Length);
117 return p.Replace("\0", "");
118 }
119
120 #region 压缩 解压缩
121 private static byte[] Compress(byte[] pBytes)
122 {
123 byte[] outdata = null;
124 using (var mMemory = new MemoryStream(pBytes))
125 using (var mStream = new DeflaterOutputStream(mMemory, new Deflater(Deflater.DEFAULT_COMPRESSION), 131072))
126 {
127 mStream.Write(pBytes, 0, pBytes.Length);
128 mStream.Flush();
129 mMemory.Flush();
130 outdata = mMemory.ToArray();
131 }
132 return outdata;
133 }
134
135 /// <summary>
136 /// 解压缩
137 /// </summary>
138 /// <param name="data"></param>
139 /// <returns></returns>
140 private static byte[] Decompress(byte[] data)
141 {
142 byte[] outdata = null;
143 using (var ms = new MemoryStream())
144 using (var inputStream = new InflaterInputStream(new MemoryStream(data), new Inflater(false)))
145 {
146 inputStream.CopyTo(ms);
147 ms.Flush();
148
149 outdata = ms.ToArray();
150 ms.Close();
151 }
152 return outdata;
153 }
154 #endregion
155
156
157 }
158 }
KRCFile
1 using System;
2 using System.CodeDom;
3 using System.Diagnostics;
4 using System.Text.RegularExpressions;
5
6 namespace KRC.KRCLib
7 {
8 /// <summary>
9 /// KRC文件行字符
10 /// </summary>
11 [DebuggerDisplay("{DebuggerDisplay}")]
12 public class KRCLyricsChar
13 {
14 /// <summary>
15 /// 字符
16 /// </summary>
17 public char Char { get; set; }
18
19 /// <summary>
20 /// 字符KRC字符串
21 /// </summary>
22 public string KRCCharString
23 {
24 get
25 {
26 return string.Format(@"<{0},{1},{2}>{3}", this.CharStart.TotalMilliseconds, this.CharDuring.TotalMilliseconds, 0, this.Char);
27 }
28 }
29
30 /// <summary>
31 /// 字符起始时间(计算时加上字符所属行的起始时间)
32 /// </summary>
33 public TimeSpan CharStart { get; set; }
34
35 /// <summary>
36 /// 字符时长
37 /// </summary>
38 public TimeSpan CharDuring { get; set; }
39
40 public KRCLyricsChar()
41 {
42 this.CharStart = TimeSpan.Zero;
43 this.CharDuring = TimeSpan.Zero;
44 }
45
46 public KRCLyricsChar(string krcCharString)
47 : this()
48 {
49 var chars = Regex.Match(krcCharString, @"<(\d+),(\d+),(\d+)>(.?)");
50
51 if (chars.Success)
52 {
53 if (chars.Groups.Count >= 4)
54 {
55 var charstart = chars.Groups[1].Value;
56 var charduring = chars.Groups[2].Value;
57 var unknowAlwaysZero = chars.Groups[3].Value;
58
59 this.CharStart = TimeSpan.FromMilliseconds(double.Parse(charstart));
60 this.CharDuring = TimeSpan.FromMilliseconds(double.Parse(charduring));
61
62 if (chars.Groups.Count >= 5)
63 {
64 var charchar = chars.Groups[4].Value;
65 this.Char = char.Parse(charchar);
66 }
67 else
68 {
69 this.Char = char.Parse(" ");
70 }
71 }
72 }
73 }
74
75 public string DebuggerDisplay
76 {
77 get
78 {
79 return string.Format(@"{0:hh\:mm\:ss\.fff} {1:hh\:mm\:ss\.fff} {2}", this.CharStart, this.CharDuring, this.Char);
80 }
81 }
82 }
83 }
KRCLyricsChar
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.Linq;
5 using System.Text.RegularExpressions;
6
7 namespace KRC.KRCLib
8 {
9 /// <summary>
10 /// KRC文件行
11 /// </summary>
12 [DebuggerDisplay("{DebuggerDisplay}")]
13 public class KRCLyricsLine
14 {
15 private readonly List<KRCLyricsChar> _chars = new List<KRCLyricsChar>();
16
17 /// <summary>
18 /// 行字符串
19 /// </summary>
20 public string KRCLineString
21 {
22 get
23 {
24 return string.Format(@"[{0},{1}]{2}", this.LineStart.TotalMilliseconds, this.LineDuring.TotalMilliseconds,
25 string.Join("", this.Chars.Select(x => x.KRCCharString)));
26 }
27 }
28
29 /// <summary>
30 /// 行开始事件
31 /// </summary>
32 public TimeSpan LineStart { get; set; }
33
34 /// <summary>
35 /// 行总时间
36 /// </summary>
37 public TimeSpan LineDuring
38 {
39 get
40 {
41 //计算行时间
42 var sum = this.Chars.Select(x => x.CharDuring.TotalMilliseconds).Sum();
43 return TimeSpan.FromMilliseconds(sum);
44 }
45 }
46
47 /// <summary>
48 /// 行内字符
49 /// </summary>
50
51 public List<KRCLyricsChar> Chars
52 {
53 get { return _chars; }
54 }
55
56 public KRCLyricsLine()
57 {
58 this.LineStart = TimeSpan.Zero;
59 }
60
61
62 public KRCLyricsLine(string krclinestring):this()
63 {
64 var regLineTime = new Regex(@"^\[(.*),(.*)\](.*)");
65
66 var m1 = regLineTime.Match(krclinestring);
67 if (m1.Success && m1.Groups.Count == 4)
68 {
69 var linestart = m1.Groups[1].Value;
70 var linelength = m1.Groups[2].Value;
71
72 this.LineStart = TimeSpan.FromMilliseconds(double.Parse(linestart));
73 //this.LineDuring = TimeSpan.FromMilliseconds(double.Parse(linelength));
74
75 var linecontent = m1.Groups[3].Value;
76
77 var chars = Regex.Matches(linecontent, @"<(\d+),(\d+),(\d+)>(.?)");
78
79 foreach (Match m in chars)
80 {
81 this.Chars.Add(new KRCLyricsChar(m.Value));
82 }
83 }
84 }
85
86 public string DebuggerDisplay
87 {
88 get
89 {
90 return string.Format(@"{0:hh\:mm\:ss\.fff} {1:hh\:mm\:ss\.fff} {2}", this.LineStart, this.LineDuring,
91 string.Join(",", this.Chars.Select(x => x.Char.ToString())));
92 }
93 }
94 }
95 }
KRCLyricsLine
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using System.Text;
6 using System.Text.RegularExpressions;
7
8 namespace KRC.KRCLib
9 {
10 /// <summary>
11 /// KRC歌词文件
12 /// </summary>
13 public class KRCLyrics
14 {
15 public List<KRCLyricsLine> Lines
16 {
17 get { return _lines; }
18 }
19
20 /// <summary>
21 /// 歌词文本
22 /// </summary>
23 public string KRCString { get; set; }
24
25 /// <summary>
26 /// ID (总是$00000000,意义未知)
27 /// </summary>
28 public string ID { get; set; }
29
30 /// <summary>
31 /// 艺术家
32 /// </summary>
33 public string Ar { get; set; }
34
35 /// <summary>
36 ///
37 /// </summary>
38 public string Al { get; set; }
39
40 /// <summary>
41 /// 标题
42 /// </summary>
43 public string Title { get; set; }
44
45 /// <summary>
46 /// 歌词文件作者
47 /// </summary>
48 public string By { get; set; }
49
50 /// <summary>
51 /// 歌曲文件Hash
52 /// </summary>
53 public string Hash { get; set; }
54
55 /// <summary>
56 /// 总时长
57 /// </summary>
58 public TimeSpan Total
59 {
60 get
61 {
62 //计算总时间=所有行时间
63 var sum = this.Lines.Select(x => x.LineDuring.TotalMilliseconds).Sum();
64 return TimeSpan.FromMilliseconds(sum);
65 }
66 }
67
68 /// <summary>
69 /// 偏移
70 /// </summary>
71 public TimeSpan Offset { get; set; }
72
73 private readonly List<KRCLyricsLine> _lines = new List<KRCLyricsLine>();
74 private readonly List<Tuple<Regex, Action<string>>> _properties;
75 private readonly Regex _regGetValueFromKeyValuePair = new Regex(@"\[(.*):(.*)\]");
76
77 /// <summary>
78 /// 默认构造
79 /// </summary>
80 public KRCLyrics()
81 {
82 //this.Total = TimeSpan.Zero;
83 this.Offset = TimeSpan.Zero;
84
85 this._properties = new List<Tuple<Regex, Action<string>>>()
86 {
87 new Tuple<Regex, Action<string>>(new Regex("\\[id:[^\\]]+\\]"), (s) => { this.ID = s; }),
88 new Tuple<Regex, Action<string>>(new Regex("\\[al:[^\\n]+\\n"), (s) => { this.Al = s; }),
89 new Tuple<Regex, Action<string>>(new Regex("\\[ar:[^\\]]+\\]"), (s) => { this.Ar = s; }),
90 new Tuple<Regex, Action<string>>(new Regex("\\[ti:[^\\]]+\\]"), (s) => { this.Title = s; }),
91 new Tuple<Regex, Action<string>>(new Regex("\\[hash:[^\\n]+\\n"), (s) => { this.Hash = s; }),
92 new Tuple<Regex, Action<string>>(new Regex("\\[by:[^\\n]+\\n"), (s) => { this.By = s; }),
93 new Tuple<Regex, Action<string>>(new Regex("\\[total:[^\\n]+\\n"), (s) =>
94 {
95 //this.Total = TimeSpan.FromMilliseconds(double.Parse(s));
96 }),
97 new Tuple<Regex, Action<string>>(new Regex("\\[offset:[^\\n]+\\n"), (s) =>
98 {
99 this.Offset = TimeSpan.FromMilliseconds(double.Parse(s));
100 }),
101 };
102 }
103
104 /// <summary>
105 /// 构造
106 /// </summary>
107 /// <param name="krcstring">KRC字符文本</param>
108 private KRCLyrics(string krcstring):this()
109 {
110 this.KRCString = krcstring;
111 this.LoadProperties();
112 this.LoadLines();
113 }
114
115 /// <summary>
116 /// 加载KRC属性
117 /// </summary>
118 private void LoadProperties()
119 {
120 foreach (var prop in _properties)
121 {
122 var m = prop.Item1.Match(this.KRCString);
123 if (m.Success)
124 {
125 var mm = _regGetValueFromKeyValuePair.Match(m.Value);
126
127 if (mm.Success && mm.Groups.Count == 3)
128 {
129 prop.Item2(mm.Groups[2].Value);
130 }
131 }
132 }
133 }
134
135 /// <summary>
136 /// 加载KRC所有行数据
137 /// </summary>
138 private void LoadLines()
139 {
140 var linesMachCollection = Regex.Matches(this.KRCString, @"\[\d{1,}[^\n]+\n");
141 foreach (Match m in linesMachCollection)
142 {
143 this.Lines.Add(new KRCLyricsLine(m.Value));
144 }
145 }
146
147 /// <summary>
148 /// 保存到文件
149 /// </summary>
150 /// <param name="outputFilePath"></param>
151 public void SaveToFile(string outputFilePath)
152 {
153 var sb = new StringBuilder();
154 sb.AppendLine(string.Format("[id:{0}]", this.ID));
155
156
157 if (!string.IsNullOrEmpty(this.Al))
158 {
159 sb.AppendLine(string.Format("[al:{0}]", this.Al));
160 }
161
162 if (!string.IsNullOrEmpty(this.Ar))
163 {
164 sb.AppendLine(string.Format("[ar:{0}]", this.Ar));
165 }
166
167 if (!string.IsNullOrEmpty(this.Title))
168 {
169 sb.AppendLine(string.Format("[ti:{0}]", this.Title));
170 }
171
172 if (!string.IsNullOrEmpty(this.Hash))
173 {
174 sb.AppendLine(string.Format("[hash:{0}]", this.Hash));
175 }
176
177 if (!string.IsNullOrEmpty(this.By))
178 {
179 sb.AppendLine(string.Format("[by:{0}]", this.By));
180 }
181
182 if (this.Total!= TimeSpan.Zero)
183 {
184 sb.AppendLine(string.Format("[total:{0}]", this.Total.TotalMilliseconds));
185 }
186
187 if (this.Offset != TimeSpan.Zero)
188 {
189 sb.AppendLine(string.Format("[offset:{0}]", this.Offset.TotalMilliseconds));
190 }
191
192
193 foreach (var line in this.Lines)
194 {
195 sb.AppendLine(line.KRCLineString);
196 }
197
198
199 var bytes = KRCFile.EncodeStringToBytes(sb.ToString());
200
201
202 File.WriteAllBytes(outputFilePath, bytes);
203
204 }
205
206 /// <summary>
207 /// 从文件加载
208 /// </summary>
209 /// <param name="inputFilePath"></param>
210 /// <returns></returns>
211 public static KRCLyrics LoadFromFile(string inputFilePath)
212 {
213 var str = KRCFile.DecodeFileToString(inputFilePath);
214
215 return LoadFromString(str);
216 }
217
218 /// <summary>
219 /// 从文本加载
220 /// </summary>
221 /// <param name="krcstring"></param>
222 /// <returns></returns>
223 public static KRCLyrics LoadFromString(string krcstring)
224 {
225 var aa = new KRCLyrics(krcstring);
226 return aa;
227 }
228 }
229 }
KRCLyrics
1 using System;
2 using System.Collections.Generic;
3 using System.ComponentModel;
4 using System.Linq;
5 using System.Runtime.InteropServices;
6 using System.Text;
7 using System.Text.RegularExpressions;
8 using KRC.KRCLib;
9
10 namespace KRC.Test
11 {
12 class Program
13 {
14 static void Main(string[] args)
15 {
16 string inputFile = @"杨钰莹.桃花运-b0c4014bd991a6a637445defa56822f9.krc";
17 string outputFile = @"123.krc";
18 KRCLyrics krc = KRCLyrics.LoadFromFile(inputFile);
19 Console.WriteLine("解码 [{0}] 完毕。", inputFile);
20 krc.SaveToFile(outputFile);
21 Console.WriteLine("另存为 [{0}] 完毕。", outputFile);
22 Console.ReadLine();
23 }
24 }
25 }