From 55b53937680a1d99593d6c15d951af8b599b7967 Mon Sep 17 00:00:00 2001 From: lordmulder Date: Sun, 15 May 2011 01:45:27 +0200 Subject: [PATCH] Implemented Cue Sheet splitter thread. Basic Cue Sheet import should work now, but only uncompressed Wave/PCM files are supported. --- LameXP_VS2010.vcxproj | 13 ++ LameXP_VS2010.vcxproj.filters | 9 ++ doc/Changelog.html | 1 + etc/Translation/Blank.ts | 80 +++++++++-- etc/Translation/LameXP_DE.ts | 80 +++++++++-- etc/Translation/LameXP_ES.ts | 80 +++++++++-- etc/Translation/LameXP_FR.ts | 80 +++++++++-- etc/Translation/LameXP_IT.ts | 80 +++++++++-- etc/Translation/LameXP_KR.ts | 80 +++++++++-- etc/Translation/LameXP_RU.ts | 80 +++++++++-- etc/Translation/LameXP_UK.ts | 80 +++++++++-- res/localization/LameXP_DE.qm | Bin 61381 -> 61650 bytes res/localization/LameXP_ES.qm | Bin 58447 -> 58702 bytes res/localization/LameXP_FR.qm | Bin 57872 -> 58131 bytes res/localization/LameXP_IT.qm | Bin 40525 -> 40778 bytes res/localization/LameXP_KR.qm | Bin 47852 -> 48057 bytes res/localization/LameXP_RU.qm | Bin 51743 -> 52034 bytes res/localization/LameXP_UK.qm | Bin 21650 -> 21947 bytes src/Config.h | 4 +- src/Dialog_CueImport.cpp | 108 ++++++++++++--- src/Dialog_CueImport.h | 13 +- src/Dialog_MainWindow.cpp | 4 +- src/Model_CueSheet.cpp | 51 ++++++- src/Model_CueSheet.h | 3 + src/Thread_CueSplitter.cpp | 253 ++++++++++++++++++++++++++++++++++ src/Thread_CueSplitter.h | 67 +++++++++ 26 files changed, 1046 insertions(+), 120 deletions(-) create mode 100644 src/Thread_CueSplitter.cpp create mode 100644 src/Thread_CueSplitter.h diff --git a/LameXP_VS2010.vcxproj b/LameXP_VS2010.vcxproj index 4725119a..9c3e62f8 100644 --- a/LameXP_VS2010.vcxproj +++ b/LameXP_VS2010.vcxproj @@ -280,6 +280,7 @@ del "$(TargetDir)imageformats\q???d4.dll" + @@ -314,6 +315,7 @@ del "$(TargetDir)imageformats\q???d4.dll" + @@ -364,6 +366,17 @@ del "$(TargetDir)imageformats\q???d4.dll" $(SolutionDir)tmp\MOC_%(Filename).cpp;%(Outputs) $(SolutionDir)tmp\MOC_%(Filename).cpp;%(Outputs) + + "$(QTDIR)\bin\moc.exe" -o "$(SolutionDir)tmp\MOC_%(Filename).cpp" "%(FullPath)" + "$(QTDIR)\bin\moc.exe" -o "$(SolutionDir)tmp\MOC_%(Filename).cpp" "%(FullPath)" + "$(QTDIR)\bin\moc.exe" -o "$(SolutionDir)tmp\MOC_%(Filename).cpp" "%(FullPath)" + MOC "$(SolutionDir)tmp\MOC_%(Filename).cpp" + MOC "$(SolutionDir)tmp\MOC_%(Filename).cpp" + MOC "$(SolutionDir)tmp\MOC_%(Filename).cpp" + $(SolutionDir)tmp\MOC_%(Filename).cpp;%(Outputs) + $(SolutionDir)tmp\MOC_%(Filename).cpp;%(Outputs) + $(SolutionDir)tmp\MOC_%(Filename).cpp;%(Outputs) + diff --git a/LameXP_VS2010.vcxproj.filters b/LameXP_VS2010.vcxproj.filters index a5fbdcd7..dc022bfe 100644 --- a/LameXP_VS2010.vcxproj.filters +++ b/LameXP_VS2010.vcxproj.filters @@ -301,6 +301,12 @@ Generated Files\MOC + + Source Files\Threads + + + Generated Files\MOC + @@ -569,6 +575,9 @@ Header Files\Models + + Header Files\Threads + diff --git a/doc/Changelog.html b/doc/Changelog.html index 018b34b7..aa29f669 100644 --- a/doc/Changelog.html +++ b/doc/Changelog.html @@ -19,6 +19,7 @@ a:visited { color: #0000EE; } Changes between v4.01 and v4.02:
  • Upgraded build environment to Microsoft Visual Studio 2010
  • Dropping support for Windows 2000 and for Windows XP RTM/SP1, Windows XP needs SP2 or SP3 now! +
  • Added Cue Sheet import wizard, only uncompressed Wave/PCM files are suppported at the moment
  • Added ATSC A/52 (AC-3) encoding support, based on Aften encoder v0.0.8+ (Git Master)
  • Added one new translation: Korean
  • Added a method to use custom tools instead of the "built-in" ones (see FAQ doc for details) diff --git a/etc/Translation/Blank.ts b/etc/Translation/Blank.ts index e8277f34..fae5335f 100644 --- a/etc/Translation/Blank.ts +++ b/etc/Translation/Blank.ts @@ -257,18 +257,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -277,6 +265,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -296,6 +340,14 @@ Discard + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -327,6 +379,10 @@ Unknown Title + + Duration + + DecoderRegistry diff --git a/etc/Translation/LameXP_DE.ts b/etc/Translation/LameXP_DE.ts index 591f21d5..78c71abd 100644 --- a/etc/Translation/LameXP_DE.ts +++ b/etc/Translation/LameXP_DE.ts @@ -257,18 +257,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -277,6 +265,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + LameXP + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + Warnung: Wenig freier Festplattenspeicher + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -296,6 +340,14 @@ Discard Schließen + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -327,6 +379,10 @@ Unknown Title + + Duration + Dauer + DecoderRegistry diff --git a/etc/Translation/LameXP_ES.ts b/etc/Translation/LameXP_ES.ts index 09e9db73..5f2eb955 100644 --- a/etc/Translation/LameXP_ES.ts +++ b/etc/Translation/LameXP_ES.ts @@ -257,18 +257,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -277,6 +265,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + LameXP + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + Alerta de poco espacio en disco + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -296,6 +340,14 @@ Discard + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -327,6 +379,10 @@ Unknown Title + + Duration + Duración + DecoderRegistry diff --git a/etc/Translation/LameXP_FR.ts b/etc/Translation/LameXP_FR.ts index 52cd9c84..63b5ebd3 100644 --- a/etc/Translation/LameXP_FR.ts +++ b/etc/Translation/LameXP_FR.ts @@ -261,18 +261,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -281,6 +269,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + LameXP + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + Avertissement d'espace disque faible + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -300,6 +344,14 @@ Discard Abandonner + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -331,6 +383,10 @@ Unknown Title + + Duration + Durée + DecoderRegistry diff --git a/etc/Translation/LameXP_IT.ts b/etc/Translation/LameXP_IT.ts index e70a9081..43f4b88f 100644 --- a/etc/Translation/LameXP_IT.ts +++ b/etc/Translation/LameXP_IT.ts @@ -257,18 +257,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -277,6 +265,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + LameXP + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + Attenzione: Poco spazio su disco + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -296,6 +340,14 @@ Discard Annulla + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -327,6 +379,10 @@ Unknown Title + + Duration + Durata + DecoderRegistry diff --git a/etc/Translation/LameXP_KR.ts b/etc/Translation/LameXP_KR.ts index 6a6b3080..f3ae76cd 100644 --- a/etc/Translation/LameXP_KR.ts +++ b/etc/Translation/LameXP_KR.ts @@ -257,18 +257,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -277,6 +265,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + LameXP + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + 디스크 공간 부족 알림 + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -296,6 +340,14 @@ Discard 닫기 + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -327,6 +379,10 @@ Unknown Title + + Duration + 길이 + DecoderRegistry diff --git a/etc/Translation/LameXP_RU.ts b/etc/Translation/LameXP_RU.ts index d8d96de5..7181c092 100644 --- a/etc/Translation/LameXP_RU.ts +++ b/etc/Translation/LameXP_RU.ts @@ -257,18 +257,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -277,6 +265,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + LameXP + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + Предупреждение: Мало свободного места на диске + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -296,6 +340,14 @@ Discard Отменить + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -327,6 +379,10 @@ Unknown Title + + Duration + Длителность + DecoderRegistry diff --git a/etc/Translation/LameXP_UK.ts b/etc/Translation/LameXP_UK.ts index 620abed1..e577d303 100644 --- a/etc/Translation/LameXP_UK.ts +++ b/etc/Translation/LameXP_UK.ts @@ -257,18 +257,6 @@ An unknown error has occured! - - The file could not be opened for reading! - - - - The file does not look like a valid Cue Sheet disc image file! - - - - Could not find a supported audio track in the Cue Sheet! - - Failed to load the Cue Sheet file: @@ -277,6 +265,62 @@ Cue Sheet Error + + The specified file could not be found! + + + + The file could not be opened for reading. Make sure you have the required rights! + + + + The provided file does not look like a valid Cue Sheet disc image file! + + + + Could not find any supported audio track in the Cue Sheet image! + + + + Note that LameXP can not handle "binary" Cue Sheet images. + + + + The selected Cue Sheet file contains inconsistent information. Take care! + + + + Choose Output Directory + + + + LameXP + LameXP + + + Error: The selected output directory is not writable! + + + + Low Diskspace Warning + Попередження про те, що недостатньо місця на диску + + + There are less than %1 GB of free diskspace available in the selected output directory. + + + + It is highly recommend to free up more diskspace before proceeding with the import! + + + + Analyzing file(s), please wait... + + + + Splitting file(s), please wait... + + CueSheetImport @@ -296,6 +340,14 @@ Discard Відмінити + + Existing Source File + + + + Missing Source File (Tracks will be skipped!) + + CueSheetModel @@ -327,6 +379,10 @@ Unknown Title + + Duration + Тривалість + DecoderRegistry diff --git a/res/localization/LameXP_DE.qm b/res/localization/LameXP_DE.qm index fb7d81fc40c5b75bb60ac47d5f91db471af3b26e..6efffd57ceb947c96f64a4698c81627d8783547a 100644 GIT binary patch delta 3907 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 delta 3796 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 V<%yqLEc5*1qb)P{E%ms#_rEDQL5KhV delta 3524 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 Ic7EUg0HszOt^fc4 diff --git a/res/localization/LameXP_FR.qm b/res/localization/LameXP_FR.qm index f2d4b746d72e68923557e77406decfd599dab008..b902e77ea19fa264093b7027f61815d2f4aaee50 100644 GIT binary patch delta 3530 zcmZWsd0b8T8-DIR+r8(WdyYxQkV;Kt%Tig&E=nN^l`T?9N%noYNmLey_Q||9*cxpL09s{l4$>KJW8>OS;GyFEL5xhNS=o0*);J zt}n1_DB);eMg`#G3tX%Lrj`N;Lx6ccfYtwS+f}f|V}Y^pV1HqO(eq50=?k{19pE<* zoIAw{c?K??{yx_U+__M|IvU*da$ti2+@~18Q1lbT@g)>|BlOeG0wXffX{{A-!UxVC zfk3~O@JLGmN@ieq$J4;hKzN?B1Iqp2)tLeeNrzXjR3OvNgva7dc-a>txClz{7)FGw z1`a&HsIoX9KL!C67b&571YF600oTGX@f>Bmv>cOkgzcW2u&_NQ&2R$N1!J=HBl@rj zlh^(X6onwP*b&IRgTN#8kpw3gT3M8i1HL5phDdC73b@rfe zz%^N25Z?sssaF?SwF2hcRu@m-3~Xhn#QcUaxvMdDf#qy93N zDw|TzXdb=>&Rk|JR(%C7OxwaxM~#;)`c10JR0cHmNEE$d!dI9Gx4P!=roehfkflNg#P<`46|q+ zRW_-DSz`X}y(7$ue}8|-tnG6R2tUIlvVRdX1x&Vi0VRBz$-9#X{IrHCEcE~mu4Rg< zdjMAsFvmZAM^hBRR8-vnCb}?Jvo+K@gMq0TOO-_DGEd_ZfJITPDq;b!C5h#}1r7;k z`Jq(dingr$$`j~&nQiuZ8*s#z9oE4Gn9!3Q=ITlZ*t34CRstiBnDFukHqyeIS}>5E zzbF}~^=8*99|5=9?E0-yK<`KF#$sY>ry-F|_dG?^tTExvGIm$tddl3K-93%ed{k!l z2hzAXrm%$qRZwEZ7L~@){Fbn%>fcZTH`xnqX`FIg*|O)vNZc&8+=mWWyOO;zgZyJg z3|l=W7jU*_e>KJed8^s_-7>)b#6CHr1#DfPuwVTs;DlVR+1mtQ=2fn_FNv&vl|au-u7hh8*~k+UPG-0+{;$YA6wcWplq7YY>)&*aw5@RdUR3uE zBNrO|Cora%oA-unE2q( zw|J#C3pkt4+bS2yrY7g`9awUQj6ZnCePpa2y?8e>%5-B0KXfbkV)jek(~5Y^dBS@Y zo&XAK_)!C?aWh`=6Elek|MUFR!&E?SJRdZbc)t3UUsFVhSN7zy_jd&fYWe;1$i|}L z`EzdMI_6>gCF_UeGHLu3hsqFO!Cn4(;cc>?8+^?h8lr<|_`A=efUIEtnHOc+Hp_(l z8U95ON!rcGH%+l2>YN0z|4EX#UT7{3po51B=Gyb5xDvsBGD$bhLFlm41=zgXxPxzL z_$c&qr|A0~glRQ%0IzBxWE5G!_(oyznCaxQnZn}aH#DJ7gr(}$K+H5Dc@t$fe}s^- zgvxE-Mo4XT5s(~&)T%|~x{X4n5AmJ%QOLQ_4e)#?V@Ly?j)EJ z;gC%IGVF>Jj(>~*5`%<_Zk>SQ$HLvGqyYcV!o9PxWcOW#M~VN?!gCUy1m*zaCJ4_@ zexzj?A-vX6V6j}o|4xB|1x@S6)JVlrW7ndRT%OVN_HRyBvQ^{K<~%jNP}6siH^q(8 z49Px2?q8wtmX1-Q2c~Jf55EVdebNMINJ&E`X`(+cUI4%+EiRAK6N z?To8L;XYX#evlF>D%VCXqLw&~(XMX42>4;WHm0`>{IWv3rHJs@1?`^Yq{PWDwFR%K zAk9+kDX%bU=?iVuXG+99MEh%mA%QBl)82{j1%^G?roe-AO(U8EKc6GQr$li7U|!#^Yf z!~2O*9jOJg!o&?}0YIzAVp6XtAl6e%F?^-F$fy=#)-W4jlcTsZ@GOa@o0!|Qkf<&e z3#R5%12!53!u5imyYgvhV^dI*K=+)&sL$ z#XGjGNGrdKbxCM0>^kj||O*;!Dn&Lf^GTJNs&`F006)?F9)k`gR+(uL}% zrH-$4p|@_*#vZL(F{vY6H6H0=Wr?&_Y0#xflzCMLU2Yu(C|{@BRrrK#tU!0(tN{4& zpsqBvmfR&lcS9l(S=`c9TXY3_4bZ*r>rD%BrtXt39W>y VL&S}v6=vl^&HJ0zSkz6$l;vA5*gmniM_ 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 p#kebNCPz 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 A<&8d delta 2460 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 J-VO(I{s&Nrd!zsW delta 3773 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 E0E&o5-2eap diff --git a/res/localization/LameXP_RU.qm b/res/localization/LameXP_RU.qm index 6eadece49df667c8b0008bbd256519d7682d674d..5dfd160a5e58894efa20cd2f5ed97ce13e459e40 100644 GIT binary patch delta 3249 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 OOd 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 N-=^3PJW$Rg{0A)7*~0(; diff --git a/src/Config.h b/src/Config.h index 10c2c019..9b97d6fd 100644 --- a/src/Config.h +++ b/src/Config.h @@ -29,8 +29,8 @@ #define VER_LAMEXP_MINOR_HI 0 #define VER_LAMEXP_MINOR_LO 2 #define VER_LAMEXP_TYPE Alpha -#define VER_LAMEXP_PATCH 14 -#define VER_LAMEXP_BUILD 515 +#define VER_LAMEXP_PATCH 15 +#define VER_LAMEXP_BUILD 520 /////////////////////////////////////////////////////////////////////////////// // Tools versions diff --git a/src/Dialog_CueImport.cpp b/src/Dialog_CueImport.cpp index 3a2c0fc1..64bb5296 100644 --- a/src/Dialog_CueImport.cpp +++ b/src/Dialog_CueImport.cpp @@ -24,8 +24,11 @@ #include "Global.h" #include "Model_CueSheet.h" #include "Model_AudioFile.h" +#include "Model_FileList.h" #include "Dialog_WorkingBanner.h" #include "Thread_FileAnalyzer.h" +#include "Thread_CueSplitter.h" +#include "LockedFile.h" #include #include @@ -40,9 +43,11 @@ // Constructor & Destructor //////////////////////////////////////////////////////////// -CueImportDialog::CueImportDialog(QWidget *parent) +CueImportDialog::CueImportDialog(QWidget *parent, FileListModel *fileList, const QString &cueFile) : - QDialog(parent) + QDialog(parent), + m_cueFileName(cueFile), + m_fileList(fileList) { //Init the dialog, from the .ui file setupUi(this); @@ -90,26 +95,33 @@ void CueImportDialog::showEvent(QShowEvent *event) // Slots //////////////////////////////////////////////////////////// -int CueImportDialog::exec(const QString &cueFile) +int CueImportDialog::exec(void) { WorkingBanner *progress = new WorkingBanner(dynamic_cast(parent())); progress->show(tr("Loading Cue Sheet file, please be patient...")); - - QFileInfo cueFileInfo(cueFile); - m_outputDir = QFileInfo(cueFile).canonicalPath(); + + QFileInfo cueFileInfo(m_cueFileName); + QDir outputDir(cueFileInfo.canonicalPath()); + m_outputDir = outputDir.canonicalPath(); setWindowTitle(QString("%1: %2").arg(windowTitle().split(":", QString::SkipEmptyParts).first().trimmed(), cueFileInfo.fileName())); if(!cueFileInfo.exists() || !cueFileInfo.isFile() || m_outputDir.isEmpty()) { - QString text = QString("%1
    %2

    %3").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(cueFile), tr("The specified file could not be found!")).replace("-", "−"); + QString text = QString("%1
    %2

    %3").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(m_cueFileName), tr("The specified file could not be found!")).replace("-", "−"); QMessageBox::warning(progress, tr("Cue Sheet Error"), text); progress->close(); LAMEXP_DELETE(progress); return CueSheetModel::ErrorIOFailure; } - int iResult = m_model->loadCueSheet(cueFile, QApplication::instance()); + outputDir.mkdir(cueFileInfo.completeBaseName()); + if(outputDir.cd(cueFileInfo.completeBaseName())) + { + m_outputDir = outputDir.canonicalPath(); + } + + int iResult = m_model->loadCueSheet(m_cueFileName, QApplication::instance()); if(iResult != CueSheetModel::ErrorSuccess) { QString errorMsg = tr("An unknown error has occured!"); @@ -130,7 +142,7 @@ int CueImportDialog::exec(const QString &cueFile) break; } - QString text = QString("%1
    %2

    %3").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(cueFile), errorMsg).replace("-", "−"); + QString text = QString("%1
    %2

    %3").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(m_cueFileName), errorMsg).replace("-", "−"); QMessageBox::warning(progress, tr("Cue Sheet Error"), text); progress->close(); LAMEXP_DELETE(progress); @@ -184,31 +196,51 @@ void CueImportDialog::importButtonClicked(void) } importCueSheet(); + accept(); } void CueImportDialog::analyzedFile(const AudioFileModel &file) { - qWarning("Received results for: %s", file.filePath().toLatin1().constData()); - m_fileInfo.insert(file.filePath(), file); + qDebug("Received result: <%s> <%s/%s>", file.filePath().toLatin1().constData(), file.formatContainerType().toLatin1().constData(), file.formatAudioType().toLatin1().constData()); + m_fileInfo << file; } //////////////////////////////////////////////////////////// -// Private FUnctions +// Private Functions //////////////////////////////////////////////////////////// void CueImportDialog::importCueSheet(void) { QStringList files; - int nFiles = m_model->getFileCount(); - //Fetch all files that are referenced in the Cue Sheet + //Fetch all files that are referenced in the Cue Sheet and lock them + int nFiles = m_model->getFileCount(); for(int i = 0; i < nFiles; i++) { - files << m_model->getFileName(i); + QString temp = m_model->getFileName(i); + try + { + m_locks << new LockedFile(temp); + } + catch(char *err) + { + qWarning("Failed to lock file: %s", err); + continue; + } + files << temp; } //Analyze all source files analyzeFiles(files); + + //Now split files according to Cue Sheet + splitFiles(); + + //Release locks + while(!m_locks.isEmpty()) + { + delete m_locks.takeFirst(); + } } void CueImportDialog::analyzeFiles(QStringList &files) @@ -217,10 +249,54 @@ void CueImportDialog::analyzeFiles(QStringList &files) WorkingBanner *progress = new WorkingBanner(dynamic_cast(parent())); FileAnalyzer *analyzer = new FileAnalyzer(files); + connect(analyzer, SIGNAL(fileSelected(QString)), progress, SLOT(setText(QString)), Qt::QueuedConnection); connect(analyzer, SIGNAL(fileAnalyzed(AudioFileModel)), this, SLOT(analyzedFile(AudioFileModel)), Qt::QueuedConnection); - progress->show(tr("Adding file(s), please wait..."), analyzer); + progress->show(tr("Analyzing file(s), please wait..."), analyzer); progress->close(); LAMEXP_DELETE(progress); } + +void CueImportDialog::splitFiles(void) +{ + WorkingBanner *progress = new WorkingBanner(this); + CueSplitter *splitter = new CueSplitter(m_outputDir, QFileInfo(m_cueFileName).completeBaseName().replace(".", " ").left(42).trimmed(), m_fileInfo); + + connect(splitter, SIGNAL(fileSelected(QString)), progress, SLOT(setText(QString)), Qt::QueuedConnection); + connect(splitter, SIGNAL(fileSplit(AudioFileModel)), m_fileList, SLOT(addFile(AudioFileModel)), Qt::QueuedConnection); + + int nFiles = m_model->getFileCount(); + for(int i = 0; i < nFiles; i++) + { + QString currentFileName = m_model->getFileName(i); + int nTracks = m_model->getTrackCount(i); + + for(int j = 0; j < nTracks; j++) + { + int trackNo = m_model->getTrackNo(i, j); + double startIndex = std::numeric_limits::quiet_NaN(); + double duration = std::numeric_limits::quiet_NaN(); + m_model->getTrackIndex(i, j, &startIndex, &duration); + + AudioFileModel metaInfo(QString().sprintf("cue://File%02d/Track%02d", i, j)); + metaInfo.setFileName(m_model->getTrackTitle(i, j)); + metaInfo.setFileArtist(m_model->getTrackPerformer(i, j)); + + try + { + splitter->addTrack(trackNo, currentFileName, startIndex, duration, metaInfo); + } + catch(char *err) + { + qWarning("Failed to add track #%02d: %s", trackNo, err); + } + } + } + + progress->show(tr("Splitting file(s), please wait..."), splitter); + progress->close(); + + LAMEXP_DELETE(splitter); + LAMEXP_DELETE(progress); +} diff --git a/src/Dialog_CueImport.h b/src/Dialog_CueImport.h index 5fc542de..523306ab 100644 --- a/src/Dialog_CueImport.h +++ b/src/Dialog_CueImport.h @@ -27,16 +27,18 @@ #include "Model_AudioFile.h" class CueSheetModel; +class LockedFile; +class FileListModel; class CueImportDialog : public QDialog, private Ui::CueSheetImport { Q_OBJECT public: - CueImportDialog(QWidget *parent); + CueImportDialog(QWidget *parent, FileListModel *fileList, const QString &cueFile); ~CueImportDialog(void); - int exec(const QString &cueFile); + int exec(void); protected: void CueImportDialog::showEvent(QShowEvent *event); @@ -50,8 +52,13 @@ private slots: private: void importCueSheet(void); void analyzeFiles(QStringList &files); + void CueImportDialog::splitFiles(void); CueSheetModel *m_model; - QMap m_fileInfo; + FileListModel *m_fileList; + + QList m_locks; + QList m_fileInfo; + QString m_cueFileName; QString m_outputDir; }; diff --git a/src/Dialog_MainWindow.cpp b/src/Dialog_MainWindow.cpp index 1aa069bc..baec9ad6 100644 --- a/src/Dialog_MainWindow.cpp +++ b/src/Dialog_MainWindow.cpp @@ -2571,8 +2571,8 @@ void MainWindow::importCueSheetActionTriggered(bool checked) QString selectedCueFile = QFileDialog::getOpenFileName(this, tr("Open Cue Sheet"), QString(), QString("%1 (*.cue)").arg(tr("Cue Sheet File"))); if(!selectedCueFile.isEmpty()) { - CueImportDialog *cueImporter = new CueImportDialog(this); - cueImporter->exec(selectedCueFile); + CueImportDialog *cueImporter = new CueImportDialog(this, m_fileListModel, selectedCueFile); + cueImporter->exec(); LAMEXP_DELETE(cueImporter); } ) diff --git a/src/Model_CueSheet.cpp b/src/Model_CueSheet.cpp index 91e89121..4a9a9f79 100644 --- a/src/Model_CueSheet.cpp +++ b/src/Model_CueSheet.cpp @@ -346,6 +346,20 @@ int CueSheetModel::getTrackCount(int fileIndex) return m_files.at(fileIndex)->trackCount(); } +int CueSheetModel::getTrackNo(int fileIndex, int trackIndex) +{ + if(fileIndex >= 0 && fileIndex < m_files.count()) + { + CueSheetFile *currentFile = m_files.at(fileIndex); + if(trackIndex >= 0 && trackIndex < currentFile->trackCount()) + { + return currentFile->track(trackIndex)->trackNo(); + } + } + + return -1; +} + void CueSheetModel::getTrackIndex(int fileIndex, int trackIndex, double *startIndex, double *duration) { *startIndex = std::numeric_limits::quiet_NaN(); @@ -363,6 +377,34 @@ void CueSheetModel::getTrackIndex(int fileIndex, int trackIndex, double *startIn } } +QString CueSheetModel::getTrackPerformer(int fileIndex, int trackIndex) +{ + if(fileIndex >= 0 && fileIndex < m_files.count()) + { + CueSheetFile *currentFile = m_files.at(fileIndex); + if(trackIndex >= 0 && trackIndex < currentFile->trackCount()) + { + CueSheetTrack *currentTrack = currentFile->track(trackIndex); + return currentTrack->performer(); + } + } + return QString(); +} + +QString CueSheetModel::getTrackTitle(int fileIndex, int trackIndex) +{ + if(fileIndex >= 0 && fileIndex < m_files.count()) + { + CueSheetFile *currentFile = m_files.at(fileIndex); + if(trackIndex >= 0 && trackIndex < currentFile->trackCount()) + { + CueSheetTrack *currentTrack = currentFile->track(trackIndex); + return currentTrack->title(); + } + } + return QString(); +} + //////////////////////////////////////////////////////////// // Cue Sheet Parser //////////////////////////////////////////////////////////// @@ -624,6 +666,7 @@ int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplic //Sanity check of track numbers if(nFiles > 0) { + int previousTrackNo = -1; bool trackNo[100]; for(int i = 0; i < 100; i++) { @@ -640,7 +683,7 @@ int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplic int nTracks = currentFile->trackCount(); if(nTracks > 1) { - for(int j = 1; j < nTracks; j++) + for(int j = 0; j < nTracks; j++) { int currentTrackNo = currentFile->track(j)->trackNo(); if(currentTrackNo > 99) @@ -648,12 +691,18 @@ int CueSheetModel::parseCueFile(QFile &cueFile, const QDir &baseDir, QCoreApplic qWarning("Track #%02d is invalid (maximum is 99), Cue Sheet is inconsistent!", currentTrackNo); return ErrorInconsistent; } + if(currentTrackNo <= previousTrackNo) + { + qWarning("Non-increasing track numbers, Cue Sheet is inconsistent!", currentTrackNo); + return ErrorInconsistent; + } if(trackNo[currentTrackNo]) { qWarning("Track #%02d exists multiple times, Cue Sheet is inconsistent!", currentTrackNo); return ErrorInconsistent; } trackNo[currentTrackNo] = true; + previousTrackNo = currentTrackNo; } } } diff --git a/src/Model_CueSheet.h b/src/Model_CueSheet.h index fa509f33..d0ff6403 100644 --- a/src/Model_CueSheet.h +++ b/src/Model_CueSheet.h @@ -61,7 +61,10 @@ public: int CueSheetModel::getFileCount(void); QString getFileName(int fileIndex); int getTrackCount(int fileIndex); + int getTrackNo(int fileIndex, int trackIndex); void getTrackIndex(int fileIndex, int trackIndex, double *startIndex, double *duration); + QString getTrackPerformer(int fileIndex, int trackIndex); + QString getTrackTitle(int fileIndex, int trackIndex); //Cue Sheet functions int loadCueSheet(const QString &cueFile, QCoreApplication *application = NULL); diff --git a/src/Thread_CueSplitter.cpp b/src/Thread_CueSplitter.cpp new file mode 100644 index 00000000..1f01f4ae --- /dev/null +++ b/src/Thread_CueSplitter.cpp @@ -0,0 +1,253 @@ +/////////////////////////////////////////////////////////////////////////////// +// LameXP - Audio Encoder Front-End +// Copyright (C) 2004-2011 LoRd_MuldeR +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// http://www.gnu.org/licenses/gpl-2.0.txt +/////////////////////////////////////////////////////////////////////////////// + +#include "Thread_CueSplitter.h" + +#include "Global.h" +#include "LockedFile.h" +#include "Model_AudioFile.h" +#include "PlaylistImporter.h" + +#include +#include +#include +#include +#include +#include + +#include + +//////////////////////////////////////////////////////////// +// Constructor +//////////////////////////////////////////////////////////// + +CueSplitter::CueSplitter(const QString &outputDir, const QString &baseName, const QList &inputFiles) +: + m_outputDir(outputDir), + m_baseName(baseName), + m_soxBin(lamexp_lookup_tool("sox.exe")) +{ + if(m_soxBin.isEmpty()) + { + qFatal("Invalid path to SoX binary. Tool not initialized properly."); + } + + qDebug("\n[CueSplitter::CueSplitter]"); + + int nInputFiles = inputFiles.count(); + for(int i = 0; i < nInputFiles; i++) + { + m_inputFiles.insert(inputFiles[i].filePath(), inputFiles[i]); + qDebug("%02d <%s>", i, inputFiles[i].filePath()); + } + + qDebug("All input files added."); + m_bSuccess = false; +} + +//////////////////////////////////////////////////////////// +// Thread Main +//////////////////////////////////////////////////////////// + +void CueSplitter::run() +{ + m_bSuccess = false; + m_abortFlag = false; + + int nTracks = min(min(min(m_trackFile.count(), m_trackNo.count()), min(m_trackOffset.count(), m_trackLength.count())), m_trackMetaInfo.count()); + + if(!QDir(m_outputDir).exists()) + { + qWarning("Output directory \"%s\" does not exist!", m_outputDir.toUtf8().constData()); + return; + } + + for(int i = 0; i < nTracks; i++) + { + QString outputFile = QString("%1/%2 - Track %3.wav").arg(m_outputDir, m_baseName, QString().sprintf("%02d", m_trackNo.at(i))); + for(int n = 2; QFileInfo(outputFile).exists(); n++) + { + outputFile = QString("%1/%2 - Track %3 (%4).wav").arg(m_outputDir, m_baseName, QString().sprintf("%02d", m_trackNo.at(i)), QString::number(n)); + } + + emit fileSelected(QFileInfo(outputFile).fileName()); + splitFile(outputFile, m_trackNo.at(i), m_trackFile.at(i), m_trackOffset.at(i), m_trackLength.at(i), m_trackMetaInfo.at(i)); + } + + qDebug("All files were split.\n"); + m_bSuccess = true; +} + +void CueSplitter::addTrack(const int trackNo, const QString &file, const double offset, const double length, const AudioFileModel &metaInfo) +{ + + if(m_inputFiles.contains(file)) + { + m_trackFile << file; + m_trackNo << trackNo; + m_trackOffset << offset; + m_trackLength << length; + m_trackMetaInfo << metaInfo; + } + else + { + throw "Unknown input file!"; + } +} + +//////////////////////////////////////////////////////////// +// Privtae Functions +//////////////////////////////////////////////////////////// + +void CueSplitter::splitFile(const QString &output, const int trackNo, const QString &file, const double offset, const double length, const AudioFileModel &metaInfo) +{ + qDebug("\n[Track %02d]", trackNo); + qDebug("File: <%s>", file.toUtf8().constData()); + qDebug("Offset: %f", offset); + qDebug("Length: %f", length); + qDebug("Artist: <%s>", metaInfo.fileArtist().toUtf8().constData()); + qDebug("Title: <%s>", metaInfo.fileName().toUtf8().constData()); + + QRegExp regExp("In:(\\d+)(\\.\\d+)*%"); + QString baseName = QFileInfo(output).fileName(); + + if(!m_inputFiles.contains(file)) + { + qWarning("Unknown input file, skipping!"); + return; + } + + AudioFileModel &inputFileInfo = m_inputFiles[file]; + if(inputFileInfo.formatContainerType().compare("Wave", Qt::CaseInsensitive) || inputFileInfo.formatAudioType().compare("PCM", Qt::CaseInsensitive)) + { + qWarning("Sorry, only Wave/PCM files are supported at this time!"); + return; + } + + QStringList args; + args << "-S" << "-V3"; + args << "--guard" << "--temp" << "."; + args << QDir::toNativeSeparators(file); + args << QDir::toNativeSeparators(output); + args << "trim"; + args << indexToString(offset); + + if((length != std::numeric_limits::quiet_NaN()) && (length != std::numeric_limits::infinity())) + { + args << indexToString(length); + } + + QProcess process; + process.setProcessChannelMode(QProcess::MergedChannels); + process.setReadChannel(QProcess::StandardOutput); + process.setWorkingDirectory(m_outputDir); + process.start(m_soxBin, args); + + if(!process.waitForStarted()) + { + qWarning("SoX process failed to create!"); + qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData()); + process.kill(); + process.waitForFinished(-1); + return; + } + + while(process.state() != QProcess::NotRunning) + { + if(m_abortFlag) + { + process.kill(); + break; + } + process.waitForReadyRead(); + if(!process.bytesAvailable() && process.state() == QProcess::Running) + { + process.kill(); + qWarning("SoX process timed out <-- killing!"); + break; + } + while(process.bytesAvailable() > 0) + { + QByteArray line = process.readLine(); + QString text = QString::fromUtf8(line.constData()).simplified(); + if(regExp.lastIndexIn(text) >= 0) + { + bool ok = false; + int progress = regExp.cap(1).toInt(&ok); + if(ok) + { + emit fileSelected(QString("%1 [%2%]").arg(baseName, QString::number(progress))); + } + } + } + } + + if(process.state() != QProcess::NotRunning) + { + process.kill(); + process.waitForFinished(-1); + } + + if(process.exitStatus() != QProcess::NormalExit || QFileInfo(output).size() == 0) + { + qWarning("Splitting has failed!"); + return; + } + + AudioFileModel outFileInfo(metaInfo); + outFileInfo.setFilePath(output); + outFileInfo.setFormatContainerType(inputFileInfo.formatContainerType()); + outFileInfo.setFormatAudioType(inputFileInfo.formatAudioType()); + outFileInfo.setFileDuration(static_cast(abs(length))); + emit fileSplit(outFileInfo); +} + +QString CueSplitter::indexToString(const double index) const +{ + if(index == std::numeric_limits::quiet_NaN() || index == std::numeric_limits::infinity() || index < 0.0) + { + return QString(); + } + + int temp = static_cast(index * 1000.0); + + int msec = temp % 1000; + int secs = temp / 1000; + + if(secs >= 3600) + { + return QString().sprintf("%d:%02d:%02d.%03d", min(99, secs / 3600), min(59, (secs % 3600) / 60), min(59, (secs % 3600) % 60), min(99, msec)); + } + else if(secs >= 60) + { + return QString().sprintf("%d:%02d.%03d", min(99, secs / 60), min(59, secs % 60), min(99, msec)); + } + else + { + return QString().sprintf("%d.%03d", min(59, secs % 60), min(99, msec)); + } +} + +//////////////////////////////////////////////////////////// +// EVENTS +//////////////////////////////////////////////////////////// + +/*NONE*/ diff --git a/src/Thread_CueSplitter.h b/src/Thread_CueSplitter.h new file mode 100644 index 00000000..b31efad1 --- /dev/null +++ b/src/Thread_CueSplitter.h @@ -0,0 +1,67 @@ +/////////////////////////////////////////////////////////////////////////////// +// LameXP - Audio Encoder Front-End +// Copyright (C) 2004-2011 LoRd_MuldeR +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// http://www.gnu.org/licenses/gpl-2.0.txt +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include + +class AudioFileModel; +class QFile; +class QDir; +class QFileInfo; + +//////////////////////////////////////////////////////////// +// Splash Thread +//////////////////////////////////////////////////////////// + +class CueSplitter: public QThread +{ + Q_OBJECT + +public: + CueSplitter(const QString &outputDir, const QString &baseName, const QList &inputFiles); + void run(); + bool getSuccess(void) { return !isRunning() && m_bSuccess; } + void addTrack(const int trackNo, const QString &file, const double offset, const double length, const AudioFileModel &metaInfo); + +signals: + void fileSelected(const QString &fileName); + void fileSplit(const AudioFileModel &file); + +private: + void splitFile(const QString &output, const int trackNo, const QString &file, const double offset, const double length, const AudioFileModel &metaInfo); + QString indexToString(const double index) const; + + const QString m_soxBin; + const QString m_outputDir; + const QString m_baseName; + bool m_bSuccess; + volatile bool m_abortFlag; + + QMap m_inputFiles; + QList m_trackFile; + QList m_trackNo; + QList m_trackOffset; + QList m_trackLength; + QList m_trackMetaInfo; +};