酷狗 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 今天 ...
随机推荐
- 我 && yii2(日志埋点,邮件提醒)
今天试着把yii2 的日志,如果发送邮件的形式实现,具体实现如下 1.环境介绍 lnmp php5.6, mysql5.5, lnmp1.2 yii2-advanced 2.配置文件的编写 在fron ...
- Smali语法编程
Smali背景: Smali,Baksmali分别是指安卓系统里的Java虚拟机(Dalvik)所使用的一种.dex格式文件的汇编器,反汇编器.其语法是一种宽松式的Jasmin/dedexer语法,而 ...
- 转:HIBERNATE一些_方法_@注解_代码示例---写的非常好
HIBERNATE一些_方法_@注解_代码示例操作数据库7步骤 : 1 创建一个SessionFactory对象 2 创建Session对象 3 开启事务Transaction : hibernate ...
- checkbox全选-取消-再全选没有显示问题
源码: <input type="checkbox" id="cleckAll" />全选 <div class="list&quo ...
- windows 8.1 下蓝屏报错:SYSTEM_SERVICE_EXCEPTION(NETIO.SYS)的解决办法
大概2周前,电脑突然蓝屏了,我上网查了一下解决办法,因为大部分内容是英文的,所以我只大概看了下,看到这个问题好像是由于软件冲突造成的,于是就把小红伞去掉了,而那天电脑也真的没有再蓝屏(之前大 ...
- sqlite本地保存数据
package com.cesecsh.ics.database; import android.content.Context; import android.database.Cursor; im ...
- 关于C++的递归(以汉诺塔为例)
关于C++,hanoi塔的递归问题一直是个经典问题,我们学习数据结构的时候也会时常用到, 因为它的时间复杂度和空间复杂度都很高,我们在实际的应用中不推荐使用这种算法,移动n个盘子, 需要2的n次幂减一 ...
- Web程序的运行原理及流程(一)
自己做Web程序的开发也有两年多了 从最开始跟风学框架 到第一用上框架的欣喜若狂 我相信每个程序员都是这样过来的 在大学学习一门语言 学会后往往很想做一个实际的项目出来 我当时第一次做WEB项目看 ...
- sql server convert 日期
),) --2016/11 ),) --2016-11-03 17:46:47
- Android 图片添加水印图片或者文字
给图片添加水印的基本思路都是载入原图,添加文字或者载入水印图片,保存图片这三个部分 添加水印图片: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...