酷狗 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>文

 

具体代码如下:

 

酷狗 KRC 文件的解析
  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

 

酷狗 KRC 文件的解析
 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
酷狗 KRC 文件的解析
 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
酷狗 KRC 文件的解析
  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 }

 

你可能感兴趣的:(文件)