/xbmc/cores/ExternalPlayer/ExternalPlayer.cpp

http://github.com/xbmc/xbmc · C++ · 683 lines · 559 code · 94 blank · 30 comment · 107 complexity · b58159e67910de3cedd7323d15d6aa54 MD5 · raw file

  1. /*
  2. * Copyright (C) 2005-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. #include "threads/SystemClock.h"
  9. #include "CompileInfo.h"
  10. #include "ExternalPlayer.h"
  11. #include "windowing/WinSystem.h"
  12. #include "dialogs/GUIDialogOK.h"
  13. #include "guilib/GUIComponent.h"
  14. #include "guilib/GUIWindowManager.h"
  15. #include "Application.h"
  16. #include "filesystem/MusicDatabaseFile.h"
  17. #include "FileItem.h"
  18. #include "utils/RegExp.h"
  19. #include "utils/StringUtils.h"
  20. #include "utils/URIUtils.h"
  21. #include "URL.h"
  22. #include "utils/XMLUtils.h"
  23. #include "utils/log.h"
  24. #include "utils/Variant.h"
  25. #include "video/Bookmark.h"
  26. #include "ServiceBroker.h"
  27. #include "cores/AudioEngine/Interfaces/AE.h"
  28. #include "cores/DataCacheCore.h"
  29. #if defined(TARGET_WINDOWS)
  30. #include "utils/CharsetConverter.h"
  31. #include <Windows.h>
  32. #endif
  33. #if defined(TARGET_ANDROID)
  34. #include "platform/android/activity/XBMCApp.h"
  35. #endif
  36. // If the process ends in less than this time (ms), we assume it's a launcher
  37. // and wait for manual intervention before continuing
  38. #define LAUNCHER_PROCESS_TIME 2000
  39. // Time (ms) we give a process we sent a WM_QUIT to close before terminating
  40. #define PROCESS_GRACE_TIME 3000
  41. // Default time after which the item's playcount is incremented
  42. #define DEFAULT_PLAYCOUNT_MIN_TIME 10
  43. using namespace XFILE;
  44. #if defined(TARGET_WINDOWS_DESKTOP)
  45. extern HWND g_hWnd;
  46. #endif
  47. CExternalPlayer::CExternalPlayer(IPlayerCallback& callback)
  48. : IPlayer(callback),
  49. CThread("ExternalPlayer")
  50. {
  51. m_bAbortRequest = false;
  52. m_bIsPlaying = false;
  53. m_playbackStartTime = 0;
  54. m_speed = 1;
  55. m_time = 0;
  56. m_hideconsole = false;
  57. m_warpcursor = WARP_NONE;
  58. m_hidexbmc = false;
  59. m_islauncher = false;
  60. m_playCountMinTime = DEFAULT_PLAYCOUNT_MIN_TIME;
  61. m_playOneStackItem = false;
  62. m_dialog = NULL;
  63. #if defined(TARGET_WINDOWS_DESKTOP)
  64. m_xPos = 0;
  65. m_yPos = 0;
  66. memset(&m_processInfo, 0, sizeof(m_processInfo));
  67. #endif
  68. }
  69. CExternalPlayer::~CExternalPlayer()
  70. {
  71. CloseFile();
  72. }
  73. bool CExternalPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
  74. {
  75. try
  76. {
  77. m_file = file;
  78. m_bIsPlaying = true;
  79. m_time = 0;
  80. m_playbackStartTime = XbmcThreads::SystemClockMillis();
  81. m_launchFilename = file.GetDynPath();
  82. CLog::Log(LOGINFO, "%s: %s", __FUNCTION__, m_launchFilename.c_str());
  83. Create();
  84. return true;
  85. }
  86. catch(...)
  87. {
  88. m_bIsPlaying = false;
  89. CLog::Log(LOGERROR,"%s - Exception thrown", __FUNCTION__);
  90. return false;
  91. }
  92. }
  93. bool CExternalPlayer::CloseFile(bool reopen)
  94. {
  95. m_bAbortRequest = true;
  96. if (m_dialog && m_dialog->IsActive()) m_dialog->Close();
  97. #if defined(TARGET_WINDOWS_DESKTOP)
  98. if (m_bIsPlaying && m_processInfo.hProcess)
  99. {
  100. TerminateProcess(m_processInfo.hProcess, 1);
  101. }
  102. #endif
  103. return true;
  104. }
  105. bool CExternalPlayer::IsPlaying() const
  106. {
  107. return m_bIsPlaying;
  108. }
  109. void CExternalPlayer::Process()
  110. {
  111. std::string mainFile = m_launchFilename;
  112. std::string archiveContent;
  113. if (m_args.find("{0}") == std::string::npos)
  114. {
  115. // Unwind archive names
  116. CURL url(m_launchFilename);
  117. if (url.IsProtocol("zip") || url.IsProtocol("rar") /* || url.IsProtocol("iso9660") ??*/ || url.IsProtocol("udf"))
  118. {
  119. mainFile = url.GetHostName();
  120. archiveContent = url.GetFileName();
  121. }
  122. if (url.IsProtocol("musicdb"))
  123. mainFile = CMusicDatabaseFile::TranslateUrl(url);
  124. if (url.IsProtocol("bluray"))
  125. {
  126. CURL base(url.GetHostName());
  127. if (base.IsProtocol("udf"))
  128. {
  129. mainFile = base.GetHostName(); /* image file */
  130. archiveContent = base.GetFileName();
  131. }
  132. else
  133. mainFile = URIUtils::AddFileToFolder(base.Get(), url.GetFileName());
  134. }
  135. }
  136. if (!m_filenameReplacers.empty())
  137. {
  138. for (unsigned int i = 0; i < m_filenameReplacers.size(); i++)
  139. {
  140. std::vector<std::string> vecSplit = StringUtils::Split(m_filenameReplacers[i], " , ");
  141. // something is wrong, go to next substitution
  142. if (vecSplit.size() != 4)
  143. continue;
  144. std::string strMatch = vecSplit[0];
  145. StringUtils::Replace(strMatch, ",,",",");
  146. bool bCaseless = vecSplit[3].find('i') != std::string::npos;
  147. CRegExp regExp(bCaseless, CRegExp::autoUtf8);
  148. if (!regExp.RegComp(strMatch.c_str()))
  149. { // invalid regexp - complain in logs
  150. CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strMatch.c_str());
  151. continue;
  152. }
  153. if (regExp.RegFind(mainFile) > -1)
  154. {
  155. std::string strPat = vecSplit[1];
  156. StringUtils::Replace(strPat, ",,",",");
  157. if (!regExp.RegComp(strPat.c_str()))
  158. { // invalid regexp - complain in logs
  159. CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strPat.c_str());
  160. continue;
  161. }
  162. std::string strRep = vecSplit[2];
  163. StringUtils::Replace(strRep, ",,",",");
  164. bool bGlobal = vecSplit[3].find('g') != std::string::npos;
  165. bool bStop = vecSplit[3].find('s') != std::string::npos;
  166. int iStart = 0;
  167. while ((iStart = regExp.RegFind(mainFile, iStart)) > -1)
  168. {
  169. int iLength = regExp.GetFindLen();
  170. mainFile = mainFile.substr(0, iStart) + regExp.GetReplaceString(strRep) + mainFile.substr(iStart + iLength);
  171. if (!bGlobal)
  172. break;
  173. }
  174. CLog::Log(LOGINFO, "%s: File matched:'%s' (RE='%s',Rep='%s') new filename:'%s'.", __FUNCTION__, strMatch.c_str(), strPat.c_str(), strRep.c_str(), mainFile.c_str());
  175. if (bStop) break;
  176. }
  177. }
  178. }
  179. CLog::Log(LOGINFO, "%s: Player : %s", __FUNCTION__, m_filename.c_str());
  180. CLog::Log(LOGINFO, "%s: File : %s", __FUNCTION__, mainFile.c_str());
  181. CLog::Log(LOGINFO, "%s: Content: %s", __FUNCTION__, archiveContent.c_str());
  182. CLog::Log(LOGINFO, "%s: Args : %s", __FUNCTION__, m_args.c_str());
  183. CLog::Log(LOGINFO, "%s: Start", __FUNCTION__);
  184. // make sure we surround the arguments with quotes where necessary
  185. std::string strFName;
  186. std::string strFArgs;
  187. #if defined(TARGET_WINDOWS_DESKTOP)
  188. // W32 batch-file handline
  189. if (StringUtils::EndsWith(m_filename, ".bat") || StringUtils::EndsWith(m_filename, ".cmd"))
  190. {
  191. // MSDN says you just need to do this, but cmd's handing of spaces and
  192. // quotes is soo broken it seems to work much better if you just omit
  193. // lpApplicationName and enclose the module in lpCommandLine in quotes
  194. //strFName = "cmd.exe";
  195. //strFArgs = "/c ";
  196. }
  197. else
  198. #endif
  199. strFName = m_filename;
  200. strFArgs.append("\"");
  201. strFArgs.append(m_filename);
  202. strFArgs.append("\" ");
  203. strFArgs.append(m_args);
  204. int nReplaced = StringUtils::Replace(strFArgs, "{0}", mainFile);
  205. if (!nReplaced)
  206. nReplaced = StringUtils::Replace(strFArgs, "{1}", mainFile) + StringUtils::Replace(strFArgs, "{2}", archiveContent);
  207. if (!nReplaced)
  208. {
  209. strFArgs.append(" \"");
  210. strFArgs.append(mainFile);
  211. strFArgs.append("\"");
  212. }
  213. #if defined(TARGET_WINDOWS_DESKTOP)
  214. if (m_warpcursor)
  215. {
  216. GetCursorPos(&m_ptCursorpos);
  217. int x = 0;
  218. int y = 0;
  219. switch (m_warpcursor)
  220. {
  221. case WARP_BOTTOM_RIGHT:
  222. x = GetSystemMetrics(SM_CXSCREEN);
  223. case WARP_BOTTOM_LEFT:
  224. y = GetSystemMetrics(SM_CYSCREEN);
  225. break;
  226. case WARP_TOP_RIGHT:
  227. x = GetSystemMetrics(SM_CXSCREEN);
  228. break;
  229. case WARP_CENTER:
  230. x = GetSystemMetrics(SM_CXSCREEN) / 2;
  231. y = GetSystemMetrics(SM_CYSCREEN) / 2;
  232. break;
  233. }
  234. CLog::Log(LOGINFO, "%s: Warping cursor to (%d,%d)", __FUNCTION__, x, y);
  235. SetCursorPos(x,y);
  236. }
  237. LONG currentStyle = GetWindowLong(g_hWnd, GWL_EXSTYLE);
  238. #endif
  239. if (m_hidexbmc && !m_islauncher)
  240. {
  241. CLog::Log(LOGINFO, "%s: Hiding %s window", __FUNCTION__, CCompileInfo::GetAppName());
  242. CServiceBroker::GetWinSystem()->Hide();
  243. }
  244. #if defined(TARGET_WINDOWS_DESKTOP)
  245. else if (currentStyle & WS_EX_TOPMOST)
  246. {
  247. CLog::Log(LOGINFO, "%s: Lowering %s window", __FUNCTION__, CCompileInfo::GetAppName());
  248. SetWindowPos(g_hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_ASYNCWINDOWPOS);
  249. }
  250. CLog::Log(LOGDEBUG, "%s: Unlocking foreground window", __FUNCTION__);
  251. LockSetForegroundWindow(LSFW_UNLOCK);
  252. #endif
  253. m_playbackStartTime = XbmcThreads::SystemClockMillis();
  254. /* Suspend AE temporarily so exclusive or hog-mode sinks */
  255. /* don't block external player's access to audio device */
  256. CServiceBroker::GetActiveAE()->Suspend();
  257. // wait for AE has completed suspended
  258. XbmcThreads::EndTime timer(2000);
  259. while (!timer.IsTimePast() && !CServiceBroker::GetActiveAE()->IsSuspended())
  260. {
  261. CThread::Sleep(50);
  262. }
  263. if (timer.IsTimePast())
  264. {
  265. CLog::Log(LOGERROR,"%s: AudioEngine did not suspend before launching external player", __FUNCTION__);
  266. }
  267. m_callback.OnPlayBackStarted(m_file);
  268. m_callback.OnAVStarted(m_file);
  269. bool ret = true;
  270. #if defined(TARGET_WINDOWS_DESKTOP)
  271. ret = ExecuteAppW32(strFName.c_str(),strFArgs.c_str());
  272. #elif defined(TARGET_ANDROID)
  273. ret = ExecuteAppAndroid(m_filename.c_str(), mainFile.c_str());
  274. #elif defined(TARGET_POSIX) && !defined(TARGET_DARWIN_EMBEDDED)
  275. ret = ExecuteAppLinux(strFArgs.c_str());
  276. #endif
  277. int64_t elapsedMillis = XbmcThreads::SystemClockMillis() - m_playbackStartTime;
  278. if (ret && (m_islauncher || elapsedMillis < LAUNCHER_PROCESS_TIME))
  279. {
  280. if (m_hidexbmc)
  281. {
  282. CLog::Log(LOGINFO, "%s: %s cannot stay hidden for a launcher process", __FUNCTION__,
  283. CCompileInfo::GetAppName());
  284. CServiceBroker::GetWinSystem()->Show(false);
  285. }
  286. {
  287. m_dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogOK>(WINDOW_DIALOG_OK);
  288. m_dialog->SetHeading(CVariant{23100});
  289. m_dialog->SetLine(1, CVariant{23104});
  290. m_dialog->SetLine(2, CVariant{23105});
  291. m_dialog->SetLine(3, CVariant{23106});
  292. }
  293. if (!m_bAbortRequest)
  294. m_dialog->Open();
  295. }
  296. m_bIsPlaying = false;
  297. CLog::Log(LOGINFO, "%s: Stop", __FUNCTION__);
  298. #if defined(TARGET_WINDOWS_DESKTOP)
  299. CServiceBroker::GetWinSystem()->Restore();
  300. if (currentStyle & WS_EX_TOPMOST)
  301. {
  302. CLog::Log(LOGINFO, "%s: Showing %s window TOPMOST", __FUNCTION__, CCompileInfo::GetAppName());
  303. SetWindowPos(g_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS);
  304. SetForegroundWindow(g_hWnd);
  305. }
  306. else
  307. #endif
  308. {
  309. CLog::Log(LOGINFO, "%s: Showing %s window", __FUNCTION__, CCompileInfo::GetAppName());
  310. CServiceBroker::GetWinSystem()->Show();
  311. }
  312. #if defined(TARGET_WINDOWS_DESKTOP)
  313. if (m_warpcursor)
  314. {
  315. m_xPos = 0;
  316. m_yPos = 0;
  317. if (&m_ptCursorpos != 0)
  318. {
  319. m_xPos = (m_ptCursorpos.x);
  320. m_yPos = (m_ptCursorpos.y);
  321. }
  322. CLog::Log(LOGINFO, "%s: Restoring cursor to (%d,%d)", __FUNCTION__, m_xPos, m_yPos);
  323. SetCursorPos(m_xPos,m_yPos);
  324. }
  325. #endif
  326. CBookmark bookmark;
  327. bookmark.totalTimeInSeconds = 1;
  328. bookmark.timeInSeconds = (elapsedMillis / 1000 >= m_playCountMinTime) ? 1 : 0;
  329. bookmark.player = m_name;
  330. m_callback.OnPlayerCloseFile(m_file, bookmark);
  331. /* Resume AE processing of XBMC native audio */
  332. if (!CServiceBroker::GetActiveAE()->Resume())
  333. {
  334. CLog::Log(LOGFATAL, "%s: Failed to restart AudioEngine after return from external player",__FUNCTION__);
  335. }
  336. // We don't want to come back to an active screensaver
  337. g_application.ResetScreenSaver();
  338. g_application.WakeUpScreenSaverAndDPMS();
  339. if (!ret || (m_playOneStackItem && g_application.CurrentFileItem().IsStack()))
  340. m_callback.OnPlayBackStopped();
  341. else
  342. m_callback.OnPlayBackEnded();
  343. }
  344. #if defined(TARGET_WINDOWS_DESKTOP)
  345. bool CExternalPlayer::ExecuteAppW32(const char* strPath, const char* strSwitches)
  346. {
  347. CLog::Log(LOGINFO, "%s: %s %s", __FUNCTION__, strPath, strSwitches);
  348. STARTUPINFOW si;
  349. memset(&si, 0, sizeof(si));
  350. si.cb = sizeof(si);
  351. si.dwFlags = STARTF_USESHOWWINDOW;
  352. si.wShowWindow = m_hideconsole ? SW_HIDE : SW_SHOW;
  353. std::wstring WstrPath, WstrSwitches;
  354. g_charsetConverter.utf8ToW(strPath, WstrPath, false);
  355. g_charsetConverter.utf8ToW(strSwitches, WstrSwitches, false);
  356. if (m_bAbortRequest) return false;
  357. BOOL ret = CreateProcessW(WstrPath.empty() ? NULL : WstrPath.c_str(),
  358. (LPWSTR) WstrSwitches.c_str(), NULL, NULL, FALSE, NULL,
  359. NULL, NULL, &si, &m_processInfo);
  360. if (ret == FALSE)
  361. {
  362. DWORD lastError = GetLastError();
  363. CLog::Log(LOGINFO, "%s - Failure: %d", __FUNCTION__, lastError);
  364. }
  365. else
  366. {
  367. int res = WaitForSingleObject(m_processInfo.hProcess, INFINITE);
  368. switch (res)
  369. {
  370. case WAIT_OBJECT_0:
  371. CLog::Log(LOGINFO, "%s: WAIT_OBJECT_0", __FUNCTION__);
  372. break;
  373. case WAIT_ABANDONED:
  374. CLog::Log(LOGINFO, "%s: WAIT_ABANDONED", __FUNCTION__);
  375. break;
  376. case WAIT_TIMEOUT:
  377. CLog::Log(LOGINFO, "%s: WAIT_TIMEOUT", __FUNCTION__);
  378. break;
  379. case WAIT_FAILED:
  380. CLog::Log(LOGINFO, "%s: WAIT_FAILED (%d)", __FUNCTION__, GetLastError());
  381. ret = FALSE;
  382. break;
  383. }
  384. CloseHandle(m_processInfo.hThread);
  385. m_processInfo.hThread = 0;
  386. CloseHandle(m_processInfo.hProcess);
  387. m_processInfo.hProcess = 0;
  388. }
  389. return (ret == TRUE);
  390. }
  391. #endif
  392. #if !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN_EMBEDDED) && defined(TARGET_POSIX)
  393. bool CExternalPlayer::ExecuteAppLinux(const char* strSwitches)
  394. {
  395. CLog::Log(LOGINFO, "%s: %s", __FUNCTION__, strSwitches);
  396. int ret = system(strSwitches);
  397. if (ret != 0)
  398. {
  399. CLog::Log(LOGINFO, "%s: Failure: %d", __FUNCTION__, ret);
  400. }
  401. return (ret == 0);
  402. }
  403. #endif
  404. #if defined(TARGET_ANDROID)
  405. bool CExternalPlayer::ExecuteAppAndroid(const char* strSwitches,const char* strPath)
  406. {
  407. CLog::Log(LOGINFO, "%s: %s", __FUNCTION__, strSwitches);
  408. bool ret = CXBMCApp::StartActivity(strSwitches, "android.intent.action.VIEW", "video/*", strPath);
  409. if (!ret)
  410. {
  411. CLog::Log(LOGINFO, "%s: Failure", __FUNCTION__);
  412. }
  413. return (ret == 0);
  414. }
  415. #endif
  416. void CExternalPlayer::Pause()
  417. {
  418. }
  419. bool CExternalPlayer::HasVideo() const
  420. {
  421. return true;
  422. }
  423. bool CExternalPlayer::HasAudio() const
  424. {
  425. return false;
  426. }
  427. bool CExternalPlayer::CanSeek()
  428. {
  429. return false;
  430. }
  431. void CExternalPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
  432. {
  433. }
  434. void CExternalPlayer::SeekPercentage(float iPercent)
  435. {
  436. }
  437. void CExternalPlayer::SetAVDelay(float fValue)
  438. {
  439. }
  440. float CExternalPlayer::GetAVDelay()
  441. {
  442. return 0.0f;
  443. }
  444. void CExternalPlayer::SetSubTitleDelay(float fValue)
  445. {
  446. }
  447. float CExternalPlayer::GetSubTitleDelay()
  448. {
  449. return 0.0;
  450. }
  451. void CExternalPlayer::SeekTime(int64_t iTime)
  452. {
  453. }
  454. void CExternalPlayer::SetSpeed(float speed)
  455. {
  456. m_speed = speed;
  457. CDataCacheCore::GetInstance().SetSpeed(1.0, speed);
  458. }
  459. std::string CExternalPlayer::GetPlayerState()
  460. {
  461. return "";
  462. }
  463. bool CExternalPlayer::SetPlayerState(const std::string& state)
  464. {
  465. return true;
  466. }
  467. bool CExternalPlayer::Initialize(TiXmlElement* pConfig)
  468. {
  469. XMLUtils::GetString(pConfig, "filename", m_filename);
  470. if (m_filename.length() > 0)
  471. {
  472. CLog::Log(LOGINFO, "ExternalPlayer Filename: %s", m_filename.c_str());
  473. }
  474. else
  475. {
  476. std::string xml;
  477. xml<<*pConfig;
  478. CLog::Log(LOGERROR, "ExternalPlayer Error: filename element missing from: %s", xml.c_str());
  479. return false;
  480. }
  481. XMLUtils::GetString(pConfig, "args", m_args);
  482. XMLUtils::GetBoolean(pConfig, "playonestackitem", m_playOneStackItem);
  483. XMLUtils::GetBoolean(pConfig, "islauncher", m_islauncher);
  484. XMLUtils::GetBoolean(pConfig, "hidexbmc", m_hidexbmc);
  485. if (!XMLUtils::GetBoolean(pConfig, "hideconsole", m_hideconsole))
  486. {
  487. #ifdef TARGET_WINDOWS_DESKTOP
  488. // Default depends on whether player is a batch file
  489. m_hideconsole = StringUtils::EndsWith(m_filename, ".bat");
  490. #endif
  491. }
  492. bool bHideCursor;
  493. if (XMLUtils::GetBoolean(pConfig, "hidecursor", bHideCursor) && bHideCursor)
  494. m_warpcursor = WARP_BOTTOM_RIGHT;
  495. std::string warpCursor;
  496. if (XMLUtils::GetString(pConfig, "warpcursor", warpCursor) && !warpCursor.empty())
  497. {
  498. if (warpCursor == "bottomright") m_warpcursor = WARP_BOTTOM_RIGHT;
  499. else if (warpCursor == "bottomleft") m_warpcursor = WARP_BOTTOM_LEFT;
  500. else if (warpCursor == "topleft") m_warpcursor = WARP_TOP_LEFT;
  501. else if (warpCursor == "topright") m_warpcursor = WARP_TOP_RIGHT;
  502. else if (warpCursor == "center") m_warpcursor = WARP_CENTER;
  503. else
  504. {
  505. warpCursor = "none";
  506. CLog::Log(LOGWARNING, "ExternalPlayer: invalid value for warpcursor: %s", warpCursor.c_str());
  507. }
  508. }
  509. XMLUtils::GetInt(pConfig, "playcountminimumtime", m_playCountMinTime, 1, INT_MAX);
  510. CLog::Log(
  511. LOGINFO,
  512. "ExternalPlayer Tweaks: hideconsole (%s), hidexbmc (%s), islauncher (%s), warpcursor (%s)",
  513. m_hideconsole ? "true" : "false", m_hidexbmc ? "true" : "false",
  514. m_islauncher ? "true" : "false", warpCursor.c_str());
  515. #ifdef TARGET_WINDOWS_DESKTOP
  516. m_filenameReplacers.push_back("^smb:// , / , \\\\ , g");
  517. m_filenameReplacers.push_back("^smb:\\\\\\\\ , smb:(\\\\\\\\[^\\\\]*\\\\) , \\1 , ");
  518. #endif
  519. TiXmlElement* pReplacers = pConfig->FirstChildElement("replacers");
  520. while (pReplacers)
  521. {
  522. GetCustomRegexpReplacers(pReplacers, m_filenameReplacers);
  523. pReplacers = pReplacers->NextSiblingElement("replacers");
  524. }
  525. return true;
  526. }
  527. void CExternalPlayer::GetCustomRegexpReplacers(TiXmlElement *pRootElement,
  528. std::vector<std::string>& settings)
  529. {
  530. int iAction = 0; // overwrite
  531. // for backward compatibility
  532. const char* szAppend = pRootElement->Attribute("append");
  533. if ((szAppend && StringUtils::CompareNoCase(szAppend, "yes") == 0))
  534. iAction = 1;
  535. // action takes precedence if both attributes exist
  536. const char* szAction = pRootElement->Attribute("action");
  537. if (szAction)
  538. {
  539. iAction = 0; // overwrite
  540. if (StringUtils::CompareNoCase(szAction, "append") == 0)
  541. iAction = 1; // append
  542. else if (StringUtils::CompareNoCase(szAction, "prepend") == 0)
  543. iAction = 2; // prepend
  544. }
  545. if (iAction == 0)
  546. settings.clear();
  547. TiXmlElement* pReplacer = pRootElement->FirstChildElement("replacer");
  548. int i = 0;
  549. while (pReplacer)
  550. {
  551. if (pReplacer->FirstChild())
  552. {
  553. const char* szGlobal = pReplacer->Attribute("global");
  554. const char* szStop = pReplacer->Attribute("stop");
  555. bool bGlobal = szGlobal && StringUtils::CompareNoCase(szGlobal, "true") == 0;
  556. bool bStop = szStop && StringUtils::CompareNoCase(szStop, "true") == 0;
  557. std::string strMatch;
  558. std::string strPat;
  559. std::string strRep;
  560. XMLUtils::GetString(pReplacer,"match",strMatch);
  561. XMLUtils::GetString(pReplacer,"pat",strPat);
  562. XMLUtils::GetString(pReplacer,"rep",strRep);
  563. if (!strPat.empty() && !strRep.empty())
  564. {
  565. CLog::Log(LOGDEBUG," Registering replacer:");
  566. CLog::Log(LOGDEBUG," Match:[%s] Pattern:[%s] Replacement:[%s]", strMatch.c_str(), strPat.c_str(), strRep.c_str());
  567. CLog::Log(LOGDEBUG," Global:[%s] Stop:[%s]", bGlobal?"true":"false", bStop?"true":"false");
  568. // keep literal commas since we use comma as a separator
  569. StringUtils::Replace(strMatch, ",",",,");
  570. StringUtils::Replace(strPat, ",",",,");
  571. StringUtils::Replace(strRep, ",",",,");
  572. std::string strReplacer = strMatch + " , " + strPat + " , " + strRep + " , " + (bGlobal ? "g" : "") + (bStop ? "s" : "");
  573. if (iAction == 2)
  574. settings.insert(settings.begin() + i++, 1, strReplacer);
  575. else
  576. settings.push_back(strReplacer);
  577. }
  578. else
  579. {
  580. // error message about missing tag
  581. if (strPat.empty())
  582. CLog::Log(LOGERROR," Missing <Pat> tag");
  583. else
  584. CLog::Log(LOGERROR," Missing <Rep> tag");
  585. }
  586. }
  587. pReplacer = pReplacer->NextSiblingElement("replacer");
  588. }
  589. }