Merged from linuxport rev21200:21590. Updated.
[xbmc:xbmc-antiquated.git] / XBMC / xbmc / cdrip / CDDARipper.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "stdafx.h"
23 #include "CDDARipper.h"
24 #include "CDDAReader.h"
25 #include "Util.h"
26 #include "EncoderLame.h"
27 #include "EncoderWav.h"
28 #include "EncoderVorbis.h"
29 #include "FileSystem/CDDADirectory.h"
30 #include "DetectDVDType.h"
31 #include "MusicInfoTagLoaderFactory.h"
32 #include "utils/LabelFormatter.h"
33 #include "MusicInfoTag.h"
34 #include "GUIWindowManager.h"
35 #include "GUIDialogOK.h"
36 #include "GUIDialogProgress.h"
37 #include "GUIDialogKeyboard.h"
38 #include "GUISettings.h"
39 #include "FileItem.h"
40 #include "FileSystem/SpecialProtocol.h"
41
42 using namespace std;
43 using namespace XFILE;
44 using namespace MUSIC_INFO;
45
46 CCDDARipper::CCDDARipper()
47 {
48   m_pEncoder = NULL;
49 }
50
51 CCDDARipper::~CCDDARipper()
52 {
53   delete m_pEncoder;
54 }
55
56 bool CCDDARipper::Init(const CStdString& strTrackFile, const CStdString& strFile, MUSIC_INFO::CMusicInfoTag* infoTag)
57 {
58   m_cdReader.Init(strTrackFile);
59
60   switch (g_guiSettings.GetInt("cddaripper.encoder"))
61   {
62   case CDDARIP_ENCODER_WAV:
63     m_pEncoder = new CEncoderWav();
64     break;
65   case CDDARIP_ENCODER_VORBIS:
66     m_pEncoder = new CEncoderVorbis();
67     break;
68   default:
69     m_pEncoder = new CEncoderLame();
70     break;
71   }
72
73   // we have to set the tags before we init the Encoder
74   if (infoTag)
75   {
76     CStdString strTrack;
77     strTrack.Format("%i", atoi(strTrackFile.substr(13, strTrackFile.size() - 13 - 5).c_str()));
78
79     m_pEncoder->SetComment("Ripped with XBMC");
80     m_pEncoder->SetArtist(infoTag->GetArtist().c_str());
81     m_pEncoder->SetTitle(infoTag->GetTitle().c_str());
82     m_pEncoder->SetAlbum(infoTag->GetAlbum().c_str());
83     m_pEncoder->SetGenre(infoTag->GetGenre().c_str());
84     m_pEncoder->SetTrack(strTrack.c_str());
85     m_pEncoder->SetYear(infoTag->GetYearString().c_str());
86   }
87
88   // init encoder
89   CStdString strFile2=CUtil::MakeLegalPath(strFile);
90   if (!m_pEncoder->Init(strFile2.c_str(), 2, 44100, 16))
91   {
92     m_cdReader.DeInit();
93     delete m_pEncoder;
94     m_pEncoder = NULL;
95     return false;
96   }
97   return true;
98 }
99
100 bool CCDDARipper::DeInit()
101 {
102   // Close the encoder
103   m_pEncoder->Close();
104
105   m_cdReader.DeInit();
106
107   delete m_pEncoder;
108   m_pEncoder = NULL;
109
110   return true;
111 }
112
113 int CCDDARipper::RipChunk(int& nPercent)
114 {
115   BYTE* pbtStream = NULL;
116   long lBytesRead = 0;
117   nPercent = 0;
118
119   // get data
120   int iResult = m_cdReader.GetData(&pbtStream, lBytesRead);
121
122   // return if rip is done or on some kind of error
123   if (iResult != CDDARIP_OK) return iResult;
124
125   // encode data
126   m_pEncoder->Encode(lBytesRead, pbtStream);
127
128   // Get progress indication
129   nPercent = m_cdReader.GetPercent();
130
131   return CDDARIP_OK;
132 }
133
134 // rip a single track from cd to hd
135 // strFileName has to be a valid filename and the directory must exist
136 bool CCDDARipper::Rip(const CStdString& strTrackFile, const CStdString& strFile, MUSIC_INFO::CMusicInfoTag& infoTag)
137 {
138   int iPercent, iOldPercent = 0;
139   bool bCancelled = false;
140   CStdString strFilename(strFile);
141
142   CLog::Log(LOGINFO, "Start ripping track %s to %s", strTrackFile.c_str(), strFile.c_str());
143
144   // if we are ripping to a samba share, rip it to hd first and then copy it it the share
145   CFileItem file(strFile, false);
146   if (file.IsRemote()) 
147   {
148     char tmp[MAX_PATH];
149 #ifndef _LINUX
150     GetTempFileName(_P("special://temp/"), "riptrack", 0, tmp);
151 #else
152     int fd;
153     strncpy(tmp, _P("special://temp/riptrackXXXXXX"), MAX_PATH);
154     if ((fd = mkstemp(tmp)) == -1)
155       strFilename = "";
156     close(fd);
157 #endif
158     strFilename = tmp;
159   }
160   
161   if (!strFilename)
162   {
163     CLog::Log(LOGERROR, "CCDDARipper: Error opening file");
164     return false;
165   }
166
167   // init ripper
168   if (!Init(strTrackFile, strFilename, &infoTag))
169   {
170     CLog::Log(LOGERROR, "Error: CCDDARipper::Init failed");
171     return false;
172   }
173
174   // setup the progress dialog
175   CGUIDialogProgress* pDlgProgress = (CGUIDialogProgress*)m_gWindowManager.GetWindow(WINDOW_DIALOG_PROGRESS);
176   CStdString strLine0, strLine1;
177   int iTrack = atoi(strTrackFile.substr(13, strTrackFile.size() - 13 - 5).c_str());
178   strLine0.Format("%s %i", g_localizeStrings.Get(606).c_str(), iTrack); // Track Number: %i
179   strLine1.Format("%s %s", g_localizeStrings.Get(607).c_str(), strFile); // To: %s
180   pDlgProgress->SetHeading(605); // Ripping
181   pDlgProgress->SetLine(0, strLine0);
182   pDlgProgress->SetLine(1, strLine1);
183   pDlgProgress->SetLine(2, "");
184   pDlgProgress->StartModal();
185   pDlgProgress->ShowProgressBar(true);
186
187   // show progress dialog
188   g_graphicsContext.Lock();
189   pDlgProgress->Progress();
190   g_graphicsContext.Unlock();
191
192   // start ripping
193   while (!bCancelled && CDDARIP_DONE != RipChunk(iPercent))
194   {
195     pDlgProgress->ProgressKeys();
196     bCancelled = pDlgProgress->IsCanceled();
197     if (!bCancelled && iPercent > iOldPercent) // update each 2%, it's a bit faster then every 1%
198     {
199       // update dialog
200       iOldPercent = iPercent;
201       pDlgProgress->SetPercentage(iPercent);
202       pDlgProgress->Progress();
203     }
204   }
205
206   // close dialog and deinit ripper
207   pDlgProgress->Close();
208   DeInit();
209
210   if (file.IsRemote() && !bCancelled)
211   {
212     // copy the ripped track to the share
213     if (!CFile::Cache(strFilename, strFile.c_str()))
214     {
215       CLog::Log(LOGINFO, "Error copying file from %s to %s", strFilename.c_str(), strFile.c_str());
216       // show error
217       g_graphicsContext.Lock();
218       CGUIDialogOK* pDlgOK = (CGUIDialogOK*)m_gWindowManager.GetWindow(WINDOW_DIALOG_OK);
219       pDlgOK->SetHeading("Error copying");
220       pDlgOK->SetLine(0, CStdString(strFilename) + " to");
221       pDlgOK->SetLine(1, strFile);
222       pDlgOK->SetLine(2, "");
223       pDlgOK->DoModal();
224       g_graphicsContext.Unlock();
225       CFile::Delete(strFilename);
226       return false;
227     }
228     // delete cached file
229     CFile::Delete(strFilename);
230   }
231
232   if (bCancelled) {
233     CLog::Log(LOGWARNING, "User Cancelled CDDA Rip");
234     CFile::Delete(strFilename);
235   }
236   else CLog::Log(LOGINFO, "Finished ripping %s", strTrackFile.c_str());
237   return !bCancelled;
238 }
239
240 // rip a single track from cd
241 bool CCDDARipper::RipTrack(CFileItem* pItem)
242 {
243   CStdString strDirectory = g_guiSettings.GetString("cddaripper.path");
244   CUtil::AddSlashAtEnd(strDirectory);
245   CFileItem ripPath(strDirectory, true);
246
247   int LegalType = LEGAL_NONE;
248   if (ripPath.IsSmb())
249     LegalType=LEGAL_WIN32_COMPAT;
250 #ifdef _WIN32PC  
251   if (ripPath.IsHD())
252     LegalType=LEGAL_WIN32_COMPAT;
253 #endif
254   
255   if (pItem->m_strPath.Find(".cdda") < 0) return false;
256   if (strDirectory.size() < 3)
257   {
258     // no rip path has been set, show error
259     CLog::Log(LOGERROR, "Error: CDDARipPath has not been set");
260     g_graphicsContext.Lock();
261     CGUIDialogOK::ShowAndGetInput(257, 608, 609, 0);
262     g_graphicsContext.Unlock();
263     return false;
264   }
265
266   // if album name is set, then we use this as the directory to place the new file in.
267   if (!pItem->GetMusicInfoTag()->GetAlbum().empty())
268   {
269     strDirectory += CUtil::MakeLegalFileName(pItem->GetMusicInfoTag()->GetAlbum().c_str(), LegalType);
270     CUtil::AddDirectorySeperator(strDirectory);
271   }
272
273   // Create directory if it doesn't exist
274   CUtil::CreateDirectoryEx(strDirectory);
275
276   CStdString strFile;
277   CUtil::AddFileToFolder(strDirectory, GetTrackName(pItem, LegalType), strFile);
278
279   return Rip(pItem->m_strPath, strFile.c_str(), *pItem->GetMusicInfoTag());
280 }
281
282 bool CCDDARipper::RipCD()
283 {
284   int iTrack = 0;
285   bool bResult = true;
286   CStdString strFile;
287   CStdString strDirectory = g_guiSettings.GetString("cddaripper.path");
288   CUtil::AddSlashAtEnd(strDirectory);
289   CFileItem ripPath(strDirectory, true);
290   bool bIsFATX = !ripPath.IsSmb();
291
292   // return here if cd is not a CDDA disc
293   MEDIA_DETECT::CCdInfo* pInfo = MEDIA_DETECT::CDetectDVDMedia::GetCdInfo();
294   if (pInfo == NULL && !pInfo->IsAudio(1))
295   {
296     CLog::Log(LOGDEBUG, "cddaripper: CD is not an audio cd");
297     return false;
298   }
299
300   if (strDirectory.size() < 3)
301   {
302     // no rip path has been set, show error
303     CLog::Log(LOGERROR, "Error: CDDARipPath has not been set");
304     g_graphicsContext.Lock();
305     CGUIDialogOK::ShowAndGetInput(257, 608, 609, 0);
306     g_graphicsContext.Unlock();
307     return false;
308   }
309
310   // get cd cdda contents
311   CFileItemList vecItems;
312   DIRECTORY::CCDDADirectory directory;
313   directory.GetDirectory("cdda://local/", vecItems);
314
315   //  Get cddb info
316   for (int i = 0; i < vecItems.Size(); ++i)
317   {
318     CFileItemPtr pItem = vecItems[i];
319     CMusicInfoTagLoaderFactory factory;
320     auto_ptr<IMusicInfoTagLoader> pLoader (factory.CreateLoader(pItem->m_strPath));
321     if (NULL != pLoader.get())
322     {
323       pLoader->Load(pItem->m_strPath, *pItem->GetMusicInfoTag()); // get tag from file
324       if (!pItem->GetMusicInfoTag()->Loaded())
325         break;  //  No CDDB info available
326     }
327   }
328
329   // if album name from first item is set,
330   // then we use this as the directory to place the new file in.
331   CStdString strAlbumDir;
332   if (!vecItems[0]->GetMusicInfoTag()->GetAlbum().empty())
333   {
334     int LegalType=LEGAL_NONE;
335     if (ripPath.IsSmb())
336       LegalType=LEGAL_WIN32_COMPAT;
337 #ifdef _WIN32PC
338     if (ripPath.IsHD())
339       LegalType=LEGAL_WIN32_COMPAT;
340 #endif
341     strAlbumDir=CUtil::MakeLegalFileName(vecItems[0]->GetMusicInfoTag()->GetAlbum().c_str(), LegalType);
342   }
343
344     // No legal fatx directory name or no album in tag
345   if (strAlbumDir.IsEmpty())
346   {
347     // create a directory based on current date
348     SYSTEMTIME datetime;
349     CStdString strDate;
350     GetLocalTime(&datetime);
351     int iNumber = 1;
352     while (1)
353     {
354       strDate.Format("%04i-%02i-%02i-%i", datetime.wYear, datetime.wMonth, datetime.wDay, iNumber);
355       if (!CGUIDialogKeyboard::ShowAndGetInput(strDate, g_localizeStrings.Get(748), false))
356         return false;
357       if (!CFile::Exists(strDirectory + strDate))
358       {
359         strAlbumDir=strDate;
360         break;
361       }
362       iNumber++;
363     }
364   }
365
366   // construct directory where the tracks are stored
367   strDirectory += strAlbumDir;
368   CUtil::AddDirectorySeperator(strDirectory);
369
370   // Create directory if it doesn't exist
371   if (!CUtil::CreateDirectoryEx(strDirectory))
372   {
373     CLog::Log(LOGERROR, "Unable to create directory '%s'", strDirectory.c_str());
374     return false;
375   }
376
377   // rip all tracks one by one, if one fails we quit and return false
378   for (int i = 0; i < vecItems.Size() && bResult == true; i++)
379   {
380     CFileItemPtr item = vecItems[i];
381     CStdString track(GetTrackName(item.get(), bIsFATX));
382
383     // construct filename
384     CUtil::AddFileToFolder(strDirectory, track, strFile);
385
386     DWORD dwTick = timeGetTime();
387
388     // don't rip non cdda items
389     if (item->m_strPath.Find(".cdda") < 0)
390       continue;
391
392     // return false if Rip returned false (this means an error or the user cancelled
393     if (!Rip(item->m_strPath, strFile.c_str(), *item->GetMusicInfoTag())) return false;
394
395     dwTick = timeGetTime() - dwTick;
396     CStdString strTmp;
397     StringUtils::SecondsToTimeString(dwTick / 1000, strTmp);
398     CLog::Log(LOGINFO, "Ripping Track %d took %s", iTrack, strTmp.c_str());
399   }
400   CLog::Log(LOGINFO, "Ripped CD succesfull");
401   return true;
402 }
403
404 const char* CCDDARipper::GetExtension(int iEncoder)
405 {
406   if (iEncoder == CDDARIP_ENCODER_WAV) return ".wav";
407   if (iEncoder == CDDARIP_ENCODER_VORBIS) return ".ogg";
408   return ".mp3";
409 }
410
411 CStdString CCDDARipper::GetTrackName(CFileItem *item, int LegalType)
412 {
413   // get track number from "cdda://local/01.cdda"
414   int trackNumber = atoi(item->m_strPath.substr(13, item->m_strPath.size() - 13 - 5).c_str());
415
416   // Format up our ripped file label
417   CFileItem destItem(*item);
418   destItem.SetLabel("");
419   CLabelFormatter formatter(g_guiSettings.GetString("cddaripper.trackformat"), "");
420   formatter.FormatLabel(&destItem);
421
422   // grab the label to use it as our ripped filename
423   CStdString track = destItem.GetLabel();
424   if (track.IsEmpty())
425     track.Format("%s%02i", "Track-", trackNumber);
426   track += GetExtension(g_guiSettings.GetInt("cddaripper.encoder"));
427
428   // make sure the filename is legal
429   track = CUtil::MakeLegalFileName(track, LegalType);
430   return track;
431 }