Ver código fonte

Обработка ошибок ytapi

Добавлена очередь видео

Добавлено предупреждение об ошибке в боковой панели
Вадим Королёв 1 ano atrás
pai
commit
d10e89daf5

+ 62 - 0
YTMPV.cbp

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<CodeBlocks_project_file>
+	<FileVersion major="1" minor="6" />
+	<Project>
+		<Option title="YTMPV" />
+		<Option pch_mode="2" />
+		<Option compiler="gcc" />
+		<Build>
+			<Target title="Debug">
+				<Option output="bin/Debug/YTMPV" prefix_auto="1" extension_auto="1" />
+				<Option object_output="obj/Debug/" />
+				<Option type="1" />
+				<Option compiler="gcc" />
+				<Compiler>
+					<Add option="-g" />
+				</Compiler>
+			</Target>
+			<Target title="Release">
+				<Option output="bin/Release/YTMPV" prefix_auto="1" extension_auto="1" />
+				<Option object_output="obj/Release/" />
+				<Option type="1" />
+				<Option compiler="gcc" />
+				<Compiler>
+					<Add option="-O2" />
+				</Compiler>
+				<Linker>
+					<Add option="-s" />
+				</Linker>
+			</Target>
+		</Build>
+		<Compiler>
+			<Add option="-Wall" />
+		</Compiler>
+		<Unit filename="bin/src/YTMPV/resources.cpp" />
+		<Unit filename="lib/Database/database.cpp" />
+		<Unit filename="lib/Database/database.hpp" />
+		<Unit filename="lib/Database/entities.hpp" />
+		<Unit filename="lib/Database/meson.build" />
+		<Unit filename="lib/YoutubeApi/meson.build" />
+		<Unit filename="lib/YoutubeApi/youtubeapi.cpp" />
+		<Unit filename="lib/YoutubeApi/youtubeapi.hpp" />
+		<Unit filename="lib/meson.build" />
+		<Unit filename="meson.build" />
+		<Unit filename="src/YTMPV/MainWindow.cpp" />
+		<Unit filename="src/YTMPV/MainWindow.hpp" />
+		<Unit filename="src/YTMPV/PrefWindow.cpp" />
+		<Unit filename="src/YTMPV/PrefWindow.hpp" />
+		<Unit filename="src/YTMPV/VideoModel.cpp" />
+		<Unit filename="src/YTMPV/VideoModel.hpp" />
+		<Unit filename="src/YTMPV/app.cpp" />
+		<Unit filename="src/YTMPV/app.hpp" />
+		<Unit filename="src/YTMPV/core.cpp" />
+		<Unit filename="src/YTMPV/core.hpp" />
+		<Unit filename="src/YTMPV/meson.build" />
+		<Unit filename="src/YTMPV/res/MainMenu.ui" />
+		<Unit filename="src/YTMPV/res/MainWindow.ui" />
+		<Unit filename="src/YTMPV/res/PrefsWindow.ui" />
+		<Unit filename="src/YTMPV/resources.gresource.xml" />
+		<Unit filename="src/YTMPV/ytmpv.cpp" />
+		<Extensions />
+	</Project>
+</CodeBlocks_project_file>

+ 35 - 0
YTMPV.layout

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<CodeBlocks_layout_file>
+	<FileVersion major="1" minor="0" />
+	<ActiveTarget name="Debug" />
+	<File name="src/YTMPV/core.hpp" open="1" top="0" tabpos="2" split="0" active="1" splitpos="0" zoom_1="1" zoom_2="0">
+		<Cursor>
+			<Cursor1 position="5018" topLine="121" />
+		</Cursor>
+	</File>
+	<File name="src/YTMPV/PrefWindow.cpp" open="1" top="0" tabpos="3" split="0" active="1" splitpos="0" zoom_1="0" zoom_2="0">
+		<Cursor>
+			<Cursor1 position="5673" topLine="173" />
+		</Cursor>
+	</File>
+	<File name="src/YTMPV/res/PrefsWindow.ui" open="0" top="0" tabpos="0" split="0" active="1" splitpos="0" zoom_1="0" zoom_2="0">
+		<Cursor>
+			<Cursor1 position="4840" topLine="111" />
+		</Cursor>
+	</File>
+	<File name="src/YTMPV/core.cpp" open="1" top="0" tabpos="1" split="0" active="1" splitpos="0" zoom_1="0" zoom_2="0">
+		<Cursor>
+			<Cursor1 position="5677" topLine="189" />
+		</Cursor>
+	</File>
+	<File name="src/YTMPV/PrefWindow.hpp" open="1" top="1" tabpos="4" split="0" active="1" splitpos="0" zoom_1="0" zoom_2="0">
+		<Cursor>
+			<Cursor1 position="1729" topLine="36" />
+		</Cursor>
+	</File>
+	<File name="meson.build" open="0" top="0" tabpos="0" split="0" active="1" splitpos="0" zoom_1="0" zoom_2="0">
+		<Cursor>
+			<Cursor1 position="160" topLine="0" />
+		</Cursor>
+	</File>
+</CodeBlocks_layout_file>

+ 15 - 8
lib/YoutubeApi/youtubeapi.cpp

@@ -18,8 +18,13 @@ namespace ytapi {
         curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
         curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &writeHttpToString);
         curl_easy_setopt(handle, CURLOPT_WRITEDATA, response);
-        curl_easy_perform(handle);
+        int result = curl_easy_perform(handle);
         curl_easy_cleanup(handle);
+
+        if (result != CURLE_OK) {
+            // Произошла ошибка
+            return false;
+        }
         return true;
     }
 
@@ -69,7 +74,7 @@ namespace ytapi {
         return written;
     }
 
-    std::vector<std::string> getVideoIDsByQuery(std::string query, std::string key)
+    SearchResults getVideoIDsByQuery(std::string query, std::string key)
     {
         std::stringstream response;
         std::string url;
@@ -78,12 +83,14 @@ namespace ytapi {
         url += "https://youtube.googleapis.com/youtube/v3/search?type=video&part=snippet&maxResult=25&q=";
         url += urlencode(query);
         
-        performCurlRequest(url, key, &response);
-        return getVideoIDsFromJSON(response.str());
-    }
+        bool successfull = performCurlRequest(url, key, &response);
+        if (!successfull) {
+            // Пустой ответ если произошла ошибка
+            return SearchResults{Status::NET_ERROR, {}};
+        }
 
-    std::vector<std::string> getVideoIDsFromJSON(std::string input) {
-        auto json = nlohmann::json::parse(input);
+        // Парсинг ответа
+        auto json = nlohmann::json::parse(response.str());
         auto items = json["items"];
         std::vector<std::string> output;
         
@@ -93,7 +100,7 @@ namespace ytapi {
             output.push_back(yt_id);
         }
 
-        return output;
+        return SearchResults{Status::OK, output};
     }
 
     // TODO: https://www.returnyoutubedislike.com/

+ 15 - 4
lib/YoutubeApi/youtubeapi.hpp

@@ -9,6 +9,13 @@
 #include <Database/database.hpp>
 
 namespace ytapi {
+
+    // Перечисление статусов ответов которые могут произойти в библиотеке
+    enum class Status {
+        OK,             // Запрос успешен   
+        NET_ERROR,      // Ошибка подключения
+        APIKEY_INVALID  // Неверный API ключ
+    };
     
     // Детали видео - необработанные
     struct Video {
@@ -23,6 +30,12 @@ namespace ytapi {
         int             rating;         // Моя оценка видео
         std::string     big_thumbnail;  // URL самого большого превью
     };
+
+    // Ответ поиска видео по запросу
+    struct SearchResults {
+        Status                      status; // Статус ответа
+        std::vector<std::string>    ytids;  // Вектор из YTID видео по запросу
+    };
     
     // Функция записи данных в std::ostringstream
     size_t writeHttpToString(char* ptr, size_t size, size_t nmemb, void *userdata);
@@ -38,17 +51,15 @@ namespace ytapi {
     std::string urldecode(const std::string& encoded);
 
     // Получает список YT_ID видео по запросу
+    // query - запрос
     // key - ключ api
-    std::vector<std::string> getVideoIDsByQuery(std::string query, std::string key);
+    SearchResults getVideoIDsByQuery(std::string query, std::string key);
 
     // Возвращает объект видео, получив данные по API
     // ytid - id видео на YouTube
     // key - ключ api
     Video getVideoByYTID(std::string ytid, std::string key);
 
-    // Возвращает список видео id из JSON списка видео
-    std::vector<std::string> getVideoIDsFromJSON(std::string input);
-
     // Подгатавливает, настраивает и выполняет CURL запрос. Возвращает
     // успешность вызова
     // url - адрес запроса

+ 27 - 7
src/YTMPV/MainWindow.cpp

@@ -101,9 +101,6 @@ namespace components {
         m_rate_like_btn->signal_clicked().connect(sigc::bind(
             sigc::mem_fun(*this, &MainWindow::onRateButtonClicked),
             RateButtonParams { core::VideoRating::LIKE } ));
-        
-        // WelcomeView
-        // TODO
     }
 
     void MainWindow::searchVideoByQuery(Glib::ustring query)
@@ -113,20 +110,30 @@ namespace components {
 
         // Получить новые результаты поиска
         db::video found;
-        std::vector<std::string>video_ids = m_appcore->searchVideos(query);
+        ytapi::SearchResults response = m_appcore->searchVideos(query);
+
+        if (response.status == ytapi::Status::NET_ERROR) {
+            searchSidebarError("A net error happened.\nCheck your internet connection.");
+            return;
+        }
+
+        if (response.status == ytapi::Status::APIKEY_INVALID) {
+            searchSidebarError("Provided invalid api key.");
+            return;
+        }
 
         // Добавить в список видео найденные видео
-        for (auto ytid : video_ids) {
+        for (auto ytid : response.ytids) {
             found = m_appcore->getVideoByYTID(ytid);
             m_video_storage->append(VideoModel::create(found));
         }
+        searchSidebarRegularMode();
     }
 
     void MainWindow::onSearchButtonClicked()
     {
         auto query = m_search_entry->get_text();
         searchVideoByQuery(query);
-        searchSidebarRegularMode();
     }
 
     void MainWindow::onStartSearchButtonClicked()
@@ -138,7 +145,6 @@ namespace components {
             Gtk::StackTransitionType::SLIDE_LEFT
         );
         searchVideoByQuery(query);
-        searchSidebarRegularMode();
     }
 
     void MainWindow::onVideoSetup(const Glib::RefPtr<Gtk::ListItem>& list_item)
@@ -240,6 +246,20 @@ namespace components {
         m_search_body->set_start_child(*box);
     }
 
+    void MainWindow::searchSidebarError(Glib::ustring description)
+    {
+        auto builder = Gtk::Builder::create_from_resource(
+            "/src/YTMPV/res/sidebar/error.ui");
+        Gtk::Box* layout = builder->get_widget<Gtk::Box>("layout");
+        Gtk::Label* label = builder->get_widget<Gtk::Label>("errorLabel");
+
+        assert(layout != nullptr);
+        assert(label != nullptr);
+
+        label->set_label(Glib::Markup::escape_text(description));
+        m_search_body->set_start_child(*layout);
+    }
+
     void MainWindow::searchSidebarVideoDetailsMode(
         Glib::ustring title,
         Glib::ustring channel_title,

+ 4 - 0
src/YTMPV/MainWindow.hpp

@@ -92,9 +92,13 @@ namespace components {
         // --Общее--
         // Оценка выбранного видео
         int m_current_rating;
+
         // Устанавливает боковую панель просмотра видео в обычный режим
         void searchSidebarRegularMode();
 
+        // Устанавливает боковую панель просмотра видео в обычный режим
+        void searchSidebarError(Glib::ustring description);
+
         // Устанавливает боковую панель просмотра видео в режим просмотра
         // информации о видео
         // title - Название видео

+ 83 - 10
src/YTMPV/PrefWindow.cpp

@@ -30,23 +30,41 @@ namespace components {
             "720p",
             "1080p"
         };
-        
-        m_quality_dropdown = m_builder->get_widget<Gtk::DropDown>("quality_entry");
-        m_cancel_btn = m_builder->get_widget<Gtk::Button>("cancel_button");
-        m_save_btn = m_builder->get_widget<Gtk::Button>("saveclose_button");
 
-        assert(m_quality_dropdown != nullptr);
-        assert(m_cancel_btn != nullptr);
-        assert(m_save_btn != nullptr);
+        m_save_btn          = m_builder->get_widget<Gtk::Button>("saveclose_button");
+        m_cancel_btn        = m_builder->get_widget<Gtk::Button>("cancel_button");
+        m_mpvpath_entry     = m_builder->get_widget<Gtk::Entry>("mpvpath_entry");
+        m_quality_dropdown  = m_builder->get_widget<Gtk::DropDown>("quality_entry");
+        m_apikey_entry      = m_builder->get_widget<Gtk::Entry>("apikey_entry");
 
-        auto string_list = Gtk::StringList::create(quality_strings);
-        m_quality_dropdown->set_model(string_list);
+        m_miniplayer_btn    = m_builder->get_widget<Gtk::CheckButton>("minimode_entry");
+        m_fullscreen_btn    = m_builder->get_widget<Gtk::CheckButton>("fullscreen_entry");
+        m_ontop_btn         = m_builder->get_widget<Gtk::CheckButton>("ontop_entry");
 
+        assert(m_quality_dropdown   != nullptr);
+        assert(m_cancel_btn         != nullptr);
+        assert(m_save_btn           != nullptr);
+        assert(m_mpvpath_entry      != nullptr);
+        assert(m_apikey_entry       != nullptr);
+        assert(m_miniplayer_btn     != nullptr);
+        assert(m_fullscreen_btn     != nullptr);
+        assert(m_ontop_btn          != nullptr);
+
+        // Кнопки
         m_save_btn->signal_clicked().connect(sigc::mem_fun(
             *this, &PrefWindow::onSaveClicked));
         m_cancel_btn->signal_clicked().connect(sigc::mem_fun(
             *this, &PrefWindow::onCancelClicked));
 
+        // Ключ API
+        m_apikey_entry->set_text(appcore->getApiKey());
+
+        // Путь к MPV
+        m_mpvpath_entry->set_text(appcore->getMpvPath());
+
+        // Качество
+        auto string_list = Gtk::StringList::create(quality_strings);
+        m_quality_dropdown->set_model(string_list);
         int selected_quality_index;
         switch(m_appcore->getQuality())
         {
@@ -70,6 +88,24 @@ namespace components {
                 break;
         }
         m_quality_dropdown->set_selected(selected_quality_index);
+
+        // Мини-плеер
+        this->prepareBoolButton(
+            m_miniplayer_btn,
+            appcore->getIsMiniplayer(),
+            BoolSettingType::MINIPLAYER);
+
+        // Полноэкранный режим
+        this->prepareBoolButton(
+            m_fullscreen_btn,
+            appcore->getFullscreen(),
+            BoolSettingType::FULLSCREEN);
+
+        // Режим поверх всех окон
+        this->prepareBoolButton(
+            m_ontop_btn,
+            appcore->getOnTop(),
+            BoolSettingType::ONTOP);
     }
 
     PrefWindow* PrefWindow::create(
@@ -98,6 +134,25 @@ namespace components {
         this->close();
     }
 
+    void PrefWindow::onBoolButtonClicked(BoolButtonParams params)
+    {
+        bool value = params.caller->get_active();
+        switch (params.setting_type)
+        {
+            case BoolSettingType::MINIPLAYER:
+                m_appcore->setMiniplayer(value);
+                break;
+            case BoolSettingType::FULLSCREEN:
+                m_appcore->setFullscreen(value);
+                break;
+            case BoolSettingType::ONTOP:
+                m_appcore->setOnTop(value);
+                break;
+            default:
+                break;
+        }
+    }
+
     void PrefWindow::saveConfig()
     {
         // Качество
@@ -125,7 +180,25 @@ namespace components {
                 break;
         }
         m_appcore->setQuality(quality);
-        
+        m_appcore->setApiKey(m_apikey_entry->get_text());
+
         m_appcore->saveConfig();
     }
+
+    void PrefWindow::prepareBoolButton(
+            Gtk::CheckButton* widget,
+            bool default_value,
+            BoolSettingType setting_type)
+    {
+        widget->set_active(default_value);
+        widget->signal_toggled().connect(
+            sigc::bind(
+                sigc::mem_fun(*this, &PrefWindow::onBoolButtonClicked),
+                BoolButtonParams {
+                    setting_type,
+                    widget
+                }
+            )
+        );
+    }
 }

+ 28 - 1
src/YTMPV/PrefWindow.hpp

@@ -7,6 +7,22 @@
 #include <gtkmm/dropdown.h>
 #include <gtkmm/entry.h>
 #include <gtkmm/button.h>
+#include <gtkmm/checkbutton.h>
+
+namespace {
+    // Настройки которые можно настроить булевыми переменными
+    enum class BoolSettingType {
+        MINIPLAYER,
+        FULLSCREEN,
+        ONTOP
+    };
+
+    // Параметры клика кнопок да/нет
+    struct BoolButtonParams {
+        BoolSettingType setting_type;
+        Gtk::CheckButton* caller;
+    };
+}
 
 namespace components {
     class PrefWindow : public Gtk::Window
@@ -23,14 +39,25 @@ namespace components {
         Glib::RefPtr<Gtk::Builder> m_builder;
         Gtk::DropDown* m_quality_dropdown;
         Gtk::Entry* m_apikey_entry;
+        Gtk::Entry* m_mpvpath_entry;
         Gtk::Button* m_cancel_btn;
         Gtk::Button* m_save_btn;
+        Gtk::CheckButton* m_miniplayer_btn;
+        Gtk::CheckButton* m_fullscreen_btn;
+        Gtk::CheckButton* m_ontop_btn;
 
         // -- Обработчики событий --
         void onCancelClicked();
         void onSaveClicked();
+        void onBoolButtonClicked(BoolButtonParams params);
 
         // Сохраняет настройки
         void saveConfig();
+
+        // Выполняет подготовку кнопки переключателя
+        void prepareBoolButton(
+            Gtk::CheckButton* widget,
+            bool default_value,
+            BoolSettingType setting_type);
     };
-}
+}

+ 71 - 5
src/YTMPV/core.cpp

@@ -1,6 +1,5 @@
 #include "core.hpp"
 #include <nlohmann/json.hpp>
-#include <YoutubeApi/youtubeapi.hpp>
 #include <Database/database.hpp>
 
 using json = nlohmann::json;
@@ -13,7 +12,9 @@ namespace core {
         int quality,
         bool launch_fullscreen,
         bool launch_ontop,
-        bool launch_miniplayer)
+        bool launch_miniplayer,
+        std::string mpvpath,
+        std::string thumbnails_dir)
         // Инициализация
         : m_apikey(apikey)
         , m_selected_ytid("")
@@ -22,6 +23,8 @@ namespace core {
         , m_launch_fullscreen(launch_fullscreen)
         , m_launch_ontop(launch_ontop)
         , m_launch_miniplayer(launch_miniplayer)
+        , m_mpvpath(mpvpath)
+        , m_thumbnails_dir(thumbnails_dir)
     {}
 
     void Core::setQuality(int quality)
@@ -38,6 +41,7 @@ namespace core {
             {"ontop",       this->m_launch_ontop},
             {"miniplayer",  this->m_launch_miniplayer},
             {"apikey",      this->m_apikey},
+            {"mpvpath",     this->m_mpvpath},
         };
         std::string dump = j_object.dump(1, '\t');
 
@@ -100,11 +104,10 @@ namespace core {
 
     std::vector<std::string> Core::getMPVLaunchParams(std::string ytid)
     {
-        // TODO: добавить настройку пути к MPV
         std::vector<std::string> output;
 
         // Путь до MPV
-        output.push_back("/usr/bin/mpv");
+        output.push_back(m_mpvpath);
 
         // Ссылка на видео
         output.push_back(getYTLink(ytid));
@@ -115,6 +118,19 @@ namespace core {
         // С качеством:
         output.push_back(getYTDLFormat());
 
+        // -- Параметры окна --
+        if (m_launch_miniplayer) {
+            output.push_back("--geometry=640x480-0-0");
+            output.push_back("--ontop");
+        } else {
+            if (m_launch_fullscreen) {
+                output.push_back("--fullscreen");
+            }
+            if (m_launch_ontop) {
+                output.push_back("--ontop");
+            }
+        }
+
         return output;
     }
 
@@ -148,8 +164,58 @@ namespace core {
         return m_quality;
     }
 
-    std::vector<std::string> Core::searchVideos(std::string query)
+    ytapi::SearchResults Core::searchVideos(std::string query)
     {
         return ytapi::getVideoIDsByQuery(query, m_apikey);
     }
+
+    void Core::setMpvPath(std::string path)
+    {
+        m_mpvpath = path;
+    }
+
+    std::string Core::getApiKey()
+    {
+        return m_apikey;
+    }
+
+    std::string Core::getMpvPath()
+    {
+        return m_mpvpath;
+    }
+
+    bool Core::getIsMiniplayer()
+    {
+        return m_launch_miniplayer;
+    }
+
+    void Core::setMiniplayer(bool value)
+    {
+        m_launch_miniplayer = value;
+    }
+
+    void Core::setFullscreen(bool value)
+    {
+        m_launch_fullscreen = value;
+    }
+
+    void Core::setOnTop(bool value)
+    {
+        m_launch_ontop = value;
+    }
+
+    bool Core::getFullscreen()
+    {
+        return m_launch_fullscreen;
+    }
+
+    bool Core::getOnTop()
+    {
+        return m_launch_ontop;
+    }
+
+    void Core::setApiKey(std::string value)
+    {
+        m_apikey = value;
+    }
 }

+ 65 - 16
src/YTMPV/core.hpp

@@ -1,5 +1,6 @@
 #pragma once
 
+#include <YoutubeApi/youtubeapi.hpp>
 #include <Database/entities.hpp>
 #include <fstream>
 #include <iostream>
@@ -30,12 +31,63 @@ namespace core
                 int quality,
                 bool launch_fullscreen,
                 bool launch_ontop,
-                bool launch_miniplayer
+                bool launch_miniplayer,
+                std::string mpvpath,
+                std::string thumbnails_dir
             );
 
+            //{ Установщики
             // Устанавливает качество видео
             void setQuality(int quality);
 
+            // Устанавливает путь к MPV
+            void setMpvPath(std::string path);
+
+            // Устанавливает выбранное видео
+            void setSelectedYTID(std::string ytid);
+
+            // Устанавливает оценку выбранного видео
+            void setCurrentRating(VideoRating rating);
+
+            // Устанавливает режим мини-проигрывателя
+            void setMiniplayer(bool value);
+
+            // Устанавливает режим полного экрана
+            void setFullscreen(bool value);
+
+            // Устанавливает режим "Поверх других окон"
+            void setOnTop(bool value);
+
+            // Устанавливает api key
+            void setApiKey(std::string value);
+            //}
+
+            //{ Возвращатели
+            // Возвращает ytid выбранного видео
+            std::string getSelectedYTID();
+
+            // Возвращает ссылку на выбранное видео
+            std::string getSelectedYTLink();
+
+            // Возвращает качество видео
+            int getQuality();
+
+            // Возвращает путь к MPV
+            std::string getMpvPath();
+
+            // Возвращает ключ API
+            std::string getApiKey();
+
+            // Возвращает параметр запуска в мини-плеере
+            bool getIsMiniplayer();
+
+            // Возвращает режим полноэкранного запуска
+            bool getFullscreen();
+
+            // Возвращает режим запуска поверх других окон
+            bool getOnTop();
+            //}
+
             // Сохраняет настройки в файл по пути m_config_path
             void saveConfig();
 
@@ -54,26 +106,17 @@ namespace core
             // Возвращает параметры запуска MPV
             std::vector<std::string> getMPVLaunchParams(std::string ytid);
 
-            // Возвращает ytid выбранного видео
-            std::string getSelectedYTID();
-
-            // Возвращает ссылку на выбранное видео
-            std::string getSelectedYTLink();
-
             // Возвращает ссылку на видео по его ytid
             std::string getYTLink(std::string ytid);
 
-            // Устанавливает выбранное видео
-            void setSelectedYTID(std::string ytid);
-
-            // Устанавливает оценку выбранного видео
-            void setCurrentRating(VideoRating rating);
+            // Возвращает список из ytid по запросу
+            ytapi::SearchResults searchVideos(std::string query);
 
-            // Возвращает качество видео
-            int getQuality();
+            // Возвращает путь к маленькому превью видео по его ytid
+            std::string getSmallThumbnailByYTID(std::string ytid);
 
-            // Возвращает список из ytid по запросу
-            std::vector<std::string> searchVideos(std::string query);
+            // Возвращает путь к большому превью видео по его ytid
+            std::string getLargeThumbnailByYTID(std::string ytid);
 
         protected:
             // ключ API
@@ -105,5 +148,11 @@ namespace core
             // Запускать ли MPV над всеми окнами, в углу экрана?
             // Имеет приоритет над m_launch_fullscreen и m_launch_ontop
             bool m_launch_miniplayer;
+
+            // Настройка пути к MPV
+            std::string m_mpvpath;
+
+            // Путь к директории кэшированных превью видео
+            std::string m_thumbnails_dir;
     };
 }

+ 47 - 0
src/YTMPV/res/MainWindow.ui

@@ -15,6 +15,53 @@
     <child>
       <object class="GtkBox" id="layout">
         <property name="orientation">vertical</property>
+
+        <child>
+          <object class="GtkBox" id="queue">
+            <property name="orientation">horizontal</property>
+            <property name="margin-bottom">8</property>
+            <property name="margin-end">8</property>
+            <property name="margin-start">8</property>
+            <property name="margin-top">8</property>
+            <property name="spacing">8</property>
+            
+            <child>
+              <object class="GtkLabel">
+                <property name="justify">left</property>
+                <property name="label" translatable="yes">Queue:</property>
+              </object>
+            </child>
+
+            <child>
+              <object class="GtkBox" id="queueButtons">
+                <property name="orientation">horizontal</property>
+                <property name="homogeneous">True</property>
+                <property name="spacing">8</property>
+                <property name="hexpand">True</property>
+                
+                <child>
+                  <object class="GtkButton" id="queuePlay">
+                    <property name="label" translatable="yes">Play</property>
+                  </object>
+                </child>
+            
+                <child>
+                  <object class="GtkButton" id="queueClear">
+                    <property name="label" translatable="yes">Clear</property>
+                  </object>
+                </child>
+                
+                <child>
+                  <object class="GtkButton" id="queueView">
+                    <property name="label" translatable="yes">View</property>
+                  </object>
+                </child>
+              </object>
+            </child>
+
+          </object>
+        </child>
+        
         <child>
           <object class="GtkBox" id="head">
             <property name="margin-bottom">8</property>

+ 109 - 4
src/YTMPV/res/PrefsWindow.ui

@@ -16,6 +16,48 @@
         <property name="margin-start">16</property>
         <property name="margin-top">16</property>
         <property name="row-spacing">16</property>
+
+        <!---MPV path-->
+        <child>
+          <object class="GtkLabel" id="mpvpath_label">
+            <property name="label" translatable="yes">Path to MPV executable:</property>
+            <property name="xalign">1</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">0</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkEntry" id="mpvpath_entry">
+            <layout>
+              <property name="column">1</property>
+              <property name="row">0</property>
+            </layout>
+          </object>
+        </child>
+        
+        <!---API key-->
+        <child>
+          <object class="GtkLabel" id="apikey_label">
+            <property name="label" translatable="yes">Youtube API key:</property>
+            <property name="xalign">1</property>
+            <layout>
+              <property name="column">0</property>
+              <property name="row">1</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+          <object class="GtkEntry" id="apikey_entry">
+            <layout>
+              <property name="column">1</property>
+              <property name="row">1</property>
+            </layout>
+          </object>
+        </child>
+
+        <!--Quality-->
         <child>
           <object class="GtkLabel" id="quality_label">
             <property name="label" translatable="yes">_Preferred quality:</property>
@@ -24,7 +66,7 @@
             <property name="xalign">1</property>
             <layout>
               <property name="column">0</property>
-              <property name="row">0</property>
+              <property name="row">2</property>
             </layout>
           </object>
         </child>
@@ -32,16 +74,78 @@
           <object class="GtkDropDown" id="quality_entry">
             <layout>
               <property name="column">1</property>
-              <property name="row">0</property>
+              <property name="row">2</property>
             </layout>
           </object>
         </child>
+
+        <!--Launch in mini-mode-->
+        <child>
+          <object class="GtkLabel" id="minimode_label">
+            <property name="label" translatable="yes">Launch videos in mini-mode:</property>
+            <property name="xalign">1</property>
+            <layout>
+                <property name="column">0</property>
+                <property name="row">3</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+            <object class="GtkCheckButton" id="minimode_entry">
+                <layout>
+                    <property name="column">1</property>
+                    <property name="row">3</property>
+                </layout>
+            </object>
+        </child>
+
+        <!--Launch in fullscreen-->
+        <child>
+          <object class="GtkLabel" id="fullscreen_label">
+            <property name="label" translatable="yes">Launch videos in fullscreen:</property>
+            <property name="xalign">1</property>
+            <layout>
+                <property name="column">0</property>
+                <property name="row">4</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+            <object class="GtkCheckButton" id="fullscreen_entry">
+                <layout>
+                    <property name="column">1</property>
+                    <property name="row">4</property>
+                </layout>
+            </object>
+        </child>
+
+        <!--Launch on top-->
+        <child>
+          <object class="GtkLabel" id="ontop_label">
+            <property name="label" translatable="yes">MPV on top of other windows:</property>
+            <property name="xalign">1</property>
+            <layout>
+                <property name="column">0</property>
+                <property name="row">5</property>
+            </layout>
+          </object>
+        </child>
+        <child>
+            <object class="GtkCheckButton" id="ontop_entry">
+                <layout>
+                    <property name="column">1</property>
+                    <property name="row">5</property>
+                </layout>
+            </object>
+        </child>
+
+        <!--Buttons-->
         <child>
           <object class="GtkButton" id="cancel_button">
             <property name="label" translatable="yes">Cancel</property>
             <layout>
               <property name="column">0</property>
-              <property name="row">2</property>
+              <property name="row">6</property>
             </layout>
           </object>
         </child>
@@ -50,10 +154,11 @@
             <property name="label" translatable="yes">Save and close</property>
             <layout>
               <property name="column">1</property>
-              <property name="row">2</property>
+              <property name="row">6</property>
             </layout>
           </object>
         </child>
+        
       </object>
     </child>
   </object>

+ 16 - 0
src/YTMPV/res/sidebar/error.ui

@@ -0,0 +1,16 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<interface>
+  <requires lib="gtk" version="4.8"/>
+  <object class="GtkBox" id="layout">
+    <property name="orientation">vertical</property>
+
+    <child>
+      <object class="GtkLabel" id="errorLabel">
+        <property name="justify">center</property>
+        <property name="label" translatable="yes">There was unknown error, sorry</property>
+        <property name="use-markup">True</property>
+        <property name="vexpand">True</property>
+      </object>
+    </child>
+  </object>
+</interface>

+ 1 - 0
src/YTMPV/resources.gresource.xml

@@ -4,5 +4,6 @@
         <file preprocess="xml-stripblanks">MainMenu.ui</file>
         <file preprocess="xml-stripblanks">MainWindow.ui</file>
         <file preprocess="xml-stripblanks">PrefsWindow.ui</file>
+        <file preprocess="xml-stripblanks">sidebar/error.ui</file>
     </gresource>
 </gresources>

+ 8 - 1
src/YTMPV/ytmpv.cpp

@@ -37,6 +37,11 @@ int main(int argc, char** argv) {
         Glib::get_user_cache_dir(),
         "ytmpv",
         "db.sqlite3");
+
+    std::string thumbnails_dir = Glib::build_filename(
+        Glib::get_user_cache_dir(),
+        "ytmpv",
+        "thumbnails");
     
     std::ifstream config_file(config_path);
     auto j_config = json::parse(config_file);
@@ -58,7 +63,9 @@ int main(int argc, char** argv) {
         j_config["quality"],
         j_config["fullscreen"],
         j_config["ontop"],
-        j_config["miniplayer"]
+        j_config["miniplayer"],
+        j_config["mpvpath"],
+        thumbnails_dir
     );
     auto app = components::App::create(&appcore);
     return app->run(argc, argv);