酷狗 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>文
具体代码如下:
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();
}
}
}
酷狗 KRC 文件的解析的更多相关文章
- 酷狗.kgtemp文件加密算法逆向
该帖转载于孤心浪子--http://www.cnblogs.com/KMBlog/p/6877752.html 酷狗音乐上的一些歌曲是不能免费下载的,然而用户仍然可以离线试听,这说明有缓存文件,并且极 ...
- 笨笨-歌词伴侣V1.2(酷狗KRC转LRC,LRC歌词批量下载)
最近由于某些热心博友在我CSDN博客上使用了我的软件,提出了一些建议,看到自己的成果有人使用并且提出了一些建议,焉有不高兴之理!刚好碰上最近研究UI界面,有了一个初步的框架,就顺手将歌词相关功能集 ...
- Python脚本之Lrc歌词去时间轴转Txt文件,附带酷狗音乐APP关联已有krc歌词
一.Lrc歌词去时间轴转Txt文件 环境:Python2.7.x, Mac(Windows需装cygwin环境,当然你也可以自己改代码,Python新手,勿喷) # -*- coding: UTF-8 ...
- 仿酷狗音乐播放器开发日志二十七 用ole为窗体增加文件拖动功能(附源码)
转载请说明原出处,谢谢~~ 中秋到了,出去玩了几天.今天把仿酷狗程序做了收尾,已经开发完成了,下一篇博客把完结的情况说一下.在这篇博客里说一下使用OLE为窗体增加文件拖拽的功能.使用播放器,我更喜欢直 ...
- Python爬取酷狗飙升榜前十首(100)首,写入CSV文件
酷狗飙升榜,写入CSV文件 爬取酷狗音乐飙升榜的前十首歌名.歌手.时间,是一个很好的爬取网页内容的例子,对爬虫不熟悉的读者可以根据这个例子熟悉爬虫是如何爬取网页内容的. 需要用到的库:requests ...
- 在线音乐播放器-----酷狗音乐api接口抓取
首先身为一个在线音乐播放器,需要前端和数据库的搭配使用. 在数据库方面,我们没有办法制作,首先是版权问题,再加上数据量.所以我们需要借用其他网络播放器的数据库. 但是这些在线播放器,如百度,酷狗,酷我 ...
- 开源小工具 酷狗、网易音乐缓存文件转mp3工具
发布一个开源小工具,支持将酷狗和网易云音乐的缓存文件转码为MP3文件. 以前写过kgtemp文件转mp3工具,正好当前又有网易云音乐缓存文件需求,因此就在原来小工具的基础上做了一点修改,增加了对网易云 ...
- Python实例---爬去酷狗音乐
项目一:获取酷狗TOP 100 http://www.kugou.com/yy/rank/home/1-8888.html 排名 文件&&歌手 时长 效果: 附源码: import t ...
- Java爬虫系列之实战:爬取酷狗音乐网 TOP500 的歌曲(附源码)
在前面分享的两篇随笔中分别介绍了HttpClient和Jsoup以及简单的代码案例: Java爬虫系列二:使用HttpClient抓取页面HTML Java爬虫系列三:使用Jsoup解析HTML 今天 ...
随机推荐
- List集合基于某个字段排序
using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Product { publ ...
- Robot Framework入门学习1 安装部署详解
安装注意: 目前Robot framework-ride不支持python3,安装时请下载python2.7版本. Robot Framework安装时出现了一点小问题,网上没有找到直接的介绍,现将安 ...
- 用数组取到当前栈内的ViewController 并根据下标取某个ViewController
NSArray *navArray = self.navigationController.viewControllers; TabsViewController *tabsVC = [[TabsVi ...
- D3(Data-Driven-Document)中的一些细节
不定期更新,给自己看,如果能帮到别人,我也很开心. 1)好像 function中默认的两个参数d,和i,如果只有i,则i实际上是d 的内容. lineG.selectAll("line&qu ...
- IIS报错 试图加载格式不正确 的程序集解决办法
一般都是由于系统位数不一致导致的 方法:64位启用32应用程序兼容(推荐) 思路就是把程序池设置为对应的应用程序(即到底要不要启用32位应用程序)
- mybatis报invalue types()错误
错误信息: Cause: org.apache.ibatis.reflection.ReflectionException: Error instantiating class cn.qd.mybat ...
- Ajax参数详解
1.url: 要求为String类型的参数,(默认为当前页地址)发送请求的地址. 2.type: 要求为String类型的参数,请求方式(post或get)默认为get.注意其他http请求方法,例如 ...
- 【分块打表】bzoj1026 [SCOI2009]windy数
#include<cstdio> using namespace std; #define BN 380000 const int table[]={0,79595,158824,2021 ...
- Traceroute命令原理(转)
Traceroute命令基本功能 该命令用于测试两个TCP/IP系统之间的网络层连通性和显示传输路径中每一跳地址,又称为路径跟踪,如果Traceroute命令测试成功,我们能够观察到从源主机到目的主机 ...
- 使用sklearn优雅地进行数据挖掘
目录 1 使用sklearn进行数据挖掘 1.1 数据挖掘的步骤 1.2 数据初貌 1.3 关键技术2 并行处理 2.1 整体并行处理 2.2 部分并行处理3 流水线处理4 自动化调参5 持久化6 回 ...