酷狗 KRC 文件的解析

清理硬盘发现以前写过一个进行一半的代码,这次补全并从硬盘删掉。

格式说明来自 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 }

 

posted @ 2014-11-18 16:40  BinSys  阅读(3046)  评论(1编辑  收藏  举报