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