diff --git a/src/mpc-hc/AppSettings.cpp b/src/mpc-hc/AppSettings.cpp index 088a118e7e6..5b91ef26d4a 100644 --- a/src/mpc-hc/AppSettings.cpp +++ b/src/mpc-hc/AppSettings.cpp @@ -213,6 +213,8 @@ CAppSettings::CAppSettings() , iLAVGPUDevice(DWORD_MAX) , nCmdVolume(0) , eSubtitleRenderer(SubtitleRenderer::INTERNAL) + , iYDLMaxHeight(0) + , bYDLAudioOnly(false) { // Internal source filter #if INTERNAL_SOURCEFILTER_CDDA @@ -1089,6 +1091,9 @@ void CAppSettings::SaveSettings() pApp->WriteProfileInt(IDS_R_SANEAR, IDS_RS_SANEAR_CROSSFEED_LEVEL, uCrossfeedLevel); } + pApp->WriteProfileInt(IDS_R_SETTINGS, IDS_RS_YDL_MAX_HEIGHT, iYDLMaxHeight); + pApp->WriteProfileInt(IDS_R_SETTINGS, IDS_RS_YDL_AUDIO_ONLY, bYDLAudioOnly); + pApp->FlushProfile(); } @@ -1826,6 +1831,9 @@ void CAppSettings::LoadSettings() pApp->GetProfileInt(IDS_R_SANEAR, IDS_RS_SANEAR_CROSSFEED_LEVEL, SaneAudioRenderer::ISettings::CROSSFEED_LEVEL_CMOY)); + iYDLMaxHeight = pApp->GetProfileInt(IDS_R_SETTINGS, IDS_RS_YDL_MAX_HEIGHT, 0); + bYDLAudioOnly = pApp->GetProfileInt(IDS_R_SETTINGS, IDS_RS_YDL_AUDIO_ONLY, FALSE); + bInitialized = true; } diff --git a/src/mpc-hc/AppSettings.h b/src/mpc-hc/AppSettings.h index 9dfb1fb3d47..a458e81ebd1 100644 --- a/src/mpc-hc/AppSettings.h +++ b/src/mpc-hc/AppSettings.h @@ -275,7 +275,9 @@ struct wmcmd_base : public ACCEL { }; wmcmd_base() - : ACCEL( { 0, 0, 0 }) + : ACCEL( { + 0, 0, 0 + }) , mouse(NONE) , mouseFS(NONE) , dwname(0) @@ -316,7 +318,9 @@ class wmcmd : public wmcmd_base return cmd > 0 && cmd == wc.cmd; } - CString GetName() const { return ResStr(dwname); } + CString GetName() const { + return ResStr(dwname); + } void Restore() { ASSERT(default_cmd); @@ -364,7 +368,9 @@ class CRemoteCtrlClient : public CAsyncSocket void SetHWND(HWND hWnd); void Connect(CString addr); void DisConnect(); - int GetStatus() const { return m_nStatus; } + int GetStatus() const { + return m_nStatus; + } }; class CWinLircClient : public CRemoteCtrlClient @@ -416,7 +422,9 @@ class CAppSettings DVD_HMSF_TIMECODE DVDPosition; CSize sizeFixedWindow; - bool HasFixedWindowSize() const { return sizeFixedWindow.cx > 0 || sizeFixedWindow.cy > 0; } + bool HasFixedWindowSize() const { + return sizeFixedWindow.cx > 0 || sizeFixedWindow.cy > 0; + } //int iFixedWidth, iFixedHeight; int iMonitor; @@ -722,7 +730,9 @@ class CAppSettings }; SubtitleRenderer GetSubtitleRenderer() const; - void SetSubtitleRenderer(SubtitleRenderer renderer) { eSubtitleRenderer = renderer; } + void SetSubtitleRenderer(SubtitleRenderer renderer) { + eSubtitleRenderer = renderer; + } static bool IsSubtitleRendererRegistered(SubtitleRenderer eSubtitleRenderer); @@ -732,7 +742,13 @@ class CAppSettings ASSERT(fKeepAspectRatio && "Keep Aspect Ratio option have to be enabled if override value is used."); return sizeAspectRatio; }; - void SetAspectRatioOverride(const CSize& ar) { sizeAspectRatio = ar; } + void SetAspectRatioOverride(const CSize& ar) { + sizeAspectRatio = ar; + } + + //YoutubeDL settings + int iYDLMaxHeight; + bool bYDLAudioOnly; private: struct FilterKey { @@ -779,10 +795,16 @@ class CAppSettings void SaveSettings(); void LoadSettings(); - void SaveExternalFilters() { if (bInitialized) { SaveExternalFilters(m_filters); } }; + void SaveExternalFilters() { + if (bInitialized) { + SaveExternalFilters(m_filters); + } + }; void UpdateSettings(); - void SetAsUninitialized() { bInitialized = false; }; + void SetAsUninitialized() { + bInitialized = false; + }; void GetFav(favtype ft, CAtlList& sl) const; void SetFav(favtype ft, CAtlList& sl); diff --git a/src/mpc-hc/MainFrm.cpp b/src/mpc-hc/MainFrm.cpp index 720d340b7fe..a1e9c38eee2 100644 --- a/src/mpc-hc/MainFrm.cpp +++ b/src/mpc-hc/MainFrm.cpp @@ -1,6 +1,6 @@ /* * (C) 2003-2006 Gabest - * (C) 2006-2017 see Authors.txt + * (C) 2006-2018 see Authors.txt * * This file is part of MPC-HC. * @@ -105,6 +105,9 @@ #include #include +#include "YoutubeDL.h" + + // IID_IAMLine21Decoder DECLARE_INTERFACE_IID_(IAMLine21Decoder_2, IAMLine21Decoder, "6E8D4A21-310C-11d0-B79A-00AA003767A7") {}; @@ -3830,6 +3833,15 @@ void CMainFrame::OnFileOpenmedia() SetForegroundWindow(); CAtlList filenames; + + if (dlg.GetFileNames().GetHead().Left(4) == _T("http") + && ProcessYoutubeDLURL(dlg.GetFileNames().GetHead(), dlg.GetAppendToPlaylist())) { + if (!dlg.GetAppendToPlaylist()) { + OpenCurPlaylistItem(); + } + return; + } + filenames.AddHeadList(&dlg.GetFileNames()); if (!dlg.HasMultipleFiles()) { @@ -3991,6 +4003,7 @@ BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCDS) PathUtils::ParseDirs(sl); bool fMulti = sl.GetCount() > 1; + bool fYoutubeDL = sl.GetHead().Left(4) == _T("http"); if (!fMulti) { sl.AddTailList(&s.slDubs); @@ -4015,7 +4028,13 @@ BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCDS) m_dwLastRun = GetTickCount64(); if ((s.nCLSwitches & CLSW_ADD) && !IsPlaylistEmpty()) { - m_wndPlaylistBar.Append(sl, fMulti, &s.slSubs); + bool r = false; + if (fYoutubeDL) { + r = ProcessYoutubeDLURL(sl.GetHead(), true); + } + if (!r) { //not an http link, or youtube-dl unavailable + m_wndPlaylistBar.Append(sl, fMulti, &s.slSubs); + } applyRandomizeSwitch(); if (s.nCLSwitches & (CLSW_OPEN | CLSW_PLAY)) { @@ -4026,8 +4045,15 @@ BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCDS) //SendMessage(WM_COMMAND, ID_FILE_CLOSEMEDIA); fSetForegroundWindow = true; - m_wndPlaylistBar.Open(sl, fMulti, &s.slSubs); + bool r = false; + if (fYoutubeDL) { + r = ProcessYoutubeDLURL(sl.GetHead(), false); + } + if (!r) { //not an http link or youtube-dl unavailable + m_wndPlaylistBar.Open(sl, fMulti, &s.slSubs); + } applyRandomizeSwitch(); + m_wndPlaylistBar.SetFirst(); OpenCurPlaylistItem((s.nCLSwitches & CLSW_STARTVALID) ? s.rtStart : 0); s.nCLSwitches &= ~CLSW_STARTVALID; @@ -4246,6 +4272,7 @@ void CMainFrame::OnDropFiles(CAtlList& slFiles, DROPEFFECT dropEffect) } } + bool bAppend = !!(dropEffect & DROPEFFECT_APPEND); // Use the first subtitle file that was just loaded if (subInputSelected.pSubStream) { AfxGetAppSettings().fEnableSubtitles = true; @@ -4260,7 +4287,17 @@ void CMainFrame::OnDropFiles(CAtlList& slFiles, DROPEFFECT dropEffect) } SendStatusMessage(filenames + ResStr(IDS_SUB_LOADED_SUCCESS), 3000); } else { - if (dropEffect & DROPEFFECT_APPEND) { + //load http url with youtube-dl, if available + if (slFiles.GetHead().Left(4) == _T("http")) { + if (ProcessYoutubeDLURL(slFiles.GetHead(), bAppend)) { + if (!bAppend) { + OpenCurPlaylistItem(); + } + return; + } + } + + if (bAppend) { m_wndPlaylistBar.Append(slFiles, true); } else { m_wndPlaylistBar.Open(slFiles, true); @@ -6936,7 +6973,7 @@ void CMainFrame::OnPlayPlay() strOSD.LoadString(IDS_PLAY_BD); } else { strOSD = GetFileName(); - if (!strOSD.IsEmpty()) { + if (!strOSD.IsEmpty() && !m_wndPlaylistBar.GetCur()->m_bYoutubeDL) { strOSD.TrimRight('/'); strOSD.Replace('\\', '/'); strOSD = strOSD.Mid(strOSD.ReverseFind('/') + 1); @@ -8825,9 +8862,13 @@ void CMainFrame::AddFavorite(bool fDisplayMessage, bool fShowDialog) } else { CPlaylistItem pli; if (m_wndPlaylistBar.GetCur(pli)) { - POSITION pos = pli.m_fns.GetHeadPosition(); - while (pos) { - args.AddTail(pli.m_fns.GetNext(pos)); + if (pli.m_bYoutubeDL) { + args.AddTail(pli.m_ydlSourceURL); + } else { + POSITION pos = pli.m_fns.GetHeadPosition(); + while (pos) { + args.AddTail(pli.m_fns.GetNext(pos)); + } } } } @@ -9021,7 +9062,13 @@ void CMainFrame::PlayFavoriteFile(CString fav) } } - m_wndPlaylistBar.Open(args, false); + SendMessage(WM_COMMAND, ID_FILE_CLOSEMEDIA); + + if (args.GetHead().Left(4) != _T("http") + || !ProcessYoutubeDLURL(args.GetHead(), false)) { + m_wndPlaylistBar.Open(args, false); + } + if (GetPlaybackMode() == PM_FILE && args.GetHead() == m_lastOMD->title) { m_pMS->SetPositions(&rtStart, AM_SEEKING_AbsolutePositioning, nullptr, AM_SEEKING_NoPositioning); OnPlayPlay(); @@ -9040,6 +9087,15 @@ void CMainFrame::OnRecentFile(UINT nID) nID -= ID_RECENT_FILE_START; CString fn; m_recentFilesMenu.GetMenuString(nID + 2, fn, MF_BYPOSITION); + + if (fn.Left(4) == _T("http")) { + SendMessage(WM_COMMAND, ID_FILE_CLOSEMEDIA); + if (ProcessYoutubeDLURL(fn, false)) { + OpenCurPlaylistItem(); + return; + } + } + if (!m_wndPlaylistBar.SelectFileInPlaylist(fn)) { CAtlList fns; fns.AddTail(fn); @@ -10570,7 +10626,7 @@ void CMainFrame::OpenFile(OpenFileData* pOFD) } // We don't keep track of piped inputs since that hardly makes any sense - if (s.fKeepHistory && fn.Find(_T("pipe:")) != 0) { + if (s.fKeepHistory && fn.Find(_T("pipe:")) != 0 && pOFD->bAddToRecent) { CRecentFileList* pMRU = bMainFile ? &s.MRU : &s.MRUDub; pMRU->ReadList(); pMRU->Add(fn); @@ -12231,7 +12287,7 @@ void CMainFrame::SendNowPlayingToSkype() if (GetPlaybackMode() == PM_FILE) { CString fn = label; - if (fn.Find(_T("://")) >= 0) { + if (!pli.m_bYoutubeDL && fn.Find(_T("://")) >= 0) { int i = fn.Find('?'); if (i >= 0) { fn = fn.Left(i); @@ -12616,7 +12672,7 @@ void CMainFrame::SetupAudioSubMenu() ATR.bQuantization, ATR.bNumberOfChannels, ResStr(ATR.bNumberOfChannels > 1 ? IDS_MAINFRM_13 : IDS_MAINFRM_12).GetString() - ); + ); } } @@ -16652,14 +16708,14 @@ CString CMainFrame::GetFileName() { CString path(m_wndPlaylistBar.GetCurFileName()); - if (m_pFSF) { + if (!m_wndPlaylistBar.GetCur()->m_bYoutubeDL && m_pFSF) { CComHeapPtr pFN; if (SUCCEEDED(m_pFSF->GetCurFile(&pFN, nullptr))) { path = pFN; } } - return PathUtils::StripPathOrUrl(path); + return m_wndPlaylistBar.GetCur()->m_bYoutubeDL ? path : PathUtils::StripPathOrUrl(path); } CString CMainFrame::GetCaptureTitle() @@ -16728,10 +16784,11 @@ void CMainFrame::UpdateDXVAStatus() bool CMainFrame::GetDecoderType(CString& type) const { if (!m_fAudioOnly) { - if (m_bUsingDXVA) + if (m_bUsingDXVA) { type = m_HWAccelType; - else + } else { type.LoadString(IDS_TOOLTIP_SOFTWARE_DECODING); + } return true; } return false; @@ -16982,3 +17039,52 @@ LRESULT CMainFrame::OnGetSubtitles(WPARAM, LPARAM lParam) pSubtitlesInfo->fileContents = UTF16To8(content); return TRUE; } + + +bool CMainFrame::ProcessYoutubeDLURL(CString url, bool append) +{ + auto& s = AfxGetAppSettings(); + CAtlList vstreams; + CAtlList astreams; + CAtlList names; + CAtlList filenames; + CYoutubeDLInstance ydl; + + m_wndStatusBar.SetStatusMessage(ResStr(IDS_CONTROLS_YOUTUBEDL)); + + if (!ydl.Run(url)) { + return false; + } + if (!ydl.GetHttpStreams(vstreams, astreams, names)) { + return false; + } + + if (!append) { + m_wndPlaylistBar.Empty(); + } + for (unsigned int i = 0; i < vstreams.GetCount(); i++) { + filenames.RemoveAll(); + + //only respect the Audio Only flag for sources that actually have separate audio streams (i.e. youtube) + if (astreams.IsEmpty() || !s.bYDLAudioOnly) { + filenames.AddTail(vstreams.GetAt(vstreams.FindIndex(i))); + } + + if (!astreams.IsEmpty()) { + filenames.AddTail(astreams.GetAt(astreams.FindIndex(i))); + } + m_wndPlaylistBar.Append(filenames, false, nullptr, + names.GetAt(names.FindIndex(i)) + + " (" + url + ")", url); + } + + CRecentFileList* mru = &s.MRU; + mru->ReadList(); + mru->Add(url); + mru->WriteList(); + + if (!append) { + m_wndPlaylistBar.SetFirst(); + } + return true; +} diff --git a/src/mpc-hc/MainFrm.h b/src/mpc-hc/MainFrm.h index 768c84ca24d..e34380b8182 100644 --- a/src/mpc-hc/MainFrm.h +++ b/src/mpc-hc/MainFrm.h @@ -95,9 +95,10 @@ class OpenMediaData class OpenFileData : public OpenMediaData { public: - OpenFileData() : rtStart(0) {} + OpenFileData() : rtStart(0), bAddToRecent(true) {} CAtlList fns; REFERENCE_TIME rtStart; + bool bAddToRecent; }; class OpenDVDData : public OpenMediaData @@ -387,11 +388,19 @@ class CMainFrame : public CFrameWnd, public CDropClient void StartWebServer(int nPort); void StopWebServer(); - int GetPlaybackMode() const { return m_iPlaybackMode; } - bool IsPlaybackCaptureMode() const { return GetPlaybackMode() == PM_ANALOG_CAPTURE || GetPlaybackMode() == PM_DIGITAL_CAPTURE; } + int GetPlaybackMode() const { + return m_iPlaybackMode; + } + bool IsPlaybackCaptureMode() const { + return GetPlaybackMode() == PM_ANALOG_CAPTURE || GetPlaybackMode() == PM_DIGITAL_CAPTURE; + } void SetPlaybackMode(int iNewStatus); - bool IsMuted() { return m_wndToolBar.GetVolume() == -10000; } - int GetVolume() { return m_wndToolBar.m_volctrl.GetPos(); } + bool IsMuted() { + return m_wndToolBar.GetVolume() == -10000; + } + int GetVolume() { + return m_wndToolBar.m_volctrl.GetPos(); + } public: CMainFrame(); @@ -1094,4 +1103,7 @@ class CMainFrame : public CFrameWnd, public CDropClient bool OpenBD(CString Path); bool GetDecoderType(CString& type) const; + +private: + bool ProcessYoutubeDLURL(CString url, bool append); }; diff --git a/src/mpc-hc/PPageAdvanced.cpp b/src/mpc-hc/PPageAdvanced.cpp index 6039a9678d0..380222bd258 100644 --- a/src/mpc-hc/PPageAdvanced.cpp +++ b/src/mpc-hc/PPageAdvanced.cpp @@ -143,6 +143,8 @@ void CPPageAdvanced::InitSettings() addIntItem(DEFAULT_TOOLBAR_SIZE, IDS_RS_DEFAULTTOOLBARSIZE, 24, s.nDefaultToolbarSize, std::make_pair(16, 128), StrRes(IDS_PPAGEADVANCED_DEFAULTTOOLBARSIZE)); addBoolItem(USE_LEGACY_TOOLBAR, IDS_RS_USE_LEGACY_TOOLBAR, false, s.bUseLegacyToolbar, StrRes(IDS_PPAGEADVANCED_USE_LEGACY_TOOLBAR)); + addIntItem(YDL_MAX_HEIGHT, IDS_RS_YDL_MAX_HEIGHT, 0, s.iYDLMaxHeight, std::make_pair(0, INT_MAX), StrRes(IDS_RS_YDL_MAX_HEIGHT)); + addBoolItem(YDL_AUDIO_ONLY, IDS_RS_YDL_AUDIO_ONLY, false, s.bYDLAudioOnly, StrRes(IDS_RS_YDL_AUDIO_ONLY)); } BOOL CPPageAdvanced::OnApply() diff --git a/src/mpc-hc/PPageAdvanced.h b/src/mpc-hc/PPageAdvanced.h index 9dac3103ee3..5ae746a2283 100644 --- a/src/mpc-hc/PPageAdvanced.h +++ b/src/mpc-hc/PPageAdvanced.h @@ -40,8 +40,12 @@ class SettingsBase } virtual ~SettingsBase() = default; - CString GetToolTipText() const { return toolTipText; } - CString GetName() const { return name; } + CString GetToolTipText() const { + return toolTipText; + } + CString GetName() const { + return name; + } virtual bool IsDefault() const PURE; virtual void ResetDefault() PURE; virtual void Apply() PURE; @@ -61,12 +65,24 @@ class SettingsBool : public SettingsBase , settingReference(settingReference) { } - bool IsDefault() const { return currentValue == defaultValue; } - void ResetDefault() { SetValue(defaultValue); } - void SetValue(bool value) { currentValue = value; } - bool GetValue() const { return currentValue; } - void Apply() { settingReference = currentValue; } - void Toggle() { currentValue = !currentValue; } + bool IsDefault() const { + return currentValue == defaultValue; + } + void ResetDefault() { + SetValue(defaultValue); + } + void SetValue(bool value) { + currentValue = value; + } + bool GetValue() const { + return currentValue; + } + void Apply() { + settingReference = currentValue; + } + void Toggle() { + currentValue = !currentValue; + } }; class SettingsInt : public SettingsBase @@ -85,12 +101,24 @@ class SettingsInt : public SettingsBase , range(std::move(range)) { } - bool IsDefault() const { return currentValue == defaultValue; } - void ResetDefault() { SetValue(defaultValue); } - void SetValue(int value) { currentValue = value; } - int GetValue() const { return currentValue; } - void Apply() { settingReference = currentValue; } - std::pair GetRange() const { return range; } + bool IsDefault() const { + return currentValue == defaultValue; + } + void ResetDefault() { + SetValue(defaultValue); + } + void SetValue(int value) { + currentValue = value; + } + int GetValue() const { + return currentValue; + } + void Apply() { + settingReference = currentValue; + } + std::pair GetRange() const { + return range; + } }; class SettingsCombo : public SettingsInt @@ -103,7 +131,9 @@ class SettingsCombo : public SettingsInt , list(std::move(list)) { } - std::deque GetList() const { return list; } + std::deque GetList() const { + return list; + } }; class SettingsCString : public SettingsBase @@ -120,11 +150,21 @@ class SettingsCString : public SettingsBase , settingReference(settingReference) { } - bool IsDefault() const { return currentValue == defaultValue; } - void ResetDefault() { SetValue(defaultValue); } - void SetValue(const CString& value) { currentValue = value; } - CString GetValue() const { return currentValue; } - void Apply() { settingReference = currentValue; } + bool IsDefault() const { + return currentValue == defaultValue; + } + void ResetDefault() { + SetValue(defaultValue); + } + void SetValue(const CString& value) { + currentValue = value; + } + CString GetValue() const { + return currentValue; + } + void Apply() { + settingReference = currentValue; + } }; class CPPageAdvanced : public CPPageBase @@ -148,6 +188,8 @@ class CPPageAdvanced : public CPPageBase AUTO_DOWNLOAD_SCORE_SERIES, DEFAULT_TOOLBAR_SIZE, USE_LEGACY_TOOLBAR, + YDL_MAX_HEIGHT, + YDL_AUDIO_ONLY, }; enum { diff --git a/src/mpc-hc/PlayerPlaylistBar.cpp b/src/mpc-hc/PlayerPlaylistBar.cpp index 61accd7dc7e..f1313448141 100644 --- a/src/mpc-hc/PlayerPlaylistBar.cpp +++ b/src/mpc-hc/PlayerPlaylistBar.cpp @@ -153,7 +153,7 @@ void CPlayerPlaylistBar::AddItem(CString fn, CAtlList* subs) AddItem(sl, subs); } -void CPlayerPlaylistBar::AddItem(CAtlList& fns, CAtlList* subs) +void CPlayerPlaylistBar::AddItem(CAtlList& fns, CAtlList* subs, CString label, CString ydl_src) { CPlaylistItem pli; @@ -180,6 +180,11 @@ void CPlayerPlaylistBar::AddItem(CAtlList& fns, CAtlList* subs } pli.AutoLoadFiles(); + if (!ydl_src.IsEmpty()) { + pli.m_label = label; + pli.m_ydlSourceURL = ydl_src; + pli.m_bYoutubeDL = true; + } m_pl.AddTail(pli); } @@ -270,7 +275,7 @@ void CPlayerPlaylistBar::ResolveLinkFiles(CAtlList& fns) } } -void CPlayerPlaylistBar::ParsePlayList(CAtlList& fns, CAtlList* subs) +void CPlayerPlaylistBar::ParsePlayList(CAtlList& fns, CAtlList* subs, CString label, CString ydl_src) { if (fns.IsEmpty()) { return; @@ -314,7 +319,7 @@ void CPlayerPlaylistBar::ParsePlayList(CAtlList& fns, CAtlList #endif } - AddItem(fns, subs); + AddItem(fns, subs, label, ydl_src); } static CString CombinePath(CPath p, CString fn) @@ -500,7 +505,7 @@ void CPlayerPlaylistBar::Open(CAtlList& fns, bool fMulti, CAtlList& fns, bool fMulti, CAtlList* subs) +void CPlayerPlaylistBar::Append(CAtlList& fns, bool fMulti, CAtlList* subs, CString label, CString ydl_src) { POSITION posFirstAdded = m_pl.GetTailPosition(); int iFirstAdded = (int)m_pl.GetCount(); @@ -512,7 +517,7 @@ void CPlayerPlaylistBar::Append(CAtlList& fns, bool fMulti, CAtlListm_fns.IsEmpty()) { + + if (pli && pli->m_bYoutubeDL) { + fn = pli->m_label; + } else if (pli && !pli->m_fns.IsEmpty()) { fn = pli->m_fns.GetHead(); } return fn; @@ -807,6 +815,7 @@ OpenMediaData* CPlayerPlaylistBar::GetCurOMD(REFERENCE_TIME rtStart) p->fns.AddTailList(&pli->m_fns); p->subs.AddTailList(&pli->m_subs); p->rtStart = rtStart; + p->bAddToRecent = !pli->m_bYoutubeDL; return p; } } diff --git a/src/mpc-hc/PlayerPlaylistBar.h b/src/mpc-hc/PlayerPlaylistBar.h index 4c3c962ec2b..b44fa3a7a5c 100644 --- a/src/mpc-hc/PlayerPlaylistBar.h +++ b/src/mpc-hc/PlayerPlaylistBar.h @@ -1,6 +1,6 @@ /* * (C) 2003-2006 Gabest - * (C) 2006-2016 see Authors.txt + * (C) 2006-2017 see Authors.txt * * This file is part of MPC-HC. * @@ -56,9 +56,9 @@ class CPlayerPlaylistBar : public CPlayerBar, public CDropClient void ResizeListColumn(); void AddItem(CString fn, CAtlList* subs); - void AddItem(CAtlList& fns, CAtlList* subs); + void AddItem(CAtlList& fns, CAtlList* subs, CString label = _T(""), CString ydl_src = _T("")); void ParsePlayList(CString fn, CAtlList* subs); - void ParsePlayList(CAtlList& fns, CAtlList* subs); + void ParsePlayList(CAtlList& fns, CAtlList* subs, CString label = _T(""), CString ydl_src = _T("")); void ResolveLinkFiles(CAtlList& fns); bool ParseBDMVPlayList(CString fn); @@ -121,7 +121,7 @@ class CPlayerPlaylistBar : public CPlayerBar, public CDropClient bool Empty(); void Open(CAtlList& fns, bool fMulti, CAtlList* subs = nullptr); - void Append(CAtlList& fns, bool fMulti, CAtlList* subs = nullptr); + void Append(CAtlList& fns, bool fMulti, CAtlList* subs = nullptr, CString label = _T(""), CString ydl_src = _T("")); void Open(CStringW vdn, CStringW adn, int vinput, int vchannel, int ainput); void Append(CStringW vdn, CStringW adn, int vinput, int vchannel, int ainput); diff --git a/src/mpc-hc/Playlist.cpp b/src/mpc-hc/Playlist.cpp index 5396a5ebf55..d8f8d32c9ed 100644 --- a/src/mpc-hc/Playlist.cpp +++ b/src/mpc-hc/Playlist.cpp @@ -41,6 +41,8 @@ CPlaylistItem::CPlaylistItem() , m_ainput(-1) , m_country(0) , m_fInvalid(false) + , m_bYoutubeDL(false) + , m_ydlSourceURL(_T("")) { m_id = m_globalid++; } @@ -72,6 +74,8 @@ CPlaylistItem& CPlaylistItem::operator=(const CPlaylistItem& pli) m_country = pli.m_country; m_posNextShuffle = pli.m_posNextShuffle; m_posPrevShuffle = pli.m_posPrevShuffle; + m_bYoutubeDL = pli.m_bYoutubeDL; + m_ydlSourceURL = pli.m_ydlSourceURL; } return *this; } diff --git a/src/mpc-hc/Playlist.h b/src/mpc-hc/Playlist.h index 48a9f2aad95..7c2a6e0d000 100644 --- a/src/mpc-hc/Playlist.h +++ b/src/mpc-hc/Playlist.h @@ -1,6 +1,6 @@ /* * (C) 2003-2006 Gabest - * (C) 2006-2012, 2015 see Authors.txt + * (C) 2006-2012, 2015, 2017 see Authors.txt * * This file is part of MPC-HC. * @@ -35,6 +35,8 @@ class CPlaylistItem public: UINT m_id; CString m_label; + bool m_bYoutubeDL; + CString m_ydlSourceURL; CAtlList m_fns; CAtlList m_subs; enum type_t { file, device } m_type; diff --git a/src/mpc-hc/SettingsDefines.h b/src/mpc-hc/SettingsDefines.h index efe017e66eb..2fb02a1a399 100644 --- a/src/mpc-hc/SettingsDefines.h +++ b/src/mpc-hc/SettingsDefines.h @@ -323,3 +323,6 @@ #define IDS_RS_SANEAR_CROSSFEED_ENABLED _T("CrossfeedEnabled") #define IDS_RS_SANEAR_CROSSFEED_CUTOFF_FREQ _T("CrossfeedCutoffFrequency") #define IDS_RS_SANEAR_CROSSFEED_LEVEL _T("CrossfeedLevel") + +#define IDS_RS_YDL_MAX_HEIGHT _T("YDLMaxHeight") +#define IDS_RS_YDL_AUDIO_ONLY _T("YDLAudioOnly") diff --git a/src/mpc-hc/YoutubeDL.cpp b/src/mpc-hc/YoutubeDL.cpp new file mode 100644 index 00000000000..26d5e603961 --- /dev/null +++ b/src/mpc-hc/YoutubeDL.cpp @@ -0,0 +1,295 @@ +/* +* (C) 2018 Nicholas Parkanyi +* +* This file is part of MPC-HC. +* +* MPC-HC is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* MPC-HC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +*/ +#include "stdafx.h" +#include "YoutubeDL.h" +#include "rapidjson/include/rapidjson/document.h" +#include "mplayerc.h" + +typedef rapidjson::GenericValue> Value; + +struct CUtf16JSON { + rapidjson::GenericDocument> d; +}; + + +CYoutubeDLInstance::CYoutubeDLInstance() + : idx_out(0), idx_err(0), + buf_out(nullptr), buf_err(nullptr), + capacity_out(0), capacity_err(0), + pJSON(new CUtf16JSON) +{ +} + +CYoutubeDLInstance::~CYoutubeDLInstance() +{ + std::free(buf_out); + std::free(buf_err); + delete pJSON; +} + +bool CYoutubeDLInstance::Run(CString url) +{ + const size_t bufsize = 2000; //2KB initial buffer size + + ///////////////////////////// + // Set up youtube-dl process + ///////////////////////////// + + PROCESS_INFORMATION proc_info; + STARTUPINFO startup_info; + SECURITY_ATTRIBUTES sec_attrib; + + + CString args = "youtube-dl -J -- \"" + url + "\""; + + ZeroMemory(&proc_info, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + + //child process must inherit the handles + sec_attrib.nLength = sizeof(SECURITY_ATTRIBUTES); + sec_attrib.lpSecurityDescriptor = NULL; + sec_attrib.bInheritHandle = true; + + if (!CreatePipe(&hStdout_r, &hStdout_w, &sec_attrib, bufsize)) { + return false; + } + if (!CreatePipe(&hStderr_r, &hStderr_w, &sec_attrib, bufsize)) { + return false; + } + + startup_info.cb = sizeof(STARTUPINFO); + startup_info.hStdOutput = hStdout_w; + startup_info.hStdError = hStderr_w; + startup_info.wShowWindow = SW_HIDE; + startup_info.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + + if (!CreateProcess(NULL, args.GetBuffer(), NULL, NULL, true, 0, + NULL, NULL, &startup_info, &proc_info)) { + return false; + } + + //we must close the parent process's write handles before calling ReadFile, + // otherwise it will block forever. + CloseHandle(hStdout_w); + CloseHandle(hStderr_w); + + + ///////////////////////////////////////////////////// + // Read in stdout and stderr through the pipe buffer + ///////////////////////////////////////////////////// + + buf_out = static_cast(std::malloc(bufsize)); + buf_err = static_cast(std::malloc(bufsize)); + capacity_out = bufsize; + capacity_err = bufsize; + + HANDLE hThreadOut, hThreadErr; + idx_out = 0; + idx_err = 0; + + hThreadOut = CreateThread(NULL, 0, BuffOutThread, this, NULL, NULL); + hThreadErr = CreateThread(NULL, 0, BuffErrThread, this, NULL, NULL); + + WaitForSingleObject(hThreadOut, INFINITE); + WaitForSingleObject(hThreadErr, INFINITE); + + if (!buf_out || !buf_err) { + throw std::bad_alloc(); + } + + //NULL-terminate the data + char* tmp; + if (idx_out == capacity_out) { + tmp = static_cast(std::realloc(buf_out, capacity_out + 1)); + if (tmp) { + buf_out = tmp; + } + } + buf_out[idx_out] = '\0'; + + if (idx_err == capacity_err) { + tmp = static_cast(std::realloc(buf_err, capacity_err + 1)); + if (tmp) { + buf_err = tmp; + } + } + buf_err[idx_err] = '\0'; + + CString err = buf_err; + DWORD exitcode; + GetExitCodeProcess(proc_info.hProcess, &exitcode); + if (exitcode) { + AfxMessageBox(err.GetBuffer(), MB_ICONERROR, 0); + return false; + } + + CloseHandle(proc_info.hProcess); + CloseHandle(proc_info.hThread); + CloseHandle(hThreadOut); + CloseHandle(hThreadErr); + CloseHandle(hStdout_r); + CloseHandle(hStderr_r); + + return loadJSON(); +} + +DWORD WINAPI CYoutubeDLInstance::BuffOutThread(void* ydl_inst) +{ + auto ydl = static_cast(ydl_inst); + DWORD read; + + while (ReadFile(ydl->hStdout_r, ydl->buf_out + ydl->idx_out, ydl->capacity_out - ydl->idx_out, &read, NULL)) { + ydl->idx_out += read; + if (ydl->idx_out == ydl->capacity_out) { + ydl->capacity_out *= 2; + char* tmp = static_cast(std::realloc(ydl->buf_out, ydl->capacity_out)); + if (tmp) { + ydl->buf_out = tmp; + } else { + std::free(ydl->buf_out); + ydl->buf_out = nullptr; + return 0; + } + } + } + + return GetLastError() == ERROR_BROKEN_PIPE ? 0 : GetLastError(); +} + +DWORD WINAPI CYoutubeDLInstance::BuffErrThread(void* ydl_inst) +{ + auto ydl = static_cast(ydl_inst); + DWORD read; + + while (ReadFile(ydl->hStderr_r, ydl->buf_err + ydl->idx_err, ydl->capacity_err - ydl->idx_err, &read, NULL)) { + ydl->idx_err += read; + if (ydl->idx_err == ydl->capacity_err) { + ydl->capacity_err *= 2; + char* tmp = static_cast(std::realloc(ydl->buf_err, ydl->capacity_err)); + if (tmp) { + ydl->buf_err = tmp; + } else { + std::free(ydl->buf_err); + ydl->buf_err = nullptr; + return 0; + } + } + } + + return GetLastError() == ERROR_BROKEN_PIPE ? 0 : GetLastError(); +} + + +//find highest resolution +void filterVideo(const Value& formats, CString& url, int reqheight = 0) +{ + int maxheight = 0; + + //no heights to compare, just return last format + if (formats[0].FindMember(_T("height")) == formats[0].MemberEnd()) { + url = formats[formats.Size() - 1][_T("url")].GetString(); + } + + for (rapidjson::SizeType i = 0; i < formats.Size(); i++) { + int curheight = 0; + if (formats[i].FindMember(_T("height")) != formats[i].MemberEnd() && !formats[i][_T("height")].IsNull()) { + curheight = formats[i][_T("height")].GetInt(); + } + if (curheight >= maxheight && (!reqheight || reqheight >= curheight)) { + maxheight = curheight; + url = formats[i][_T("url")].GetString(); + } + } +} + +//find audio track with highest bitrate (some sites (youtube, vidme) have audio and videos streams separate) +bool filterAudio(const Value& formats, CString& url) +{ + float maxtbr = 0.0; + bool found = false; + + for (rapidjson::SizeType i = 0; i < formats.Size(); i++) { + //only want audio streams + //youtube and vidme mark audio-only with vcodec = "none" + if (formats[i].HasMember(_T("vcodec")) && + !formats[i][_T("vcodec")].IsNull() && + CString(formats[i][_T("vcodec")].GetString()) == _T("none") && + formats[i].HasMember(_T("tbr")) && + !formats[i][_T("tbr")].IsNull()) { + float curtbr = formats[i][_T("tbr")].GetFloat(); + if (curtbr >= maxtbr) { + maxtbr = curtbr; + url = formats[i][_T("url")].GetString(); + found = true; + } + } + } + return found; +} + +bool CYoutubeDLInstance::GetHttpStreams(CAtlList& videos, CAtlList& audio, CAtlList& names) +{ + CString url; + CString extractor = pJSON->d[_T("extractor")].GetString(); + auto& s = AfxGetAppSettings(); + + if (!bIsPlaylist) { + names.AddTail(pJSON->d[_T("title")].GetString()); + + //detect generic http link; JSON fields below may not exist + if (extractor == _T("generic")) { + videos.AddTail(pJSON->d[_T("formats")][0][_T("url")].GetString()); + return true; + } + + filterVideo(pJSON->d[_T("formats")], url, s.iYDLMaxHeight); + videos.AddTail(url); + + //find separate audio stream, if applicable + if (filterAudio(pJSON->d[_T("formats")], url)) { + audio.AddTail(url); + } + } else { + const Value& entries = pJSON->d[_T("entries")]; + + for (rapidjson::SizeType i = 0; i < entries.Size(); i++) { + filterVideo(entries[i][_T("formats")], url, s.iYDLMaxHeight); + videos.AddTail(url); + names.AddTail(entries[i][_T("title")].GetString()); + + if (filterAudio(entries[i][_T("formats")], url)) { + audio.AddTail(url); + } + } + } + return true; +} + + +bool CYoutubeDLInstance::loadJSON() +{ + //the JSON buffer is ASCII with Unicode encoded with escape characters + pJSON->d.Parse>(buf_out); + if (pJSON->d.HasParseError()) { + return false; + } + bIsPlaylist = pJSON->d.FindMember(_T("entries")) != pJSON->d.MemberEnd(); + return true; +} diff --git a/src/mpc-hc/YoutubeDL.h b/src/mpc-hc/YoutubeDL.h new file mode 100644 index 00000000000..15dd36cf407 --- /dev/null +++ b/src/mpc-hc/YoutubeDL.h @@ -0,0 +1,48 @@ +/* +* (C) 2018 Nicholas Parkanyi +* +* This file is part of MPC-HC. +* +* MPC-HC is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 3 of the License, or +* (at your option) any later version. +* +* MPC-HC is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +*/ +#pragma once +#include "stdafx.h" + +struct CUtf16JSON; + +class CYoutubeDLInstance +{ +public: + CYoutubeDLInstance(); + ~CYoutubeDLInstance(); + + bool Run(CString url); + bool GetHttpStreams(CAtlList& videos, CAtlList& audio, CAtlList& names); + +private: + CUtf16JSON* pJSON; + bool bIsPlaylist; + HANDLE hStdout_r, hStdout_w; + HANDLE hStderr_r, hStderr_w; + char* buf_out; + char* buf_err; + DWORD idx_out; + DWORD idx_err; + DWORD capacity_out, capacity_err; + + bool loadJSON(); + static DWORD WINAPI BuffOutThread(void* ydl_inst); + static DWORD WINAPI BuffErrThread(void* ydl_inst); +}; diff --git a/src/mpc-hc/mpc-hc.rc b/src/mpc-hc/mpc-hc.rc index afefb6bc49b..dc49c044157 100644 --- a/src/mpc-hc/mpc-hc.rc +++ b/src/mpc-hc/mpc-hc.rc @@ -3549,7 +3549,6 @@ STRINGTABLE BEGIN IDS_SUBTITLE_RENDERER_VS_FILTER "VSFilter / DirectVobSub" IDS_SUBTITLE_RENDERER_XY_SUB_FILTER "XySubFilter" - IDS_SUBTITLE_RENDERER_ASS_FILTER "AssFilter" IDS_SUBDL_DLG_PROVIDER_COL "Provider" IDS_SUBDL_DLG_HI_COL "Hearing Impaired" IDS_SUBDL_DLG_DOWNLOADS_COL "Downloads" @@ -3566,6 +3565,28 @@ BEGIN IDS_SUBUL_DLG_STATUS_READY "Ready..." END +STRINGTABLE +BEGIN + IDS_CMD_PNS "/pns ""name""\tSpecify Pan & Scan preset name to use" + IDS_CMD_ICONASSOC "/iconsassoc\tReassociate format icons" + IDS_CMD_NOFOCUS "/nofocus\t\tOpen MPC-HC in background" + IDS_CMD_WEBPORT "/webport N\tStart web interface on specified port" + IDS_CMD_DEBUG "/debug\t\tShow debug information in OSD" + IDS_CMD_NOCRASHREPORTER "/nocrashreporter\tDisable the crash reporter" + IDS_CMD_SLAVE "/slave ""hWnd""\tUse MPC-HC as slave" + IDS_CMD_HWGPU "/hwgpu ""index""\tSet the index of the GPU used for hardware decoding.\n\t\tOnly available for CUVID and DXVA2 (copy-back)" + IDS_CMD_RESET "/reset\t\tRestore default settings" + IDS_CMD_HELP "/help /h /?\tShow help about command line switches" + IDS_PPAGEADVANCED_SCORE "Threshold value for subtitles score which are going to be automatically downloaded. Higher values means that more accurately matched subtitles will be loaded, lower values could result in incorrect subtitles being loaded, but there is no one perfect value. Pick one that works best for you." + IDS_PPAGE_FS_CLN_AUDIO_DELAY "Audio Delay (ms)" + IDS_PPAGEADVANCED_DEFAULTTOOLBARSIZE + "Size in pixels of the default toolbar." + IDS_PPAGEADVANCED_USE_LEGACY_TOOLBAR + "Use legacy toolbar instead of new vectorized one." + IDS_SUBTITLE_RENDERER_ASS_FILTER "AssFilter" + IDS_SUBMENU_COPYURL "Copy URL" +END + STRINGTABLE BEGIN IDS_SUBUL_DLG_STATUS_NOTIMPLEMENTED "Not implemented." @@ -3627,18 +3648,24 @@ BEGIN IDS_CMD_LOCK "/lock\t\tLock workstation after playback" IDS_CMD_MONITOROFF "/monitoroff\tTurn off the monitor after playback" IDS_CMD_PLAYNEXT "/playnext\t\tOpen next file in the folder after playback" +END + +STRINGTABLE +BEGIN + IDS_CMD_VIEWPRESET "/viewpreset N\tStart with specific preset,\n\t\twhere N is either ""1"" Minimal, ""2"" Compact or ""3"" Normal" IDS_CMD_MUTE "/mute\t\tMute the audio" + IDS_CMD_VOLUME "/volume N\tSet Volume, where N is a range from 0 to 100" + IDS_YOUTUBEDL_NOT_FOUND "Youtube-dl failed to launch. Make sure youtube-dl is in PATH." + IDS_CONTROLS_YOUTUBEDL "Calling youtube-dl..." END STRINGTABLE BEGIN IDS_CMD_FULLSCREEN "/fullscreen\tStart in fullscreen mode" - IDS_CMD_VIEWPRESET "/viewpreset N\tStart with specific preset,\n\t\twhere N is either ""1"" Minimal, ""2"" Compact or ""3"" Normal" IDS_CMD_MINIMIZED "/minimized\tStart in minimized mode" IDS_CMD_NEW "/new\t\tUse a new instance of the player" IDS_CMD_ADD "/add\t\tAdd ""pathname"" to playlist, can be combined with /open and /play" IDS_CMD_RANDOMIZE "/randomize\tRandomize the playlist" - IDS_CMD_VOLUME "/volume N\tSet Volume, where N is a range from 0 to 100" IDS_CMD_REGVID "/regvid\t\tCreate file associations for video files" IDS_CMD_REGAUD "/regaud\t\tCreate file associations for audio files" IDS_CMD_REGPL "/regpl\t\tCreate file associations for playlist files" @@ -3652,27 +3679,6 @@ BEGIN IDS_CMD_SHADERPRESET "/shaderpreset ""Pr""\tStart using ""Pr"" shader preset" END -STRINGTABLE -BEGIN - IDS_CMD_PNS "/pns ""name""\tSpecify Pan & Scan preset name to use" - IDS_CMD_ICONASSOC "/iconsassoc\tReassociate format icons" - IDS_CMD_NOFOCUS "/nofocus\t\tOpen MPC-HC in background" - IDS_CMD_WEBPORT "/webport N\tStart web interface on specified port" - IDS_CMD_DEBUG "/debug\t\tShow debug information in OSD" - IDS_CMD_NOCRASHREPORTER "/nocrashreporter\tDisable the crash reporter" - IDS_CMD_SLAVE "/slave ""hWnd""\tUse MPC-HC as slave" - IDS_CMD_HWGPU "/hwgpu ""index""\tSet the index of the GPU used for hardware decoding.\n\t\tOnly available for CUVID and DXVA2 (copy-back)" - IDS_CMD_RESET "/reset\t\tRestore default settings" - IDS_CMD_HELP "/help /h /?\tShow help about command line switches" - IDS_PPAGEADVANCED_SCORE "Threshold value for subtitles score which are going to be automatically downloaded. Higher values means that more accurately matched subtitles will be loaded, lower values could result in incorrect subtitles being loaded, but there is no one perfect value. Pick one that works best for you." - IDS_PPAGE_FS_CLN_AUDIO_DELAY "Audio Delay (ms)" - IDS_PPAGEADVANCED_DEFAULTTOOLBARSIZE - "Size in pixels of the default toolbar." - IDS_PPAGEADVANCED_USE_LEGACY_TOOLBAR - "Use legacy toolbar instead of new vectorized one." - IDS_SUBMENU_COPYURL "Copy URL" -END - #endif // English (United States) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/src/mpc-hc/mpc-hc.vcxproj b/src/mpc-hc/mpc-hc.vcxproj index e083699fcfc..437246ed241 100644 --- a/src/mpc-hc/mpc-hc.vcxproj +++ b/src/mpc-hc/mpc-hc.vcxproj @@ -272,6 +272,7 @@ NotUsing + @@ -417,6 +418,7 @@ + diff --git a/src/mpc-hc/mpc-hc.vcxproj.filters b/src/mpc-hc/mpc-hc.vcxproj.filters index d5ff7f00372..cbf8550c00f 100644 --- a/src/mpc-hc/mpc-hc.vcxproj.filters +++ b/src/mpc-hc/mpc-hc.vcxproj.filters @@ -468,6 +468,7 @@ Helpers + @@ -875,6 +876,7 @@ Helpers + diff --git a/src/mpc-hc/resource.h b/src/mpc-hc/resource.h index a8888166b92..ce78a0254cc 100644 --- a/src/mpc-hc/resource.h +++ b/src/mpc-hc/resource.h @@ -1572,6 +1572,8 @@ #define IDS_CMD_VIEWPRESET 57536 #define IDS_CMD_MUTE 57537 #define IDS_CMD_VOLUME 57538 +#define IDS_YOUTUBEDL_NOT_FOUND 57539 +#define IDS_CONTROLS_YOUTUBEDL 57540 // Next default values for new objects //