浏览代码

Добавлено отображение видео в окне

Вадим Королёв 1 年之前
父节点
当前提交
8480d11c37

+ 28 - 1
lib/YoutubeApi/youtubeapi.cpp

@@ -20,6 +20,19 @@ namespace ytapi {
         curl_easy_cleanup(handle);
         return true;
     }
+
+    void performCurlDownload(std::string url, std::string save_path) {       
+        CURL* handle = curl_easy_init();
+        if (!handle) return;
+        FILE* fp;
+        fp = fopen(save_path.c_str(), "wb");
+        curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
+        curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &writeHttpToFile);
+        curl_easy_setopt(handle, CURLOPT_WRITEDATA, fp);
+        curl_easy_perform(handle);
+        curl_easy_cleanup(handle);
+        fclose(fp);
+    }
     
     std::string urlencode(const std::string& decoded) {
         const auto encoded_value = curl_easy_escape(nullptr, decoded.c_str(), static_cast<int>(decoded.length()));
@@ -41,12 +54,19 @@ namespace ytapi {
         return result;
     }
 
-    size_t write_http_data(char* ptr, size_t size, size_t nmemb, void* userdata) {
+    size_t write_http_data(char* ptr, size_t size, size_t nmemb, void* userdata)
+    {
         std::string data((const char*) ptr, (size_t) size * nmemb);
         *((std::stringstream*) userdata) << data;
         return size * nmemb;
     }
 
+    size_t writeHttpToFile(void* ptr, size_t size, size_t nmemb, FILE* userdata)
+    {
+        size_t written = fwrite(ptr, size, nmemb, userdata);
+        return written;
+    }
+
     std::vector<std::string> getVideoIDsByQuery(std::string query) {
         std::stringstream response;
         std::string url;
@@ -103,4 +123,11 @@ namespace ytapi {
 
         return output;
     }
+
+    void downloadMediumThumnail(std::string yt_id, std::string save_path)
+    {
+        // Формирование URL
+        std::string url = "https://img.youtube.com/vi/" + yt_id + "/default.jpg";
+        performCurlDownload(url, save_path);
+    }
 }

+ 11 - 1
lib/YoutubeApi/youtubeapi.hpp

@@ -29,7 +29,10 @@ namespace ytapi {
     void youtubeInit(std::string key);
     
     // Функция записи данных в std::ostringstream
-    size_t write_http_data(char *ptr, size_t size, size_t nmemb, void *userdata);
+    size_t write_http_data(char* ptr, size_t size, size_t nmemb, void *userdata);
+
+    // Функция записи данных в файл на диске
+    size_t writeHttpToFile(void* ptr, size_t size, size_t nmemb, FILE* userdata);
 
     // Кодирует URL-строку
     // https://stackoverflow.com/a/154627
@@ -50,4 +53,11 @@ namespace ytapi {
     // Подгатавливает, настраивает и выполняет CURL запрос. Возвращает
     // успешность вызова
     bool performCurlRequest(std::string url, std::stringstream* response);
+
+    // Подгатавливает, настраивает и выполняет CURL запрос на скачивание файла
+    // Файл сохранится в save_path
+    bool performCurlRequest(std::string url, std::string save_path);
+
+    // Скачивает превью видео и сохраняет в путь save_path
+    void downloadMediumThumnail(std::string yt_id, std::string save_path);
 }

+ 83 - 5
src/YTMPV/MainWindow.cpp

@@ -13,19 +13,45 @@ namespace components {
         set_default_size(800, 600);
         set_icon_name("youtube");
 
+        // Searchbar
         m_search_btn.set_child(*(this->getButtonContents("Поиск", "find")));
         m_search_btn.signal_clicked().connect(sigc::mem_fun(
             *this,
             &MainWindow::on_btn_clicked
         ));
-
-        // Searchbar
+        m_search_field.set_hexpand(true);
         m_search_box.append(m_search_field);
         m_search_box.append(m_search_btn);
+        m_search_box.set_margin(8);
+
+        // --Body-
+        // Модель видео
+        m_video_storage = Gio::ListStore<VideoModel>::create();
+        m_video_storage->append(VideoModel::create(1, "7yZvzo4_s6Q", "Hermitcraft 10 - Ep 12:  Who Has The Best Storage System???"));
+
+        // Модель выбора
+        auto selection_model = Gtk::SingleSelection::create(m_video_storage);
+        selection_model->set_autoselect(false);
+        selection_model->set_can_unselect(true);
+        m_video_list.set_model(selection_model);
 
-        // Body
+        // Создание фабрики
+        auto factory = Gtk::SignalListItemFactory::create();
+        factory->signal_setup().connect(
+            sigc::mem_fun(*this, &MainWindow::onVideoSetup));
+        factory->signal_bind().connect(
+            sigc::mem_fun(*this, &MainWindow::onVideoBind));
+        m_video_list.set_factory(factory);
+
+        // Прокручиваемое окно
+        m_video_scroll.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
+        m_video_scroll.set_expand(true);
+        m_video_scroll.set_child(m_video_list);
+
+        m_body.append(m_video_scroll);
 
         // Layout
+        m_layout.set_homogeneous(false);
         m_layout.append(m_search_box);
         m_layout.append(m_body);
         
@@ -38,7 +64,59 @@ namespace components {
 
     void MainWindow::on_btn_clicked()
     {
-        std::cout << "CLICK!" << std::endl;
+        //m_video_storage->append(VideoModel::create(2, "test."));
+    }
+
+    void MainWindow::onVideoSetup(const Glib::RefPtr<Gtk::ListItem>& list_item)
+    {
+        auto box_main = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
+        auto thumbnail = Gtk::make_managed<Gtk::Picture>();
+        auto label = Gtk::make_managed<Gtk::Label>("", Gtk::Align::START);
+        box_main->append(*thumbnail);
+        box_main->append(*label);
+        list_item->set_child(*box_main);
+    }
+
+    void MainWindow::onVideoBind(const Glib::RefPtr<Gtk::ListItem>& list_item)
+    {
+        auto pos = list_item->get_position();
+        if (pos == GTK_INVALID_LIST_POSITION) return;
+        auto item = m_video_storage->get_item(pos);
+        
+        auto box_main = dynamic_cast<Gtk::Box*>(list_item->get_child());
+        if (!box_main) return;
+
+        auto thumbnail = dynamic_cast<Gtk::Picture*>(box_main->get_first_child());
+        if (!thumbnail) return;
+
+        // Загрузка изображения превью видео с диска
+        std::string thumbnail_path = "thumbnails/default/" + item->m_yt_id + ".jpg";
+        Glib::RefPtr<Gdk::Pixbuf> thumbnail_pixbuf;
+        try {
+            thumbnail_pixbuf = Gdk::Pixbuf::create_from_file(thumbnail_path);
+        } catch (Glib::FileError& e) {
+            switch (e.code()) {
+                case Glib::FileError::Code::NO_SUCH_ENTITY:
+                    // Превью на диске не найдено. Скачиваем...
+                    ytapi::downloadMediumThumnail(item->m_yt_id, thumbnail_path);
+                    // ...и присваиваем
+                    thumbnail_pixbuf = Gdk::Pixbuf::create_from_file(thumbnail_path);
+                    break;
+
+                default:
+                    std::cout << "Unhandled error when creating thumbnail. Code is: " << e.code(); 
+                    break;
+            }
+        }
+        auto thumbnail_texture = Gdk::Texture::create_for_pixbuf(thumbnail_pixbuf);
+        thumbnail->set_paintable(thumbnail_texture);
+        thumbnail->set_can_shrink(false);
+        thumbnail->set_content_fit(Gtk::ContentFit::COVER);
+
+        auto lbl_title = dynamic_cast<Gtk::Label*>(thumbnail->get_next_sibling());
+        if (!lbl_title) return;
+
+        lbl_title->set_text(item->m_title);
     }
 
     Gtk::Box* MainWindow::getButtonContents(Glib::ustring text, std::string icon_name)
@@ -47,7 +125,7 @@ namespace components {
         auto icon = Gio::Icon::create(icon_name);
         auto img_icon = Gtk::make_managed<Gtk::Image>(icon);
         auto lbl_main = Gtk::make_managed<Gtk::Label>(text);
-        lbl_main->set_expand(true);
+        lbl_main->set_hexpand(false);
 
         // Добавление в контейнер
         auto box_main = Gtk::make_managed<Gtk::Box>(

+ 25 - 7
src/YTMPV/MainWindow.hpp

@@ -2,15 +2,26 @@
 #include <iostream>
 #include <gtkmm/window.h>
 #include <gtkmm/button.h>
-#include <gtkmm/image.h>
+#include <gtkmm/picture.h>
 #include <gtkmm/box.h>
 #include <gtkmm/label.h>
 #include <gtkmm/entry.h>
+#include <gtkmm/singleselection.h>
+#include <gtkmm/listview.h>
+#include <gtkmm/signallistitemfactory.h>
+#include <gtkmm/scrolledwindow.h>
 
 #include <glibmm/ustring.h>
+#include <glibmm/fileutils.h>
+
+#include <gdkmm/pixbuf.h>
+#include <gdkmm/texture.h>
+
 #include <giomm/icon.h>
+#include <giomm/liststore.h>
 
 #include "VideoModel.hpp"
+#include <YoutubeApi/youtubeapi.hpp>
 
 namespace components {
     // Главное окно приложения
@@ -21,20 +32,27 @@ namespace components {
 
     protected:
         // --Обработка событий--
+        // Клик кнопки
         void on_btn_clicked();
+        // При создании элемента в m_video_list
+        void onVideoSetup(const Glib::RefPtr<Gtk::ListItem>& list_item);
+        // При записи данных в m_video_list
+        void onVideoBind(const Glib::RefPtr<Gtk::ListItem>& list_item);
 
         // --Виджеты--
-        Gtk::Box    m_layout;       // Главный контейнер
+        Gtk::Box                m_layout;       // Главный контейнер
         // Поиск
-        Gtk::Box    m_search_box;   // Панель поиска
-        Gtk::Entry  m_search_field; // Поле ввода запроса
-        Gtk::Button m_search_btn;   // Кнопка поиска
+        Gtk::Box                m_search_box;   // Панель поиска
+        Gtk::Entry              m_search_field; // Поле ввода запроса
+        Gtk::Button             m_search_btn;   // Кнопка поиска
         // Основное
-        Gtk::Box    m_body;         // Главное
+        Gtk::Box                m_body;         // Главное
+        Gtk::ScrolledWindow     m_video_scroll; // Окно прокрутки для видео
+        Gtk::ListView           m_video_list;   // Список видео
 
         // --Общее--
         // Модель отображения видео
-        Glib::RefPtr<Gio::ListStore<VideoModel>> m_video_store;
+        Glib::RefPtr<Gio::ListStore<VideoModel>> m_video_storage;
         // Возвращает дочерний виджет для кнопок, которым нужны иконка с текстом
         Gtk::Box* getButtonContents(Glib::ustring text, std::string icon_name);
     };

+ 8 - 6
src/YTMPV/VideoModel.cpp

@@ -1,18 +1,20 @@
 #include "VideoModel.hpp"
 
 namespace components {
-    Glib::RefPtr<VideoModel> VideoModel::create(int col_id, const Glib::ustring& col_name)
+    Glib::RefPtr<VideoModel> VideoModel::create(int id, std::string yt_id, const Glib::ustring& title)
     {
         return Glib::make_refptr_for_instance<VideoModel>(
             new VideoModel(
-                col_id,
-                col_name
+                id,
+                yt_id,
+                title
             )
         );
     }
 
-    VideoModel::VideoModel(int col_id, const Glib::ustring& col_name)
-        : m_col_id(col_id)
-        , m_col_name(col_name)
+    VideoModel::VideoModel(int id, std::string yt_id, const Glib::ustring& title)
+        : m_id(id)
+        , m_yt_id(yt_id)
+        , m_title(title)
         {}
 }

+ 10 - 4
src/YTMPV/VideoModel.hpp

@@ -1,16 +1,22 @@
 #pragma once
 #include <glibmm/ustring.h>
 #include <giomm/liststore.h>
+#include <string>
 
 namespace components {
     class VideoModel : public Glib::Object
     {
     public:
-        int m_col_id;
-        Glib::ustring m_col_name;
-        static Glib::RefPtr<VideoModel> create(int col_id, const Glib::ustring& col_name);
+        // ID в БД
+        int m_id;
+        // ID на YouTube
+        std::string m_yt_id;
+        // Название видео
+        Glib::ustring m_title;
+        // Метод создания
+        static Glib::RefPtr<VideoModel> create(int id, std::string yt_id, const Glib::ustring& title);
 
     protected:
-        VideoModel(int col_id, const Glib::ustring& col_name);
+        VideoModel(int id, std::string yt_id, const Glib::ustring& title);
     };
 }

+ 2 - 0
thumbnails/default/.gitignore

@@ -0,0 +1,2 @@
+*
+!.gitignore

二进制
thumbnails/default/7yZvzo4_s6Q.jpg