酷狗 KRC 文件的解析

时间:2021-06-30 12:11:44

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

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

具体代码如下:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System.Diagnostics; namespace KRC.KRCLib
{
public static class KRCFile
{
/// <summary>
/// 异或加密 密钥
/// </summary>
public static readonly char[] KRCFileXorKey = { '@', 'G', 'a', 'w', '^', '', 't', 'G', 'Q', '', '', '-', 'Î', 'Ò', 'n', 'i' }; /// <summary>
/// KRC 文件头
/// </summary>
public static readonly char[] KRCFileHead = { 'k', 'r', 'c', '' }; /// <summary>
/// KRC 文件头的字节
/// </summary>
public static readonly byte[] KRCFileHeadBytes = { 0x6B, 0x72, 0x63, 0x31 }; /// <summary>
/// 解码
/// </summary>
public static string DecodeFileToString(string krcFilePath)
{
//krc1
var headBytes = new byte[];
byte[] encodedBytes;
byte[] zipedBytes; using (var krcfs = new FileStream(krcFilePath, FileMode.Open))
{
encodedBytes = new byte[krcfs.Length - headBytes.Length];
zipedBytes = new byte[krcfs.Length - headBytes.Length]; //读文件头标记
krcfs.Read(headBytes, , headBytes.Length); //读XOR加密的内容
krcfs.Read(encodedBytes, , encodedBytes.Length); //关闭文件
krcfs.Close();
} for (var i = ; i < encodedBytes.Length; i++)
{
zipedBytes[i] = (byte)(encodedBytes[i] ^ KRCFileXorKey[i % ]);
} //前面3字节是 UTF-8 的 BOM
var unzipedBytes = Decompress(zipedBytes); //编码器带有BOM输出时多了3字节,所以跳过开头的3字节bom
var text = RemoveBom(Encoding.UTF8.GetString(unzipedBytes)); return text;
} /// <summary>
/// 编码到字节数组
/// </summary>
/// <param name="inText"></param>
/// <returns></returns>
public static byte[] EncodeStringToBytes(string inText)
{
//用默认的,编码时带有UTF-8的BOM
byte[] inbytes = Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(inText)).ToArray(); byte[] zipedBytes = Compress(inbytes); int encodedBytesLength = zipedBytes.Length; var encodedBytes = new byte[zipedBytes.Length]; for (int i = ; i < encodedBytesLength; i++)
{
int l = i % ; encodedBytes[i] = (byte)(zipedBytes[i] ^ KRCFileXorKey[l]);
} byte[] byets = null; using (var ms = new MemoryStream())
{
ms.Write(KRCFileHeadBytes, , KRCFileHeadBytes.Length);
ms.Write(encodedBytes, , encodedBytes.Length);
ms.Flush();
byets = ms.ToArray();
} return byets;
} /// <summary>
/// 移除UTF-8 BOM
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
private static string RemoveBom(string p)
{
string bomMarkUtf8 = Encoding.UTF8.GetString(Encoding.UTF8.GetPreamble());
if (p.StartsWith(bomMarkUtf8))
p = p.Remove(, bomMarkUtf8.Length);
return p.Replace("\0", "");
} #region 压缩 解压缩
private static byte[] Compress(byte[] pBytes)
{
byte[] outdata = null;
using (var mMemory = new MemoryStream(pBytes))
using (var mStream = new DeflaterOutputStream(mMemory, new Deflater(Deflater.DEFAULT_COMPRESSION), ))
{
mStream.Write(pBytes, , pBytes.Length);
mStream.Flush();
mMemory.Flush();
outdata = mMemory.ToArray();
}
return outdata;
} /// <summary>
/// 解压缩
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
private static byte[] Decompress(byte[] data)
{
byte[] outdata = null;
using (var ms = new MemoryStream())
using (var inputStream = new InflaterInputStream(new MemoryStream(data), new Inflater(false)))
{
inputStream.CopyTo(ms);
ms.Flush(); outdata = ms.ToArray();
ms.Close();
}
return outdata;
}
#endregion }
}

KRCFile

 using System;
using System.CodeDom;
using System.Diagnostics;
using System.Text.RegularExpressions; namespace KRC.KRCLib
{
/// <summary>
/// KRC文件行字符
/// </summary>
[DebuggerDisplay("{DebuggerDisplay}")]
public class KRCLyricsChar
{
/// <summary>
/// 字符
/// </summary>
public char Char { get; set; } /// <summary>
/// 字符KRC字符串
/// </summary>
public string KRCCharString
{
get
{
return string.Format(@"<{0},{1},{2}>{3}", this.CharStart.TotalMilliseconds, this.CharDuring.TotalMilliseconds, , this.Char);
}
} /// <summary>
/// 字符起始时间(计算时加上字符所属行的起始时间)
/// </summary>
public TimeSpan CharStart { get; set; } /// <summary>
/// 字符时长
/// </summary>
public TimeSpan CharDuring { get; set; } public KRCLyricsChar()
{
this.CharStart = TimeSpan.Zero;
this.CharDuring = TimeSpan.Zero;
} public KRCLyricsChar(string krcCharString)
: this()
{
var chars = Regex.Match(krcCharString, @"<(\d+),(\d+),(\d+)>(.?)"); if (chars.Success)
{
if (chars.Groups.Count >= )
{
var charstart = chars.Groups[].Value;
var charduring = chars.Groups[].Value;
var unknowAlwaysZero = chars.Groups[].Value; this.CharStart = TimeSpan.FromMilliseconds(double.Parse(charstart));
this.CharDuring = TimeSpan.FromMilliseconds(double.Parse(charduring)); if (chars.Groups.Count >= )
{
var charchar = chars.Groups[].Value;
this.Char = char.Parse(charchar);
}
else
{
this.Char = char.Parse(" ");
}
}
}
} public string DebuggerDisplay
{
get
{
return string.Format(@"{0:hh\:mm\:ss\.fff} {1:hh\:mm\:ss\.fff} {2}", this.CharStart, this.CharDuring, this.Char);
}
}
}
}

KRCLyricsChar

 using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions; namespace KRC.KRCLib
{
/// <summary>
/// KRC文件行
/// </summary>
[DebuggerDisplay("{DebuggerDisplay}")]
public class KRCLyricsLine
{
private readonly List<KRCLyricsChar> _chars = new List<KRCLyricsChar>(); /// <summary>
/// 行字符串
/// </summary>
public string KRCLineString
{
get
{
return string.Format(@"[{0},{1}]{2}", this.LineStart.TotalMilliseconds, this.LineDuring.TotalMilliseconds,
string.Join("", this.Chars.Select(x => x.KRCCharString)));
}
} /// <summary>
/// 行开始事件
/// </summary>
public TimeSpan LineStart { get; set; } /// <summary>
/// 行总时间
/// </summary>
public TimeSpan LineDuring
{
get
{
//计算行时间
var sum = this.Chars.Select(x => x.CharDuring.TotalMilliseconds).Sum();
return TimeSpan.FromMilliseconds(sum);
}
} /// <summary>
/// 行内字符
/// </summary> public List<KRCLyricsChar> Chars
{
get { return _chars; }
} public KRCLyricsLine()
{
this.LineStart = TimeSpan.Zero;
} public KRCLyricsLine(string krclinestring):this()
{
var regLineTime = new Regex(@"^\[(.*),(.*)\](.*)"); var m1 = regLineTime.Match(krclinestring);
if (m1.Success && m1.Groups.Count == )
{
var linestart = m1.Groups[].Value;
var linelength = m1.Groups[].Value; this.LineStart = TimeSpan.FromMilliseconds(double.Parse(linestart));
//this.LineDuring = TimeSpan.FromMilliseconds(double.Parse(linelength)); var linecontent = m1.Groups[].Value; var chars = Regex.Matches(linecontent, @"<(\d+),(\d+),(\d+)>(.?)"); foreach (Match m in chars)
{
this.Chars.Add(new KRCLyricsChar(m.Value));
}
}
} public string DebuggerDisplay
{
get
{
return string.Format(@"{0:hh\:mm\:ss\.fff} {1:hh\:mm\:ss\.fff} {2}", this.LineStart, this.LineDuring,
string.Join(",", this.Chars.Select(x => x.Char.ToString())));
}
}
}
}

KRCLyricsLine

 using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions; namespace KRC.KRCLib
{
/// <summary>
/// KRC歌词文件
/// </summary>
public class KRCLyrics
{
public List<KRCLyricsLine> Lines
{
get { return _lines; }
} /// <summary>
/// 歌词文本
/// </summary>
public string KRCString { get; set; } /// <summary>
/// ID (总是$00000000,意义未知)
/// </summary>
public string ID { get; set; } /// <summary>
/// 艺术家
/// </summary>
public string Ar { get; set; } /// <summary>
///
/// </summary>
public string Al { get; set; } /// <summary>
/// 标题
/// </summary>
public string Title { get; set; } /// <summary>
/// 歌词文件作者
/// </summary>
public string By { get; set; } /// <summary>
/// 歌曲文件Hash
/// </summary>
public string Hash { get; set; } /// <summary>
/// 总时长
/// </summary>
public TimeSpan Total
{
get
{
//计算总时间=所有行时间
var sum = this.Lines.Select(x => x.LineDuring.TotalMilliseconds).Sum();
return TimeSpan.FromMilliseconds(sum);
}
} /// <summary>
/// 偏移
/// </summary>
public TimeSpan Offset { get; set; } private readonly List<KRCLyricsLine> _lines = new List<KRCLyricsLine>();
private readonly List<Tuple<Regex, Action<string>>> _properties;
private readonly Regex _regGetValueFromKeyValuePair = new Regex(@"\[(.*):(.*)\]"); /// <summary>
/// 默认构造
/// </summary>
public KRCLyrics()
{
//this.Total = TimeSpan.Zero;
this.Offset = TimeSpan.Zero; this._properties = new List<Tuple<Regex, Action<string>>>()
{
new Tuple<Regex, Action<string>>(new Regex("\\[id:[^\\]]+\\]"), (s) => { this.ID = s; }),
new Tuple<Regex, Action<string>>(new Regex("\\[al:[^\\n]+\\n"), (s) => { this.Al = s; }),
new Tuple<Regex, Action<string>>(new Regex("\\[ar:[^\\]]+\\]"), (s) => { this.Ar = s; }),
new Tuple<Regex, Action<string>>(new Regex("\\[ti:[^\\]]+\\]"), (s) => { this.Title = s; }),
new Tuple<Regex, Action<string>>(new Regex("\\[hash:[^\\n]+\\n"), (s) => { this.Hash = s; }),
new Tuple<Regex, Action<string>>(new Regex("\\[by:[^\\n]+\\n"), (s) => { this.By = s; }),
new Tuple<Regex, Action<string>>(new Regex("\\[total:[^\\n]+\\n"), (s) =>
{
//this.Total = TimeSpan.FromMilliseconds(double.Parse(s));
}),
new Tuple<Regex, Action<string>>(new Regex("\\[offset:[^\\n]+\\n"), (s) =>
{
this.Offset = TimeSpan.FromMilliseconds(double.Parse(s));
}),
};
} /// <summary>
/// 构造
/// </summary>
/// <param name="krcstring">KRC字符文本</param>
private KRCLyrics(string krcstring):this()
{
this.KRCString = krcstring;
this.LoadProperties();
this.LoadLines();
} /// <summary>
/// 加载KRC属性
/// </summary>
private void LoadProperties()
{
foreach (var prop in _properties)
{
var m = prop.Item1.Match(this.KRCString);
if (m.Success)
{
var mm = _regGetValueFromKeyValuePair.Match(m.Value); if (mm.Success && mm.Groups.Count == )
{
prop.Item2(mm.Groups[].Value);
}
}
}
} /// <summary>
/// 加载KRC所有行数据
/// </summary>
private void LoadLines()
{
var linesMachCollection = Regex.Matches(this.KRCString, @"\[\d{1,}[^\n]+\n");
foreach (Match m in linesMachCollection)
{
this.Lines.Add(new KRCLyricsLine(m.Value));
}
} /// <summary>
/// 保存到文件
/// </summary>
/// <param name="outputFilePath"></param>
public void SaveToFile(string outputFilePath)
{
var sb = new StringBuilder();
sb.AppendLine(string.Format("[id:{0}]", this.ID)); if (!string.IsNullOrEmpty(this.Al))
{
sb.AppendLine(string.Format("[al:{0}]", this.Al));
} if (!string.IsNullOrEmpty(this.Ar))
{
sb.AppendLine(string.Format("[ar:{0}]", this.Ar));
} if (!string.IsNullOrEmpty(this.Title))
{
sb.AppendLine(string.Format("[ti:{0}]", this.Title));
} if (!string.IsNullOrEmpty(this.Hash))
{
sb.AppendLine(string.Format("[hash:{0}]", this.Hash));
} if (!string.IsNullOrEmpty(this.By))
{
sb.AppendLine(string.Format("[by:{0}]", this.By));
} if (this.Total!= TimeSpan.Zero)
{
sb.AppendLine(string.Format("[total:{0}]", this.Total.TotalMilliseconds));
} if (this.Offset != TimeSpan.Zero)
{
sb.AppendLine(string.Format("[offset:{0}]", this.Offset.TotalMilliseconds));
} foreach (var line in this.Lines)
{
sb.AppendLine(line.KRCLineString);
} var bytes = KRCFile.EncodeStringToBytes(sb.ToString()); File.WriteAllBytes(outputFilePath, bytes); } /// <summary>
/// 从文件加载
/// </summary>
/// <param name="inputFilePath"></param>
/// <returns></returns>
public static KRCLyrics LoadFromFile(string inputFilePath)
{
var str = KRCFile.DecodeFileToString(inputFilePath); return LoadFromString(str);
} /// <summary>
/// 从文本加载
/// </summary>
/// <param name="krcstring"></param>
/// <returns></returns>
public static KRCLyrics LoadFromString(string krcstring)
{
var aa = new KRCLyrics(krcstring);
return aa;
}
}
}

KRCLyrics

 using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using KRC.KRCLib; namespace KRC.Test
{
class Program
{
static void Main(string[] args)
{
string inputFile = @"杨钰莹.桃花运-b0c4014bd991a6a637445defa56822f9.krc";
string outputFile = @"123.krc";
KRCLyrics krc = KRCLyrics.LoadFromFile(inputFile);
Console.WriteLine("解码 [{0}] 完毕。", inputFile);
krc.SaveToFile(outputFile);
Console.WriteLine("另存为 [{0}] 完毕。", outputFile);
Console.ReadLine();
}
}
}