酷狗 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 今天 ...
随机推荐
- zabbix nagios 类nagios 之 不以性能为前提的开发和监控都是瞎扯淡
从最初的nagios到现在强大的zabbix 3.0,我想说,不以性能为前提的开发和监控都是瞎扯淡? 首先我对这两款监控软件的认识: zabbix,很多企业都在用,给人的感觉是很炫,不过我个人觉得虽然 ...
- TCP协议学习记录 (二) Ping程序
简单的实现了一个ping程序,没有做icmp差错报文的检查. 支持自定义字节数,支持发包个数 #pragma pack(4) #define ECHO_REQUEST 8 #define DATASI ...
- CS0012: 类型“System.Data.Objects.DataClasses.EntityObject”在未被引用的程序集中定义
entity framework,没在view引用 实体对象时,一直没问题,引用后爆出这个错误来 CS0012: 类型"System.Data.Objects.DataClasses.En ...
- HTML超标记语言
Html超文本标记语言,负责描绘Web世界的骨架. 〇.工具 http;//www.w3cchool.com.cn 一.Tim Bemers Lee 万维网之父: Html设计者: W3C创始人: ...
- opencv 处女作
显示一幅图:主要是运用功能:imread namedWindow imshowimread:从字面意思我们就可以看懂,用来读取图片的:namedWindow:显然,我们也可以看到这是用来命名窗口名称的 ...
- Flashback for MySQL 5.7
实现原理 flashback的概念最早出现于Oracle数据库,用于快速恢复用户的误操作. flashback for MySQL用于恢复由DML语句引起的误操作,目前不支持DDL语句.例如下面的语句 ...
- ListView13添加2
Columns=//添加列总行的标题 GridLines=true //显示网格线 添加数据------------- listView1.Items.Add("123123123" ...
- 关于window.open()中文传值乱码问题的解决方法
最近组长叫我做一个把查询出的数据用POI导出Excel的功能,我前台把要查询的字段通过如下传回后台时,发现出现了乱码问题 window.open('user!export?name='+name); ...
- hdoj 2039 三角形
Problem Description 给定三条边,请你判断一下能不能组成一个三角形. Input 输入数据第一行包含一个数M,接下有M行,每行一个实例,包含三个正数A,B,C.其中A,B,C & ...
- 1032: [JSOI2007]祖码Zuma
链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1032 Description 这是一个流行在Jsoi的游戏,名称为祖玛.精致细腻的背景,外加神 ...