1 /*
2  *  Copyright (C) 2012-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "CDDARipJob.h"
10 #include "Encoder.h"
11 #include "EncoderFFmpeg.h"
12 #include "FileItem.h"
13 #include "ServiceBroker.h"
14 #include "utils/log.h"
15 #include "utils/SystemInfo.h"
16 #include "Util.h"
17 #include "dialogs/GUIDialogExtendedProgressBar.h"
18 #include "filesystem/File.h"
19 #include "filesystem/SpecialProtocol.h"
20 #include "guilib/GUIComponent.h"
21 #include "guilib/GUIWindowManager.h"
22 #include "guilib/LocalizeStrings.h"
23 #include "settings/AdvancedSettings.h"
24 #include "settings/Settings.h"
25 #include "settings/SettingsComponent.h"
26 #include "utils/StringUtils.h"
27 #include "storage/MediaManager.h"
28 #include "addons/AddonManager.h"
29 #include "addons/AudioEncoder.h"
30 
31 #if defined(TARGET_WINDOWS)
32 #include "platform/win32/CharsetConverter.h"
33 #endif
34 
35 using namespace ADDON;
36 using namespace MUSIC_INFO;
37 using namespace XFILE;
38 
CCDDARipJob(const std::string & input,const std::string & output,const CMusicInfoTag & tag,int encoder,bool eject,unsigned int rate,unsigned int channels,unsigned int bps)39 CCDDARipJob::CCDDARipJob(const std::string& input,
40                          const std::string& output,
41                          const CMusicInfoTag& tag,
42                          int encoder,
43                          bool eject,
44                          unsigned int rate,
45                          unsigned int channels, unsigned int bps) :
46   m_rate(rate), m_channels(channels), m_bps(bps), m_tag(tag),
47   m_input(input), m_output(CUtil::MakeLegalPath(output)), m_eject(eject),
48   m_encoder(encoder)
49 {
50 }
51 
52 CCDDARipJob::~CCDDARipJob() = default;
53 
DoWork()54 bool CCDDARipJob::DoWork()
55 {
56   CLog::Log(LOGINFO, "Start ripping track %s to %s", m_input.c_str(),
57                                                      m_output.c_str());
58 
59   // if we are ripping to a samba share, rip it to hd first and then copy it it the share
60   CFileItem file(m_output, false);
61   if (file.IsRemote())
62     m_output = SetupTempFile();
63 
64   if (m_output.empty())
65   {
66     CLog::Log(LOGERROR, "CCDDARipper: Error opening file");
67     return false;
68   }
69 
70   // init ripper
71   CFile reader;
72   CEncoder* encoder = nullptr;
73   if (!reader.Open(m_input,READ_CACHED) || !(encoder=SetupEncoder(reader)))
74   {
75     CLog::Log(LOGERROR, "Error: CCDDARipper::Init failed");
76     return false;
77   }
78 
79   // setup the progress dialog
80   CGUIDialogExtendedProgressBar* pDlgProgress =
81       CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS);
82   CGUIDialogProgressBarHandle* handle = pDlgProgress->GetHandle(g_localizeStrings.Get(605));
83 
84   int iTrack = atoi(m_input.substr(13, m_input.size() - 13 - 5).c_str());
85   std::string strLine0 = StringUtils::Format("%02i. %s - %s", iTrack,
86                                             m_tag.GetArtistString().c_str(),
87                                             m_tag.GetTitle().c_str());
88   handle->SetText(strLine0);
89 
90   // start ripping
91   int percent=0;
92   int oldpercent=0;
93   bool cancelled(false);
94   int result;
95   while (!cancelled && (result=RipChunk(reader, encoder, percent)) == 0)
96   {
97     cancelled = ShouldCancel(percent,100);
98     if (percent > oldpercent)
99     {
100       oldpercent = percent;
101       handle->SetPercentage(static_cast<float>(percent));
102     }
103   }
104 
105   // close encoder ripper
106   encoder->CloseEncode();
107   delete encoder;
108   reader.Close();
109 
110   if (file.IsRemote() && !cancelled && result == 2)
111   {
112     // copy the ripped track to the share
113     if (!CFile::Copy(m_output, file.GetPath()))
114     {
115       CLog::Log(LOGERROR, "CDDARipper: Error copying file from %s to %s",
116                 m_output.c_str(), file.GetPath().c_str());
117       CFile::Delete(m_output);
118       return false;
119     }
120     // delete cached file
121     CFile::Delete(m_output);
122   }
123 
124   if (cancelled)
125   {
126     CLog::Log(LOGWARNING, "User Cancelled CDDA Rip");
127     CFile::Delete(m_output);
128   }
129   else if (result == 1)
130     CLog::Log(LOGERROR, "CDDARipper: Error ripping %s", m_input.c_str());
131   else if (result < 0)
132     CLog::Log(LOGERROR, "CDDARipper: Error encoding %s", m_input.c_str());
133   else
134   {
135     CLog::Log(LOGINFO, "Finished ripping %s", m_input.c_str());
136     if (m_eject)
137     {
138       CLog::Log(LOGINFO, "Ejecting CD");
139       CServiceBroker::GetMediaManager().EjectTray();
140     }
141   }
142 
143   handle->MarkFinished();
144 
145   return !cancelled && result == 2;
146 }
147 
RipChunk(CFile & reader,CEncoder * encoder,int & percent)148 int CCDDARipJob::RipChunk(CFile& reader, CEncoder* encoder, int& percent)
149 {
150   percent = 0;
151 
152   uint8_t stream[1024];
153 
154   // get data
155   int result = reader.Read(stream, 1024);
156 
157   // return if rip is done or on some kind of error
158   if (result <= 0)
159     return 1;
160 
161   // encode data
162   int encres=encoder->Encode(result, stream);
163 
164   // Get progress indication
165   percent = static_cast<int>(reader.GetPosition()*100/reader.GetLength());
166 
167   if (reader.GetPosition() == reader.GetLength())
168     return 2;
169 
170   return -(1-encres);
171 }
172 
SetupEncoder(CFile & reader)173 CEncoder* CCDDARipJob::SetupEncoder(CFile& reader)
174 {
175   CEncoder* encoder = nullptr;
176   const std::string audioEncoder = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_AUDIOCDS_ENCODER);
177   if (audioEncoder == "audioencoder.kodi.builtin.aac" || audioEncoder == "audioencoder.kodi.builtin.wma")
178   {
179     std::shared_ptr<IEncoder> enc(new CEncoderFFmpeg());
180     encoder = new CEncoder(enc);
181   }
182   else
183   {
184     const AddonInfoPtr addonInfo =
185         CServiceBroker::GetAddonMgr().GetAddonInfo(audioEncoder, ADDON_AUDIOENCODER);
186     if (addonInfo)
187     {
188       std::shared_ptr<IEncoder> enc = std::make_shared<CAudioEncoder>(addonInfo);
189       encoder = new CEncoder(enc);
190     }
191   }
192   if (!encoder)
193     return NULL;
194 
195   // we have to set the tags before we init the Encoder
196   const std::string strTrack = StringUtils::Format("%li", strtol(m_input.substr(13, m_input.size() - 13 - 5).c_str(), nullptr, 10));
197 
198   const std::string itemSeparator = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator;
199 
200   encoder->SetComment(std::string("Ripped with ") + CSysInfo::GetAppName());
201   encoder->SetArtist(StringUtils::Join(m_tag.GetArtist(), itemSeparator));
202   encoder->SetTitle(m_tag.GetTitle());
203   encoder->SetAlbum(m_tag.GetAlbum());
204   encoder->SetAlbumArtist(StringUtils::Join(m_tag.GetAlbumArtist(), itemSeparator));
205   encoder->SetGenre(StringUtils::Join(m_tag.GetGenre(), itemSeparator));
206   encoder->SetTrack(strTrack);
207   encoder->SetTrackLength(static_cast<int>(reader.GetLength()));
208   encoder->SetYear(m_tag.GetYearString());
209 
210   // init encoder
211   if (!encoder->Init(m_output.c_str(), m_channels, m_rate, m_bps))
212     delete encoder, encoder = nullptr;
213 
214   return encoder;
215 }
216 
SetupTempFile()217 std::string CCDDARipJob::SetupTempFile()
218 {
219   char tmp[MAX_PATH + 1];
220 #if defined(TARGET_WINDOWS)
221   using namespace KODI::PLATFORM::WINDOWS;
222   wchar_t tmpW[MAX_PATH];
223   GetTempFileName(ToW(CSpecialProtocol::TranslatePath("special://temp/")).c_str(), L"riptrack", 0, tmpW);
224   auto tmpString = FromW(tmpW);
225   strncpy_s(tmp, tmpString.length(), tmpString.c_str(), MAX_PATH);
226 #else
227   int fd;
228   strncpy(tmp, CSpecialProtocol::TranslatePath("special://temp/riptrackXXXXXX").c_str(), MAX_PATH);
229   if ((fd = mkstemp(tmp)) == -1)
230    tmp[0] = '\0';
231   if (fd != -1)
232     close(fd);
233 #endif
234   return tmp;
235 }
236 
operator ==(const CJob * job) const237 bool CCDDARipJob::operator==(const CJob* job) const
238 {
239   if (strcmp(job->GetType(),GetType()) == 0)
240   {
241     const CCDDARipJob* rjob = dynamic_cast<const CCDDARipJob*>(job);
242     if (rjob)
243     {
244       return m_input  == rjob->m_input &&
245              m_output == rjob->m_output;
246     }
247   }
248   return false;
249 }
250