Board logo

标题: 按照歌手名分类整理文件的批处理分析 [打印本页]

作者: namejm    时间: 2010-1-19 23:19     标题: 按照歌手名分类整理文件的批处理分析

  这是我在CCF上帮别人解答的一个求助帖(原帖在此:https://bbs.et8.net/bbs/showthread.php?t=989473),感觉这个案例会对很多人有用,于是做了这个教程,详细讲解其中的代码。

  以下是楼主的需求(有删节合并排版,只保留与本教程相关之处):
  发这个贴是因为最近买了一个礼光歌库, 1TB, 3万多首歌, 歌库文件形式是: 每一首歌都有"歌手"-"歌名".mkv文件和同名的.ksc文件(索引文件). 可是这个歌库目录分类比较混乱, 都是以数字来命名目录, 而且同一个歌手的歌不一定在同一个目录里等. 虽然暂时不影响使用(因为礼光是根据ksc文件来索引歌曲文件的, 不受歌曲在磁盘上目录情况的影响), 但是不利于将来手工更新歌曲. 于是开始尝试重新整理这个歌库. 基本的文件目录结构是参考礼光系统里的分类:
  1. 先分语种, 有以下目录: 国语, 粤语, 台语, 闽南话, 韩语, 日语, 英语
  2. 每个语种下再分以下目录: 流行,戏曲,民歌,儿歌,生日歌,革命歌,怀旧歌,迪斯科,情歌对唱,影视金曲
  3. "流行"目录是大头, 下面再分以下目录: 男歌手, 女歌手, 乐队组合.
  计划第一步是把所有的文件复制到同一个目录下, 按照歌手名生成新文件夹, 并把那些mkv, ksc文件依照歌手名移到相应的歌手目录里, 同时删除文件名里面的歌手名和分隔符-.
计划第二步是希望把歌手目录分类整理到下面三个目录里:
  "男歌手", "女歌手" 和 "乐队组合".
  我初步的想法是先准备三个文本文件, 分别是上面三个分类的名单, 然后通过批处理对歌手目录和名单进行比较, 符合的就move到相应的目录.

  对于第一步的解决方案:

  根据楼主的描述,这些.mkv或.ksc文件的名称都很有规律,都是按照"歌手-歌名"的格式命名的,因此,只需要以"-"做分隔符号,分别提取它前后的字符串即可;而获取.mkv和.ksc文件名,使用 dir /a-d /b *.mkv *.ksc即可,于是,就有了如下代码(第一个“-”之前必须为歌手名,歌曲名中允许带“-”号):
  1. @echo off
  2. for /f "tokens=1* delims=-" %%i in ('dir /a-d /b *.mkv *.ksc') do (
  3.     md "%%i" 2>nul
  4.     ren "%%i-%%j" "%%j"
  5.     move "%%j" "%%i"
  6. )
  7. pause
复制代码
  有一些细节需要注意:

  1、使用 tokens=1* 截取第一个短横杠之后的所有字符串,而不是用 tokens=1,2来截取以短横杠分隔的第一节和第二节字符串 ,主要是为了防止歌名中出现短横杠;
  2、创建文件夹的时候,无需事先判断文件夹是否已经存在,直接使用 md "%%i" 2>nul 即可,它可以创建不存在的文件夹,并可以保持已经存在的文件夹不发生任何改变;当文件夹已经存在的时候,需要使用 2>nul 来屏蔽出错信息;

  当我的代码发出来之后,楼主又增加了一条需求:
  忘记了一个地方, 能不能加一个功能, 不然歌曲文件不能被索引:在移动文件之前, 先修改ksc文件的内容. ksc文件是一个文本文件, 作为mkv歌曲文件的索引文件. 需要将里面的一句

karaoke.CommonVideo := '周杰伦-稻香.MKV';

修改为

karaoke.CommonVideo := '稻香.MKV';

ksc文件的内容一般是这样的

karaoke.tag('歌名', '稻香');
karaoke.tag('缩写', 'DX');
karaoke.tag('歌手', '周杰伦');
karaoke.tag('字数', '2');
karaoke.tag('语种', '国语');
karaoke.tag('歌类', '男人');
karaoke.tag('电影', 'False');
karaoke.tag('风格', '流行');
karaoke.tag('流行', 'True');
karaoke.tag('音量', '200');
karaoke.tag('声道', '2');
karaoke.tag('语音', '0');
karaoke.tag('介质', '10');
karaoke.tag('时间', '2008-9-5');
karaoke.tag('歌星拼音', 'ZJL');
karaoke.mtvmode :=true;
karaoke.videofilename := '';
karaoke.audiofilename := '*.wav';
karaoke.XSDVideoMode := 4;
karaoke.CommonVideo := '周杰伦-稻香.mkv';

  由于楼主当时的举例比较特殊,需要修改的那一行正好位于最后一行上,因此,我的第一反应就是用 findstr "$" 来过滤掉最后一行内容,再追加修改后的内容;后来,根据楼主的说明,发现这个 karaoke.CommonVideo := '周杰伦-稻香.MKV'; 可能出现在任意行上,这样一来,过滤掉最后一行内容的方案行不通了,只能另辟蹊径。

  既然能位于任意一行,那么,把它过滤掉之后,再追加到最后一行上,不就可以行得通么?

  于是,新代码就出炉了:
  1. @echo off
  2. for /f "tokens=1* delims=-" %%i in ('dir /a-d /b *.mkv') do (
  3.     md "%%i" 2>nul
  4.     ren "%%i-%%j" "%%j"
  5.     move "%%j" "%%i"
  6.     findstr /ivc:"karaoke.CommonVideo" "%%i-%%~nj.ksc">"%%i\%%~nj.ksc"
  7.     echo karaoke.CommonVideo := '%%j';>>"%%i\%%~nj.ksc"
  8.     del "%%i-%%~nj.ksc"
  9. )
  10. pause
复制代码
  几点说明:

  1、由于*.mkv与*.ksc成对出现,找到了*.mkv文件名,*.ksc文件名也就手到擒来了,因此,for语句的第一对括号中,只需 dir /a-d /b *.mkv 即可;如果还像上一次那样使用 dir /a-d /b *.mkv *.ksc 语句,那么,在接下来的代码中,还得判断获取到的字符串是*.mkv还是*.ksc,势必会使用变量延迟语句;而变量延迟会带来一些其他问题:要么会丢弃感叹号,要么会因为频繁地开启和关闭变量延迟而导致效率低下;
  2、findstr /ivc:"karaoke.CommonVideo" "%%i-%%~nj.ksc">"%%i\%%~nj.ksc" 语句中,/i表示兼容大小写,/v表示过滤掉含有特定字符串的行,而/c:则表示它之后的字符串是普通的字符串,其中的特殊字符将被视为普通字符——因为字符串"karaoke.CommonVideo"中含有findstr命令下的特殊字符点号;findstr语句会把匹配的内容全部读取到内存中后,一次性地输出,所以,导出到"%%i\%%~nj.ksc"的时候,只需使用>即可,而无需使用追加命令>>,这样一来,就可以多次运行这个代码,而无需担心后面几次运行的结果会追加到"%%i\%%~nj.ksc"中去——如果在这里使用了>>,则它的上一句还得使用 cd.>"%%i\%%~nj.ksc" 语句来先行清空前一次运行产生的结果,这样的代码显得不够简洁;

  第二步的解决方案是这样的:

  假设已经用前面的代码按照歌手名整理了文件,那么,可以按照如下步骤来分类歌手文件夹:

  1、把歌手名分别存入 男歌手.txt、女歌手.txt、乐队组合.txt 中,一行一条记录,前后不能有多余的字符,把这些文件放入歌手名文件夹同级的目录下;
  2、保证文件夹下没有其他txt文件;
  3、运行如下代码:
  1. @echo off
  2. for %%i in (*.txt) do (
  3.     md %%~ni 2>nul
  4.     for /f "delims=" %%j in (%%i) do move "%%j" %%~ni
  5. )
复制代码
  几点说明:

  1、获取当前文件夹下的某类文件,可以用 dir /a-d /b *.txt 这样的代码,也可以使用 for %%i in (*.txt) do echo %%i 这样的语句,主要的区别在于:前者可以获取任意属性的文件,后者不能得到带隐藏属性的文件;由于前者还需要放入for语句中解析,不如后者用for语句来得直接,所以效率要稍逊于后者;
  2、md %%~ni 2>nul ,可千万别忘了其中的~n!因为 %%i 获取的是形如 男歌手.txt 之类带后缀名的字符串,还需要用 ~n 来去掉其中的后缀名;
  3、由于歌手的名字中,有可能出现空格、&等特殊字符,所以,在用 for /f 语句解析文本内容的时候,一定要使用 "delims="  来获取整行字符串,以防获取到的字符串在空格或&字符处断开;

  把以上代码综合一下,就可以得到这样的代码:
  1. @echo off
  2. for /f "tokens=1* delims=-" %%i in ('dir /a-d /b *.mkv') do (
  3.     md "%%i" 2>nul
  4.     ren "%%i-%%j" "%%j"
  5.     move "%%j" "%%i"
  6.     findstr /ivc:"karaoke.CommonVideo" "%%i-%%~nj.ksc">"%%i\%%~nj.ksc"
  7.     echo karaoke.CommonVideo := '%%j';>>"%%i\%%~nj.ksc"
  8.     del "%%i-%%~nj.ksc"
  9. )
  10. for %%i in (*.txt) do (
  11.     md %%~ni 2>nul
  12.     for /f "delims=" %%j in (%%i) do move "%%j" %%~ni
  13. )
复制代码
  运行一下,楼主的所有需求就统统搞定了。
作者: asnahu    时间: 2010-1-20 17:01

这个问题主要针对点歌系统,一般用户基本用不上。
作者: batman    时间: 2010-1-20 17:13     标题: 回复 2楼 的帖子

虽然原代码在实际应用上很多人都用不上,但是这个代码从头到尾的编写过程和思路,我看应是大家好好参考

和学习的,受人之鱼不如受人之渔。。。
作者: zqz0012005    时间: 2010-1-20 18:49

这份心值得大家感激涕零。

补充完善一下:
ren "%%i-%%j" "%%j"
move "%%j" "%%i"
可以再节约点,合并为一句:
move "%%i-%%j" "%%i\%%j"
且鉴于findstr的诸多Bug,能用find的时候就尽量不要用findstr
find /i /v "karaoke.CommonVideo" "%%i-%%~nj.ksc">"%%i\%%~nj.ksc"
作者: summerflower    时间: 2010-1-23 18:26

为什么要用md "字符串" 2>nul?
后面为什么要跟个2?
我试了一下,这样会建立两个目录耶,“字符串”和“2”两个目录
作者: Batcher    时间: 2010-1-23 19:36     标题: 回复 5楼 的帖子

2和大于号之间不要有空格
作者: mrhxn    时间: 2010-3-2 11:28

原始的文件如果是这样,如何完成呢?
111.mkv
111.ksc
ksc文件内容:
karaoke.tag('歌名', '稻香');
karaoke.tag('缩写', 'DX');
karaoke.tag('歌手', '周杰伦');
karaoke.tag('字数', '2');
karaoke.tag('语种', '国语');
karaoke.tag('歌类', '男人');
karaoke.tag('电影', 'False');
karaoke.tag('风格', '流行');
karaoke.tag('流行', 'True');
karaoke.tag('音量', '200');
karaoke.tag('声道', '2');
karaoke.tag('语音', '0');
karaoke.tag('介质', '10');
karaoke.tag('时间', '2008-9-5');
karaoke.tag('歌星拼音', 'ZJL');
karaoke.mtvmode :=true;
karaoke.videofilename := '';
karaoke.audiofilename := '*.wav';
karaoke.XSDVideoMode := 4;
karaoke.CommonVideo := '111.mkv';
作者: ubcc    时间: 2011-3-6 22:32

我现在暂时还用不上的




欢迎光临 批处理之家 (http://www.bathome.net/) Powered by Discuz! 7.2