最近想惡補下英語,找了很多MP3播放軟體,但是沒有哪個能按照句子播放時間來播放聲音的,想想自己學習過的WinForm,為什麼不自己寫一個程式呢?一個想法出現在腦海中,說幹就幹,花了一天時間,先研究了一下LRC檔案的結構,windows media player控制項的使用,加上一些簡單的控制,這個簡陋的LRC複讀機就誕生了!在網上得到很多朋友的協助,現將全部來源程式公開,以表謝意,您可以隨意使用,修改.如果程式造成了死機及至電腦起火爆炸,很抱歉,不是我啟動並執行!
代碼很簡單,思路那就更簡單,有句英文怎麼說呢:it’s ugly, it’s simple, but it works!
這個複讀機功能如下:
可以播放帶有lrc檔案的mp3檔案,按照每個句子的的時間來進行聲音片段的播放.
使用方法:
先點擊選擇檔案,選擇一個有配套LRC的mp3,注意檔案名稱必須相同,不然程式會報錯
然後,程式就會自動載入LRC檔案,並將常值內容讀取到右邊的listBox中,你可以在listBox中雙擊,鍵盤上下,斷行符號什麼的隨便你,總之,想聽哪句點哪句,讀完就暫停了.或者你將英文顯示關掉,只用上一句和下一句控制朗讀,覺得一句一句聽得不過癮,就點全部播放,相信到這兒你看出來了,這個小軟體就是用來練習聽力的
要開發,先研究最基本的東西,這個小軟體需要用到lrc,為什麼用它呢?
先說說LRC檔案的結構:
比如這個新概念的一個MP3的LRC檔案內容
[al:新概念英語(二)]
[ar:MP3 同步字幕版(美音)]
[ti:A Private Conversation]
[by:更多學習內容,請到VeryCD.com搜尋“露珠”]
[00:09.77]Why did the writer complain to the people behind him?
[00:15.91]Last week I went to the theatre.
[00:19.39]I had a very good seat.
[00:22.07]The play was very interesting.
[00:25.10]I did not enjoy it.
[00:27.43]A young man and a young woman were sitting behind me.
[00:31.96]They were talking loudly.
[00:34.59]I got very angry.
[00:37.41]I could not hear the actors.
[00:40.64]I turned round.
[00:42.80]I looked at the man and the woman angrily.
[00:47.27]They did not pay any attention.
[00:50.69]In the end, I could not bear it.
[00:54.98]I turned round again.
[00:57.75]'I can't hear a word!' I said angrily.
[01:03.45]'It's none of your business,'
[01:06.27]the young man said rudely.
[01:09.00]'This is a private conversation!'
前四行不用看,咱們關心的是下面,[]內部就是時間了,比如第一行[00:09.77]表示從09.77秒開始,在下面一句的15.91秒處結束;
[00:15.91]Last week I went to the theatre.這行從15.91秒處開始,在下面一句19.39秒處結束,下面的都一樣,最後一句
[01:09.00]'This is a private conversation!'從1分9秒開始,結束時間?mp3播放的結束時間
現在的問題是:如何取得時間以及文本的內容呢?
先說時間,全部都是分鐘在前,秒在後,格式都一樣[??:??.??],這種非常規律的文本用Regex來取值是最簡單不過了,如果你還不懂Regex,請先去學習了再繼續往下看:
/d{2}:/d{2}./d{2}就可以得到整個時間字串,把全部的時間取出來,再做成時間對,比如
00:09.77 00:15.91
00:15.91 00:19.39
以開始時間和結束時間命名,這樣就可以用一個struct來儲存每一對:
struct 時間對 { public double 開始時間; public double 結束時間; //如果想在listBox中顯示開始時間和結束時間,需要重載ToString方法 public override string ToString() { return string.Format("{0,8}-{1,8}", 開始時間, 結束時間); } }
有了時間對,咱們不就可以在WMP中按照某一段時間來播放了?在WMP的研究中發現要想在WMP中從09.77秒處播放,需要使用double資料類型!
如所示Ctlcontrols.currentPosition只接受double,這個double和時間是個什麼關係?網上沒有找到相關的資料,只好自己做實驗來研究了,弄了個mp3,給這個currentPosition賦值,發現其實很簡單,就是個秒數,比如currentPosition=09.77,播放條一下就跑到09.77秒處了;那麼1分9秒呢?1*60+9,OK,知道如何來控制播放的時間了,講這些是因為需要將01:09.00這種字串轉換成double類型,需要單獨取出分鐘01和秒數09.00,奈何?
Regex可以從一個匹配字串中一次取出二個,上面的/d{2}:/d{2}./d{2},改成(/d{2}):(/d{2}./d{2})就可以了,然後在程式中取出這二組,轉換成double即可:
private static double 擷取匹配時間(string 內容來源){ Regex 時間匹配 = new Regex("(//d{2}):(//d{2}.//d{2})", RegexOptions.None); if (時間匹配.IsMatch(內容來源)) { Match ma = 時間匹配.Match(內容來源); double 分鐘 = Convert.ToDouble(ma.Groups[1].Value); double 秒數 = Convert.ToDouble(ma.Groups[2].Value); return 分鐘 * 60 + 秒數; } else { return -1; }}
有了能夠取出一個字串中的時間的方法,再使用一個File.ReadAllLines將lrc檔案的每一行都讀取進一個string[]中,弄一個迴圈就可以得到整個lrc檔案的時間對:
public List時間對> 得到時間對列表() { List時間對> 時間對列表 = new List時間對>(); 時間對 coupleTime = new 時間對(); //讀取LRC檔案的每一行並擷取時間 for (int i = 0; i if (擷取匹配時間(LRC檔案內容[i]) //沒有找到匹配項,繼續 { continue; } else { coupleTime.開始時間 = 擷取匹配時間(LRC檔案內容[i]); coupleTime.結束時間 = 擷取匹配時間(LRC檔案內容[i + 1]); 時間對列表.Add(coupleTime); } } //最後一個結束時間需要用MP3的總長度賦值,或者偷懶,double.MaxValue:) coupleTime.開始時間 = 擷取匹配時間(LRC檔案內容[LRC檔案內容.Length - 1]); coupleTime.結束時間 = double.MaxValue; 時間對列表.Add(coupleTime); return 時間對列表; }OK,時間的問題解決掉;常值內容如何得到呢?其實也可以和時間一起擷取,效率高,不過考慮到Regex不好寫,再加上反正lrc檔案小,現在電腦速度快,用簡單清晰略帶冗餘的方式來處理好嘍:
private static string 擷取匹配英文短語(string 內容來源){ //注意,如果正則是[a-zA-Z/s/./?'!,]+$,也就是沒有兩邊的() //那麼取的值就是空,因為並沒有組存在! //要使用Groups來擷取,必須在兩邊加上() //Regex 文字匹配 = new Regex(@"([a-zA-Z/d/s/./?'!,_-]+$)", RegexOptions.None); string 純英文LRC=@"([a-zA-Z/s/./?'’!,,_/d-]+$)"; string 中英文LRC=@"";//留一個位置,以後還可以讀取帶中文翻譯的LRC string regexString=純英文LRC+中英文LRC; Regex 文字匹配 = new Regex(regexString, RegexOptions.Multiline); if (文字匹配.IsMatch(內容來源)) { Match ma = 文字匹配.Match(內容來源); return string.IsNullOrEmpty(ma.Groups[1].Value) ? ma.Groups[2].Value : ma.Groups[1].Value; } else { return null; } //這樣也可以得到正確的值 //Regex 文字匹配 = new Regex(@"[a-zA-Z/s/./?'!,]+$", RegexOptions.None); //if (文字匹配.IsMatch(內容來源)) //{ // Match ma = 文字匹配.Match(內容來源); // return ma.Value; //} //else //{ // return null; //}}
這個擷取有一點兒小小問題,就是只能擷取到僅僅含有英文的lrc檔案,如果有中文,那麼就只會得到null,因為Regex([a-zA-Z/d/s/./?'!,_-]+$)最後是以$結束的.
比如這種lrc檔案:
[00:00.440]--- lesson1 SCHOOLS AND EDUCATION 學校和教育
[00:06.160]--- Basic Patterns 基本句型
[00:09.160]What college should I attend in the fall?^秋季我上哪一所大學好呢?
[00:14.080]I’m majoring in history.^我學的是曆史專業。
[00:17.880]Can you tell me where is the dormitory?^你能告訴我宿舍在哪嗎?
[00:22.240]I’m taking extra classes this semester.^這個學期我額外選修了一些課程。
[00:26.720]Are you having problems with chemistry?^你在化學方面有困難嗎?
[00:30.760]I’m not worried about this test.^我並不擔心這次考試。
[00:34.520]The prices on textbooks have gone up.^課本的價格上漲了。
[00:38.880]Our class has a meeting after lunch.^午餐後我們班要開一個會議。
英文字串就擷取不到了,因為最後結束的是中文字元,有非常懂Regex的還請賜教,如何從上面的字串中一次性取得英文字串和中文字串呢?我自己研究了一下,這個英文標點和中文標點可真是讓人頭大,少寫一個或者寫錯,什麼也得不到!
待續...