changed: New label formatting that more logically drops separators.
[xbmc:xbmc-antiquated.git] / xbmc / cdrip / CDDARipper.cpp
1 #include "stdafx.h"
2 #include "CDDARipper.h"
3 #include "CDDAReader.h"
4 #include "..\util.h"
5 #include "EncoderLame.h"
6 #include "EncoderWav.h"
7 #include "EncoderVorbis.h"
8 #include "..\filesystem\CDDADirectory.h"
9 #include "..\detectdvdtype.h"
10 #include "../MusicInfoTagLoaderFactory.h"
11 #include "../utils/LabelFormatter.h"
12
13 using namespace XFILE;
14
15 CCDDARipper::CCDDARipper()
16 {
17   m_pEncoder = NULL;
18 }
19
20 CCDDARipper::~CCDDARipper()
21 {
22   if (m_pEncoder) delete m_pEncoder;
23 }
24
25 bool CCDDARipper::Init(const CStdString& strTrackFile, const CStdString& strFile, MUSIC_INFO::CMusicInfoTag* infoTag)
26 {
27   m_cdReader.Init(strTrackFile);
28
29   switch (g_guiSettings.GetInt("cddaripper.encoder"))
30   {
31   case CDDARIP_ENCODER_WAV:
32     m_pEncoder = new CEncoderWav();
33     break;
34   case CDDARIP_ENCODER_VORBIS:
35     m_pEncoder = new CEncoderVorbis();
36     break;
37   default:
38     m_pEncoder = new CEncoderLame();
39     break;
40   }
41
42   // we have to set the tags before we init the Encoder
43   if (infoTag)
44   {
45     CStdString strTrack;
46     strTrack.Format("%i", atoi(strTrackFile.substr(13, strTrackFile.size() - 13 - 5).c_str()));
47
48     m_pEncoder->SetComment("Ripped with XBMC");
49     m_pEncoder->SetArtist(infoTag->GetArtist().c_str());
50     m_pEncoder->SetTitle(infoTag->GetTitle().c_str());
51     m_pEncoder->SetAlbum(infoTag->GetAlbum().c_str());
52     m_pEncoder->SetGenre(infoTag->GetGenre().c_str());
53     m_pEncoder->SetTrack(strTrack.c_str());
54     m_pEncoder->SetYear(infoTag->GetYear().c_str());
55   }
56
57   // init encoder
58   CStdString strFile2=strFile;
59   if (CUtil::IsHD(strFile))
60     CUtil::GetFatXQualifiedPath(strFile2);
61   if (!m_pEncoder->Init(strFile2.c_str(), 2, 44100, 16))
62   {
63     m_cdReader.DeInit();
64     delete m_pEncoder;
65     m_pEncoder = NULL;
66     return false;
67   }
68   return true;
69 }
70
71 bool CCDDARipper::DeInit()
72 {
73   // Close the encoder
74   m_pEncoder->Close();
75
76   m_cdReader.DeInit();
77
78   if (m_pEncoder) delete m_pEncoder;
79   m_pEncoder = NULL;
80
81   return true;
82 }
83
84 int CCDDARipper::RipChunk(int& nPercent)
85 {
86   BYTE* pbtStream = NULL;
87   long lBytesRead = 0;
88
89   // get data
90   int iResult = m_cdReader.GetData(&pbtStream, lBytesRead);
91
92   // return if rip is done or on some kind of error
93   if (iResult != CDDARIP_OK) return iResult;
94
95   // encode data
96   m_pEncoder->Encode(lBytesRead, pbtStream);
97
98   // Get progress indication
99   nPercent = m_cdReader.GetPercent();
100
101   return CDDARIP_OK;
102 }
103
104 // rip a single track from cd to hd
105 // strFileName has to be a valid filename and the directory must exist
106 bool CCDDARipper::Rip(const CStdString& strTrackFile, const CStdString& strFile, MUSIC_INFO::CMusicInfoTag& infoTag)
107 {
108   int iPercent, iOldPercent = 0;
109   bool bCancelled = false;
110   const char* strFilename = strFile.c_str();
111
112   CLog::Log(LOGINFO, "Start ripping track %s to %s", strTrackFile.c_str(), strFile.c_str());
113
114   // if we are ripping to a samba share, rip it to hd first and then copy it it the share
115   CFileItem file(strFile, false);
116   if (file.IsRemote()) strFilename = tempnam("Z:\\", "");
117   if (!strFilename)
118   {
119     CLog::Log(LOGERROR, "CCDDARipper: Error opening file");
120     return false;
121   }
122
123   // init ripper
124   if (!Init(strTrackFile, strFilename, &infoTag))
125   {
126     CLog::Log(LOGERROR, "Error: CCDDARipper::Init failed");
127     return false;
128   }
129
130   // setup the progress dialog
131   CGUIDialogProgress* pDlgProgress = (CGUIDialogProgress*)m_gWindowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
132   CStdString strLine0, strLine1;
133   int iTrack = atoi(strTrackFile.substr(13, strTrackFile.size() - 13 - 5).c_str());
134   strLine0.Format("%s %i", g_localizeStrings.Get(606).c_str(), iTrack); // Track Number: %i
135   strLine1.Format("%s %s", g_localizeStrings.Get(607).c_str(), strFile); // To: %s
136   pDlgProgress->SetHeading(605); // Ripping
137   pDlgProgress->SetLine(0, strLine0);
138   pDlgProgress->SetLine(1, strLine1);
139   pDlgProgress->SetLine(2, "");
140   pDlgProgress->StartModal();
141   pDlgProgress->ShowProgressBar(true);
142
143   // show progress dialog
144   g_graphicsContext.Lock();
145   pDlgProgress->Progress();
146   g_graphicsContext.Unlock();
147
148   // start ripping
149   while (!bCancelled && CDDARIP_DONE != RipChunk(iPercent))
150   {
151     pDlgProgress->ProgressKeys();
152     bCancelled = pDlgProgress->IsCanceled();
153     if (!bCancelled && iPercent > (iOldPercent + 2)) // update each 2%, it's a bit faster then every 1%
154     {
155       // update dialog
156       iOldPercent = iPercent;
157       pDlgProgress->SetPercentage(iPercent);
158       pDlgProgress->Progress();
159     }
160   }
161
162   // close dialog and deinit ripper
163   pDlgProgress->Close();
164   DeInit();
165
166   if (file.IsRemote() && !bCancelled)
167   {
168     // copy the ripped track to the share
169     if (!CFile::Cache(strFilename, strFile.c_str()))
170     {
171       CLog::Log(LOGINFO, "Error copying file from %s to %s", strFilename, strFile.c_str());
172       // show error
173       g_graphicsContext.Lock();
174       CGUIDialogOK* pDlgOK = (CGUIDialogOK*)m_gWindowManager.GetWindow(WINDOW_DIALOG_OK);
175       pDlgOK->SetHeading("Error copying");
176       pDlgOK->SetLine(0, CStdString(strFilename) + " to");
177       pDlgOK->SetLine(1, strFile);
178       pDlgOK->SetLine(2, "");
179       pDlgOK->DoModal();
180       g_graphicsContext.Unlock();
181       CFile::Delete(strFilename);
182       return false;
183     }
184     // delete cached file
185     CFile::Delete(strFilename);
186   }
187
188   if (bCancelled) CLog::Log(LOGWARNING, "User Cancelled CDDA Rip");
189   else CLog::Log(LOGINFO, "Finished ripping %s", strTrackFile.c_str());
190   return !bCancelled;
191 }
192
193 // rip a single track from cd
194 bool CCDDARipper::RipTrack(CFileItem* pItem)
195 {
196   int iTrack = 0;
197   CStdString strDirectory = g_guiSettings.GetString("cddaripper.path");
198   if (!CUtil::HasSlashAtEnd(strDirectory)) CUtil::AddDirectorySeperator(strDirectory);
199   CFileItem ripPath(strDirectory, true);
200   bool bIsFATX = !ripPath.IsSmb();
201
202   if (pItem->m_strPath.Find(".cdda") < 0) return false;
203   if (strDirectory.size() < 3)
204   {
205     // no rip path has been set, show error
206     CLog::Log(LOGERROR, "Error: CDDARipPath has not been set");
207     g_graphicsContext.Lock();
208     CGUIDialogOK::ShowAndGetInput(257, 608, 609, 0);
209     g_graphicsContext.Unlock();
210     return false;
211   }
212
213   // if album name is set, then we use this as the directory to place the new file in.
214   if (pItem->GetMusicInfoTag()->GetAlbum().size() > 0)
215   {
216     strDirectory += CUtil::MakeLegalFileName(pItem->GetMusicInfoTag()->GetAlbum().c_str(), bIsFATX);
217     CUtil::AddDirectorySeperator(strDirectory);
218   }
219
220   // Create directory if it doesn't exist
221   CUtil::CreateDirectoryEx(strDirectory);
222
223   CStdString strFile;
224   CUtil::AddFileToFolder(strDirectory, GetTrackName(pItem, bIsFATX), strFile);
225
226   return Rip(pItem->m_strPath, strFile.c_str(), *pItem->GetMusicInfoTag());
227 }
228
229 bool CCDDARipper::RipCD()
230 {
231   int iTrack = 0;
232   bool bResult = true;
233   CStdString strFile;
234   CStdString strDirectory = g_guiSettings.GetString("cddaripper.path");
235   if (!CUtil::HasSlashAtEnd(strDirectory)) CUtil::AddDirectorySeperator(strDirectory);
236   CFileItem ripPath(strDirectory, true);
237   bool bIsFATX = !ripPath.IsSmb();
238
239   // return here if cd is not a CDDA disc
240   MEDIA_DETECT::CCdInfo* pInfo = MEDIA_DETECT::CDetectDVDMedia::GetCdInfo();
241   if (pInfo == NULL && !pInfo->IsAudio(1))
242   {
243     CLog::Log(LOGDEBUG, "cddaripper: CD is not an audio cd");
244     return false;
245   }
246
247   if (strDirectory.size() < 3)
248   {
249     // no rip path has been set, show error
250     CLog::Log(LOGERROR, "Error: CDDARipPath has not been set");
251     g_graphicsContext.Lock();
252     CGUIDialogOK::ShowAndGetInput(257, 608, 609, 0);
253     g_graphicsContext.Unlock();
254     return false;
255   }
256
257   // get cd cdda contents
258   CFileItemList vecItems;
259   DIRECTORY::CCDDADirectory directory;
260   directory.GetDirectory("cdda://local/", vecItems);
261
262   //  Get cddb info
263   for (int i = 0; i < vecItems.Size(); ++i)
264   {
265     CFileItem* pItem = vecItems[i];
266     CMusicInfoTagLoaderFactory factory;
267     auto_ptr<IMusicInfoTagLoader> pLoader (factory.CreateLoader(pItem->m_strPath));
268     if (NULL != pLoader.get())
269     {
270       pLoader->Load(pItem->m_strPath, *pItem->GetMusicInfoTag()); // get tag from file
271       if (!pItem->GetMusicInfoTag()->Loaded())
272         break;  //  No CDDB info available
273     }
274   }
275
276   // if album name from first item is set,
277   // then we use this as the directory to place the new file in.
278   CStdString strAlbumDir;
279   if (vecItems[0]->GetMusicInfoTag()->GetAlbum().size() > 0)
280   {
281     strAlbumDir=CUtil::MakeLegalFileName(vecItems[0]->GetMusicInfoTag()->GetAlbum().c_str(), bIsFATX);
282   }
283
284     // No legal fatx directory name or no album in tag
285   if (strAlbumDir.IsEmpty())
286   {
287     // create a directory based on current date
288     SYSTEMTIME datetime;
289     CStdString strDate;
290     GetLocalTime(&datetime);
291     int iNumber = 1;
292     while (1)
293     {
294       strDate.Format("%04i-%02i-%02i-%i", datetime.wYear, datetime.wMonth, datetime.wDay, iNumber);
295       if (!CGUIDialogKeyboard::ShowAndGetInput(strDate, g_localizeStrings.Get(748), false))
296         return false;
297       if (!CFile::Exists(strDirectory + strDate))
298       {
299         strAlbumDir=strDate;
300         break;
301       }
302       iNumber++;
303     }
304   }
305
306   // construct directory where the tracks are stored
307   strDirectory += strAlbumDir;
308   CUtil::AddDirectorySeperator(strDirectory);
309
310   // Create directory if it doesn't exist
311   if (!CUtil::CreateDirectoryEx(strDirectory))
312   {
313     CLog::Log(LOGERROR, "Unable to create directory '%s'", strDirectory.c_str());
314     return false;
315   }
316
317   // rip all tracks one by one, if one fails we quit and return false
318   for (int i = 0; i < vecItems.Size() && bResult == true; i++)
319   {
320     CStdString track(GetTrackName(vecItems[i], bIsFATX));
321
322     // construct filename
323     CUtil::AddFileToFolder(strDirectory, track, strFile);
324
325     DWORD dwTick = timeGetTime();
326
327     // don't rip non cdda items
328     if (vecItems[i]->m_strPath.Find(".cdda") < 0)
329       continue;
330
331     // return false if Rip returned false (this means an error or the user cancelled
332     if (!Rip(vecItems[i]->m_strPath, strFile.c_str(), *vecItems[i]->GetMusicInfoTag())) return false;
333
334     dwTick = timeGetTime() - dwTick;
335     CStdString strTmp;
336     StringUtils::SecondsToTimeString(dwTick / 1000, strTmp);
337     CLog::Log(LOGINFO, "Ripping Track %d took %s", iTrack, strTmp.c_str());
338   }
339   CLog::Log(LOGINFO, "Ripped CD succesfull");
340   return true;
341 }
342
343 char* CCDDARipper::GetExtension(int iEncoder)
344 {
345   if (iEncoder == CDDARIP_ENCODER_WAV) return ".wav";
346   if (iEncoder == CDDARIP_ENCODER_VORBIS) return ".ogg";
347   return ".mp3";
348 }
349
350 CStdString CCDDARipper::GetTrackName(CFileItem *item, bool isFatX)
351 {
352   // get track number from "cdda://local/01.cdda"
353   int trackNumber = atoi(item->m_strPath.substr(13, item->m_strPath.size() - 13 - 5).c_str());
354
355   // Format up our ripped file label
356   CFileItem destItem(*item);
357   destItem.SetLabel("");
358   CLabelFormatter formatter(g_guiSettings.GetString("cddaripper.trackformat"), "");
359   formatter.FormatLabel(&destItem);
360
361   // grab the label to use it as our ripped filename
362   CStdString track = destItem.GetLabel();
363   if (track.IsEmpty())
364     track.Format("%s%02i", "Track-", trackNumber);
365   track += GetExtension(g_guiSettings.GetInt("cddaripper.encoder"));
366
367   // make sure the filename is legal
368   track = CUtil::MakeLegalFileName(track.c_str(), isFatX);
369   return track;
370 }