Commit bffa093d authored by Julius Künzel's avatar Julius Künzel
Browse files

Cleanup and add documentation

parent 50f59a4e
......@@ -52,7 +52,7 @@ find_package(KF5 REQUIRED COMPONENTS Archive Bookmarks CoreAddons Config ConfigW
# Qt
set(QT_MIN_VERSION 5.11.0)
find_package(Qt5 REQUIRED COMPONENTS Core DBus Widgets Svg Quick QuickControls2 Concurrent QuickWidgets Multimedia)
find_package(Qt5 REQUIRED COMPONENTS Core DBus Widgets Svg Quick QuickControls2 Concurrent QuickWidgets Multimedia NetworkAuth)
add_definitions(-DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}")
......
# How To Write Provider Configurations
Intruction written 2021 by Julius Künzel as part of Kdenlive (www.kdenlive.org)
## Overview
Provider configuration files are json files.
Here is an example for [freesound](https://freesound.org)
```json
{
"name": "Freesound",
"homepage": "https://freesound.org",
"type": "sound",
"integration": "buildin",
"downloadOAuth2": true,
"clientkey": "1234",
"api": {
"root": "https://freesound.org/apiv2",
"oauth2": {
"authorizationUrl": "https://freesound.org/apiv2/oauth2/authorize/",
"accessTokenUrl": "https://freesound.org/apiv2/oauth2/access_token/",
"clientId": "9876"
},
"search": {
"req": {
"path": "/search/text/",
"method": "GET",
"params": [
{ "key": "format", "value": "json" },
{ "key": "fields", "value": "id,url,name,description,license,filesize,duration,username,download,previews,images,type" },
{ "key": "page_size", "value": "%perpage%" },
{ "key": "page", "value": "%pagenum%" },
{ "key": "query", "value": "%query%" },
{ "key": "token", "value": "%clientkey%" }
]
},
"res": {
"format": "json",
"resultCount":"count",
"list":"results",
"name":"name",
"filetype":"type",
"id":"id",
"url":"url",
"licenseUrl":"license",
"description": "description",
"author": "username",
"authorUrl": "$https://freesound.org/people/{username}",
"duration": "duration",
"filesize":"filesize",
"downloadUrl": "download",
"previewUrl": "previews.preview-hq-mp3",
"imageUrl": "images.waveform_m"
}
}
}
}
```
## Base Structure
Each provider config files should only specify a certain media type such as `video`. If the provider provids multiple types, create one config file for each.
| Key | Type | Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| name | String | yes | Name of the provider (not translatable!!!) |
| homepage | String | yes | Url pointing to the providers webpage |
| type | String | yes | one of `video`, `image`, `music`, `sound` |
| integration | String | yes | Must be `buildin` as this is the only supported value at the moment |
| clientkey | String | If OAuth2 or `%clientkey%` is used | The client key to access the api. </br>*Kdenlive has some keys build in: `%pixabay_apikey%`, `%freesound_apikey%` and `%pexels_apikey%` will be replaced by a key for the certain provider.* |
| downloadOAuth2 | bool | no | Whether OAuth2 authentification is need to download files
| api | Object | yes | see [Api](#Api) |
## Api
The `api` object describs the api endpoints
| Key | Type | Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| root | String | yes | The apis base url (should *not* end with `/`) |
| oauth2 | Object | If [`downloadOAuth2`](#Base-Structure) is `true` | See [OAuth2](#OAuth2) |
| search | Object | yes | See [Search](#Search) |
| downloadUrls | Object | yes | See [Fetch Download Urls](#Fetch-Download-Urls) |
### OAuth2
| Key | Type | Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| authorizationUrl | String | yes | The url to authorize |
| accessTokenUrl | String | yes | The url to request an access token |
| clientId | String | yes | The clients id |
### Search
The `search` object should hold the two objects `req` and `res`.
#### Request
| Key | Type | Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| path | String | yes | Path to the search endpoint (appended to [`root`](#Api), should start with `/`) |
| method | String | yes | HTTP method. Only `GET` is supported at the moment |
| params | Array of Objects | no | List of HTTP params |
| header | Array of Objects| no | List HTTP headers |
The objects in the arrays in `params` and `header` should contain two fields: `key` and `value`
#### Response
| Key | Type | Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| format | String | yes | Format of the response. Only `json` is supported at the moment |
| resultCount | String | yes | Number of total result |
| list | String | | Name of the key containing the list holding the search results. All of the following fields are relative to a item of this list |
| name | String | no | The items name |
| filetype | String | no | The items filetype |
| id | String | yes | Name of the key in a list element holding the items id |
| url | String | | Url to the item to be opened in a external browser |
| licenseUrl | String | | Url to the license to be opened in a external browser. If you use a [template](#Templates), always use link to the english license version. Kdenlive can generate License names out of links to Creative Commons, Pexels and Pixabay Licenses for all other links the License name is "Unknown License". |
| description | String | no | Description of the item |
| author | String | no | Name of the items author |
| authorUrl | String | no | Link to the items authors page to be opened in a external browser |
| duration | String | no | Duration of the item (for video and audio)|
| width | String | no | Width of the item (for video and image) |
| height | String | no | Height of the item (for video and image) |
| downloadUrl | String | no, if `downloadUrls` or [Fetch Download Urls](#Fetch-Download-Urls) | To be used when there is only one download url (i.e. one file version) for the item |
| downloadUrls | Object | no, if `downloadUrl` or [Fetch Download Urls](#Fetch-Download-Urls) | To be used when there are multiple download url (i.e. one file version) for the item. See description in [table](#Mutliple-Download-Urls) below |
| previewUrl | String | no | Url to preview file of the item |
| imageUrl | String | no | Url to image thumb of the item (for audio e.g. album cover, for video a still)|
##### Mutliple Download Urls
| Key | Type | Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| key | String | yes | Name of the key containing the list holding the files. The following fields are relative to a item of this list |
| url | String | yes | Name of the key in a list element holding the download url |
| name | String | yes | Label for the certain file verison |
###### Example
```json
"downloadUrls": {
"key": "video_files",
"url":"link",
"name":"${quality} {width}x{height}"
}
```
### Fetch Download Urls
Only necessary in special cases (e.g. https://archive.org) when no download urls are provided with the search response i.e. neither `downloadUrl` nor `downloadUrls` can be set in [Search `res`](#Response)
For the `req` object see the description of the [`req` obeject for `search`](#Request)
The `res` object should hold two fileds: `format` (same as in [search response](#Response)) and `downloadUrl` (same as in [search response](#Response)) or `downloadUrls`. The field `downloadUrls` is again similar to the one in [search response](#Response), but has some additional fields:
| Key | Type | Required | Description |
| :------------- | :------------- | :------------- | :------------- |
| isObject | Boolean | no | Whether the list not a normal array but a object and each subobject contains metadata about a file (e.g. `"files": { "file1": { "name": "&" }, "file2": { … }, …}`)|
| format | String | no | Format of the file |
If `isObject` is `true` there is a special case for `format`, `url` and `name`: if they have the value `&` (or `{&}` within a [template](#Templates)) it is replace by the key of the parent object. In the example in the table above this means `name` will be `file1`
#### Example
```json
"downloadUrls": {
"req": {
"path": "/details/%id%",
"method": "GET",
"params": [
{ "key": "output", "value": "json"}
]
},
"res": {
"format": "json",
"downloadUrls": {"key":"files", "isObject":true, "url":"$http://archive.org/download/%id%{&}", "format": "format", "name":"${source} {format} {width}x{height}"}
}
}
```
### Special Keys
Special keys are avaible for the following fields in [Search `res`](#Response): `author`, `authorUrl`, `name`, `filetype`, `description`, `id`, `url`, `licenseUrl`, `imageUrl`, `previewUrl`, `downloadUrl`, `downloadUrls.url` and `downloadUrls.name`.
In [`res` of Fetch Download Urls](#Fetch-Download-Urls) they are avaible for these fields: `downloadUrl`, `downloadUrls.format`,`downloadUrls.url` and `downloadUrls.name`
#### Placeholders
Placeholders are expressions that will be replaced by something.
For `params` and `header` these of placeholders are available.
| Placeholder | Replaced By | Hint |
| :------------- | :------------- | :------------- |
| %query% | The search query the user entered | Only for search |
| %pagenum% | Th number of the page to request results for | Only for search |
| %perpage% | Number of results that should be shown per page | |
| %shortlocale% | Short local like `en-US` (at the moment always `en-US`) | |
| %clientkey% | The clients apikey defined in [`clientkey`](#Base-Structure) | |
| %id% | Id of the item to fetch urls for | Only for Fetch Download Urls |
#### Templates
Templates must always start with `$`. Within a template string you can use keys like this `{username}`.
*NOTE: You can not use the general key like `author`. You must use the key of the response like `username` in the [example for freesound](#Overview)!*
##### Example
If your response looks like
```json
{
"results": [
{
"name": "example",
"files": [
{"picname": "nice-picture", "id": "1234"},
...
]
}
]
}
```
and your config like this
```json
{
...
"api": {
...
"search": {
...
"res" {
...
"downloadUrls": {
"key": "files",
"url":"$https://example.org/files/{picname}-{id}",
"name":"picname"
}
}
}
}
}
```
you will finally get `https://example.org/files/nice-picture-1234` as url.
......@@ -140,6 +140,7 @@ target_link_libraries(kdenliveLib
Qt5::QuickWidgets
Qt5::Concurrent
Qt5::Multimedia
Qt5::NetworkAuth
${OPENGL_LIBRARIES}
${OPENGLES_LIBRARIES}
${MLT_LIBRARIES}
......
......@@ -352,17 +352,13 @@ void MainWindow::init()
// Online resources widget
ResourceWidget *onlineResources = new ResourceWidget(this);
m_onlineResourcesDock = addDock(i18n("Online Resources"), QStringLiteral("onlineresources"), onlineResources);
//connect(m_clipMonitor, &Monitor::addClipToProject, this, &Bin::slotAddClipToProject);
//connect(m_clipMonitor, &Monitor::refreshCurrentClip, this, &Bin::slotOpenCurrent);
connect(onlineResources, &ResourceWidget::previewClip, [&](const QString &path) {
m_clipMonitor->slotPreviewOnlineResource(path);
m_clipMonitorDock->show();
m_clipMonitorDock->raise();
});
connect(onlineResources, &ResourceWidget::addClip, this, &MainWindow::slotAddProjectClip);
/*connect(spectrumDock, &QDockWidget::visibilityChanged, this, [&](bool visible) {
m_audioSpectrum->dockVisible(visible);
});*/
connect(onlineResources, &ResourceWidget::addLicenseInfo, this, &MainWindow::slotAddTextNote);
// Close library and audiospectrum and others on first run
screenGrabDock->close();
......@@ -3756,14 +3752,7 @@ void MainWindow::slotDownloadResources()
currentFolder = KdenliveSettings::defaultprojectfolder();
}
m_onlineResourcesDock->show();
m_onlineResourcesDock->raise();
//auto *d = new ResourceWidget(currentFolder);
//auto *d = new ResourceDialog(currentFolder);
//connect(d, &ResourceWidget::addClip, this, &MainWindow::slotAddProjectClip);
//connect(d, &ResourceWidget::addLicenseInfo, this, &MainWindow::slotAddTextNote);
//d->show();
//d->raise();
//d->activateWindow();
m_onlineResourcesDock->raise();;
}
void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString &keyframes)
......
set(kdenlive_SRCS
${kdenlive_SRCS}
#onlineresources/logindialog.cpp
onlineresources/providermodel.cpp
onlineresources/providersrepository.cpp
onlineresources/resourcewidget.cpp
......
This diff is collapsed.
/***************************************************************************
* Copyright (C) 2021 by Julius Künzel *
* Copyright (C) 2021 by Julius Künzel (jk.kdedev@smartlab.uber.space) *
* Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
......@@ -29,6 +30,7 @@
#include <QNetworkReply>
#include <QTemporaryFile>
#include <QPixmap>
#include <QtNetworkAuth>
struct ResourceItemInfo
{
......@@ -38,7 +40,6 @@ struct ResourceItemInfo
QString id;
QString infoUrl;
QString license;
QString attributionText;
QString author;
QString authorUrl;
int width;
......@@ -58,17 +59,18 @@ class ProviderModel : public QObject
Q_OBJECT
public:
enum SERVICETYPE { UNKNOWN = 0, AUDIO = 1, VIDEO = 2, IMAGE = 3};
enum INTEGRATIONTYPE { BUILDIN = 1, BROWSER = 2};
ProviderModel() = delete;
ProviderModel(const QString &path);
void authorize();
void refreshAccessToken();
bool is_valid() const;
QString name() const;
QString homepage() const;
ProviderModel::SERVICETYPE type() const;
ProviderModel::INTEGRATIONTYPE integratonType() const;
QString attribution() const;
bool downloadOAuth2() const;
bool requiresLogin() const;
public slots:
void slotStartSearch(const QString &searchText, int page);
......@@ -76,10 +78,10 @@ public slots:
//void slotShowResults(QNetworkReply *reply);
protected:
QOAuth2AuthorizationCodeFlow m_oauth2;
QString m_path;
QString m_name;
QString m_homepage;
INTEGRATIONTYPE m_integrationtype;
SERVICETYPE m_type;
QString m_clientkey;
QString m_attribution;
......@@ -93,6 +95,9 @@ private:
void validate();
QUrl getSearchUrl(const QString &searchText, const int page = 1);
QUrl getFilesUrl(const QString &id);
QJsonValue objectGetValue(QJsonObject item, QString key);
QString objectGetString(QJsonObject item, QString key, const QString &id = QString(), const QString &parentKey = QString());
QString replacePlaceholders(QString string, const QString query = QString(), const int page = 0, const QString id = QString());
std::pair<QList<ResourceItemInfo>, const int> parseSearchResponse(const QByteArray &res);
std::pair<QStringList, QStringList> parseFilesResponse(const QByteArray &res, const QString &id);
QTemporaryFile *m_tmpThumbFile;
......@@ -102,6 +107,9 @@ signals:
void searchDone(QList<ResourceItemInfo> &list, const int pageCount);
void searchError(const QString &msg = QString());
void fetchedFiles(QStringList, QStringList, const QString &token = QString());
void authenticated(const QString &token);
void usePreview();
void authorizeWithBrowser(const QUrl &url);
};
......
/***************************************************************************
* Copyright (C) 2021 by Julius Künzel *
* Copyright (C) 2021 by Julius Künzel (jk.kdedev@smartlab.uber.space) *
* Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
......@@ -46,7 +47,7 @@ void ProvidersRepository::refresh(bool fullRefresh) {
m_providers.clear();
}
// Helper function to check a profile and print debug info
// Helper function to check a provider and print debug info
auto check_provider = [&](std::unique_ptr<ProviderModel> &provider, const QString &file) {
if (m_providers.count(file) > 0) {
return false;
......@@ -59,7 +60,7 @@ void ProvidersRepository::refresh(bool fullRefresh) {
};
QStringList profilesFiles;
// list Providers
// list providers
QStringList customProfilesDir = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("resourceproviders"), QStandardPaths::LocateDirectory);
for (const auto &dir : qAsConst(customProfilesDir)) {
QStringList files = QDir(dir).entryList(QDir::Files);
......@@ -67,7 +68,6 @@ void ProvidersRepository::refresh(bool fullRefresh) {
profilesFiles << QDir(dir).absoluteFilePath(file);
}
}
qDebug() << "Files: " << profilesFiles;
// Iterate through files
for (const auto &file : qAsConst(profilesFiles)) {
......
/***************************************************************************
* Copyright (C) 2021 by Julius Künzel *
* Copyright (C) 2021 by Julius Künzel (jk.kdedev@smartlab.uber.space) *
* Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
......@@ -22,16 +23,14 @@
#ifndef PROVIDERSREPOSITORY_H
#define PROVIDERSREPOSITORY_H
#include "definitions.h" //for QString hash function
#include "definitions.h"
#include "providermodel.hpp"
#include <QReadWriteLock>
//#include <QString>
//#include <memory>
#include <mutex>
#include <unordered_map>
/** @brief TODO This class is used to read all the profiles available to the user (MLT defaults one and Custom ones).
* You can then query profiles based on their paths
/** @brief This class is used to read all the provider configs available.
* You can then query providers based on their paths
* Note that this class is a Singleton, with Mutex protections to allow concurrent access.
*/
......
This diff is collapsed.
/***************************************************************************
* Copyright (C) 2021 by Julius Künzel (jk.kdedev@smartlab.uber.space) *
* Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program 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 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program 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 <https://www.gnu.org/licenses/>.*
***************************************************************************/
#ifndef REOURCEWIDGET_H
#define REOURCEWIDGET_H
......@@ -11,9 +33,7 @@
#include <QSlider>
#include <QListWidgetItem>
#include <QProcess>
//#include <QOAuth2AuthorizationCodeFlow>
class OAuth2;
#include <QtNetworkAuth>
const int imageRole = Qt::UserRole;
const int urlRole = Qt::UserRole + 1;
......@@ -42,21 +62,13 @@ public:
explicit ResourceWidget(QWidget *parent = nullptr);
~ResourceWidget() override;
protected:
bool eventFilter(QObject *obj, QEvent *ev) override;
private slots:
void slotChangeProvider();
/**
* @brief ResourceWidget::slotOpenUrl. Opens the file on the URL using the associated application via a KRun object
* @param url
*/
void slotOpenUrl(const QString &url);
void slotStartSearch();
void slotSearchFinished(QList<ResourceItemInfo> &list, const int pageCount);
void slotUpdateCurrentItem();
void slotSetIconSize(int size);
void slotZoomView(bool zoomIn);
void slotPreviewItem();
void slotChooseVersion(const QStringList &urls, const QStringList &labels, const QString &accessToken = QString());
void slotSaveItem(const QString &originalUrl = QString(), const QString &accessToken = QString());
......@@ -67,8 +79,6 @@ private:
std::unique_ptr<ProviderModel> *m_currentProvider;
QListWidgetItem *m_currentItem;
QTemporaryFile *m_tmpThumbFile;
OAuth2 *m_pOAuth2;
//QSlider *m_slider;
/** @brief Default icon size for the views. */
QSize m_iconSize;
int wheelAccumulatedDelta;
......
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FreeSound_UI</class>
<widget class="QDialog" name="FreeSound_UI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>519</width>
<height>420</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Service</string>
</property>
</widget>
</item>
<item>
<widget class="KComboBox" name="service_list">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="config_button">
<property name="text">
<string>...</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0">
<widget class="QPushButton" name="page_prev">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;&lt;</string>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<widget class="QPushButton" name="button_search">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<widget class="QListWidget" name="search_results">
<property name="alternatingRowColors">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLineEdit" name="search_text">
<property name="placeholderText">
<string/>
</property>
<property name="showClearButton" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QPushButton" name="page_next">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&gt;&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QSpinBox" name="page_number">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimum">
<number>1</number>
</property>