1
0
Fork 0

moved from github

This commit is contained in:
chaos 2023-12-05 15:13:13 +00:00
commit 42179bae07
No known key found for this signature in database
118 changed files with 7559 additions and 0 deletions

5
.clang-format Normal file
View file

@ -0,0 +1,5 @@
---
Language: Cpp
BasedOnStyle: WebKit
...

40
.gitignore vendored Normal file
View file

@ -0,0 +1,40 @@
build
mpv-build
*.o
KittehPlayer
moc_*
*AppImage*
.qmake.stash
qrc_qml.cpp
appdir
AppDir
Makefile
linuxdeploy*
other_libs
sffmpeg
*.qmake.stash
*.qmlc
*.jsc
src_qml*
qmlcache*
qrc_src*
discord-rpc
*.kate-swp
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
*_autogen
*.core
.core
*qmlcache*
cmake_install.cmake
spdlog

29
.travis.yml Normal file
View file

@ -0,0 +1,29 @@
language: cpp
compiler: gcc
sudo: require
dist: bionic
before_install:
- mkdir -p $HOME/.cache/apt/partial
- sudo rm -rf /var/cache/apt/archives
- sudo ln -s $HOME/.cache/apt /var/cache/apt/archives
- sudo add-apt-repository ppa:beineri/opt-qt-5.12.6-bionic -y
- sudo apt-get update
install:
- sudo apt-get -y install build-essential git qt512-meta-minimal qt512quickcontrols qt512quickcontrols2 qt512svg qt512x11extras qt512graphicaleffects qt512svg libgl1-mesa-dev libmpv-dev libgl1-mesa-dev x11proto-xext-dev libx11-dev python3-setuptools
- sudo apt-get build-dep mpv
- source /opt/qt*/bin/qt*-env.sh
script:
- time bash scripts/makeappimage.sh
after_success:
- time bash scripts/upload.sh
branches:
except:
- # Do not build tags that we create when we upload to GitHub Releases
- /^(?i:continuous)/

12
.vscode/c_cpp_properties.json vendored Normal file
View file

@ -0,0 +1,12 @@
{
"configurations": [
{
"name": "Linux",
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
"intelliSenseMode": "gcc-x64",
"cStandard": "c11",
"cppStandard": "c++17"
}
],
"version": 4
}

110
CMakeLists.txt Normal file
View file

@ -0,0 +1,110 @@
cmake_minimum_required(VERSION 3.5.1)
project(VideoPlayer)
include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR})
set(CMAKE_AUTOMOC ON)
include(ExternalProject)
find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
find_package(Qt5Concurrent REQUIRED)
find_package(Qt5 CONFIG REQUIRED COMPONENTS Qml Quick Gui Widgets Core X11Extras)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(QML_DEBUG "enable qml debug" OFF)
if(QML_DEBUG)
add_definitions(-DQT_QML_DEBUG)
endif()
option(OLD_UBUNTU "old ubuntu" OFF)
find_package(Qt5QuickCompiler)
if(Qt5QuickCompiler_FOUND)
qtquick_compiler_add_resources(qml_QRC src/qml/qml.qrc)
else()
qt5_add_resources(qml_QRC src/qml/qml.qrc)
endif()
find_package(PkgConfig)
pkg_check_modules(MPV REQUIRED mpv)
pkg_check_modules(X11 x11)
pkg_check_modules(Xext xext)
option(PINEPHONE "for pinephone/lima gpu" OFF)
if(PINEPHONE)
add_definitions(-DPINEPHONE)
endif()
option(USE_EXTERNAL_SPDLOG "use external spdlog" OFF)
if(USE_EXTERNAL_SPDLOG)
include_directories(${EXTERNAL_SPDLOG_PATH}/include)
include_directories(${EXTERNAL_SPDLOG_PATH})
include_directories(/app)
else()
execute_process(
COMMAND git clone --depth 1 https://github.com/gabime/spdlog.git
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)
include_directories(${CMAKE_BINARY_DIR}/spdlog/include)
endif()
if(MPV_VERSION VERSION_GREATER "1.27.0")
set(SOURCES ${SOURCES} src/Backends/MPV/MPVBackend.cpp)
else()
add_definitions(-DDISABLE_MPV_RENDER_API)
endif()
if(X11_FOUND AND Xext_FOUND)
add_definitions(-DENABLE_X11)
endif(X11_FOUND AND Xext_FOUND)
link_directories(/usr/local/lib)
#src/Backends/DirectMPV/DirectMPVBackend.cpp
set(SOURCES
src/main.cpp
src/utils.cpp
src/enums.cpp
src/Process.cpp
src/ThumbnailCache.cpp
src/logger.cpp
src/qmldebugger.cpp
src/registerTypes.cpp
src/Backends/MPVCommon/MPVCommon.cpp
src/Backends/MPVNoFBO/MPVNoFBOBackend.cpp
${SOURCES}
)
set(CMAKE_BUILD_TYPE DEBUG)
option(DEBUG "debugging out" OFF)
if(DEBUG)
SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -ggdb -g3 -Og")
else()
SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -s")
endif()
SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-command-line-argument")
add_executable(VideoPlayer ${SOURCES} ${qml_QRC})
set_property(TARGET VideoPlayer PROPERTY CXX_STANDARD 14)
# Use the Qml/Quick modules from Qt 5.
target_link_libraries(VideoPlayer
${MPV_LIBRARIES}
${X11_LIBRARIES}
${Xext_LIBRARIES}
Qt5::X11Extras
)
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS})
qt5_use_modules(VideoPlayer Qml Quick Core Gui Widgets X11Extras)
install (TARGETS ${PROJECT_NAME} DESTINATION bin)
install (FILES "${PROJECT_NAME}.desktop" DESTINATION share/applications)
install (FILES "${PROJECT_NAME}.png" DESTINATION share/icons/hicolor/256x256/apps)

25
DOCS.md Normal file
View file

@ -0,0 +1,25 @@
# VideoPlayer
A video player with many themes based on Qt, QML and libmpv.
## Config Location
- On linux the config file will be located at `~/.config/VideoPlayer/VideoPlayer.conf`
## Supported Languages
- `english` English
- `spanish` Español
- `german` Deutsch
- `french` Française
- `italian` Italiano
- `russian` Русский
- `norwegian` Norwegian
- `tokipona` toki pona
- `telugu` తెలుగు
- `vietnamese` Tiếng Việt
- Left = config value; Right = language name
- If your language isn't listed above then please either contact me (details at bottom of readme) or make a PR with the translations which are located in the file `src/qml/utils/translations.js`.
## MPV Config
- Since VideoPlayer's default backend is based on libmpv, you can use the mpv.conf from `~/.config/mpv/mpv.conf`.
## SVP Support
- VideoPlayer works with SVP, you just need to follow the same guides as for MPV.

373
LICENSE.txt Normal file
View file

@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

43
README.md Normal file
View file

@ -0,0 +1,43 @@
# VideoPlayer
![made with c++](https://forthebadge.com/images/badges/made-with-c-plus-plus.svg)
![libmpv 2.29+](https://img.shields.io/badge/libmpv-2.29+-blue.svg?logo=qt&style=for-the-badge)
![qt 5.12](https://img.shields.io/badge/Qt-5.12-41cd52.svg?logo=qt&style=for-the-badge)
A video player based on Qt, QML and libmpv with themes for many online video players..
## Themes
Screenshots for youtube, niconico and roosterteeth/videojs themes are in screenshots folder
## How to install
### From source
#### Dependencies
##### Arch Linux
```
pacman -S git cmake qt5-svg qt5-declarative qt5-quickcontrols qt5-quickcontrols2 qt5-graphicaleffects mpv
```
##### Ubuntu Bionic
```
sudo add-apt-repository ppa:beineri/opt-qt-5.12.6-bionic -y
sudo apt update
sudo apt install build-essential git qt512-meta-minimal qt512quickcontrols qt512quickcontrols2 qt512svg qt512x11extras qt512graphicaleffects qt512svg libgl1-mesa-dev libmpv-dev
```
##### Debian
```
sudo apt install build-essential cmake qtquickcontrols2-5-dev qtbase5-dev qtdeclarative5-dev libqt5x11extras5-dev libmpv-dev qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-extras qml-module-qtquick-layouts qml-module-qtquick-dialogs qml-module-qtquick-privatewidgets qml-module-qtquick-localstorage qml-module-qt-labs-settings qml-module-qt-labs-platform qtbase5-private-dev libqt5svg5
```
- Note that I don't know if this is the full list yet, pop a issue up if building fails.
#### Instructions
- Clone repo
- `mkdir build && cd build`
- `cmake .. -DCMAKE_INSTALL_PREFIX=/usr`
- `make`
- `sudo make install`
- The finished player will then be installed and you can launch it using `VideoPlayer`
## Configuration
- For docs on VideoPlayer please view DOCS.md or `man VideoPlayer`

10
VideoPlayer.desktop Normal file
View file

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=VideoPlayer
Comment=Video Player
Icon=VideoPlayer
Exec=VideoPlayer %U
Terminal=false
Categories=AudioVideo;Audio;Video;Player;TV;
MimeType=application/ogg;application/x-ogg;application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/musepack;audio/x-musepack;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;audio/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;application/x-cue;audio/m3u;
X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb,videoplayer

BIN
VideoPlayer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

1
VideoPlayer.svg Normal file
View file

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Generator: Gravit.io --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="isolation:isolate" viewBox="0 0 180 180" width="180pt" height="180pt"><defs><clipPath id="_clipPath_pedkB2BO9LJqqpfzYxiy2CNxBWzVm7YQ"><rect width="180" height="180"/></clipPath></defs><g clip-path="url(#_clipPath_pedkB2BO9LJqqpfzYxiy2CNxBWzVm7YQ)"><path d="M 39.207 35.457 L 139.793 35.457 C 145.886 35.457 150.833 40.403 150.833 46.497 L 150.833 147.083 C 150.833 153.176 145.886 158.123 139.793 158.123 L 39.207 158.123 C 33.114 158.123 28.167 153.176 28.167 147.083 L 28.167 46.497 C 28.167 40.403 33.114 35.457 39.207 35.457 Z" style="fill:none;stroke:#F45884;stroke-width:5;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:2;"/><polygon points="72.681,35.362,45.563,35.362,59.122,11.877" fill="none" vector-effect="non-scaling-stroke" stroke-width="5" stroke="rgb(244,88,132)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/><path d="M 45.978 46.139 L 133.567 46.139 C 137.502 46.139 140.697 49.333 140.697 53.268 L 140.697 140.857 C 140.697 144.792 137.502 147.987 133.567 147.987 L 45.978 147.987 C 42.043 147.987 38.849 144.792 38.849 140.857 L 38.849 53.268 C 38.849 49.333 42.043 46.139 45.978 46.139 Z" style="fill:none;stroke:#626262;stroke-width:4;stroke-linecap:square;stroke-miterlimit:2;"/><polygon points="136.388,35.362,109.27,35.362,122.829,11.877" fill="none" vector-effect="non-scaling-stroke" stroke-width="5" stroke="rgb(244,88,132)" stroke-linejoin="miter" stroke-linecap="square" stroke-miterlimit="3"/><polygon points="120.167,100.314,71.188,128.592,71.188,72.036" fill="rgb(98,98,98)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

8
format-code.sh Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -x
SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
pushd $SOURCE_DIR
find . -name "*.qml" -exec qmlfmt -i 2 -w {} \;
clang-format -style=file -i $(find src -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h")
#find . -name "*.cpp" -o -name "*.hpp" -o -name "*.c" -o -name "*.h" -exec clang-format -style=file -i {} \;
popd

BIN
screenshots/NicoNico.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 KiB

BIN
screenshots/YouTube.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

9
scripts/rendersvgs.sh Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -x
SOURCE_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
cd ${SOURCE_DIR}/../src/qml/icons
for file in `find . -name "*.svg"`; do
rendersvg "$file" "$(echo $file | sed s/.svg/.png/)"
done

View file

@ -0,0 +1,332 @@
#include "src/Backends/MPV/MPVBackend.hpp"
#include "src/Backends/MPVCommon/MPVCommon.hpp"
#include "src/qthelper.hpp"
#include "src/utils.hpp"
#include <QByteArray>
#include <QCoreApplication>
#include <QDebug>
#include <QEvent>
#include <QGuiApplication>
#include <QIcon>
#include <QMetaObject>
#include <QObject>
#include <QOpenGLContext>
#include <QOpenGLFramebufferObject>
#include <QQuickWindow>
#include <QtCore/qglobal.h>
#include <clocale>
#include <iostream>
#include <mpv/render_gl.h>
#include <stdexcept>
#include <stdio.h>
class QQuickItem;
class QSize;
#if defined(__linux__) || defined(__FreeBSD__)
#ifdef ENABLE_X11
#include <QX11Info> // IWYU pragma: keep
#include <QtX11Extras/QX11Info> // IWYU pragma: keep
#include <X11/Xlib.h> // IWYU pragma: keep
#include <X11/Xutil.h> // IWYU pragma: keep
#include <qx11info_x11.h> // IWYU pragma: keep
#endif
#include <qpa/qplatformnativeinterface.h> // IWYU pragma: keep
#endif
bool usedirect = false;
namespace {
void wakeup(void* ctx)
{
QCoreApplication::postEvent((MPVBackend*)ctx, new QEvent(QEvent::User));
}
void on_mpv_redraw(void* ctx)
{
QMetaObject::invokeMethod(
reinterpret_cast<MPVBackend*>(ctx), "update", Qt::QueuedConnection);
}
static void*
get_proc_address_mpv(void* ctx, const char* name)
{
return reinterpret_cast<void*>(
reinterpret_cast<QOpenGLContext*>(ctx)->getProcAddress(QByteArray(name)));
}
} // namespace
class MpvRenderer : public QQuickFramebufferObject::Renderer {
MPVBackend* obj;
public:
MpvRenderer(MPVBackend* new_obj)
: obj{ new_obj }
{
if (usedirect) {
int r = mpv_opengl_cb_init_gl(obj->mpv_gl_cb, NULL, get_proc_address_mpv, QOpenGLContext::currentContext());
if (r < 0) {
std::cout << "No." << std::endl;
throw std::runtime_error("failed to initialize mpv GL context");
}
}
}
virtual ~MpvRenderer() {}
// This function is called when a new FBO is needed.
// This happens on the initial frame.
QOpenGLFramebufferObject* createFramebufferObject(const QSize& size)
{
// init mpv_gl:
if (!obj->mpv_gl && !usedirect) {
mpv_opengl_init_params gl_init_params{ get_proc_address_mpv,
QOpenGLContext::currentContext(),
nullptr };
mpv_render_param params[]{
{ MPV_RENDER_PARAM_API_TYPE,
const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL) },
{ MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params },
{ MPV_RENDER_PARAM_INVALID, nullptr },
{ MPV_RENDER_PARAM_INVALID, nullptr }
};
#if defined(__linux__) || defined(__FreeBSD__)
#ifdef ENABLE_X11
if (QGuiApplication::platformName().contains("xcb")) {
params[2].type = MPV_RENDER_PARAM_X11_DISPLAY;
params[2].data = QX11Info::display();
}
#endif
if (QGuiApplication::platformName().contains("wayland")) {
params[2].type = MPV_RENDER_PARAM_WL_DISPLAY;
auto* native = QGuiApplication::platformNativeInterface();
params[2].data = native->nativeResourceForWindow("display", NULL);
}
#endif
if (mpv_render_context_create(&obj->mpv_gl, obj->mpv, params) < 0) {
std::cout << "Failed to use render API, try setting Backend/direct to true in settings." << std::endl;
throw std::runtime_error("failed to initialize mpv GL context");
}
mpv_render_context_set_update_callback(obj->mpv_gl, on_mpv_redraw, obj);
}
QMetaObject::invokeMethod(obj, "startPlayer");
return QQuickFramebufferObject::Renderer::createFramebufferObject(size);
}
void render()
{
obj->window()->resetOpenGLState();
QOpenGLFramebufferObject* fbo = framebufferObject();
if (usedirect) {
mpv_opengl_cb_draw(obj->mpv_gl_cb, fbo->handle(), fbo->width(), fbo->height());
} else {
mpv_opengl_fbo mpfbo{ .fbo = static_cast<int>(fbo->handle()),
.w = fbo->width(),
.h = fbo->height(),
.internal_format = 0 };
int flip_y{ 0 };
mpv_render_param params[] = { { MPV_RENDER_PARAM_OPENGL_FBO, &mpfbo },
{ MPV_RENDER_PARAM_FLIP_Y, &flip_y },
{ MPV_RENDER_PARAM_INVALID, nullptr } };
mpv_render_context_render(obj->mpv_gl, params);
}
obj->window()->resetOpenGLState();
}
};
MPVBackend::MPVBackend(QQuickItem* parent)
: QQuickFramebufferObject(parent)
, mpv{ mpv_create() }
, mpv_gl(nullptr)
, mpv_gl_cb(nullptr)
{
if (!mpv)
throw std::runtime_error("could not create mpv context");
QSettings settings;
usedirect = settings.value("Backend/direct", false).toBool();
mpv_set_option_string(mpv, "terminal", "true");
mpv_set_option_string(mpv, "msg-level", "all=v");
// Fix?
mpv_set_option_string(mpv, "ytdl", "yes");
mpv_set_option_string(mpv, "slang", "en");
mpv_set_option_string(mpv, "config", "yes");
mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playback-abort", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "audio-device-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
mpv_request_log_messages(mpv, "v");
mpv_set_wakeup_callback(mpv, wakeup, this);
if (mpv_initialize(mpv) < 0)
throw std::runtime_error("could not initialize mpv context");
if (usedirect) {
mpv_set_option_string(mpv, "vo", "libmpv");
mpv_gl_cb = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpv_gl_cb)
throw std::runtime_error("OpenGL not compiled in");
mpv_opengl_cb_set_update_callback(mpv_gl_cb, on_mpv_redraw, (void*)this);
} else {
mpv_set_option_string(mpv, "vo", "libmpv");
}
connect(this,
&MPVBackend::onUpdate,
this,
&MPVBackend::doUpdate,
Qt::QueuedConnection);
connect(this,
&MPVBackend::positionChanged,
this,
&MPVBackend::updateDurationString,
Qt::QueuedConnection);
connect(this,
&MPVBackend::durationChanged,
this,
&MPVBackend::updateDurationString,
Qt::QueuedConnection);
}
MPVBackend::~MPVBackend()
{
printf("Shutting down...\n");
Utils::SetDPMS(true);
command("write-watch-later-config");
if (usedirect && mpv_gl_cb) {
mpv_opengl_cb_uninit_gl(mpv_gl_cb);
} else if (mpv_gl) {
mpv_render_context_free(mpv_gl);
}
mpv_terminate_destroy(mpv);
printf("MPV terminated.\n");
}
void MPVBackend::on_update(void* ctx)
{
MPVBackend* self = (MPVBackend*)ctx;
emit self->onUpdate();
}
void MPVBackend::doUpdate()
{
update();
}
QVariant
MPVBackend::getProperty(const QString& name) const
{
return mpv::qt::get_property_variant(mpv, name);
}
void MPVBackend::command(const QVariant& params)
{
mpv::qt::node_builder node(params);
mpv_command_node(mpv, node.node(), nullptr);
}
void MPVBackend::setProperty(const QString& name, const QVariant& value)
{
mpv::qt::node_builder node(value);
qDebug() << "Setting property" << name << "to" << value;
mpv_set_property(mpv, name.toUtf8().data(), MPV_FORMAT_NODE, node.node());
}
void MPVBackend::setOption(const QString& name, const QVariant& value)
{
mpv::qt::set_option_variant(mpv, name, value);
}
QVariant
MPVBackend::playerCommand(const Enums::Commands& cmd)
{
return playerCommand(cmd, QVariant("NoArgProvided"));
}
QVariant
MPVBackend::playerCommand(const Enums::Commands& cmd, const QVariant& args)
{
return MPVCommon::playerCommand(this, cmd, args);
}
QString
MPVBackend::getStats()
{
return MPVCommon::getStats(this);
}
void MPVBackend::updateDurationString(int numTime)
{
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
MPVCommon::updateDurationString(this, numTime, metaMethod);
}
void MPVBackend::toggleOnTop()
{
onTop = !onTop;
Utils::AlwaysOnTop(window()->winId(), onTop);
}
bool MPVBackend::event(QEvent* event)
{
if (event->type() == QEvent::User) {
on_mpv_events();
}
return QObject::event(event);
}
void MPVBackend::on_mpv_events()
{
while (mpv) {
mpv_event* event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
}
handle_mpv_event(event);
}
}
QVariantMap
MPVBackend::getAudioDevices(const QVariant& drivers) const
{
return MPVCommon::getAudioDevices(drivers);
}
void MPVBackend::handle_mpv_event(mpv_event* event)
{
MPVCommon::handle_mpv_event(this, event);
}
QQuickFramebufferObject::Renderer*
MPVBackend::createRenderer() const
{
window()->setIcon(QIcon(":/icon.png"));
window()->setPersistentOpenGLContext(true);
window()->setPersistentSceneGraph(true);
return new MpvRenderer(const_cast<MPVBackend*>(this));
}

View file

@ -0,0 +1,99 @@
#ifndef MPVBackend_H
#define MPVBackend_H
#include "src/backendinterface.hpp"
#include "src/enums.hpp"
#include <QEvent>
#include <QMetaType>
#include <QObject>
#include <QQuickFramebufferObject>
#include <QQuickItem>
#include <QSettings>
#include <QString>
#include <QVariant>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
#include <mpv/render.h>
class MPVBackend
: public QQuickFramebufferObject,
public BackendInterface {
Q_INTERFACES(BackendInterface)
Q_OBJECT
Q_PROPERTY(bool logging READ logging WRITE setLogging)
mpv_handle* mpv;
mpv_render_context* mpv_gl;
mpv_opengl_cb_context* mpv_gl_cb;
QSettings settings;
bool onTop = false;
bool m_logging = true;
friend class MpvRenderer;
public:
static void on_update(void* ctx);
MPVBackend(QQuickItem* parent = 0);
virtual ~MPVBackend();
virtual Renderer* createRenderer() const;
void setLogging(bool a)
{
if (a != m_logging) {
m_logging = a;
}
}
bool logging() const { return m_logging; }
int lastTime = 0;
double lastSpeed = 0;
QString totalDurationString;
QString lastPositionString;
public slots:
QVariant playerCommand(const Enums::Commands& command, const QVariant& args);
QVariant playerCommand(const Enums::Commands& command);
void toggleOnTop();
QString getStats();
// Optional but handy for MPV or custom backend settings.
void command(const QVariant& params);
void setProperty(const QString& name, const QVariant& value);
void setOption(const QString& name, const QVariant& value);
QVariant getProperty(const QString& name) const;
// Just used for adding missing audio devices to list.
QVariantMap getAudioDevices(const QVariant& drivers) const;
bool event(QEvent* event);
signals:
void onUpdate();
void mpv_events();
void onMpvEvent(mpv_event* event);
// All below required for Player API
void playStatusChanged(const Enums::PlayStatus& status);
void volumeStatusChanged(const Enums::VolumeStatus& status);
void volumeChanged(const int& volume);
void durationChanged(const double& duration);
void positionChanged(const double& position);
void cachedDurationChanged(const double& duration);
void playlistPositionChanged(const double& position);
void titleChanged(const QString& title);
void durationStringChanged(const QString& string);
void tracksChanged(const QVariantList& tracks);
void audioDevicesChanged(const QVariantMap& devices);
void playlistChanged(const QVariantList& devices);
void chaptersChanged(const QVariantList& devices);
void speedChanged(const double& speed);
private slots:
void doUpdate();
void on_mpv_events();
void updateDurationString(int numTime);
private:
void handle_mpv_event(mpv_event* event);
};
#endif

View file

@ -0,0 +1,574 @@
#include "src/Backends/MPVCommon/MPVCommon.hpp"
#include "spdlog/logger.h"
#include "src/backendinterface.hpp"
#include "src/logger.h"
#include "src/utils.hpp"
#include <QByteArray>
#include <QCoreApplication>
#include <QJsonObject>
#include <QList>
#include <QLocale>
#include <QMap>
#include <QMetaMethod>
#include <QMetaObject>
#include <QObject>
#include <QSettings>
#include <exception>
#include <memory>
#include <spdlog/fmt/fmt.h>
#include <string.h>
auto mpvLogger = initLogger("mpv");
QString humanSize(uint64_t bytes)
{
const char* suffix[5] = { "B", "KB", "MB", "GB", "TB" };
char length = sizeof(suffix) / sizeof(suffix[0]);
int i = 0;
double dblBytes = bytes;
if (bytes > 1024) {
for (i = 0; (bytes / 1024) > 0 && i < length - 1; i++, bytes /= 1024)
dblBytes = bytes / 1024.0;
}
static char output[200];
sprintf(output, "%.02lf %s", dblBytes, suffix[i]);
return QString(output);
}
static inline QVariant mpvnode_to_variant(const mpv_node* node)
{
if (!node) {
return QVariant();
}
switch (node->format) {
case MPV_FORMAT_STRING:
return QVariant(QString::fromUtf8(node->u.string));
case MPV_FORMAT_FLAG:
return QVariant(static_cast<bool>(node->u.flag));
case MPV_FORMAT_INT64:
return QVariant(static_cast<qlonglong>(node->u.int64));
case MPV_FORMAT_DOUBLE:
return QVariant(node->u.double_);
case MPV_FORMAT_NODE_ARRAY: {
mpv_node_list* list = node->u.list;
QVariantList qlist;
for (int n = 0; n < list->num; n++)
qlist.append(mpvnode_to_variant(&list->values[n]));
return QVariant(qlist);
}
case MPV_FORMAT_NODE_MAP: {
mpv_node_list* list = node->u.list;
QVariantMap qmap;
for (int n = 0; n < list->num; n++) {
qmap.insert(QString::fromUtf8(list->keys[n]),
mpvnode_to_variant(&list->values[n]));
}
return QVariant(qmap);
}
default: // MPV_FORMAT_NONE, unknown values (e.g. future extensions)
return QVariant();
}
}
namespace MPVCommon {
QString getStats(BackendInterface* b)
{
QString stats;
stats = "<style> blockquote { text-indent: 0px; margin-left:40px; margin-top: 0px; "
"margin-bottom: 0px; padding-bottom: 0px; padding-top: 0px; padding-left: "
"0px; } b span p br { margin-bottom: 0px; margin-top: 0px; padding-top: "
"0px; padding-botom: 0px; text-indent: 0px; } </style>";
QString filename = b->getProperty("filename").toString();
// File Info
stats += "<b>File:</b> " + filename;
stats += "<blockquote>";
QString title = b->getProperty("media-title").toString();
if (title != filename) {
stats += "<b>Title:</b> " + title + "<br>";
}
QString fileFormat = b->getProperty("file-format").toString();
stats += "<b>Format/Protocol:</b> " + fileFormat + "<br>";
double cacheUsed = b->getProperty("cache-used").toDouble();
int demuxerSecs = b->getProperty("demuxer-cache-duration").toInt();
QVariantMap demuxerState = b->getProperty("demuxer-cache-state").toMap();
int demuxerCache = demuxerState.value("fw-bytes", QVariant(0)).toInt();
if (demuxerSecs + demuxerCache + cacheUsed > 0) {
QString cacheStats;
cacheStats += "<b>Total Cache:</b> ";
cacheStats += humanSize(demuxerCache + cacheUsed);
cacheStats += " (<b>Demuxer:</b> ";
cacheStats += humanSize(demuxerCache);
cacheStats += ", ";
cacheStats += QString::number(demuxerSecs) + "s) ";
double cacheSpeed = b->getProperty("cache-speed").toDouble();
if (cacheSpeed > 0) {
cacheStats += "<b>Speed:</b> ";
cacheStats += humanSize(demuxerSecs);
cacheStats += "/s";
}
cacheStats += "<br>";
stats += cacheStats;
}
QString fileSize = humanSize(b->getProperty("file-size").toInt()).remove("-");
stats += "<b>Size:</b> " + fileSize + "<br>";
stats += "</blockquote>";
// Video Info
QVariant videoParams = b->getProperty("video-params");
if (videoParams.isNull()) {
videoParams = b->getProperty("video-out-params");
}
if (!videoParams.isNull()) {
stats += "<b>Video:</b> " + b->getProperty("video-codec").toString();
stats += "<blockquote>";
QString avsync = QString::number(b->getProperty("avsync").toDouble(), 'f', 3);
stats += "<b>A-V:</b> " + QString(avsync) + "<br>";
stats += "<b>Dropped Frames:</b> ";
int dFDC = b->getProperty("decoder-frame-drop-count").toInt();
if (dFDC > 0) {
stats += QString::number(dFDC) + " (decoder) ";
}
int fDC = b->getProperty("frame-drop-count").toInt();
if (fDC > 0) {
stats += QString::number(fDC) + " (output)";
}
stats += "<br>";
int dFPS = b->getProperty("display-fps").toInt();
int eDFPS = b->getProperty("estimated-display-fps").toInt();
if ((dFPS + eDFPS) > 0) {
stats += "<b>Display FPS:</b> ";
if (dFPS > 0) {
stats += QString::number(dFPS);
stats += " (specified) ";
}
if (eDFPS > 0) {
stats += QString::number(eDFPS);
stats += " (estimated)";
}
stats += "<br>";
}
int cFPS = b->getProperty("container-fps").toInt();
int eVFPS = b->getProperty("estimated-vf-fps").toInt();
if ((cFPS + eVFPS) > 0) {
stats += "<b>FPS:</b> ";
if (cFPS > 0) {
stats += QString::number(cFPS);
stats += " (specified) ";
}
if (eVFPS > 0) {
stats += QString::number(eVFPS);
stats += " (estimated)";
}
stats += "<br>";
}
QVariantMap vPM = videoParams.toMap();
stats += "<b>Native Resolution:</b> ";
stats += vPM["w"].toString() + " x " + vPM["h"].toString();
stats += "<br>";
stats += "<b>Window Scale:</b> ";
stats += vPM["window-scale"].toString();
stats += "<br>";
stats += "<b>Aspect Ratio:</b> ";
stats += vPM["aspect"].toString();
stats += "<br>";
stats += "<b>Pixel Format:</b> ";
stats += vPM["pixelformat"].toString();
stats += "<br>";
stats += "<b>Primaries:</b> ";
stats += vPM["primaries"].toString();
stats += " <b>Colormatrix:</b> ";
stats += vPM["colormatrix"].toString();
stats += "<br>";
stats += "<b>Levels:</b> ";
stats += vPM["colorlevels"].toString();
double sigPeak = vPM.value("sig-peak", QVariant(0.0)).toInt();
if (sigPeak > 0) {
stats += " (HDR Peak: " + QString::number(sigPeak) + ")";
}
stats += "<br>";
stats += "<b>Gamma:</b> ";
stats += vPM["gamma"].toString();
stats += "<br>";
int pVB = b->getProperty("packet-video-bitrate").toInt();
if (pVB > 0) {
stats += "<b>Bitrate:</b> ";
stats += humanSize(pVB) + "/s";
stats += "<br>";
}
stats += "</blockquote>";
}
QVariant audioParams = b->getProperty("audio-params");
if (audioParams.isNull()) {
audioParams = b->getProperty("audio-out-params");
}
if (!audioParams.isNull()) {
stats += "<b>Audio:</b> " + b->getProperty("audio-codec").toString();
stats += "<blockquote>";
QVariantMap aPM = audioParams.toMap();
stats += "<b>Format:</b> ";
stats += aPM["format"].toString();
stats += "<br>";
stats += "<b>Sample Rate:</b> ";
stats += aPM["samplerate"].toString() + " Hz";
stats += "<br>";
stats += "<b>Channels:</b> ";
stats += aPM["chanel-count"].toString();
stats += "<br>";
int pAB = b->getProperty("packet-audio-bitrate").toInt();
if (pAB > 0) {
stats += "<b>Bitrate:</b> ";
stats += humanSize(pAB) + "/s";
stats += "<br>";
}
stats += "</blockquote>";
}
return stats;
}
QVariant playerCommand(BackendInterface* b, const Enums::Commands& cmd, const QVariant& args)
{
switch (cmd) {
case Enums::Commands::TogglePlayPause: {
b->command(QVariantList() << "cycle"
<< "pause");
break;
}
case Enums::Commands::ToggleMute: {
b->command(QVariantList() << "cycle"
<< "mute");
break;
}
case Enums::Commands::SetAudioDevice: {
b->setProperty("audio-device", args.toString());
break;
}
case Enums::Commands::SetVolume: {
b->command(QVariantList() << "set"
<< "volume" << args);
break;
}
case Enums::Commands::AddVolume: {
b->command(QVariantList() << "add"
<< "volume" << args);
break;
}
case Enums::Commands::AddSpeed: {
QString speedString = QString::number(b->getProperty("speed").toDouble() + args.toDouble());
QVariant newSpeed = QVariant(speedString.left(speedString.lastIndexOf('.') + 2));
b->playerCommand(Enums::Commands::SetSpeed, newSpeed);
break;
}
case Enums::Commands::SubtractSpeed: {
QString speedString = QString::number(b->getProperty("speed").toDouble() - args.toDouble());
QVariant newSpeed = QVariant(speedString.left(speedString.lastIndexOf('.') + 2));
b->playerCommand(Enums::Commands::SetSpeed, newSpeed);
break;
}
case Enums::Commands::ChangeSpeed: {
b->playerCommand(
Enums::Commands::SetSpeed,
QVariant(b->getProperty("speed").toDouble() * args.toDouble()));
break;
}
case Enums::Commands::SetSpeed: {
b->command(QVariantList() << "set"
<< "speed" << args.toString());
break;
}
case Enums::Commands::ToggleStats: {
b->command(QVariantList() << "script-binding"
<< "stats/display-stats-toggle");
break;
}
case Enums::Commands::NextAudioTrack: {
b->command(QVariantList() << "cycle"
<< "audio");
break;
}
case Enums::Commands::NextSubtitleTrack: {
b->command(QVariantList() << "cycle"
<< "sub");
break;
}
case Enums::Commands::NextVideoTrack: {
b->command(QVariantList() << "cycle"
<< "video");
break;
}
case Enums::Commands::PreviousPlaylistItem: {
b->command(QVariantList() << "playlist-prev");
break;
}
case Enums::Commands::NextPlaylistItem: {
b->command(QVariantList() << "playlist-next"
<< "force");
break;
}
case Enums::Commands::LoadFile: {
b->command(QVariantList() << "loadfile" << args);
break;
}
case Enums::Commands::AppendFile: {
b->command(QVariantList() << "loadfile" << args << "append-play");
break;
}
case Enums::Commands::SeekAbsolute: {
b->command(QVariantList() << "seek" << args << "absolute");
break;
}
case Enums::Commands::Seek: {
b->command(QVariantList() << "seek" << args);
break;
}
case Enums::Commands::ForwardFrame: {
b->command(QVariantList() << "frame-step");
break;
}
case Enums::Commands::BackwardFrame: {
b->command(QVariantList() << "frame-back-step");
break;
}
case Enums::Commands::SetTrack: {
b->command(QVariantList() << "set" << args.toList()[0] << args.toList()[1]);
break;
}
case Enums::Commands::SetPlaylistPos: {
b->command(QVariantList() << "set"
<< "playlist-pos" << args);
break;
}
case Enums::Commands::ForcePause: {
b->command(QVariantList() << "set"
<< "pause"
<< "yes");
break;
}
case Enums::Commands::PreviousChapter: {
b->command(QVariantList() << "add" << "chapter" << "-1");
break;
}
case Enums::Commands::NextChapter: {
b->command(QVariantList() << "add" << "chapter" << "1");
break;
}
default: {
//qDebug() << "Command not found: " << cmd;
break;
}
}
return QVariant("NoOutput");
}
void updateDurationString(BackendInterface* b, int numTime, QMetaMethod metaMethod)
{
QVariant speed = b->getProperty("speed");
QSettings settings;
if (metaMethod.name() == "positionChanged") {
if (speed != b->lastSpeed) {
b->lastSpeed = speed.toDouble();
} else {
if (numTime == b->lastTime) {
return;
}
}
b->lastTime = numTime;
b->lastPositionString = Utils::createTimestamp(b->lastTime);
} else if (metaMethod.name() == "durationChanged") {
b->totalDurationString = Utils::createTimestamp(numTime);
}
QString durationString;
durationString += b->lastPositionString;
durationString += " / ";
durationString += b->totalDurationString;
if (b->lastSpeed != 1) {
if (settings.value("Appearance/themeName", "").toString() != "RoosterTeeth") {
durationString += " (" + speed.toString() + "x)";
}
}
emit b->durationStringChanged(durationString);
}
void handle_mpv_event(BackendInterface* b, mpv_event* event)
{
switch (event->event_id) {
case MPV_EVENT_PROPERTY_CHANGE: {
mpv_event_property* prop = (mpv_event_property*)event->data;
if (strcmp(prop->name, "time-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double*)prop->data;
emit b->positionChanged(time);
}
} else if (strcmp(prop->name, "duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double time = *(double*)prop->data;
emit b->durationChanged(time);
}
} else if (strcmp(prop->name, "mute") == 0 || strcmp(prop->name, "volume") == 0) {
double volume = b->getProperty("volume").toDouble();
bool mute = b->getProperty("mute").toBool();
if (mute || volume == 0) {
emit b->volumeStatusChanged(Enums::VolumeStatus::Muted);
} else {
if (volume < 25) {
emit b->volumeStatusChanged(Enums::VolumeStatus::Low);
} else {
emit b->volumeStatusChanged(Enums::VolumeStatus::Normal);
}
}
// emit volumeChanged(volume);
} else if (strcmp(prop->name, "media-title") == 0) {
if (prop->format == MPV_FORMAT_STRING) {
char* title = *(char**)prop->data;
emit b->titleChanged(QString(title));
}
} else if (strcmp(prop->name, "demuxer-cache-duration") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double duration = *(double*)prop->data;
emit b->cachedDurationChanged(duration);
}
} else if (strcmp(prop->name, "playlist-pos") == 0) {
if (prop->format == MPV_FORMAT_DOUBLE) {
double pos = *(double*)prop->data;
emit b->playlistPositionChanged(pos);
}
} else if (strcmp(prop->name, "pause") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
if (mpvnode_to_variant(nod).toBool()) {
emit b->playStatusChanged(Enums::PlayStatus::Paused);
// Utils::SetScreensaver(window()->winId(), true);
} else {
emit b->playStatusChanged(Enums::PlayStatus::Playing);
// Utils::SetScreensaver(window()->winId(), false);
}
} else if (strcmp(prop->name, "track-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->tracksChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "audio-device-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->audioDevicesChanged(b->getAudioDevices(mpvnode_to_variant(nod)));
} else if (strcmp(prop->name, "playlist") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->playlistChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "chapter-list") == 0) {
mpv_node* nod = (mpv_node*)prop->data;
emit b->chaptersChanged(mpvnode_to_variant(nod).toList());
} else if (strcmp(prop->name, "speed") == 0) {
double speed = *(double*)prop->data;
emit b->speedChanged(speed);
}
break;
}
case MPV_EVENT_LOG_MESSAGE: {
struct mpv_event_log_message* msg = (struct mpv_event_log_message*)event->data;
QString logMsg = "[" + QString(msg->prefix) + "] " + QString(msg->text).trimmed();
QString msgLevel = QString(msg->level);
if (msgLevel.startsWith("d")) {
mpvLogger->info("{}", logMsg.toStdString());
} else if (msgLevel.startsWith("t")) {
mpvLogger->warn("{}", logMsg.toStdString());
} else if (msgLevel.startsWith("v")) {
mpvLogger->trace("{}", logMsg.toStdString());
} else if (msgLevel.startsWith("i")) {
mpvLogger->info("{}", logMsg.toStdString());
} else {
mpvLogger->warn("What: {}", logMsg.toStdString());
}
break;
}
case MPV_EVENT_SHUTDOWN: {
qApp->exit();
break;
}
default: {
break;
}
}
}
QVariantMap getAudioDevices(const QVariant& drivers)
{
QVariantMap newDrivers;
if (drivers.isNull()) {
return newDrivers;
}
QSequentialIterable iterable = drivers.value<QSequentialIterable>();
foreach (const QVariant& v, iterable) {
QVariantMap item = v.toMap();
newDrivers[item["description"].toString()] = item;
}
return newDrivers;
}
}

View file

@ -0,0 +1,21 @@
#ifndef MPVCommon_H
#define MPVCommon_H
#include "src/enums.hpp"
#include <QMetaType>
#include <QString>
#include <QVariant>
#include <mpv/client.h>
class BackendInterface;
class QMetaMethod;
namespace MPVCommon {
QString getStats(BackendInterface* b);
QVariant playerCommand(BackendInterface* b, const Enums::Commands& cmd, const QVariant& args);
void updateDurationString(BackendInterface* b, int numTime, QMetaMethod metaMethod);
void handle_mpv_event(BackendInterface* b, mpv_event* event);
QVariantMap getAudioDevices(const QVariant& drivers);
}
#endif

View file

@ -0,0 +1,294 @@
#include "src/Backends/MPVNoFBO/MPVNoFBOBackend.hpp"
#include "src/Backends/MPVCommon/MPVCommon.hpp"
#include "src/qthelper.hpp"
#include "src/utils.hpp"
#include <QApplication>
#include <QByteArray>
#include <QCoreApplication>
#include <QDebug>
#include <QEvent>
#include <QIcon>
#include <QMetaObject>
#include <QOpenGLContext>
#include <QQuickWindow>
#include <clocale>
#include <stdexcept>
#include <stdio.h>
#include <stdlib.h>
void nofbowakeup(void* ctx)
{
QCoreApplication::postEvent((MPVNoFBOBackend*)ctx, new QEvent(QEvent::User));
}
static void*
get_proc_address(void* ctx, const char* name)
{
(void)ctx;
QOpenGLContext* glctx = QOpenGLContext::currentContext();
if (!glctx)
return NULL;
return (void*)glctx->getProcAddress(QByteArray(name));
}
MPVNoFBORenderer::MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl)
: mpv(a_mpv)
, mpv_gl(a_mpv_gl)
, window(0)
, size()
{
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
if (r < 0)
qDebug() << "could not initialize OpenGL";
}
MPVNoFBORenderer::~MPVNoFBORenderer()
{
// Until this call is done, we need to make sure the player remains
// alive. This is done implicitly with the mpv::qt::Handle instance
// in this class.
exit(0);
}
void MPVNoFBORenderer::paint()
{
window->resetOpenGLState();
// This uses 0 as framebuffer, which indicates that mpv will render directly
// to the frontbuffer. Note that mpv will always switch framebuffers
// explicitly. Some QWindow setups (such as using QQuickWidget) actually
// want you to render into a FBO in the beforeRendering() signal, and this
// code won't work there.
// The negation is used for rendering with OpenGL's flipped coordinates.
mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height());
window->resetOpenGLState();
}
MPVNoFBOBackend::MPVNoFBOBackend(QQuickItem* parent)
: QQuickItem(parent)
, mpv_gl(0)
, renderer(0)
{
mpv = mpv_create();
if (!mpv)
throw std::runtime_error("could not create mpv context");
mpv_set_option_string(mpv, "terminal", "no");
mpv_set_option_string(mpv, "msg-level", "all=v");
// Fix?
mpv_set_option_string(mpv, "ytdl", "yes");
mpv_set_option_string(mpv, "slang", "en");
mpv_set_option_string(mpv, "config", "yes");
mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playback-abort", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "audio-device-list", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE);
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE);
mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE);
mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
mpv_set_wakeup_callback(mpv, nofbowakeup, this);
if (mpv_initialize(mpv) < 0)
throw std::runtime_error("could not initialize mpv context");
// Make use of the MPV_SUB_API_OPENGL_CB API.
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
// Setup the callback that will make QtQuick update and redraw if there
// is a new video frame. Use a queued connection: this makes sure the
// doUpdate() function is run on the GUI thread.
mpv_gl = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
if (!mpv_gl)
throw std::runtime_error("OpenGL not compiled in");
mpv_opengl_cb_set_update_callback(
mpv_gl, MPVNoFBOBackend::on_update, (void*)this);
connect(this,
&MPVNoFBOBackend::onUpdate,
this,
&MPVNoFBOBackend::doUpdate,
Qt::QueuedConnection);
connect(this,
&MPVNoFBOBackend::positionChanged,
&MPVNoFBOBackend::updateDurationString);
connect(this,
&MPVNoFBOBackend::durationChanged,
&MPVNoFBOBackend::updateDurationString);
connect(this,
&QQuickItem::windowChanged,
this,
&MPVNoFBOBackend::handleWindowChanged);
}
MPVNoFBOBackend::~MPVNoFBOBackend()
{
printf("Shutting down...\n");
qApp->quit();
printf("MPV terminated.\n");
}
void MPVNoFBOBackend::sync()
{
if (!renderer) {
window()->setIcon(QIcon(":/icon.png"));
renderer = new MPVNoFBORenderer(mpv, mpv_gl);
connect(window(),
&QQuickWindow::beforeRendering,
renderer,
&MPVNoFBORenderer::paint,
Qt::DirectConnection);
QMetaObject::invokeMethod(this, "startPlayer");
}
renderer->window = window();
renderer->size = window()->size() * window()->devicePixelRatio();
}
void MPVNoFBOBackend::swapped()
{
mpv_opengl_cb_report_flip(mpv_gl, 0);
}
void MPVNoFBOBackend::cleanup()
{
if (renderer) {
delete renderer;
renderer = 0;
}
}
void MPVNoFBOBackend::on_update(void* ctx)
{
MPVNoFBOBackend* self = (MPVNoFBOBackend*)ctx;
emit self->onUpdate();
}
void MPVNoFBOBackend::doUpdate()
{
window()->update();
update();
}
QVariant
MPVNoFBOBackend::getProperty(const QString& name) const
{
return mpv::qt::get_property_variant(mpv, name);
}
void MPVNoFBOBackend::command(const QVariant& params)
{
mpv::qt::command_variant(mpv, params);
}
void MPVNoFBOBackend::setProperty(const QString& name, const QVariant& value)
{
mpv::qt::set_property_variant(mpv, name, value);
}
void MPVNoFBOBackend::setOption(const QString& name, const QVariant& value)
{
mpv::qt::set_option_variant(mpv, name, value);
}
void MPVNoFBOBackend::launchAboutQt()
{
QApplication* qapp = qobject_cast<QApplication*>(QCoreApplication::instance());
qapp->aboutQt();
}
QVariant
MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd)
{
return playerCommand(cmd, QVariant("NoArgProvided"));
}
QVariant
MPVNoFBOBackend::playerCommand(const Enums::Commands& cmd,
const QVariant& args)
{
return MPVCommon::playerCommand(this, cmd, args);
}
void MPVNoFBOBackend::handleWindowChanged(QQuickWindow* win)
{
if (!win)
return;
connect(win,
&QQuickWindow::beforeSynchronizing,
this,
&MPVNoFBOBackend::sync,
Qt::DirectConnection);
connect(win,
&QQuickWindow::sceneGraphInvalidated,
this,
&MPVNoFBOBackend::cleanup,
Qt::DirectConnection);
connect(win,
&QQuickWindow::frameSwapped,
this,
&MPVNoFBOBackend::swapped,
Qt::DirectConnection);
win->setClearBeforeRendering(false);
}
void MPVNoFBOBackend::toggleOnTop()
{
onTop = !onTop;
Utils::AlwaysOnTop(window()->winId(), onTop);
}
bool MPVNoFBOBackend::event(QEvent* event)
{
if (event->type() == QEvent::User) {
on_mpv_events();
}
return QObject::event(event);
}
void MPVNoFBOBackend::on_mpv_events()
{
while (mpv) {
mpv_event* event = mpv_wait_event(mpv, 0);
if (event->event_id == MPV_EVENT_NONE) {
break;
}
handle_mpv_event(event);
}
}
void MPVNoFBOBackend::updateDurationString(int numTime)
{
QMetaMethod metaMethod = sender()->metaObject()->method(senderSignalIndex());
MPVCommon::updateDurationString(this, numTime, metaMethod);
}
QVariantMap
MPVNoFBOBackend::getAudioDevices(const QVariant& drivers) const
{
return MPVCommon::getAudioDevices(drivers);
}
void MPVNoFBOBackend::handle_mpv_event(mpv_event* event)
{
MPVCommon::handle_mpv_event(this, event);
}
QString
MPVNoFBOBackend::getStats()
{
return MPVCommon::getStats(this);
}

View file

@ -0,0 +1,118 @@
#ifndef MPVNoFBOBackend_H
#define MPVNoFBOBackend_H
#include "src/backendinterface.hpp"
#include "src/enums.hpp"
#include <QEvent>
#include <QMetaType>
#include <QObject>
#include <QQuickItem>
#include <QQuickWindow>
#include <QSettings>
#include <QSize>
#include <QString>
#include <QVariant>
#include <mpv/client.h>
#include <mpv/opengl_cb.h>
class MPVNoFBORenderer : public QObject {
Q_OBJECT
mpv_handle* mpv;
mpv_opengl_cb_context* mpv_gl;
public:
QQuickWindow* window;
QSize size;
friend class MpvObject;
MPVNoFBORenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl);
virtual ~MPVNoFBORenderer();
public slots:
void paint();
};
class MPVNoFBOBackend
: public QQuickItem,
public BackendInterface {
Q_INTERFACES(BackendInterface)
Q_OBJECT
Q_PROPERTY(bool logging READ logging WRITE setLogging)
mpv_handle* mpv;
mpv_opengl_cb_context* mpv_gl;
MPVNoFBORenderer* renderer;
bool onTop = false;
bool m_logging = true;
QSettings settings;
public:
static void on_update(void* ctx);
void setLogging(bool a)
{
if (a != m_logging) {
m_logging = a;
}
}
bool logging() const { return m_logging; }
MPVNoFBOBackend(QQuickItem* parent = 0);
virtual ~MPVNoFBOBackend();
int lastTime = 0;
double lastSpeed = 0;
QString totalDurationString;
QString lastPositionString;
public slots:
QVariant playerCommand(const Enums::Commands& command, const QVariant& args);
QVariant playerCommand(const Enums::Commands& command);
void launchAboutQt();
void toggleOnTop();
QString getStats();
// Optional but handy for MPV or custom backend settings.
void command(const QVariant& params);
void setProperty(const QString& name, const QVariant& value);
void setOption(const QString& name, const QVariant& value);
QVariant getProperty(const QString& name) const;
void sync();
void swapped();
void cleanup();
// Just used for adding missing audio devices to list.
QVariantMap getAudioDevices(const QVariant& drivers) const;
bool event(QEvent* event);
signals:
void onUpdate();
void mpv_events();
// All below required for Player API
void playStatusChanged(const Enums::PlayStatus& status);
void volumeStatusChanged(const Enums::VolumeStatus& status);
void volumeChanged(const int& volume);
void durationChanged(const double& duration);
void positionChanged(const double& position);
void cachedDurationChanged(const double& duration);
void playlistPositionChanged(const double& position);
void titleChanged(const QString& title);
void durationStringChanged(const QString& string);
void tracksChanged(const QVariantList& tracks);
void audioDevicesChanged(const QVariantMap& devices);
void playlistChanged(const QVariantList& devices);
void chaptersChanged(const QVariantList& devices);
void speedChanged(const double& speed);
private slots:
void doUpdate();
void on_mpv_events();
void updateDurationString(int numTime);
void handleWindowChanged(QQuickWindow* win);
private:
void handle_mpv_event(mpv_event* event);
};
#endif

25
src/Process.cpp Normal file
View file

@ -0,0 +1,25 @@
#include "Process.h"
#include <QStringList>
#include <QVariant>
class QObject;
Process::Process(QObject* parent)
: QProcess(parent)
{
}
void Process::start(const QString& program, const QVariantList& arguments)
{
QStringList args;
for (int i = 0; i < arguments.length(); i++)
args << arguments[i].toString();
QProcess::start(program, args);
}
QString
Process::getOutput()
{
return QProcess::readAllStandardOutput();
}

19
src/Process.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef Process_H
#define Process_H
#include <QMetaType>
#include <QObject>
#include <QProcess>
#include <QString>
class Process : public QProcess {
Q_OBJECT
public:
explicit Process(QObject* parent = 0);
Q_INVOKABLE void start(const QString& program, const QVariantList& arguments);
Q_INVOKABLE QString getOutput();
};
#endif

66
src/ThumbnailCache.cpp Normal file
View file

@ -0,0 +1,66 @@
#include "ThumbnailCache.h"
#include <QBuffer>
#include <QByteArray>
#include <QCryptographicHash>
#include <QFileInfo>
#include <QIODevice>
#include <QImage>
#include <QImageReader>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QStandardPaths>
#include <QUrl>
ThumbnailCache::ThumbnailCache(QObject* parent)
: QObject(parent)
, manager(new QNetworkAccessManager(this))
{
cacheFolder = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/thumbs");
if (!cacheFolder.exists()) {
cacheFolder.mkpath(".");
}
}
void ThumbnailCache::addURL(const QString& name, const QString& mediaURL)
{
QString hashedURL = QString(
QCryptographicHash::hash(name.toUtf8(), QCryptographicHash::Md5).toHex());
QString cacheFilename = hashedURL + ".jpg";
QString cachedFilePath = cacheFolder.absoluteFilePath(cacheFilename);
if (cacheFolder.exists(cacheFilename)) {
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
return;
}
QString url(mediaURL);
QFileInfo isFile = QFileInfo(url);
if (isFile.exists()) {
QImageReader reader(url);
QImage image = reader.read();
image.save(cachedFilePath, "JPG");
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
return;
}
QNetworkRequest request(url);
QNetworkReply* reply = manager->get(request);
connect(reply, &QNetworkReply::finished, [=] {
QByteArray response_data = reply->readAll();
QBuffer buffer(&response_data);
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
QImage image = reader.read();
image.save(cachedFilePath, "JPG");
emit thumbnailReady(name, mediaURL, "file://" + cachedFilePath);
});
}

25
src/ThumbnailCache.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef ThumbnailCache_H
#define ThumbnailCache_H
#include <QDir>
#include <QNetworkAccessManager>
#include <QObject>
#include <QString>
class ThumbnailCache : public QObject {
Q_OBJECT
public:
explicit ThumbnailCache(QObject* parent = nullptr);
public slots:
Q_INVOKABLE void addURL(const QString& name, const QString& url);
signals:
void thumbnailReady(const QString& name, const QString& url,
const QString& filePath);
private:
QNetworkAccessManager* manager;
QDir cacheFolder;
};
#endif

46
src/backendinterface.hpp Normal file
View file

@ -0,0 +1,46 @@
#ifndef BackendInterface_H
#define BackendInterface_H
#include "enums.hpp"
#include <QObject>
class BackendInterface {
public:
virtual ~BackendInterface(){};
int lastTime = 0;
double lastSpeed = 0;
QString totalDurationString;
QString lastPositionString;
public slots:
// All 5 required for Player API
virtual QVariant playerCommand(const Enums::Commands& command,
const QVariant& args)
= 0;
virtual QVariant playerCommand(const Enums::Commands& command) = 0;
virtual void toggleOnTop() = 0;
// Optional but handy for MPV or custom backend settings.
virtual void command(const QVariant& params) = 0;
virtual void setProperty(const QString& name, const QVariant& value) = 0;
virtual void setOption(const QString& name, const QVariant& value) = 0;
virtual QVariant getProperty(const QString& name) const = 0;
virtual QVariantMap getAudioDevices(const QVariant& drivers) const = 0;
signals:
// All below required for Player API
virtual void playStatusChanged(const Enums::PlayStatus& status) = 0;
virtual void volumeStatusChanged(const Enums::VolumeStatus& status) = 0;
virtual void volumeChanged(const int& volume) = 0;
virtual void durationChanged(const double& duration) = 0;
virtual void positionChanged(const double& position) = 0;
virtual void cachedDurationChanged(const double& duration) = 0;
virtual void playlistPositionChanged(const double& position) = 0;
virtual void titleChanged(const QString& title) = 0;
virtual void durationStringChanged(const QString& string) = 0;
virtual void tracksChanged(const QVariantList& tracks) = 0;
virtual void audioDevicesChanged(const QVariantMap& devices) = 0;
virtual void playlistChanged(const QVariantList& devices) = 0;
virtual void chaptersChanged(const QVariantList& devices) = 0;
virtual void speedChanged(const double& speed) = 0;
};
Q_DECLARE_INTERFACE(BackendInterface, "NamedKitten.BackendInterface");
#endif

1
src/enums.cpp Normal file
View file

@ -0,0 +1 @@
#include "enums.hpp"

62
src/enums.hpp Normal file
View file

@ -0,0 +1,62 @@
#ifndef ENUMS_HPP
#define ENUMS_HPP
#include <QObject>
#include <QString>
namespace Enums {
Q_NAMESPACE
enum class PlayStatus : int {
Playing = 0,
Paused = 1
};
Q_ENUM_NS(PlayStatus)
enum class VolumeStatus : int {
Muted = 0,
Low = 1,
Normal = 2
};
Q_ENUM_NS(VolumeStatus)
enum class Commands : int {
TogglePlayPause = 0,
ToggleMute = 1,
SetAudioDevice = 2,
AddVolume = 3,
SetVolume = 4,
AddSpeed = 5,
SubtractSpeed = 6,
ChangeSpeed = 7,
SetSpeed = 8,
ToggleStats = 9,
NextAudioTrack = 10,
NextVideoTrack = 11,
NextSubtitleTrack = 12,
PreviousPlaylistItem = 13,
NextPlaylistItem = 14,
LoadFile = 15,
AppendFile = 16,
Seek = 17,
SeekAbsolute = 18,
ForwardFrame = 19,
BackwardFrame = 20,
SetTrack = 21,
SetPlaylistPos = 22,
ForcePause = 23,
PreviousChapter = 24,
NextChapter = 25,
};
Q_ENUM_NS(Commands)
enum class Backends : int {
MPVBackend = 0,
DirectMPVBackend = 1
};
Q_ENUM_NS(Backends)
}
// Forces meta generation.
class Dummy : public QObject {
Q_OBJECT
};
#endif

43
src/logger.cpp Normal file
View file

@ -0,0 +1,43 @@
// clang-format off
#include <spdlog/spdlog.h> // IWYU pragma: export
#include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: export
#include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: export
#include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: export
#include "spdlog/common.h"
#include "spdlog/details/file_helper-inl.h"
#include "spdlog/sinks/ansicolor_sink-inl.h"
#include "spdlog/sinks/base_sink-inl.h"
#include "spdlog/sinks/basic_file_sink-inl.h"
#include "spdlog/spdlog-inl.h"
// clang-format on
#include <QByteArray>
#include <QSettings>
#include <QString>
#include <QVariant>
#include <iosfwd> // IWYU pragma: keep
#include <iterator>
#include <memory> // IWYU pragma: keep
#include <vector>
std::shared_ptr<spdlog::logger>
initLogger(std::string name)
{
QSettings settings("VideoPlayer", "VideoPlayer");
QString logFile = settings.value("Logging/logFile", "/tmp/VideoPlayer.log").toString();
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(std::make_shared<spdlog::sinks::stdout_color_sink_mt>());
auto fileLogger = std::make_shared<spdlog::sinks::basic_file_sink_mt>(logFile.toUtf8().constData());
fileLogger->set_level(spdlog::level::trace);
sinks.push_back(fileLogger);
auto console = std::make_shared<spdlog::logger>(name, begin(sinks), end(sinks));
console->set_pattern("[%l][%n] %v%$");
spdlog::register_logger(console);
console->set_level(spdlog::level::info);
return spdlog::get(name);
}

12
src/logger.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef LOGGER_HPP
#define LOGGER_HPP
#include <iosfwd> // IWYU pragma: keep
#include <memory> // IWYU pragma: keep
#include <spdlog/sinks/basic_file_sink.h> // IWYU pragma: keep
#include <spdlog/sinks/daily_file_sink.h> // IWYU pragma: keep
#include <spdlog/sinks/stdout_color_sinks.h> // IWYU pragma: keep
#include <spdlog/spdlog.h> // IWYU pragma: keep
std::shared_ptr<spdlog::logger> initLogger(std::string name);
#endif

135
src/main.cpp Normal file
View file

@ -0,0 +1,135 @@
#include <QApplication>
#include <QProcess>
#include <QQmlApplicationEngine>
#include <QtGlobal>
#include <QtQml>
#include <locale.h>
#ifdef QT_QML_DEBUG
#warning "QML Debugging Enabled!!!"
#include <QQmlDebug>
#endif
#include "logger.h"
#include "spdlog/logger.h"
#include <QSettings>
#include <QString>
#include <QUrl>
#include <QVariant>
#include <cstdlib>
#include <exception>
#include <iosfwd>
#include <memory>
#include <spdlog/fmt/fmt.h>
#include <fstream>
extern void registerTypes();
#ifdef WIN32
#include "setenv_mingw.hpp"
#endif
auto qmlLogger = initLogger("qml");
auto miscLogger = initLogger("misc");
void spdLogger(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
std::string localMsg = msg.toUtf8().constData();
std::shared_ptr<spdlog::logger> logger;
if (QString(context.category).startsWith(QString("qml"))) {
logger = qmlLogger;
} else {
logger = miscLogger;
}
switch (type) {
case QtDebugMsg:
logger->debug("{}", localMsg);
break;
case QtInfoMsg:
logger->info("{}", localMsg);
break;
case QtWarningMsg:
logger->warn("{}", localMsg);
break;
case QtCriticalMsg:
logger->critical("{}", localMsg);
break;
case QtFatalMsg:
logger->critical("{}", localMsg);
abort();
}
}
int main(int argc, char* argv[])
{
qInstallMessageHandler(spdLogger);
auto launcherLogger = initLogger("launcher");
launcherLogger->info("Starting up!");
setenv("QT_QUICK_CONTROLS_STYLE", "Desktop", 1);
QApplication app(argc, argv);
app.setOrganizationName("VideoPlayer");
app.setOrganizationDomain("owo.monster");
app.setApplicationName("VideoPlayer");
#ifdef QT_QML_DEBUG
// Allows debug.
QQmlDebuggingEnabler enabler;
#endif
QSettings settings;
bool ranFirstTimeSetup = settings.value("Setup/ranSetup", false).toBool();
#ifdef __linux__
bool pinephone;
#ifdef PINEPHONE
pinephone = true;
#else
pinephone = false;
#endif
// There once was a hacky piece of a code, now its gone.
// TODO: launch a opengl window or use offscreen to see if GL_ARB_framebuffer_object
// can be found
if (!(settings.value("Backend/disableSunxiCheck", false).toBool() || ranFirstTimeSetup) || pinephone ) {
std::string buf;
std::ifstream modulesFd;
modulesFd.open("/proc/modules");
if(modulesFd.is_open()) {
while(!modulesFd.eof()) {
std::getline(modulesFd, buf);
if(buf.find("sunxi") != std::string::npos || buf.find("sun8i") != std::string::npos) {
launcherLogger->info("Running on sunxi, switching to NoFBO.");
settings.setValue("Appearance/clickToPause", false);
settings.setValue("Appearance/doubleTapToSeek", true);
settings.setValue("Appearance/scaleFactor", 2.2);
settings.setValue("Appearance/subtitlesFontSize", 38);
settings.setValue("Appearance/uiFadeTimer", 0);
settings.setValue("Backend/fbo", false);
}
}
} else {
launcherLogger->info("(THIS IS NOT AN ERROR) Cant open /proc/modules.");
}
}
#endif
settings.setValue("Setup/ranSetup", true);
QString newpath = QProcessEnvironment::systemEnvironment().value("APPDIR", "") + "/usr/bin:" + QProcessEnvironment::systemEnvironment().value("PATH", "");
setenv("PATH", newpath.toUtf8().constData(), 1);
registerTypes();
setlocale(LC_NUMERIC, "C");
launcherLogger->info("Loading player...");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
return app.exec();
}

View file

@ -0,0 +1,77 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Dialogs 1.3
import QtQuick.Window 2.2
import player 1.0
Dialog {
id: debugDialog
title: "Debug"
height: 480
width: 720
modality: Qt.NonModal
standardButtons: Dialog.NoButton
Component {
id: delegate
Item {
width: 200; height: 30
Label {
text: theOutput
}
}
}
ListModel {
id: modelly
}
ListView {
id: output
model: modelly
delegate: delegate
height: 50
width: parent.width
anchors {
top: parent.top
bottom: input.top
left: parent.left
right: parent.right
}
}
TextField {
id: input
width: parent.width
height: 40
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
text: "h"
function doJsEval() {
var output;
try {
let result = eval(input.text)
output = result instanceof Array ? "[" + String(result) + "]" : String(result)
modelly.append({theOutput: output})
} catch (e) {
output = String(e)
modelly.append({theOutput: output})
}
}
Keys.onReturnPressed: {
doJsEval()
event.accepted = true
}
}
Action {
shortcut: "Ctrl+Shift+i"
onTriggered: {
debugDialog.open()
}
}
}

View file

@ -0,0 +1,220 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Dialogs 1.3
import QtQuick.Window 2.2
import player 1.0
Dialog {
id: playlistDialog
title: "Playlist"
height: Math.max(480, childrenRect.height * playlistListView.count)
width: 720
modality: Qt.NonModal
property int thumbnailJobsRunning: 0
property variant thumbnailJobs: []
property int titleJobsRunning: 0
property variant titleJobs: []
function addThumbnailToCache(name, output) {
output = output.replace("maxresdefault", "sddefault").split('\n')[0]
thumbnailCache.addURL(name, output)
thumbnailJobs.shift()
thumbnailJobsRunning -= 1
}
ThumbnailCache {
id: thumbnailCache
}
Rectangle {
visible: false
id: titleGetter
signal titleFound(string name, string title)
}
Timer {
interval: 500
repeat: true
triggeredOnStart: true
running: true
onTriggered: {
if (thumbnailJobsRunning < 2) {
if (thumbnailJobs.length > 0) {
if (thumbnailJobs[0].startsWith(
"https://www.youtube.com/playlist?list=")) {
thumbnailJobs.shift()
return
}
var component = Qt.createComponent("ThumbnailProcess.qml")
var thumbnailerProcess = component.createObject(playlistDialog, {
"name": thumbnailJobs[0]
})
if (String(thumbnailJobs[0]).indexOf("://") !== -1) {
thumbnailerProcess.start("youtube-dl",
["--get-thumbnail", thumbnailJobs[0]])
} else {
thumbnailerProcess.start(
"ffmpegthumbnailer",
["-i", thumbnailJobs[0], "-o", "/tmp/" + Qt.md5(
thumbnailJobs[0]) + ".png"])
}
thumbnailJobsRunning += 1
}
}
}
}
Timer {
interval: 100
repeat: true
triggeredOnStart: true
running: true
onTriggered: {
if (titleJobsRunning < 5) {
if (titleJobs.length > 0) {
if (titleJobs[0].startsWith("https://www.youtube.com/playlist?list=")) {
titleJobs.shift()
return
}
var component = Qt.createComponent("TitleProcess.qml")
var titleProcess = component.createObject(playlistDialog, {
"name": titleJobs[0]
})
titleProcess.start("youtube-dl", ["--get-title", titleJobs[0]])
titleJobs.shift()
titleJobsRunning += 1
}
}
}
}
Connections {
target: player
onPlaylistChanged: function (playlist) {
playlistModel.clear()
thumbnailJobs = []
titleJobs = []
titleJobsRunning = 0
thumbnailJobsRunning = 0
for (var thing in playlist) {
var item = playlist[thing]
playlistModel.append({
"playlistItemTitle": item["title"],
"playlistItemFilename": item["filename"],
"current": item["current"],
"playlistPos": thing
})
}
}
}
Component {
id: playlistDelegate
Item {
id: playlistItem
property string itemURL: ""
property string itemTitle: ""
width: playlistDialog.width
height: childrenRect.height
function getText(title, filename) {
var itemText = ""
if (title.length > 0) {
itemText += '<b>Title:</b> ' + title + "<br>"
}
if (filename.length > 0) {
itemText += '<b>Filename:</b> ' + filename
}
return itemText
}
Connections {
target: thumbnailCache
onThumbnailReady: function (name, url, path) {
console.error(name,url,path,playlistItem.itemURL)
if (name == playlistItem.itemURL) {
thumbnail.source = path
}
}
}
Connections {
target: titleGetter
onTitleFound: function (name, title) {
if (name == playlistItem.itemURL) {
titleJobsRunning -= 1
playlistItem.itemTitle = title
}
}
}
Image {
id: thumbnail
source: ""
height: source.toString().length > 1 ? 144 : 0
width: source.toString().length > 1 ? 256 : 0
}
Button {
width: parent.width - 20
id: playlistItemButton
font.pixelSize: 12
padding: 0
anchors.left: thumbnail.right
bottomPadding: 0
contentItem: Text {
id: playlistItemText
font: parent.font
color: "white"
text: playlistItem.getText(itemTitle, itemURL)
height: parent.height
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
wrapMode: Text.Wrap
}
onClicked: {
player.playerCommand(Enums.Commands.SetPlaylistPos, playlistPos)
}
background: Rectangle {
color: current ? "orange" : "transparent"
}
}
Component.onCompleted: {
if (typeof playlistItemTitle !== "undefined") {
playlistItem.itemTitle = playlistItemTitle
} else {
playlistDialog.titleJobs.push(playlistItemFilename)
}
if (typeof playlistItemFilename !== "undefined") {
playlistItem.itemURL = playlistItemFilename
} else {
playlistItem.itemURL = ""
}
playlistDialog.thumbnailJobs.push(playlistItemFilename)
}
}
}
ListView {
id: playlistListView
anchors.fill: parent
model: ListModel {
id: playlistModel
}
delegate: playlistDelegate
highlight: Item {}
snapMode: ListView.SnapToItem
flickableDirection: Flickable.VerticalFlick
boundsBehavior: Flickable.StopAtBounds
ScrollBar.vertical: ScrollBar {
active: playlistListView.count > 1 ? true : true
}
focus: true
}
Component.onCompleted: {
playlistDialog.open()
}
}

View file

@ -0,0 +1,140 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
import player 1.0
Dialog {
id: settingsDialog
title: translate.getTranslation("SETTINGS", i18n.language)
height: 100
width: 720
modality: Qt.NonModal
signal done
ScrollView {
id: content
height: parent.height
width: parent.width
clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
Item {
id: settingsContent
implicitHeight: childrenRect.height
implicitWidth: childrenRect.width
ColumnLayout {
Text {
height: 30
text: translate.getTranslation("LANGUAGE", i18n.language)
verticalAlignment: Text.AlignVCenter
}
LanguageSettings {
Layout.leftMargin: 30
}
Text {
height: 30
text: translate.getTranslation("APPEARANCE", i18n.language)
verticalAlignment: Text.AlignVCenter
}
CheckBox {
checked: appearance.titleOnlyOnFullscreen
onClicked: appearance.titleOnlyOnFullscreen = !appearance.titleOnlyOnFullscreen
text: translate.getTranslation("TITLE_ONLY_ON_FULLSCREEN",
i18n.language)
Layout.leftMargin: 30
}
CheckBox {
checked: appearance.doubleTapToSeek
onClicked: appearance.doubleTapToSeek = !appearance.doubleTapToSeek
text: translate.getTranslation("DOUBLE_TAP_TO_SEEK", i18n.language)
Layout.leftMargin: 30
}
Item {
Layout.leftMargin: 30
Layout.bottomMargin: 10
height: 30
Text {
id: seekByLabel
height: 30
text: translate.getTranslation("DOUBLE_TAP_TO_SEEK_BY",
i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: seekBy
anchors.left: seekByLabel.right
anchors.leftMargin: 10
validator: IntValidator {}
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: appearance.doubleTapToSeekBy
function setSeekBy() {
appearance.doubleTapToSeekBy = parseInt(seekBy.text)
}
onEditingFinished: setSeekBy()
}
}
Item {
height: 30
Layout.bottomMargin: 10
Layout.leftMargin: 30
Text {
id: fontLabel
height: 30
text: translate.getTranslation("FONT", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: fontInput
anchors.left: fontLabel.right
anchors.leftMargin: 10
text: appearance.fontName
function setFont() {
appearance.fontName = fontInput.text
}
onEditingFinished: setFont()
}
}
Item {
Layout.leftMargin: 30
Layout.bottomMargin: 10
height: 30
Text {
id: uiFadeTimeLabel
height: 30
text: translate.getTranslation("UI_FADE_TIME", i18n.language)
verticalAlignment: Text.AlignVCenter
}
TextField {
id: uiFadeTimeInput
anchors.left: uiFadeTimeLabel.right
anchors.leftMargin: 10
validator: IntValidator {
bottom: 0
}
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: appearance.uiFadeTimer
function setUIFadeTime() {
appearance.uiFadeTimer = parseInt(uiFadeTimeInput.text)
}
onEditingFinished: setUIFadeTime()
}
}
}
}
}
Connections {
target: settingsDialog
onAccepted: {
seekBy.setSeekBy()
fontInput.setFont()
uiFadeTimeInput.setUIFadeTime()
settingsDialog.done()
}
}
Component.onCompleted: {
settingsDialog.open()
}
}

View file

@ -0,0 +1,34 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import "translations.js" as Translations
ComboBox {
id: languageSelector
height: 30
editable: false
pressed: true
model: Object.keys(Translations.languages).map(function (key) {
return Translations.languages[key]
})
delegate: ItemDelegate {
height: 25
width: languageSelector.width
contentItem: Text {
text: modelData
color: "#21be2b"
font: languageSelector.font
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
highlighted: languageSelector.highlightedIndex === index
}
onActivated: {
console.warn(currentText)
i18n.language = Object.keys(Translations.languages).filter(function (key) {
return Translations.languages[key] === currentText
})[0]
}
Component.onCompleted: {
currentIndex = languageSelector.find(Translations.languages[i18n.language])
}
}

View file

@ -0,0 +1,12 @@
import QtQuick.Controls 2.3
import player 1.0
Action {
property string deviceID: "none"
checkable: false
checked: false
onTriggered: {
player.playerCommand(Enums.Commands.SetAudioDevice, deviceID)
}
}

View file

@ -0,0 +1,22 @@
import QtQuick 2.0
Rectangle {
id: chapterMarker
property int time: 0
color: getAppearanceValueForTheme(appearance.themeName, "chapterMarkerColor")
width: 4
height: parent.height
x: progressBar.background.width / progressBar.to * time
z: 90000
anchors {
top: parent.top
bottom: parent.bottom
}
Connections {
target: player
onChaptersChanged: {
chapterMarker.destroy()
}
}
}

View file

@ -0,0 +1,26 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
MenuItem {
id: menuItem
implicitHeight: 20
contentItem: Text {
text: menuItem.text
opacity: 1
color: menuItem.highlighted ? "#5a50da" : "white"
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
font {
family: appearance.fontName
bold: menuItem.highlighted
}
}
background: Rectangle {
anchors.fill: parent
opacity: 1
color: menuItem.highlighted ? "#c0c0f0" : "transparent"
}
}

View file

@ -0,0 +1,13 @@
import player 1.0
Process {
id: thumbnailerProcess
property string name: ""
onFinished: function () {
if (String(name).indexOf("://") !== -1) {
playlistDialog.addThumbnailToCache(name, getOutput())
} else {
playlistDialog.addThumbnailToCache(name, "/tmp/" + Qt.md5(name) + ".png")
}
}
}

View file

@ -0,0 +1,9 @@
import player 1.0
Process {
id: titleProcess
property string name: ""
onReadyRead: function () {
titleGetter.titleFound(name, getOutput())
}
}

View file

@ -0,0 +1,14 @@
import QtQuick.Controls 2.3
import player 1.0
Action {
id: trackItem
property string trackType: "none"
property string trackID: "none"
checkable: true
checked: false
onTriggered: {
player.playerCommand(Enums.Commands.SetTrack, [trackType, trackID])
}
}

View file

@ -0,0 +1,8 @@
import player 1.0
SmoothButton {
iconSource: "icons/" + appearance.themeName + "/backward.svg"
onClicked: {
player.playerCommand(Enums.Commands.Seek, "-10")
}
}

View file

@ -0,0 +1,8 @@
import player 1.0
SmoothButton {
iconSource: "icons/" + appearance.themeName + "/forward.svg"
onClicked: {
player.playerCommand(Enums.Commands.Seek, "10")
}
}

View file

@ -0,0 +1,6 @@
SmoothButton {
iconSource: "icons/" + appearance.themeName + "/fullscreen.svg"
onClicked: {
toggleFullscreen()
}
}

View file

@ -0,0 +1,17 @@
import QtQuick 2.0
import player 1.0
SmoothButton {
property var playing: Enums.PlayStatus.Playing
iconSource: "icons/" + appearance.themeName
+ (playing == Enums.PlayStatus.Playing ? "/pause.svg" : "/play.svg")
onClicked: {
player.playerCommand(Enums.Commands.TogglePlayPause)
}
Connections {
target: player
onPlayStatusChanged: function (status) {
playing = status
}
}
}

View file

@ -0,0 +1,8 @@
import player 1.0
SmoothButton {
iconSource: "icons/" + appearance.themeName + "/next.svg"
onClicked: {
player.playerCommand(Enums.Commands.NextPlaylistItem)
}
}

View file

@ -0,0 +1,23 @@
import QtQuick 2.0
import player 1.0
SmoothButton {
id: playlistPrevButton
iconSource: "icons/" + appearance.themeName + "/prev.svg"
visible: appearance.themeName == "Youtube" ? false : true
onClicked: {
player.playerCommand(Enums.Commands.PreviousPlaylistItem)
}
Connections {
target: player
onPlaylistPositionChanged: function (position) {
if (appearance.themeName == "YouTube") {
if (position != 0) {
visible = true
} else {
visible = false
}
}
}
}
}

View file

@ -0,0 +1,21 @@
import player 1.0
SmoothButton {
id: settingsButton
iconSource: "icons/" + appearance.themeName + "/settings.svg"
onClicked: {
switch (appearance.themeName) {
case "YouTube":
appearance.themeName = "RoosterTeeth"
break
case "RoosterTeeth":
appearance.themeName = "Niconico"
break
case "Niconico":
appearance.themeName = "YouTube"
break
default:
appearance.themeName = "YouTube"
}
}
}

View file

@ -0,0 +1,50 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtGraphicalEffects 1.0
Control {
id: root
hoverEnabled: true
property alias iconSource: buttonImage.source
property alias containsMouse: mouseArea.containsMouse
background: null
focusPolicy: Qt.NoFocus
signal clicked
leftPadding: root.height / (appearance.themeName == "Niconico" ? 2.5 : 12)
rightPadding: root.leftPadding
contentItem: Image {
id: buttonImage
smooth: false
fillMode: Image.PreserveAspectFit
sourceSize.height: Math.floor(
root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
sourceSize.width: Math.floor(
root.parent.height / (appearance.themeName == "Niconico" ? 1.8 : 1.25))
}
ColorOverlay {
id: colorOverlay
anchors.fill: buttonImage
source: buttonImage
color: getAppearanceValueForTheme(appearance.themeName, "buttonColor")
cached: true
Binding on color {
when: root.hovered
value: root.hovered ? getAppearanceValueForTheme(
appearance.themeName,
"buttonHoverColor") : getAppearanceValueForTheme(
appearance.themeName, "buttonColor")
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
onClicked: root.clicked()
}
}

View file

@ -0,0 +1,32 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import player 1.0
Text {
id: speedText
text: "1x"
verticalAlignment: Text.AlignVCenter
color: speedStatusMouseArea.containsMouse ? getAppearanceValueForTheme(
appearance.themeName,
"buttonHoverColor") : getAppearanceValueForTheme(
appearance.themeName,
"buttonColor")
font {
family: appearance.fontName
pixelSize: layout.height / 2.5
}
Connections {
target: player
onSpeedChanged: function (speed) {
text = String(speed) + "x"
}
}
MouseArea {
id: speedStatusMouseArea
anchors.fill: parent
height: parent.height
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
}
}

View file

@ -0,0 +1,18 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
Text {
id: timeLabel
text: "0:00 / 0:00"
color: "white"
font.family: appearance.fontName
font.pixelSize: layout.height / 2.5
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
Connections {
target: player
onDurationStringChanged: function (durationString) {
timeLabel.text = durationString
}
}
}

View file

@ -0,0 +1,71 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import player 1.0
Rectangle {
id: volumeSliderArea
height: visible ? 70 : 0
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
visible: false
Slider {
id: volumeSlider
anchors.fill: parent
to: 100
value: 100
orientation: Qt.Vertical
implicitWidth: Math.max(
background ? background.implicitWidth : 0,
(handle ? handle.implicitWidth : 0) + leftPadding + rightPadding)
implicitHeight: Math.max(background.implicitHeight,
handle.implicitHeight + topPadding + bottomPadding)
padding: 6
Connections {
target: player
onVolumeChanged: function (volume) {
volumeSlider.value = volume
}
}
onMoved: {
player.playerCommand(Enums.Commands.SetVolume,
Math.round(volumeSlider.value).toString())
}
handle: Rectangle {
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
y: volumeSlider.topPadding + (volumeSlider.visualPosition
* (volumeSlider.availableHeight - height))
implicitWidth: 10
implicitHeight: 10
radius: width / 2
color: "white"
border.width: 0
}
background: Rectangle {
x: volumeSlider.leftPadding + ((volumeSlider.availableWidth - width) / 2)
y: volumeSlider.topPadding
implicitWidth: 4
implicitHeight: 70
width: implicitWidth
height: volumeSlider.availableHeight
radius: 3
color: getAppearanceValueForTheme(appearance.themeName,
"progressBackgroundColor")
Rectangle {
y: volumeSlider.visualPosition * parent.height
width: 4
height: volumeSlider.position * parent.height
radius: 3
color: getAppearanceValueForTheme(appearance.themeName,
"volumeSliderBackground")
}
}
}
}

View file

@ -0,0 +1,207 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import player 1.0
Slider {
id: progressBar
objectName: "progressBar"
property string currentMediaURL: ""
property bool playing: false
property bool center: false
to: 1
value: 0.0
Rectangle {
id: timestampBox
visible: false
width: hoverProgressLabel.width
height: hoverProgressLabel.height
z: 100
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
Text {
id: hoverProgressLabel
text: "0:00"
color: "white"
font.family: appearance.fontName
font.pixelSize: mainWindow.virtualHeight / 50
horizontalAlignment: Text.AlignHCenter
renderType: Text.NativeRendering
}
}
Connections {
target: player
onPlayStatusChanged: function (status) {
if (status == Enums.PlayStatus.Playing) {
progressBar.playing = true
} else if (status == Enums.PlayStatus.Paused) {
progressBar.playing = false
}
}
onPositionChanged: function (position) {
if (!pressed) {
progressBar.value = position
}
}
onDurationChanged: function (duration) {
progressBar.to = duration
}
onCachedDurationChanged: function (duration) {
cachedLength.duration = duration
}
}
onMoved: {
player.playerCommand(Enums.Commands.SeekAbsolute, value)
}
function getProgressBarHeight(nyan, isMouse) {
var x = fun.nyanCat ? mainWindow.virtualHeight / 64 : mainWindow.virtualHeight / 380
if (appearance.themeName == "Niconico" && !fun.nyanCat) {
return x * 2
} else if (isMouse & !fun.nyanCat) {
return x * 2
} else {
return x
}
}
function getHandleVisibility(themeName, isMouse) {
if (fun.nyanCat) {
return true
}
if (appearance.themeName == "Niconico") {
return isMouse
} else {
return true
}
}
MouseArea {
id: mouseAreaProgressBar
width: progressBar.width
height: parent.height
anchors.fill: parent
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
z: 100
property string currentTime: ""
onEntered: timestampBox.visible = true
onExited: timestampBox.visible = false
onPositionChanged: {
// code taken from https://github.com/qt/qtquickcontrols2/blob/39892547145ba4e73bebee86352bd384732b5d19/src/quicktemplates2/qquickslider.cpp#L138
var a = ((mouseAreaProgressBar.mouseX - (handleRect.width / 2))
/ (progressBar.availableWidth - handleRect.width)) * progressBar.to
hoverProgressLabel.text = utils.createTimestamp(a)
timestampBox.x = mouseAreaProgressBar.mouseX - (timestampBox.width / 2)
timestampBox.y = progressBackground.y - timestampBox.height * 2
}
}
background: Rectangle {
anchors.bottom: parent.bottom
anchors.bottomMargin: progressBar.center ? (progressBar.height / 2) - (height / 2) : 0
id: progressBackground
z: 30
width: progressBar.availableWidth
height: progressBar.getProgressBarHeight(fun.nyanCat,
mouseAreaProgressBar.containsMouse)
color: getAppearanceValueForTheme(appearance.themeName,
"progressBackgroundColor")
ProgressBar {
id: cachedLength
background: null
contentItem: Item {
Rectangle {
width: cachedLength.visualPosition * parent.width
height: parent.height
color: getAppearanceValueForTheme(appearance.themeName,
"progressCachedColor")
AnimatedImage {
visible: fun.nyanCat
height: parent.height
id: nyancacheimation
smooth: false
anchors.fill: parent
source: "qrc:/icons/nyancache.gif"
fillMode: Image.TileHorizontally
}
}
}
z: 40
to: progressBar.to
property int duration
value: progressBar.value + duration
anchors.fill: parent
}
Item {
anchors.fill: parent
id: chapterMarkers
z: 60
Connections {
target: player
onChaptersChanged: function (chapters) {
for (var i = 0, len = chapters.length; i < len; i++) {
var component = Qt.createComponent("ChapterMarkerItem.qml")
var marker = component.createObject(chapterMarkers, {
"time": chapters[i]["time"]
})
if (marker == null) {
console.error(component.errorString())
}
}
}
}
}
Rectangle {
id: progressLength
z: 50
anchors.left: progressBackground.left
width: progressBar.visualPosition * parent.width
height: parent.height
color: getAppearanceValueForTheme(appearance.themeName,
"progressSliderColor")
Image {
visible: fun.nyanCat
id: rainbow
anchors.fill: parent
height: parent.height
width: parent.width
source: "qrc:/icons/rainbow.png"
fillMode: Image.TileHorizontally
}
}
}
handle: Rectangle {
z: 70
id: handleRect
x: progressBar.visualPosition * (progressBar.availableWidth - width)
anchors.verticalCenter: parent.background.verticalCenter
implicitHeight: radius
implicitWidth: radius
radius: mainWindow.virtualHeight / 59
color: appearance.themeName
== "RoosterTeeth" ? "white" : fun.nyanCat ? "transparent" : getAppearanceValueForTheme(
appearance.themeName,
"progressSliderColor")
visible: getHandleVisibility(appearance.themeName,
mouseAreaProgressBar.containsMouse)
AnimatedImage {
z: 80
visible: fun.nyanCat
paused: progressBar.pressed
height: mainWindow.virtualHeight / 28
id: nyanimation
smooth: false
anchors.centerIn: parent
source: "qrc:/icons/nyancat.gif"
fillMode: Image.PreserveAspectFit
}
}
}

View file

@ -0,0 +1,22 @@
import QtQuick 2.0
import player 1.0
SmoothButton {
id: volumeButton
iconSource: "icons/" + appearance.themeName + "/volume-up.svg"
onClicked: {
player.playerCommand(Enums.Commands.ToggleMute)
}
Connections {
target: player
onVolumeStatusChanged: function (status) {
if (status == Enums.VolumeStatus.Muted) {
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-mute.svg"
} else if (status == Enums.VolumeStatus.Low) {
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-down.svg"
} else if (status == Enums.VolumeStatus.Normal) {
volumeButton.iconSource = "qrc:/icons/" + appearance.themeName + "/volume-up.svg"
}
}
}
}

View file

@ -0,0 +1,69 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import player 1.0
Slider {
id: volumeBar
to: 100
value: 100
palette.dark: "#f00"
hoverEnabled: true
implicitWidth: Math.max(
background ? background.implicitWidth : 0,
(handle ? handle.implicitWidth : 0) + leftPadding + rightPadding)
implicitHeight: Math.max(
background ? background.implicitHeight : 0,
(handle ? handle.implicitHeight : 0) + topPadding + bottomPadding)
onMoved: {
player.playerCommand(Enums.Commands.SetVolume,
Math.round(volumeBar.value).toString())
}
Connections {
target: player
onVolumeChanged: function (volume) {
volumeBar.value = volume
}
}
handle: Rectangle {
x: volumeBar.leftPadding + volumeBar.visualPosition * (volumeBar.availableWidth - width)
y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2
implicitWidth: height
implicitHeight: layout.height / 2.6
radius: height
visible: appearance.themeName == "Niconico" ? false : true
color: "#f6f6f6"
border.color: "#f6f6f6"
}
background: Rectangle {
x: volumeBar.leftPadding
y: volumeBar.topPadding + volumeBar.availableHeight / 2 - height / 2
implicitWidth: layout.width / 11
implicitHeight: appearance.themeName == "Niconico" ? layout.height / 6 : layout.height / 10
width: volumeBar.availableWidth
height: implicitHeight
color: getAppearanceValueForTheme(appearance.themeName,
"progressBackgroundColor")
Rectangle {
width: volumeBar.visualPosition * parent.width
height: parent.height
color: getAppearanceValueForTheme(appearance.themeName,
"volumeSliderBackground")
}
MouseArea {
acceptedButtons: Qt.NoButton
z: 10
anchors.fill: parent
propagateComposedEvents: true
onWheel: {
if (wheel.angleDelta.y < 0) {
volumeBar.value -= 5
} else {
volumeBar.value += 5
}
}
}
}
}

View file

@ -0,0 +1,87 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import player 1.0
Item {
id: controlsBarItem
property var combinedHeight: progressBar.height + controlsBackground.height
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
Connections {
target: appearance
onThemeNameChanged: setControlsTheme(appearance.themeName)
}
function setControlsTheme(themeName) {
for (var i = 0; i < controlsBar.children.length; ++i) {
if (controlsBar.children[i].objectName == "buttonLayout") {
controlsBar.children[i].destroy()
}
}
var component = Qt.createComponent(themeName + "ButtonLayout.qml")
if (component.status == Component.Error) {
console.error("Error loading component: " + component.errorString())
}
component.createObject(controlsBar, {})
}
VideoProgress {
id: progressBar
visible: mainWindow.controlsShowing
&& appearance.themeName != "RoosterTeeth"
bottomPadding: 0
rightPadding: 0
leftPadding: 0
z: 20
anchors {
bottom: controlsBackground.top
left: controlsBackground.left
right: controlsBackground.right
leftMargin: parent.width / 128
rightMargin: parent.width / 128
bottomMargin: 0
}
}
Rectangle {
id: controlsBackground
height: controlsBar.visible ? controlsBar.height
+ (appearance.themeName
== "RoosterTeeth" ? 0 : progressBar.topPadding) : 0
Layout.fillWidth: true
Layout.fillHeight: true
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
visible: mainWindow.controlsShowing
z: 10
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
}
Item {
id: controlsBar
height: mainWindow.controlsShowing ? mainWindow.virtualHeight / 20 : 0
visible: mainWindow.controlsShowing
z: 30
anchors {
right: parent.right
rightMargin: parent.width / 128
left: parent.left
leftMargin: parent.width / 128
bottom: parent.bottom
bottomMargin: 0
}
}
Component.onCompleted: {
setControlsTheme(appearance.themeName)
}
}

View file

@ -0,0 +1,90 @@
import QtQuick 2.0
import player 1.0
Item {
objectName: "buttonLayout"
id: layout
anchors.fill: controlsBar
PlayPauseButton {
id: playPauseButton
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
}
VolumeButton {
id: volumeButton
anchors {
left: playPauseButton.right
top: parent.top
bottom: parent.bottom
}
}
VolumeSlider {
anchors {
left: volumeButton.right
top: parent.top
bottom: parent.bottom
}
}
PlaylistPrevButton {
id: playlistPrevButton
anchors {
right: backwardButton.left
top: parent.top
bottom: parent.bottom
}
}
BackwardButton {
id: backwardButton
anchors {
right: timeLabel.left
top: parent.top
bottom: parent.bottom
}
}
TimeLabel {
id: timeLabel
anchors {
centerIn: parent
top: parent.top
bottom: parent.bottom
}
}
ForwardButton {
id: forwardButton
anchors {
left: timeLabel.right
top: parent.top
bottom: parent.bottom
}
}
PlaylistNextButton {
id: playlistNextButton
anchors {
left: forwardButton.right
top: parent.top
bottom: parent.bottom
}
}
FullscreenButton {
id: fullscreenButton
anchors {
right: settingsButton.left
top: parent.top
bottom: parent.bottom
}
}
SettingsButton {
id: settingsButton
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
}
}

View file

@ -0,0 +1,111 @@
import QtQuick 2.0
import player 1.0
Item {
objectName: "buttonLayout"
id: layout
anchors.fill: controlsBar
PlayPauseButton {
id: playPauseButton
anchors {
top: parent.top
bottom: parent.bottom
left: parent.left
}
}
MouseArea {
id: mouseAreaVolumeArea
anchors {
right: volumeSliderArea.right
bottom: volumeButton.bottom
left: volumeButton.left
}
height: parent.height + (volumeSliderArea.visible ? volumeSliderArea.height : 0)
hoverEnabled: true
z: 500
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
onEntered: {
mouseAreaPlayerTimer.stop()
}
onExited: {
mouseAreaPlayerTimer.restart()
}
}
VolumeButton {
id: volumeButton
anchors {
left: playPauseButton.right
top: parent.top
bottom: parent.bottom
}
hoverEnabled: true
}
VerticalVolume {
id: volumeSliderArea
anchors {
bottom: volumeButton.top
left: volumeButton.left
right: volumeButton.right
}
width: volumeButton.width
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
}
TimeLabel {
id: timeLabel
anchors {
left: volumeButton.right
top: parent.top
bottom: parent.bottom
}
}
VideoProgress {
id: videoProgressRoosterTeeth
anchors {
top: parent.top
bottom: parent.bottom
left: timeLabel.right
leftMargin: parent.width / 128
right: speedText.left
rightMargin: parent.width / 128
}
rightPadding: 0
leftPadding: 0
height: parent.height
to: progressBar.to
value: progressBar.value
center: true
}
SpeedText {
id: speedText
anchors {
top: parent.top
bottom: parent.bottom
right: fullscreenButton.left
}
}
FullscreenButton {
id: fullscreenButton
anchors {
right: settingsButton.left
top: parent.top
bottom: parent.bottom
}
}
SettingsButton {
id: settingsButton
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
}
}

View file

@ -0,0 +1,92 @@
import QtQuick 2.0
import player 1.0
Item {
objectName: "buttonLayout"
id: layout
anchors.fill: controlsBar
height: parent.height
PlaylistPrevButton {
id: playlistPrevButton
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
width: visible ? playlistNextButton.width : 0
}
PlayPauseButton {
id: playPauseButton
anchors {
left: playlistPrevButton.right
top: parent.top
bottom: parent.bottom
}
leftPadding: 14
}
PlaylistNextButton {
id: playlistNextButton
anchors {
left: playPauseButton.right
top: parent.top
bottom: parent.bottom
}
}
MouseArea {
id: mouseAreaVolumeArea
anchors.fill: volumeSlider
width: volumeSlider.width
hoverEnabled: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
z: 100
}
VolumeButton {
id: volumeButton
anchors {
left: playlistNextButton.right
top: parent.top
bottom: parent.bottom
}
z: 50
}
VolumeSlider {
id: volumeSlider
anchors {
left: volumeButton.right
top: parent.top
bottom: parent.bottom
}
height: parent.height
visible: mouseAreaVolumeArea.containsMouse || volumeButton.hovered
width: visible ? implicitWidth : 0
}
TimeLabel {
anchors {
left: volumeSlider.right
top: parent.top
bottom: parent.bottom
leftMargin: parent.width / 128
}
}
SettingsButton {
id: settingsButton
anchors {
right: fullscreenButton.left
top: parent.top
bottom: parent.bottom
}
}
FullscreenButton {
id: fullscreenButton
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
}
}

View file

@ -0,0 +1,12 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
Menu {
width: 300
background: Rectangle {
implicitWidth: parent.width
implicitHeight: 10
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
}
delegate: CustomMenuItem {}
}

View file

@ -0,0 +1,603 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Dialogs 1.3
import Qt.labs.platform 1.0 as LabsPlatform
import player 1.0
import "codes.js" as LanguageCodes
MenuBar {
id: menuBar
height: mainWindow.virtualHeight / 32
function anythingOpen() {
for (var i = 0, len = menuBar.count; i < len; i++) {
if (menuBar.menuAt(i).opened) {
return true
}
}
return false
}
Connections {
target: player
onTracksChanged: function (tracks) {
menuBar.updateTracks(tracks)
}
}
function updateTracks(tracks) {
var newTracks = tracks
var trackMenus = [audioMenu, videoMenu, subMenu]
for (var a = 0; a < trackMenus.length; a++) {
var menu = trackMenus[a]
for (var i = 0, len = menu.count; i < len; i++) {
var action = menu.actionAt(i)
if (action) {
if (action.trackID != "no") {
menu.removeAction(action)
}
}
}
}
for (var i = 0, len = newTracks.length; i < len; i++) {
var track = newTracks[i]
var trackID = track["id"]
var trackType = track["type"]
var trackLang = LanguageCodes.localeCodeToEnglish(String(track["lang"]))
trackLang = trackLang == undefined ? "" : trackLang
var trackTitle = track["title"] == undefined ? "" : track["title"] + " "
var component = Qt.createComponent("TrackItem.qml")
var menu, menuGroup, itemText, type;
if (trackType == "sub") {
menu = subMenu;
menuGroup = subMenuGroup;
itemText = trackLang;
} else if (trackType == "audio") {
menu = audioMenu;
menuGroup = audioMenuGroup;
itemText = trackTitle + trackLang;
} else if (trackType == "video") {
menu = videoMenu;
menuGroup = videoMenuGroup;
itemText = "Video " + trackID + trackTitle;
}
var action = component.createObject(menu, {
"text": itemText,
"trackID": String(trackID),
"trackType": trackType == "sub" ? "sid" : trackType == "video" ? "vid" : "aid",
"checked": track["selected"]
})
action.ActionGroup.group = menuGroup
menu.addAction(action)
}
}
FileDialog {
id: fileDialog
title: translate.getTranslation("OPEN_FILE", i18n.language)
nameFilters: ["All files (*)"]
selectMultiple: false
onAccepted: {
player.playerCommand(Enums.Commands.LoadFile, String(fileDialog.fileUrl))
fileDialog.close()
}
onRejected: {
fileDialog.close()
}
}
Dialog {
id: loadDialog
title: translate.getTranslation("URL_FILE_PATH", i18n.language)
standardButtons: StandardButton.Cancel | StandardButton.Open
onAccepted: {
player.playerCommand(Enums.Commands.LoadFile, pathText.text)
pathText.text = ""
}
TextField {
id: pathText
placeholderText: translate.getTranslation("URL_FILE_PATH", i18n.language)
}
}
Loader {
id: playlistDialogLoader
active: false
source: "PlaylistDialog.qml"
}
Connections {
target: playlistDialogLoader.item
onDone: {
playlistDialogLoader.active = false
}
}
Loader {
id: settingsDialogLoader
active: false
source: "SettingsDialog.qml"
}
Connections {
target: settingsDialogLoader.item
onDone: {
settingsDialogLoader.active = false
}
}
delegate: MenuBarItem {
id: menuBarItem
padding: 4
topPadding: padding
leftPadding: padding
rightPadding: padding
bottomPadding: padding
contentItem: Text {
id: menuBarItemText
text: menuBarItem.text
font {
family: appearance.fontName
pixelSize: menuBar.height / 2
bold: menuBarItem.highlighted
}
opacity: 1
color: menuBarItem.highlighted ? "#5a50da" : "white"
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
renderType: Text.NativeRendering
}
background: Rectangle {
implicitWidth: 10
implicitHeight: 10
opacity: 1
color: menuBarItem.highlighted ? "#c0c0f0" : "transparent"
}
}
background: Rectangle {
width: parent.width
implicitHeight: 10
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
}
CustomMenu {
id: fileMenuBarItem
title: translate.getTranslation("FILE_MENU", i18n.language)
font.family: appearance.fontName
Action {
text: translate.getTranslation("OPEN_FILE", i18n.language)
onTriggered: fileDialog.open()
shortcut: keybinds.openFile
}
Action {
text: translate.getTranslation("OPEN_URL", i18n.language)
onTriggered: loadDialog.open()
shortcut: keybinds.openURI
}
Action {
text: translate.getTranslation("UPDATE_APPIMAGE", i18n.language)
onTriggered: utils.updateAppImage()
}
Action {
text: translate.getTranslation("EXIT", i18n.language)
onTriggered: Qt.quit()
shortcut: keybinds.quit
}
}
CustomMenu {
id: playbackMenuBarItem
title: translate.getTranslation("PLAYBACK", i18n.language)
Action {
text: translate.getTranslation("PLAY_PAUSE", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.TogglePlayPause)
}
shortcut: String(keybinds.playPause)
}
Action {
text: translate.getTranslation("REWIND_10S", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.Seek, "-10")
}
shortcut: keybinds.rewind10
}
Action {
text: translate.getTranslation("FORWARD_10S", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.Seek, "10")
}
shortcut: keybinds.forward10
}
Action {
text: translate.getTranslation("REWIND_5S", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.Seek, "-5")
}
shortcut: keybinds.rewind5
}
Action {
text: translate.getTranslation("FORWARD_5S", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.Seek, "5")
}
shortcut: keybinds.forward5
}
Action {
text: translate.getTranslation("SPEED_DECREASE_POINT_ONE", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.SubtractSpeed, 0.1)
}
shortcut: keybinds.decreaseSpeedByPointOne
}
Action {
text: translate.getTranslation("SPEED_INCREASE_POINT_ONE", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.AddSpeed, 0.1)
}
shortcut: keybinds.increaseSpeedByPointOne
}
Action {
text: translate.getTranslation("HALVE_SPEED", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.ChangeSpeed, 0.5)
}
shortcut: keybinds.halveSpeed
}
Action {
text: translate.getTranslation("DOUBLE_SPEED", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.ChangeSpeed, 2)
}
shortcut: keybinds.doubleSpeed
}
Action {
text: translate.getTranslation("FORWARD_FRAME", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.ForwardFrame)
}
shortcut: keybinds.forwardFrame
}
Action {
text: translate.getTranslation("BACKWARD_FRAME", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.BackwardFrame)
}
shortcut: keybinds.backwardFrame
}
Action {
text: translate.getTranslation("PREVIOUS_CHAPTER", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.PreviousChapter)
}
shortcut: keybinds.previousChapter
}
Action {
text: translate.getTranslation("NEXT_CHAPTER", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.NextChapter)
}
shortcut: keybinds.nextChapter
}
}
CustomMenu {
id: audioMenuBarItem
title: translate.getTranslation("AUDIO", i18n.language)
Action {
text: translate.getTranslation("CYCLE_AUDIO_TRACK", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.NextAudioTrack)
}
shortcut: keybinds.cycleAudio
}
Action {
text: translate.getTranslation("INCREASE_VOLUME", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.AddVolume, "2")
}
shortcut: keybinds.increaseVolume
}
Action {
text: translate.getTranslation("DECREASE_VOLUME", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.AddVolume, "-2")
}
shortcut: keybinds.decreaseVolume
}
Action {
text: translate.getTranslation("MUTE_VOLUME", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.ToggleMute)
}
shortcut: keybinds.mute
}
MenuSeparator {}
CustomMenu {
title: translate.getTranslation("AUDIO_DEVICES", i18n.language)
id: audioDeviceMenu
objectName: "audioDeviceMenu"
Connections {
target: player
onAudioDevicesChanged: function (ad) {
audioDeviceMenu.updateAudioDevices(ad)
}
}
function updateAudioDevices(audioDevices) {
for (var i = 0, len = audioDeviceMenu.count; i < len; i++) {
audioDeviceMenu.takeAction(0)
}
for (var thing in audioDevices) {
var audioDevice = audioDevices[thing]
var component = Qt.createComponent("AudioDeviceItem.qml")
var action = component.createObject(audioDeviceMenu, {
"text": audioDevices[thing]["description"],
"deviceID": String(
audioDevices[thing]["name"])
})
action.ActionGroup.group = audioDeviceMenuGroup
audioDeviceMenu.addAction(action)
}
}
ScrollView {
clip: true
ActionGroup {
id: audioDeviceMenuGroup
}
}
}
MenuSeparator {}
CustomMenu {
title: translate.getTranslation("AUDIO", i18n.language)
id: audioMenu
ActionGroup {
id: audioMenuGroup
}
TrackItem {
text: translate.getTranslation("DISABLE_TRACK", i18n.language)
trackType: "aid"
trackID: "no"
ActionGroup.group: audioMenuGroup
}
}
}
CustomMenu {
id: videoMenuBarItem
title: translate.getTranslation("VIDEO", i18n.language)
Action {
text: translate.getTranslation("CYCLE_VIDEO", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.NextVideoTrack)
}
shortcut: keybinds.cycleVideo
}
MenuSeparator {}
CustomMenu {
title: translate.getTranslation("VIDEO", i18n.language)
id: videoMenu
ActionGroup {
id: videoMenuGroup
}
TrackItem {
text: translate.getTranslation("DISABLE_TRACK", i18n.language)
trackType: "vid"
trackID: "no"
ActionGroup.group: videoMenuGroup
}
}
}
CustomMenu {
id: subsMenuBarItem
title: translate.getTranslation("SUBTITLES", i18n.language)
Action {
text: translate.getTranslation("CYCLE_SUB_TRACK", i18n.language)
onTriggered: {
player.playerCommand(Enums.Commands.NextSubtitleTrack)
}
shortcut: keybinds.cycleSub
}
MenuSeparator {}
CustomMenu {
title: translate.getTranslation("SUBTITLES", i18n.language)
id: subMenu
ActionGroup {
id: subMenuGroup
}
TrackItem {
text: translate.getTranslation("DISABLE_TRACK", i18n.language)
trackType: "sid"
trackID: "no"
ActionGroup.group: subMenuGroup
}
}
}
CustomMenu {
id: viewMenuBarItem
title: translate.getTranslation("VIEW", i18n.language)
CustomMenu {
title: translate.getTranslation("THEME", i18n.language)
id: themeMenu
Action {
text: "YouTube"
onTriggered: appearance.themeName = text
checkable: true
checked: appearance.themeName == text
}
Action {
text: "Niconico"
onTriggered: appearance.themeName = text
checkable: true
checked: appearance.themeName == text
}
Action {
text: "RoosterTeeth"
onTriggered: appearance.themeName = text
checkable: true
checked: appearance.themeName == text
}
}
Action {
text: translate.getTranslation("FULLSCREEN", i18n.language)
onTriggered: {
toggleFullscreen()
}
shortcut: keybinds.fullscreen
}
Action {
text: translate.getTranslation("STATS", i18n.language)
onTriggered: {
statsForNerdsText.visible = !statsForNerdsText.visible
}
shortcut: keybinds.statsForNerds
}
Action {
text: translate.getTranslation("TOGGLE_NYAN_CAT", i18n.language)
onTriggered: {
fun.nyanCat = !fun.nyanCat
}
shortcut: keybinds.nyanCat
}
Action {
text: translate.getTranslation("TOGGLE_ALWAYS_ON_TOP", i18n.language)
onTriggered: {
player.toggleOnTop()
}
}
Action {
text: translate.getTranslation("PLAYLIST_MENU", i18n.language)
onTriggered: {
playlistDialogLoader.active = true
}
}
Action {
text: translate.getTranslation("SETTINGS", i18n.language)
onTriggered: {
settingsDialogLoader.active = true
}
}
Action {
// Pretty sure for legal reasons this is needed unless I buy a Qt License
text: translate.getTranslation("ABOUT_QT", i18n.language)
onTriggered: {
utils.launchAboutQt()
}
}
}
Item {
id: skipToNinthDuration
property var duration: 0
Connections {
target: player
onDurationChanged: function (duration) {
skipToNinthDuration.duration = duration
}
}
}
function skipToNinth(val) {
var skipto = 0
if (val != 0) {
skipto = Math.floor(skipToNinthDuration.duration / 9 * val)
}
player.playerCommand(Enums.Commands.SeekAbsolute, skipto)
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "1"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "2"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "3"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "4"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "5"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "6"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "7"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "8"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "9"
}
Action {
onTriggered: skipToNinth(parseInt(shortcut))
shortcut: "0"
}
Action {
onTriggered: player.command(keybinds.customKeybind0Command)
shortcut: keybinds.customKeybind0
}
Action {
onTriggered: player.command(keybinds.customKeybind1Command)
shortcut: keybinds.customKeybind1
}
Action {
onTriggered: player.command(keybinds.customKeybind2Command)
shortcut: keybinds.customKeybind2
}
Action {
onTriggered: player.command(keybinds.customKeybind3Command)
shortcut: keybinds.customKeybind3
}
Action {
onTriggered: player.command(keybinds.customKeybind4Command)
shortcut: keybinds.customKeybind4
}
Action {
onTriggered: player.command(keybinds.customKeybind5Command)
shortcut: keybinds.customKeybind5
}
Action {
onTriggered: player.command(keybinds.customKeybind6Command)
shortcut: keybinds.customKeybind6
}
Action {
onTriggered: player.command(keybinds.customKeybind7Command)
shortcut: keybinds.customKeybind7
}
Action {
onTriggered: player.command(keybinds.customKeybind8Command)
shortcut: keybinds.customKeybind8
}
Action {
onTriggered: player.command(keybinds.customKeybind9Command)
shortcut: keybinds.customKeybind9
}
}

View file

@ -0,0 +1,68 @@
import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Window 2.2
Item {
id: menuTitleBar
height: menuBar.height
visible: mainWindow.controlsShowing
function anythingOpen() {
return menuBar.anythingOpen()
}
anchors {
left: parent.left
right: parent.right
top: parent.top
}
MainMenu {
id: menuBar
}
Rectangle {
height: menuBar.height
color: getAppearanceValueForTheme(appearance.themeName, "mainBackground")
anchors {
right: parent.right
left: menuBar.right
top: parent.top
}
Text {
id: titleLabel
objectName: "titleLabel"
text: translate.getTranslation("TITLE", i18n.language)
color: "white"
width: parent.width
height: parent.height
fontSizeMode: Text.VerticalFit
opacity: 1
visible: menuTitleBar.visible
&& ((!appearance.titleOnlyOnFullscreen)
|| (mainWindow.visibility == Window.FullScreen
|| mainWindow.visibility == Window.Maximized))
font {
family: appearance.fontName
bold: true
pixelSize: appearance.scaleFactor * (height - anchors.topMargin - anchors.bottomMargin - 2)
}
anchors {
left: parent.left
leftMargin: 4
bottom: parent.bottom
bottomMargin: 4
top: parent.top
}
Connections {
target: player
onTitleChanged: function (title) {
titleLabel.text = title
mainWindow.title = "VideoPlayer - " + title
}
}
}
}
}

View file

@ -0,0 +1,20 @@
import QtQuick 2.0
import "translations.js" as Translations
Item {
function getTranslation(code, language) {
var lang = Translations.translations[language]
if (lang == undefined || lang == "undefined") {
return "TranslationNotFound"
}
var text = String(Translations.translations[i18n.language][code])
if (text == "undefined") {
console.warn(code, "missing for language", language)
}
var args = Array.prototype.slice.call(arguments, 1)
var i = 0
return text.replace(/%s/g, function () {
return args[i++]
})
}
}

954
src/qml/Utils/codes.js Normal file
View file

@ -0,0 +1,954 @@
function localeCodeToEnglish(loc) {
if (typeof loc !== 'string')
throw new TypeError('Input must be string')
var parts = loc.split('-'), ISO639_1 = {
ab: "Abkhazian",
aa: "Afar",
af: "Afrikaans",
ak: "Akan",
sq: "Albanian",
am: "Amharic",
ar: "Arabic",
an: "Aragonese",
hy: "Armenian",
as: "Assamese",
av: "Avaric",
ae: "Avestan",
ay: "Aymara",
az: "Azerbaijani",
bm: "Bambara",
ba: "Bashkir",
eu: "Basque",
be: "Belarusian",
bn: "Bengali",
bh: "Bihari languages",
bi: "Bislama",
nb: "Norwegian Bokmål",
bs: "Bosnian",
br: "Breton",
bg: "Bulgarian",
my: "Burmese",
es: "Spanish",
ca: "Valencian",
km: "Central Khmer",
ch: "Chamorro",
ce: "Chechen",
ny: "Nyanja",
zh: "Chinese",
za: "Zhuang",
cu: "Old Slavonic",
cv: "Chuvash",
kw: "Cornish",
co: "Corsican",
cr: "Cree",
hr: "Croatian",
cs: "Czech",
da: "Danish",
dv: "Maldivian",
nl: "Flemish",
dz: "Dzongkha",
en: "English",
eo: "Esperanto",
et: "Estonian",
ee: "Ewe",
fo: "Faroese",
fj: "Fijian",
fi: "Finnish",
fr: "French",
ff: "Fulah",
gd: "Scottish Gaelic",
gl: "Galician",
lg: "Ganda",
ka: "Georgian",
de: "German",
ki: "Kikuyu",
el: "Greek, Modern (1453-)",
kl: "Kalaallisut",
gn: "Guarani",
gu: "Gujarati",
ht: "Haitian Creole",
ha: "Hausa",
he: "Hebrew",
hz: "Herero",
hi: "Hindi",
ho: "Hiri Motu",
hu: "Hungarian",
is: "Icelandic",
io: "Ido",
ig: "Igbo",
id: "Indonesian",
ia: "Interlingua (International Auxiliary Language Association)",
ie: "Occidental",
iu: "Inuktitut",
ik: "Inupiaq",
ga: "Irish",
it: "Italian",
ja: "Japanese",
jv: "Javanese",
kn: "Kannada",
kr: "Kanuri",
ks: "Kashmiri",
kk: "Kazakh",
rw: "Kinyarwanda",
ky: "Kyrgyz",
kv: "Komi",
kg: "Kongo",
ko: "Korean",
kj: "Kwanyama",
ku: "Kurdish",
lo: "Lao",
la: "Latin",
lv: "Latvian",
lb: "Luxembourgish",
li: "Limburgish",
ln: "Lingala",
lt: "Lithuanian",
lu: "Luba-Katanga",
mk: "Macedonian",
mg: "Malagasy",
ms: "Malay",
ml: "Malayalam",
mt: "Maltese",
gv: "Manx",
mi: "Maori",
mr: "Marathi",
mh: "Marshallese",
ro: "Romanian",
mn: "Mongolian",
na: "Nauru",
nv: "Navajo",
nd: "North Ndebele",
nr: "South Ndebele",
ng: "Ndonga",
ne: "Nepali",
se: "Northern Sami",
no: "Norwegian",
nn: "Nynorsk, Norwegian",
ii: "Sichuan Yi",
oc: "Occitan (post 1500)",
oj: "Ojibwa",
or: "Oriya",
om: "Oromo",
os: "Ossetic",
pi: "Pali",
pa: "Punjabi",
ps: "Pushto",
fa: "Persian",
pl: "Polish",
pt: "Portuguese",
qu: "Quechua",
rm: "Romansh",
rn: "Rundi",
ru: "Russian",
sm: "Samoan",
sg: "Sango",
sa: "Sanskrit",
sc: "Sardinian",
sr: "Serbian",
sn: "Shona",
sd: "Sindhi",
si: "Sinhalese",
sk: "Slovak",
sl: "Slovenian",
so: "Somali",
st: "Sotho, Southern",
su: "Sundanese",
sw: "Swahili",
ss: "Swati",
sv: "Swedish",
tl: "Tagalog",
ty: "Tahitian",
tg: "Tajik",
ta: "Tamil",
tt: "Tatar",
te: "Telugu",
th: "Thai",
bo: "Tibetan",
ti: "Tigrinya",
to: "Tonga (Tonga Islands)",
ts: "Tsonga",
tn: "Tswana",
tr: "Turkish",
tk: "Turkmen",
tw: "Twi",
ug: "Uyghur",
uk: "Ukrainian",
ur: "Urdu",
uz: "Uzbek",
ve: "Venda",
vi: "Vietnamese",
vo: "Volapük",
wa: "Walloon",
cy: "Welsh",
fy: "Western Frisian",
wo: "Wolof",
xh: "Xhosa",
yi: "Yiddish",
yo: "Yoruba",
zu: "Zulu"
}, ISO639_2 = {
abk: "Abkhazian",
ace: "Achinese",
ach: "Acoli",
ada: "Adangme",
ady: "Adyghe",
aar: "Afar",
afh: "Afrihili",
afr: "Afrikaans",
afa: "Afro-Asiatic languages",
ain: "Ainu",
aka: "Akan",
akk: "Akkadian",
alb: "Albanian",
sqi: "Albanian",
gsw: "Swiss German",
ale: "Aleut",
alg: "Algonquian languages",
tut: "Altaic languages",
amh: "Amharic",
anp: "Angika",
apa: "Apache languages",
ara: "Arabic",
arg: "Aragonese",
arp: "Arapaho",
arw: "Arawak",
arm: "Armenian",
hye: "Armenian",
rup: "Macedo-Romanian",
art: "Artificial languages",
asm: "Assamese",
ast: "Leonese",
ath: "Athapascan languages",
aus: "Australian languages",
map: "Austronesian languages",
ava: "Avaric",
ave: "Avestan",
awa: "Awadhi",
aym: "Aymara",
aze: "Azerbaijani",
ban: "Balinese",
bat: "Baltic languages",
bal: "Baluchi",
bam: "Bambara",
bai: "Bamileke languages",
bad: "Banda languages",
bnt: "Bantu languages",
bas: "Basa",
bak: "Bashkir",
baq: "Basque",
eus: "Basque",
btk: "Batak languages",
bej: "Beja",
bel: "Belarusian",
bem: "Bemba",
ben: "Bengali",
ber: "Berber languages",
bho: "Bhojpuri",
bih: "Bihari languages",
bik: "Bikol",
byn: "Blin",
bin: "Edo",
bis: "Bislama",
zbl: "Blissymbols",
nob: "Norwegian Bokmål",
bos: "Bosnian",
bra: "Braj",
bre: "Breton",
bug: "Buginese",
bul: "Bulgarian",
bua: "Buriat",
bur: "Burmese",
mya: "Burmese",
cad: "Caddo",
spa: "Spanish",
cat: "Valencian",
cau: "Caucasian languages",
ceb: "Cebuano",
cel: "Celtic languages",
cai: "Central American Indian languages",
khm: "Central Khmer",
chg: "Chagatai",
cmc: "Chamic languages",
cha: "Chamorro",
che: "Chechen",
chr: "Cherokee",
nya: "Nyanja",
chy: "Cheyenne",
chb: "Chibcha",
chi: "Chinese",
zho: "Chinese",
chn: "Chinook jargon",
chp: "Dene Suline",
cho: "Choctaw",
zha: "Zhuang",
chu: "Old Slavonic",
chk: "Chuukese",
chv: "Chuvash",
nwc: "Old Newari",
syc: "Classical Syriac",
rar: "Rarotongan",
cop: "Coptic",
cor: "Cornish",
cos: "Corsican",
cre: "Cree",
mus: "Creek",
crp: "Creoles and pidgins",
cpe: "Creoles and pidgins, English based",
cpf: "Creoles and pidgins, French-based",
cpp: "Creoles and pidgins, Portuguese-based",
crh: "Crimean Turkish",
hrv: "Croatian",
cus: "Cushitic languages",
cze: "Czech",
ces: "Czech",
dak: "Dakota",
dan: "Danish",
dar: "Dargwa",
del: "Delaware",
div: "Maldivian",
zza: "Zazaki",
din: "Dinka",
doi: "Dogri",
dgr: "Dogrib",
dra: "Dravidian languages",
dua: "Duala",
dut: "Flemish",
nld: "Flemish",
dum: "Dutch, Middle (ca.1050-1350)",
dyu: "Dyula",
dzo: "Dzongkha",
frs: "Eastern Frisian",
efi: "Efik",
egy: "Egyptian (Ancient)",
eka: "Ekajuk",
elx: "Elamite",
eng: "English",
enm: "English, Middle (1100-1500)",
ang: "English, Old (ca.450-1100)",
myv: "Erzya",
epo: "Esperanto",
est: "Estonian",
ewe: "Ewe",
ewo: "Ewondo",
fan: "Fang",
fat: "Fanti",
fao: "Faroese",
fij: "Fijian",
fil: "Pilipino",
fin: "Finnish",
fiu: "Finno-Ugrian languages",
fon: "Fon",
fre: "French",
fra: "French",
frm: "French, Middle (ca.1400-1600)",
fro: "French, Old (842-ca.1400)",
fur: "Friulian",
ful: "Fulah",
gaa: "Ga",
gla: "Scottish Gaelic",
car: "Galibi Carib",
glg: "Galician",
lug: "Ganda",
gay: "Gayo",
gba: "Gbaya",
gez: "Geez",
geo: "Georgian",
kat: "Georgian",
ger: "German",
deu: "German",
nds: "Saxon, Low",
gmh: "German, Middle High (ca.1050-1500)",
goh: "German, Old High (ca.750-1050)",
gem: "Germanic languages",
kik: "Kikuyu",
gil: "Gilbertese",
gon: "Gondi",
gor: "Gorontalo",
got: "Gothic",
grb: "Grebo",
grc: "Greek, Ancient (to 1453)",
gre: "Greek, Modern (1453-)",
ell: "Greek, Modern (1453-)",
kal: "Kalaallisut",
grn: "Guarani",
guj: "Gujarati",
gwi: "Gwich'in",
hai: "Haida",
hat: "Haitian Creole",
hau: "Hausa",
haw: "Hawaiian",
heb: "Hebrew",
her: "Herero",
hil: "Hiligaynon",
him: "Western Pahari languages",
hin: "Hindi",
hmo: "Hiri Motu",
hit: "Hittite",
hmn: "Mong",
hun: "Hungarian",
hup: "Hupa",
iba: "Iban",
ice: "Icelandic",
isl: "Icelandic",
ido: "Ido",
ibo: "Igbo",
ijo: "Ijo languages",
ilo: "Iloko",
arc: "Official Aramaic (700-300 BCE)",
smn: "Inari Sami",
inc: "Indic languages",
ine: "Indo-European languages",
ind: "Indonesian",
inh: "Ingush",
ina: "Interlingua (International Auxiliary Language Association)",
ile: "Occidental",
iku: "Inuktitut",
ipk: "Inupiaq",
ira: "Iranian languages",
gle: "Irish",
mga: "Irish, Middle (900-1200)",
sga: "Irish, Old (to 900)",
iro: "Iroquoian languages",
ita: "Italian",
jpn: "Japanese",
jav: "Javanese",
kac: "Kachin",
jrb: "Judeo-Arabic",
jpr: "Judeo-Persian",
kbd: "Kabardian",
kab: "Kabyle",
xal: "Oirat",
kam: "Kamba",
kan: "Kannada",
kau: "Kanuri",
pam: "Pampanga",
kaa: "Kara-Kalpak",
krc: "Karachay-Balkar",
krl: "Karelian",
kar: "Karen languages",
kas: "Kashmiri",
csb: "Kashubian",
kaw: "Kawi",
kaz: "Kazakh",
kha: "Khasi",
khi: "Khoisan languages",
kho: "Sakan",
kmb: "Kimbundu",
kin: "Kinyarwanda",
kir: "Kyrgyz",
tlh: "tlhIngan-Hol",
kom: "Komi",
kon: "Kongo",
kok: "Konkani",
kor: "Korean",
kos: "Kosraean",
kpe: "Kpelle",
kro: "Kru languages",
kua: "Kwanyama",
kum: "Kumyk",
kur: "Kurdish",
kru: "Kurukh",
kut: "Kutenai",
lad: "Ladino",
lah: "Lahnda",
lam: "Lamba",
day: "Land Dayak languages",
lao: "Lao",
lat: "Latin",
lav: "Latvian",
ltz: "Luxembourgish",
lez: "Lezghian",
lim: "Limburgish",
lin: "Lingala",
lit: "Lithuanian",
jbo: "Lojban",
dsb: "Lower Sorbian",
loz: "Lozi",
lub: "Luba-Katanga",
lua: "Luba-Lulua",
lui: "Luiseno",
smj: "Lule Sami",
lun: "Lunda",
luo: "Luo (Kenya and Tanzania)",
lus: "Lushai",
mac: "Macedonian",
mkd: "Macedonian",
mad: "Madurese",
mag: "Magahi",
mai: "Maithili",
mak: "Makasar",
mlg: "Malagasy",
may: "Malay",
msa: "Malay",
mal: "Malayalam",
mlt: "Maltese",
mnc: "Manchu",
mdr: "Mandar",
man: "Mandingo",
mni: "Manipuri",
mno: "Manobo languages",
glv: "Manx",
mao: "Maori",
mri: "Maori",
arn: "Mapudungun",
mar: "Marathi",
chm: "Mari",
mah: "Marshallese",
mwr: "Marwari",
mas: "Masai",
myn: "Mayan languages",
men: "Mende",
mic: "Micmac",
min: "Minangkabau",
mwl: "Mirandese",
moh: "Mohawk",
mdf: "Moksha",
rum: "Romanian",
ron: "Romanian",
mkh: "Mon-Khmer languages",
lol: "Mongo",
mon: "Mongolian",
mos: "Mossi",
mul: "Multiple languages",
mun: "Munda languages",
nqo: "N'Ko",
nah: "Nahuatl languages",
nau: "Nauru",
nav: "Navajo",
nde: "North Ndebele",
nbl: "South Ndebele",
ndo: "Ndonga",
nap: "Neapolitan",
new: "Newari",
nep: "Nepali",
nia: "Nias",
nic: "Niger-Kordofanian languages",
ssa: "Nilo-Saharan languages",
niu: "Niuean",
zxx: "Not applicable",
nog: "Nogai",
non: "Norse, Old",
nai: "North American Indian languages",
frr: "Northern Frisian",
sme: "Northern Sami",
nso: "Sotho, Northern",
nor: "Norwegian",
nno: "Nynorsk, Norwegian",
nub: "Nubian languages",
iii: "Sichuan Yi",
nym: "Nyamwezi",
nyn: "Nyankole",
nyo: "Nyoro",
nzi: "Nzima",
oci: "Occitan (post 1500)",
pro: "Provençal, Old (to 1500)",
oji: "Ojibwa",
ori: "Oriya",
orm: "Oromo",
osa: "Osage",
oss: "Ossetic",
oto: "Otomian languages",
pal: "Pahlavi",
pau: "Palauan",
pli: "Pali",
pag: "Pangasinan",
pan: "Punjabi",
pap: "Papiamento",
paa: "Papuan languages",
pus: "Pushto",
per: "Persian",
fas: "Persian",
peo: "Persian, Old (ca.600-400 B.C.)",
phi: "Philippine languages",
phn: "Phoenician",
pon: "Pohnpeian",
pol: "Polish",
por: "Portuguese",
pra: "Prakrit languages",
que: "Quechua",
raj: "Rajasthani",
rap: "Rapanui",
roa: "Romance languages",
roh: "Romansh",
rom: "Romany",
run: "Rundi",
rus: "Russian",
sal: "Salishan languages",
sam: "Samaritan Aramaic",
smi: "Sami languages",
smo: "Samoan",
sad: "Sandawe",
sag: "Sango",
san: "Sanskrit",
sat: "Santali",
srd: "Sardinian",
sas: "Sasak",
sco: "Scots",
sel: "Selkup",
sem: "Semitic languages",
srp: "Serbian",
srr: "Serer",
shn: "Shan",
sna: "Shona",
scn: "Sicilian",
sid: "Sidamo",
sgn: "Sign Languages",
bla: "Siksika",
snd: "Sindhi",
sin: "Sinhalese",
sit: "Sino-Tibetan languages",
sio: "Siouan languages",
sms: "Skolt Sami",
den: "Slave (Athapascan)",
sla: "Slavic languages",
slo: "Slovak",
slk: "Slovak",
slv: "Slovenian",
sog: "Sogdian",
som: "Somali",
son: "Songhai languages",
snk: "Soninke",
wen: "Sorbian languages",
sot: "Sotho, Southern",
sai: "South American Indian languages",
alt: "Southern Altai",
sma: "Southern Sami",
srn: "Sranan Tongo",
suk: "Sukuma",
sux: "Sumerian",
sun: "Sundanese",
sus: "Susu",
swa: "Swahili",
ssw: "Swati",
swe: "Swedish",
syr: "Syriac",
tgl: "Tagalog",
tah: "Tahitian",
tai: "Tai languages",
tgk: "Tajik",
tmh: "Tamashek",
tam: "Tamil",
tat: "Tatar",
tel: "Telugu",
ter: "Tereno",
tet: "Tetum",
tha: "Thai",
tib: "Tibetan",
bod: "Tibetan",
tig: "Tigre",
tir: "Tigrinya",
tem: "Timne",
tiv: "Tiv",
tli: "Tlingit",
tpi: "Tok Pisin",
tkl: "Tokelau",
tog: "Tonga (Nyasa)",
ton: "Tonga (Tonga Islands)",
tsi: "Tsimshian",
tso: "Tsonga",
tsn: "Tswana",
tum: "Tumbuka",
tup: "Tupi languages",
tur: "Turkish",
ota: "Turkish, Ottoman (1500-1928)",
tuk: "Turkmen",
tvl: "Tuvalu",
tyv: "Tuvinian",
twi: "Twi",
udm: "Udmurt",
uga: "Ugaritic",
uig: "Uyghur",
ukr: "Ukrainian",
umb: "Umbundu",
mis: "Uncoded languages",
und: "Undetermined",
hsb: "Upper Sorbian",
urd: "Urdu",
uzb: "Uzbek",
vai: "Vai",
ven: "Venda",
vie: "Vietnamese",
vol: "Volapük",
vot: "Votic",
wak: "Wakashan languages",
wln: "Walloon",
war: "Waray",
was: "Washo",
wel: "Welsh",
cym: "Welsh",
fry: "Western Frisian",
wal: "Wolaytta",
wol: "Wolof",
xho: "Xhosa",
sah: "Yakut",
yao: "Yao",
yap: "Yapese",
yid: "Yiddish",
yor: "Yoruba",
ypk: "Yupik languages",
znd: "Zande languages",
zap: "Zapotec",
zen: "Zenaga",
zul: "Zulu",
zun: "Zuni"
}, ISO3166_1 = {
AF: "AFGHANISTAN",
AX: "ÅLAND ISLANDS",
AL: "ALBANIA",
DZ: "ALGERIA",
AS: "AMERICAN SAMOA",
AD: "ANDORRA",
AO: "ANGOLA",
AI: "ANGUILLA",
AQ: "ANTARCTICA",
AG: "ANTIGUA AND BARBUDA",
AR: "ARGENTINA",
AM: "ARMENIA",
AW: "ARUBA",
AU: "AUSTRALIA",
AT: "AUSTRIA",
AZ: "AZERBAIJAN",
BS: "BAHAMAS",
BH: "BAHRAIN",
BD: "BANGLADESH",
BB: "BARBADOS",
BY: "BELARUS",
BE: "BELGIUM",
BZ: "BELIZE",
BJ: "BENIN",
BM: "BERMUDA",
BT: "BHUTAN",
BO: "BOLIVIA, PLURINATIONAL STATE OF",
BQ: "BONAIRE, SINT EUSTATIUS AND SABA",
BA: "BOSNIA AND HERZEGOVINA",
BW: "BOTSWANA",
BV: "BOUVET ISLAND",
BR: "BRAZIL",
IO: "BRITISH INDIAN OCEAN TERRITORY",
BN: "BRUNEI DARUSSALAM",
BG: "BULGARIA",
BF: "BURKINA FASO",
BI: "BURUNDI",
KH: "CAMBODIA",
CM: "CAMEROON",
CA: "CANADA",
CV: "CAPE VERDE",
KY: "CAYMAN ISLANDS",
CF: "CENTRAL AFRICAN REPUBLIC",
TD: "CHAD",
CL: "CHILE",
CN: "CHINA",
CX: "CHRISTMAS ISLAND",
CC: "COCOS (KEELING) ISLANDS",
CO: "COLOMBIA",
KM: "COMOROS",
CG: "CONGO",
CD: "CONGO, THE DEMOCRATIC REPUBLIC OF THE",
CK: "COOK ISLANDS",
CR: "COSTA RICA",
CI: "CÔTE D'IVOIRE",
HR: "CROATIA",
CU: "CUBA",
CW: "CURAÇAO",
CY: "CYPRUS",
CZ: "CZECH REPUBLIC",
DK: "DENMARK",
DJ: "DJIBOUTI",
DM: "DOMINICA",
DO: "DOMINICAN REPUBLIC",
EC: "ECUADOR",
EG: "EGYPT",
SV: "EL SALVADOR",
GQ: "EQUATORIAL GUINEA",
ER: "ERITREA",
EE: "ESTONIA",
ET: "ETHIOPIA",
FK: "FALKLAND ISLANDS (MALVINAS)",
FO: "FAROE ISLANDS",
FJ: "FIJI",
FI: "FINLAND",
FR: "FRANCE",
GF: "FRENCH GUIANA",
PF: "FRENCH POLYNESIA",
TF: "FRENCH SOUTHERN TERRITORIES",
GA: "GABON",
GM: "GAMBIA",
GE: "GEORGIA",
DE: "GERMANY",
GH: "GHANA",
GI: "GIBRALTAR",
GR: "GREECE",
GL: "GREENLAND",
GD: "GRENADA",
GP: "GUADELOUPE",
GU: "GUAM",
GT: "GUATEMALA",
GG: "GUERNSEY",
GN: "GUINEA",
GW: "GUINEA-BISSAU",
GY: "GUYANA",
HT: "HAITI",
HM: "HEARD ISLAND AND MCDONALD ISLANDS",
VA: "HOLY SEE (VATICAN CITY STATE)",
HN: "HONDURAS",
HK: "HONG KONG",
HU: "HUNGARY",
IS: "ICELAND",
IN: "INDIA",
ID: "INDONESIA",
IR: "IRAN, ISLAMIC REPUBLIC OF",
IQ: "IRAQ",
IE: "IRELAND",
IM: "ISLE OF MAN",
IL: "ISRAEL",
IT: "ITALY",
JM: "JAMAICA",
JP: "JAPAN",
JE: "JERSEY",
JO: "JORDAN",
KZ: "KAZAKHSTAN",
KE: "KENYA",
KI: "KIRIBATI",
KP: "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF",
KR: "KOREA, REPUBLIC OF",
KW: "KUWAIT",
KG: "KYRGYZSTAN",
LA: "LAO PEOPLE'S DEMOCRATIC REPUBLIC",
LV: "LATVIA",
LB: "LEBANON",
LS: "LESOTHO",
LR: "LIBERIA",
LY: "LIBYA",
LI: "LIECHTENSTEIN",
LT: "LITHUANIA",
LU: "LUXEMBOURG",
MO: "MACAO",
MK: "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF",
MG: "MADAGASCAR",
MW: "MALAWI",
MY: "MALAYSIA",
MV: "MALDIVES",
ML: "MALI",
MT: "MALTA",
MH: "MARSHALL ISLANDS",
MQ: "MARTINIQUE",
MR: "MAURITANIA",
MU: "MAURITIUS",
YT: "MAYOTTE",
MX: "MEXICO",
FM: "MICRONESIA, FEDERATED STATES OF",
MD: "MOLDOVA, REPUBLIC OF",
MC: "MONACO",
MN: "MONGOLIA",
ME: "MONTENEGRO",
MS: "MONTSERRAT",
MA: "MOROCCO",
MZ: "MOZAMBIQUE",
MM: "MYANMAR",
NA: "NAMIBIA",
NR: "NAURU",
NP: "NEPAL",
NL: "NETHERLANDS",
NC: "NEW CALEDONIA",
NZ: "NEW ZEALAND",
NI: "NICARAGUA",
NE: "NIGER",
NG: "NIGERIA",
NU: "NIUE",
NF: "NORFOLK ISLAND",
MP: "NORTHERN MARIANA ISLANDS",
NO: "NORWAY",
OM: "OMAN",
PK: "PAKISTAN",
PW: "PALAU",
PS: "PALESTINIAN TERRITORY, OCCUPIED",
PA: "PANAMA",
PG: "PAPUA NEW GUINEA",
PY: "PARAGUAY",
PE: "PERU",
PH: "PHILIPPINES",
PN: "PITCAIRN",
PL: "POLAND",
PT: "PORTUGAL",
PR: "PUERTO RICO",
QA: "QATAR",
RE: "RÉUNION",
RO: "ROMANIA",
RU: "RUSSIAN FEDERATION",
RW: "RWANDA",
BL: "SAINT BARTHÉLEMY",
SH: "SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA",
KN: "SAINT KITTS AND NEVIS",
LC: "SAINT LUCIA",
MF: "SAINT MARTIN (FRENCH PART)",
PM: "SAINT PIERRE AND MIQUELON",
VC: "SAINT VINCENT AND THE GRENADINES",
WS: "SAMOA",
SM: "SAN MARINO",
ST: "SAO TOME AND PRINCIPE",
SA: "SAUDI ARABIA",
SN: "SENEGAL",
RS: "SERBIA",
SC: "SEYCHELLES",
SL: "SIERRA LEONE",
SG: "SINGAPORE",
SX: "SINT MAARTEN (DUTCH PART)",
SK: "SLOVAKIA",
SI: "SLOVENIA",
SB: "SOLOMON ISLANDS",
SO: "SOMALIA",
ZA: "SOUTH AFRICA",
GS: "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS",
SS: "SOUTH SUDAN",
ES: "SPAIN",
LK: "SRI LANKA",
SD: "SUDAN",
SR: "SURINAME",
SJ: "SVALBARD AND JAN MAYEN",
SZ: "SWAZILAND",
SE: "SWEDEN",
CH: "SWITZERLAND",
SY: "SYRIAN ARAB REPUBLIC",
TW: "TAIWAN, PROVINCE OF CHINA",
TJ: "TAJIKISTAN",
TZ: "TANZANIA, UNITED REPUBLIC OF",
TH: "THAILAND",
TL: "TIMOR-LESTE",
TG: "TOGO",
TK: "TOKELAU",
TO: "TONGA",
TT: "TRINIDAD AND TOBAGO",
TN: "TUNISIA",
TR: "TURKEY",
TM: "TURKMENISTAN",
TC: "TURKS AND CAICOS ISLANDS",
TV: "TUVALU",
UG: "UGANDA",
UA: "UKRAINE",
AE: "UNITED ARAB EMIRATES",
GB: "UNITED KINGDOM",
US: "UNITED STATES",
UM: "UNITED STATES MINOR OUTLYING ISLANDS",
UY: "URUGUAY",
UZ: "UZBEKISTAN",
VU: "VANUATU",
VE: "VENEZUELA, BOLIVARIAN REPUBLIC OF",
VN: "VIET NAM",
VG: "VIRGIN ISLANDS, BRITISH",
VI: "VIRGIN ISLANDS, U.S.",
WF: "WALLIS AND FUTUNA",
EH: "WESTERN SAHARA",
YE: "YEMEN",
ZM: "ZAMBIA",
ZW: "ZIMBABWE"
}
if (parts.length > 2)
throw new SyntaxError('Unexpected number of segments ' + parts.length)
if (parts.length > 1)
return (ISO639_1[parts[0]] || ISO639_2[parts[0]]
|| parts[0]) + ', ' + (ISO3166_1[parts[1]] || parts[1])
if (parts.length > 0)
return ISO639_1[parts[0]] || ISO639_2[parts[0]] || ISO3166_1[parts[0]]
|| parts[0]
return ''
}

View file

@ -0,0 +1,471 @@
var languages = {
"english": "English",
"spanish": "Español",
"german": "Deutsch",
"french": "Française",
"italian": "Italiano",
"russian": "Русский",
"norwegian": "Norwegian",
"tokipona": "toki pona",
"telugu": "తెలుగు",
"vietnamese": "Tiếng Việt"
}
var translations = {
english: {
OPEN_FILE: "Open file",
URL_FILE_PATH: "URL / File Path",
FILE_MENU: "File",
OPEN_URL: "Open URL",
EXIT: "Exit",
PLAYBACK: "Playback",
PLAY_PAUSE: "Play/Pause",
REWIND_10S: "Rewind 10s",
FORWARD_10S: "Forward 10s",
REWIND_5S: "Rewind 5s",
FORWARD_5S: "Forward 5s",
SPEED_DECREASE_POINT_ONE: "Speed -0.1",
SPEED_INCREASE_POINT_ONE: "Speed +0.1",
HALVE_SPEED: "Halve Speed",
DOUBLE_SPEED: "Double Speed",
BACKWARD_FRAME: "Back a frame",
FORWARD_FRAME: "Forward a frame",
AUDIO: "Audio",
INCREASE_VOLUME: "Increase Volume",
DECREASE_VOLUME: "Decrease Volume",
MUTE_VOLUME: "Mute",
VIDEO: "Video",
CYCLE_VIDEO: "Cycle video",
SUBTITLES: "Subtitles",
CYCLE_SUB_TRACK: "Cycle Subtitle Track",
CYCLE_AUDIO_TRACK: "Cycle Audio Track",
VIEW: "View",
FULLSCREEN: "Fullscreen",
STATS: "Statistics",
TOGGLE_NYAN_CAT: "Toggle Nyan Cat",
ABOUT: "About",
ABOUT_QT: "About Qt",
TITLE: "Title",
TOGGLE_ALWAYS_ON_TOP: "Toggle Always On Top",
DISABLE_TRACK: "Disable Track",
AUDIO_DEVICES: "Audio Devices",
PLAYLIST_MENU: "Playlist Menu",
THEME: "Theme",
SETTINGS: "Settings",
LANGUAGE: "Language",
APPEARANCE: "Appearance",
TITLE_ONLY_ON_FULLSCREEN: "Show title when only in fullscreen",
CLICK_TO_PAUSE: "Tap/Click to pause",
DOUBLE_TAP_TO_SEEK: "Double Tap/Click to seek",
DOUBLE_TAP_TO_SEEK_BY: "Seek by",
FONT: "Font",
SUBTITLES_FONT_SIZE: "Subtitles Font Size",
UI_FADE_TIME: "UI Fade Time (ms)",
UPDATE_APPIMAGE: "Update (AppImage Only)",
PREVIOUS_CHAPTER: "Previous Chapter",
NEXT_CHAPTER: "Next Chapter"
},
spanish: {
OPEN_FILE: "Abrir archivo",
URL_FILE_PATH: "URL / Dirección de archivo",
FILE_MENU: "Archivo",
OPEN_URL: "Abrir URL",
EXIT: "Salir",
PLAYBACK: "Volver a reproducir",
PLAY_PAUSE: "Reproducir/Pausar",
REWIND_10S: "Retrasar 10s",
FORWARD_10S: "Adelantar 10s",
REWIND_5S: "Retrasar 5s",
FORWARD_5S: "Adelantar 5s",
SPEED_DECREASE_POINT_ONE: "Velocidad -0.1",
SPEED_INCREASE_POINT_ONE: "Velocidad +0.1",
HALVE_SPEED: "Dividir velocidad a la mitad",
DOUBLE_SPEED: "Duplicar velocidad",
BACKWARD_FRAME: "Retrasar un frame",
FORWARD_FRAME: "Adelantar un frame",
AUDIO: "Audio",
INCREASE_VOLUME: "Subir Volumen",
DECREASE_VOLUME: "Bajar Volumen",
MUTE_VOLUME: "Silenciar",
VIDEO: "Vídeo",
CYCLE_VIDEO: "Siguiente vídeo",
SUBTITLES: "Subtítulos",
CYCLE_SUB_TRACK: "Siguientes subtítulos",
CYCLE_AUDIO_TRACK: "Siguiente pista de audio",
VIEW: "Ver",
FULLSCREEN: "Pantalla completa",
STATS: "Estadísticas",
TOGGLE_NYAN_CAT: "Activar Nyan Cat",
ABOUT: "Ayuda",
ABOUT_QT: "Sobre Qt",
TITLE: "Título",
TOGGLE_ALWAYS_ON_TOP: "Fijar ventana",
DISABLE_TRACK: "Eliminar pista"
},
german: {
OPEN_FILE: "Datei öffnen",
URL_FILE_PATH: "URL / Datei-Pfad",
FILE_MENU: "Datei",
OPEN_URL: "URL öffnen",
EXIT: "Beenden",
PLAYBACK: "Wiedergabe",
PLAY_PAUSE: "Wiedergabe/Pause",
REWIND_10S: "Rückspulen um 10s",
FORWARD_10S: "Vorspulen um 10s",
REWIND_5S: "Rückspulen um 5s",
FORWARD_5S: "Vorspulen um 5s",
SPEED_DECREASE_POINT_ONE: "Geschwindigkeit -0.1",
SPEED_INCREASE_POINT_ONE: "Geschwindigkeit +0.1",
HALVE_SPEED: "Halbe Geschwindigkeit",
DOUBLE_SPEED: "Doppelte Geschwindigkeit",
BACKWARD_FRAME: "Ein Bild zurück",
FORWARD_FRAME: "Ein Bild vor",
AUDIO: "Audio",
INCREASE_VOLUME: "Lautstärke erhöhen",
DECREASE_VOLUME: "Lautstärke verringern",
MUTE_VOLUME: "Stumm",
VIDEO: "Video",
CYCLE_VIDEO: "Wechsle Video",
SUBTITLES: "Untertitel",
CYCLE_SUB_TRACK: "Wechsle Untertitel-Spur",
CYCLE_AUDIO_TRACK: "Wechsle Audiospur",
VIEW: "Ansicht",
FULLSCREEN: "Vollbild",
STATS: "Statistiken",
TOGGLE_NYAN_CAT: "Schalte Nyan Cat An/Aus",
ABOUT: "Über",
ABOUT_QT: "Über QT",
TITLE: "Titel",
TOGGLE_ALWAYS_ON_TOP: "Immer im Vordergrund",
DISABLE_TRACK: "Spur deaktivieren",
AUDIO_DEVICES: "Audiogeräte",
PLAYLIST_MENU: "Wiedergabeliste",
THEME: "Darstellung",
SETTINGS: "Einstellungen",
LANGUAGE: "Sprache",
APPEARANCE: "Aussehen",
TITLE_ONLY_ON_FULLSCREEN: "Titel nur im Vollbildmodus anzeigen",
CLICK_TO_PAUSE: "Berühren/Klicken zum Pausieren",
DOUBLE_TAP_TO_SEEK: "Doppeltap/Klicken zum Spulen",
DOUBLE_TAP_TO_SEEK_BY: "Spulen um (s)",
FONT: "Schriftart",
SUBTITLES_FONT_SIZE: "Untertitel-Schriftgröße",
UI_FADE_TIME: "Ausblendzeit Benutzeroberfläche (ms)"
},
french: {
OPEN_FILE: "Ouvrir le fichier",
URL_FILE_PATH: "URL / Chemin du fichier",
FILE_MENU: "Fichier",
OPEN_URL: "Ouvrir l'URL",
EXIT: "Fermer",
PLAYBACK: "Lecture",
PLAY_PAUSE: "Jouer/Mettre en pause",
REWIND_10S: "Reculer de 10s",
FORWARD_10S: "Avancer de 10s",
REWIND_5S: "Reculer de 5s",
FORWARD_5S: "Avancer de 5s",
SPEED_DECREASE_POINT_ONE: "Vitesse -0.1",
SPEED_INCREASE_POINT_ONE: "Vitesse +0.1",
HALVE_SPEED: "Réduire de moitié la vitesse",
DOUBLE_SPEED: "Doubler la vitesse",
BACKWARD_FRAME: "Reculer d'une image",
FORWARD_FRAME: "Avancer d'une image",
AUDIO: "Audio",
CYCLE_AUDIO_TRACK: "Chancer de piste audio",
INCREASE_VOLUME: "Augmenter le volume",
DECREASE_VOLUME: "Réduire le volume",
MUTE_VOLUME: "Muet",
VIDEO: "Vidéo",
CYCLE_VIDEO: "Changer de vidéo",
SUBTITLES: "Sous-titres",
CYCLE_SUB_TRACK: "Changer de piste de sous-titres",
VIEW: "Voir",
FULLSCREEN: "Plein écran",
STATS: "Statistiques",
TOGGLE_NYAN_CAT: "basculer Nyan Cat",
ABOUT: "A propos",
ABOUT_QT: "A propos de Qt",
TITLE: "Titre",
TOGGLE_ALWAYS_ON_TOP: "Toujours au-dessu",
DISABLE_TRACK: "Désactiver la piste",
AUDIO_DEVICES: "Périphériques audio",
PLAYLIST_MENU: "Menu de la liste de lecture",
THEME: "Thème"
},
italian: {
OPEN_FILE: "Apri file",
URL_FILE_PATH: "URL / Percorso file",
FILE_MENU: "File",
OPEN_URL: "Apri URL",
EXIT: "Esci",
PLAYBACK: "Riproduzione",
PLAY_PAUSE: "Play/Pausa",
REWIND_10S: "Indietro 10s",
FORWARD_10S: "Avanti 10s",
REWIND_5S: "Indietro 5s",
FORWARD_5S: "Avanti 5s",
SPEED_DECREASE_POINT_ONE: "Velocità -0.1",
SPEED_INCREASE_POINT_ONE: "Velocità +0.1",
HALVE_SPEED: "Dimezza velocità",
DOUBLE_SPEED: "Raddoppia velocità",
BACKWARD_FRAME: "Indietro di un fotogramma",
FORWARD_FRAME: "Avanti di un fotogramma",
AUDIO: "Audio",
INCREASE_VOLUME: "Aumenta Volume",
DECREASE_VOLUME: "Diminuisci Volume",
MUTE_VOLUME: "Muto",
VIDEO: "Video",
CYCLE_VIDEO: "Ripeti video",
SUBTITLES: "Sottotitoli",
CYCLE_SUB_TRACK: "Ripeti traccia sottotitoli",
CYCLE_AUDIO_TRACK: "Ripeti traccia audio",
VIEW: "Vedi",
FULLSCREEN: "Schermo intero",
STATS: "Statistiche",
TOGGLE_NYAN_CAT: "Attiva Nyan Cat",
ABOUT: "Info",
ABOUT_QT: "Info su Qt",
TITLE: "Titolo",
TOGGLE_ALWAYS_ON_TOP: "Mantieni in primo piano",
DISABLE_TRACK: "Disabilita traccia",
AUDIO_DEVICES: "Dispositivi audio",
PLAYLIST_MENU: "Menu Playlist",
THEME: "Tema",
SETTINGS: "Impostazioni",
LANGUAGE: "Lingua",
APPEARANCE: "Appearance", //gonna need more context for this
TITLE_ONLY_ON_FULLSCREEN: "Mostra il titolo solo in modalità a schermo intero", //kinda long but can't think of a shorter way
CLICK_TO_PAUSE: "Tap/Click per mettere in pausa",
DOUBLE_TAP_TO_SEEK: "Doppio Tap/Click per andare avanti",
DOUBLE_TAP_TO_SEEK_BY: "vai avanti di (secondi)",
FONT: "Font",
SUBTITLES_FONT_SIZE: "Dimensione Sottotitoli",
UI_FADE_TIME: "Fade Interfaccia (millisecondi)"
},
russian: {
OPEN_FILE: "Открыть...",
URL_FILE_PATH: "Ввести URL / путь к файлу...",
FILE_MENU: "Файл",
OPEN_URL: "Открыть URL...",
EXIT: "Выход",
PLAYBACK: "Воспроизведение",
PLAY_PAUSE: "Воспроизвести/Остановить",
REWIND_10S: "Перемотать назад на 10 секунд",
FORWARD_10S: "Перемотать вперед на 10 секунд",
REWIND_5S: "Перемотать назад на 5 секунд",
FORWARD_5S: "Перемотать вперед на 5 секунд",
SPEED_DECREASE_POINT_ONE: "Уменьшить скорость на 0.1",
SPEED_INCREASE_POINT_ONE: "Увеличить скорость на 0.1",
HALVE_SPEED: "Замедлить воспроизведение в 2 раза",
DOUBLE_SPEED: "Ускорить воспроизведение в 2 раза",
BACKWARD_FRAME: "Вернуться к предыдущему кадру",
FORWARD_FRAME: "Перейти к следующему кадру",
AUDIO: "Звук",
INCREASE_VOLUME: "Увеличить громкость",
DECREASE_VOLUME: "Уменьшить громкость",
MUTE_VOLUME: "Заглушить звук",
VIDEO: "Видео",
CYCLE_VIDEO: "Зациклить воспроизведение видео",
SUBTITLES: "Субтитры",
CYCLE_SUB_TRACK: "Зациклить дорожку субтитров",
CYCLE_AUDIO_TRACK: "Зациклить аудиодорожку",
VIEW: "Вид",
FULLSCREEN: "Во весь экран",
STATS: "Статистика",
TOGGLE_NYAN_CAT: "Нян-кот",
ABOUT: "О программе",
ABOUT_QT: "О используемой версии Qt...",
TITLE: "Заголовок",
SETTINGS: "Настройки"
},
norwegian: {
OPEN_FILE: "Åpne Fil",
URL_FILE_PATH: "Lenke / Filbane",
FILE_MENU: "Fil",
OPEN_URL: "Åpne lenke",
EXIT: "Avslutt",
PLAYBACK: "Avspilling",
PLAY_PAUSE: "Spill/Pause",
REWIND_10S: "Spol Tilbake 10s",
FORWARD_10S: "Spol Frem 10s",
REWIND_5S: "Spol Tilbake 5s",
FORWARD_5S: "Spol Frem 5s",
SPEED_DECREASE_POINT_ONE: "Hastighet -0.1",
SPEED_INCREASE_POINT_ONE: "Hastighet +0.1",
HALVE_SPEED: "Halv Hastighet",
DOUBLE_SPEED: "Dobbel Hastighet",
BACKWARD_FRAME: "Gå Tilbake Én Ramme",
FORWARD_FRAME: "Gå Frem Én Ramme",
AUDIO: "Lydspor",
INCREASE_VOLUME: "Øk Lydstyrke",
DECREASE_VOLUME: "Senk Lydstyrke",
MUTE_VOLUME: "Demp",
VIDEO: "Videospor",
CYCLE_VIDEO: "Sirkuler Gjennom Videospor",
SUBTITLES: "Undertekster",
CYCLE_SUB_TRACK: "Sirkuler Gjennom Undertekster",
CYCLE_AUDIO_TRACK: "Sirkuler Gjennom Lydspor",
VIEW: "Vis",
FULLSCREEN: "Fullskjerm",
STATS: "Statistikk",
TOGGLE_NYAN_CAT: "Skru Av/På Nyan Cat",
ABOUT: "Om",
ABOUT_QT: "Om Qt",
TITLE: "Tittel",
TOGGLE_ALWAYS_ON_TOP: "Alltid Øverst",
DISABLE_TRACK: "Skru Av Spor"
},
tokipona: {
OPEN_FILE: "open e lipu",
URL_FILE_PATH: "URL / nasin lipu",
FILE_MENU: "lipu",
OPEN_URL: "open e URL",
EXIT: "pini",
PLAYBACK: "tawa",
PLAY_PAUSE: "tawa/awen",
REWIND_10S: "tawa monsi mute",
FORWARD_10S: "tawa sinpin mute",
REWIND_5S: "tawa monsi lili",
FORWARD_5S: "tawa sinpin lili",
SPEED_DECREASE_POINT_ONE: "lili lili e tawa",
SPEED_INCREASE_POINT_ONE: "mute lili e tawa",
HALVE_SPEED: "lili mute e tawa",
DOUBLE_SPEED: "mute mute e tawa",
BACKWARD_FRAME: "monsi pi sitelen wan",
FORWARD_FRAME: "sinpin pi sitelen wan",
AUDIO: "kalama",
INCREASE_VOLUME: "mute e kalama",
DECREASE_VOLUME: "lili e kalama",
MUTE_VOLUME: "awen e kalama",
VIDEO: "sitelen tawa",
CYCLE_VIDEO: "ante e sitelen tawa",
SUBTITLES: "sitelen anpa",
CYCLE_SUB_TRACK: "ante e sitelen anpa",
CYCLE_AUDIO_TRACK: "ante e kalama",
VIEW: "lukin",
FULLSCREEN: "lupa lukin ale",
STATS: "nanpa",
TOGGLE_NYAN_CAT: "open anu awen e soweli Nian",
ABOUT: "ni li seme?",
ABOUT_QT: "QT li seme?",
TITLE: "nimi",
TOGGLE_ALWAYS_ON_TOP: "sewi ala sewi e lipu",
DISABLE_TRACK: "awen e linja",
AUDIO_DEVICES: "ila kalama",
PLAYLIST_MENU: "lipu musi",
THEME: "len ijo",
SETTINGS: "ken ijo",
LANGUAGE: "toki",
APPEARANCE: "lukin ijo",
TITLE_ONLY_ON_FULLSCREEN: "lupa lukin ale taso la, sitelen e nimi",
CLICK_TO_PAUSE: "luka la, awen",
DOUBLE_TAP_TO_SEEK: "luka tu la, lukin",
DOUBLE_TAP_TO_SEEK_BY: "luka tu la, lukin kepeken",
FONT: "sitelen",
SUBTITLES_FONT_SIZE: "suli pi sitelen anpa",
UI_FADE_TIME: "tenpo pi wawa UI",
UPDATE_APPIMAGE: "sin e ijo (AppImage taso)"
},
telugu: {
OPEN_FILE: "ఫైల్ తెరువు",
URL_FILE_PATH: "లింకు / ఫైల్ ఉన్న ప్రదేశం",
FILE_MENU: "ఫైల్ మెనూ",
OPEN_URL: "URL తెరువు",
EXIT: "నిష్క్రమణ",
PLAYBACK: "ప్లేబ్యాక్",
PLAY_PAUSE: "ప్లే/పౌస్",
REWIND_10S: "10సె వెనక్కి",
FORWARD_10S: "10సె ముందుకి",
REWIND_5S: "5సె వెనక్కి",
FORWARD_5S: "5సె వెనక్కి",
SPEED_DECREASE_POINT_ONE: "వేగం -0.1",
SPEED_INCREASE_POINT_ONE: "వేగం +0.1",
HALVE_SPEED: "వేగం సగానికి",
DOUBLE_SPEED: "వేగం రెట్టింపు",
BACKWARD_FRAME: "ఫ్రేము వెనక్కి",
FORWARD_FRAME: "ఫ్రేము ముందుకి",
AUDIO: "ఆడియో మెనూ",
INCREASE_VOLUME: "ధ్వని పెంచు",
DECREASE_VOLUME: "ధ్వని తగ్గించు",
MUTE_VOLUME: "మ్యూట్ చేయి",
VIDEO: "వీడియో మెనూ",
CYCLE_VIDEO: "వీడియో ని సైకిల్ చేయి",
SUBTITLES: "ఉపశీర్షిక/subtitles",
CYCLE_SUB_TRACK: "ఉపశీర్షిక ట్రాక్ ని సైకిల్ చేయి",
CYCLE_AUDIO_TRACK: "ఆడియో ట్రాక్ ని సైకిల్ చేయి",
VIEW: "దర్శన",
FULLSCREEN: "స్క్రీన్ నింపు",
STATS: "గణాంకాలు",
TOGGLE_NYAN_CAT: "Nyan Cat నుండి/కు మార్చు",
ABOUT: "మా గురుంచి",
ABOUT_QT: "Qt గురించి",
TITLE: "టైటిల్",
TOGGLE_ALWAYS_ON_TOP: "పైవైపు నుండి/కు మార్చు",
DISABLE_TRACK: "ట్రాక్ ని ఆపు",
AUDIO_DEVICES: "ఆడియో పరికరాలు",
PLAYLIST_MENU: "ప్లేయలిస్ట్ మెనూ",
THEME: "థీమ్ ",
SETTINGS: "సెట్టింగులు",
LANGUAGE: "భాష ",
APPEARANCE: "ప్రదర్శన",
TITLE_ONLY_ON_FULLSCREEN: "స్క్రీన్ నింపినప్పుడే టైటిల్ చూపు",
CLICK_TO_PAUSE: "పౌస్ చేయడానికి క్లిక్/టాప్ చేయండి",
DOUBLE_TAP_TO_SEEK: "సీక్ చేయడానికి డబల్ టాప్/క్లిక్ చేయండి",
DOUBLE_TAP_TO_SEEK_BY: "సీక్ ఆధారం",
FONT: "పదాలు/ఫాంట్",
SUBTITLES_FONT_SIZE: "సుబ్టైటల్ పదాలు/ఫాంట్ సైజు",
UI_FADE_TIME: "UI వీడిపోవు(మిల్లీ సె)",
UPDATE_APPIMAGE: "అప్డేట్ చేయి (AppImage Only)"
},
vietnamese: {
OPEN_FILE: "Mở Tập Tin",
URL_FILE_PATH: "URL / Đường dẫn tập tin",
FILE_MENU: "Tập Tin",
OPEN_URL: "Mở URL",
EXIT: "Thoát",
PLAYBACK: "Phát lại",
PLAY_PAUSE: "Phát/Tạm dừng",
REWIND_10S: "Tua lại 10 giây",
FORWARD_10S: "Tua tiến 10 giây",
REWIND_5S: "Tua lại 5 giây",
FORWARD_5S: "Tua tiến 5 giây",
SPEED_DECREASE_POINT_ONE: "Tốc độ -0.1",
SPEED_INCREASE_POINT_ONE: "Tốc độ +0.1",
HALVE_SPEED: "Tốc độ phân đôi",
DOUBLE_SPEED: "Tốc độ nhân đôi",
BACKWARD_FRAME: "Khung trước",
FORWARD_FRAME: "Khung tiếp theo",
AUDIO: "Âm thanh",
INCREASE_VOLUME: "Tăng âm lượng",
DECREASE_VOLUME: "Giảm âm lượng",
MUTE_VOLUME: "Tắt âm thanh",
VIDEO: "Video",
CYCLE_VIDEO: "Đảo video",
SUBTITLES: "Phụ đề",
CYCLE_SUB_TRACK: "Đảo Subtitle Track",
CYCLE_AUDIO_TRACK: "Đảo Audio Track",
VIEW: "Nhìn",
FULLSCREEN: "Toàn màn hình",
STATS: "Thống kê",
TOGGLE_NYAN_CAT: "Bật/Tắt Nyan Cat",
ABOUT: "Giới thiệu",
ABOUT_QT: "Giới thiệu về Qt",
TITLE: "Tiêu đề",
TOGGLE_ALWAYS_ON_TOP: "Bật/Tắt luôn ở trên",
DISABLE_TRACK: "Tắt Track",
AUDIO_DEVICES: "Thiết bị Audio",
PLAYLIST_MENU: "Menu danh sách phát",
THEME: "Diện mạo",
SETTINGS: "Tùy chỉnh",
LANGUAGE: "Ngôn ngữ",
APPEARANCE: "Giao diện",
TITLE_ONLY_ON_FULLSCREEN: "Chỉ hiện thị tiêu đè khi ở toàn màn hình",
CLICK_TO_PAUSE: "Chạm/Click để tạm dừng",
DOUBLE_TAP_TO_SEEK: "Chạm/Click đúp để lướt",
DOUBLE_TAP_TO_SEEK_BY: "Lướt đến",
FONT: "Kiểu chữ",
SUBTITLES_FONT_SIZE: "Kích thước kiểu chữ của phụ đề",
UI_FADE_TIME: "Thời gian làm mợ giao diện (ms)",
UPDATE_APPIMAGE: "Cập nhật (Chỉ dành cho AppImage)"
}
}

BIN
src/qml/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,2 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerSeekBackwardButton-icon"><path d="M18.3 29A38 38 0 1 1 23 76.7a4 4 0 0 0-5.7 0l-2.8 2.8a4 4 0 0 0 0 5.7A50 50 0 1 0 8 22.8l-2-1.2a4 4 0 0 0-6 3.5v18.2a4 4 0 0 0 6 3.5L21.7 38a4 4 0 0 0 .2-7L18.3 29zM42 66a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V40h-2a2 2 0 0 1-2-2v-4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v32zm32 0a2 2 0 0 1-2 2H52a2 2 0 0 1-2-2V34c0-1.1.9-2 2-2h20a2 2 0 0 1 2 2v32zm-8-26h-8v20h8V40z"></path></svg>

After

Width:  |  Height:  |  Size: 564 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerSeekForwardButton-icon"><path d="M81.7 29A38 38 0 1 0 77 76.7a4 4 0 0 1 5.7 0l2.8 2.8a4 4 0 0 1 0 5.7A50 50 0 1 1 92 22.8l2-1.2a4 4 0 0 1 6 3.5v18.2a4 4 0 0 1-6 3.5L78.3 38a4 4 0 0 1-.2-7l3.7-2.1zM37 66a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2V40h-2a2 2 0 0 1-2-2v-4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v32zm32 0a2 2 0 0 1-2 2H47a2 2 0 0 1-2-2V34c0-1.1.9-2 2-2h20a2 2 0 0 1 2 2v32zm-8-26h-8v20h8V40z"></path></svg>

After

Width:  |  Height:  |  Size: 561 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="EnableFullScreenButton-icon"><path d="M60 92a4 4 0 0 1 4-4h15.5L59 67.5a4 4 0 0 1 0-5.7l2.8-2.8a4 4 0 0 1 5.7 0L88 79.5V64a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v32a4 4 0 0 1-4 4H64a4 4 0 0 1-4-4v-4zM40 8a4 4 0 0 1-4 4H20.5L41 32.5a4 4 0 0 1 0 5.7L38.2 41a4 4 0 0 1-5.7 0L12 20.5V36a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4h32a4 4 0 0 1 4 4v4z"></path></svg>

After

Width:  |  Height:  |  Size: 512 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerSkipNextButton-icon"><path d="M34.6 46.6a4 4 0 0 1 0 6.8L6 71.2A4 4 0 0 1 0 67.8V32.2a4 4 0 0 1 6.1-3.4l28.5 17.8zM74.6 46.6a4 4 0 0 1 0 6.8L46 71.2a4 4 0 0 1-6.1-3.4V32.2a4 4 0 0 1 6.1-3.4l28.5 17.8zM100 4a4 4 0 0 0-4-4h-8a4 4 0 0 0-4 4v92a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4V4z"></path></svg>

After

Width:  |  Height:  |  Size: 452 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 88 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerPauseButton-icon"><path d="M32 4a4 4 0 0 0-4-4H4a4 4 0 0 0-4 4v92a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4V4zM88 4a4 4 0 0 0-4-4H60a4 4 0 0 0-4 4v92a4 4 0 0 0 4 4h24a4 4 0 0 0 4-4V4z"></path></svg>

After

Width:  |  Height:  |  Size: 350 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerPlayButton-icon"><path d="M95 42a8.9 8.9 0 0 1 0 16L12.9 99A8.9 8.9 0 0 1 0 91.2V9a8.9 8.9 0 0 1 12.8-8C33.2 11.1 73 31 95.1 42.1z"></path></svg>

After

Width:  |  Height:  |  Size: 309 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="SeekToHeadButton-icon"><path d="M65.4 46.6a4 4 0 0 0 0 6.8L94 71.2a4 4 0 0 0 6.1-3.4V32.2a4 4 0 0 0-6.1-3.4L65.4 46.6zM25.4 46.6a4 4 0 0 0 0 6.8L54 71.2a4 4 0 0 0 6.1-3.4V32.2a4 4 0 0 0-6.1-3.4L25.4 46.6zM0 4a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v92a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4z"></path></svg>

After

Width:  |  Height:  |  Size: 447 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="PlayerOptionButton-icon"><path d="M56.5 3.2A4 4 0 0 0 53 0H47a4 4 0 0 0-3.6 3L41 14a4 4 0 0 1-2.6 2.9c-1.3.4-2.5 1-3.7 1.5a4 4 0 0 1-3.9-.2l-9.3-5.8a4 4 0 0 0-4.8.4 52.2 52.2 0 0 0-4 4 4 4 0 0 0-.4 4.8l5.8 9.3a4 4 0 0 1 .2 4c-.6 1.1-1 2.3-1.5 3.6a4 4 0 0 1-2.9 2.6L3.2 43.5A4 4 0 0 0 0 47V53a4 4 0 0 0 3 3.6L14 59a4 4 0 0 1 2.9 2.6c.4 1.3 1 2.5 1.5 3.7a4 4 0 0 1-.2 3.9l-5.8 9.3a4 4 0 0 0 .4 4.8c1.2 1.4 2.6 2.8 4 4a4 4 0 0 0 4.8.4l9.3-5.8a4 4 0 0 1 4-.2c1.1.6 2.3 1 3.6 1.5a4 4 0 0 1 2.6 3l2.5 10.6A4 4 0 0 0 47 100H53a4 4 0 0 0 3.6-3L59 86a4 4 0 0 1 2.6-2.9c1.3-.4 2.5-1 3.7-1.5a4 4 0 0 1 3.9.2l9.3 5.8a4 4 0 0 0 4.8-.4c1.4-1.2 2.8-2.6 4-4a4 4 0 0 0 .4-4.8L82 69.2a4 4 0 0 1-.2-4c.6-1.1 1-2.3 1.5-3.6a4 4 0 0 1 3-2.6l10.6-2.5A4 4 0 0 0 100 53V47a4 4 0 0 0-3-3.6L86 41a4 4 0 0 1-2.9-2.6c-.4-1.3-1-2.5-1.5-3.7a4 4 0 0 1 .2-3.9l5.8-9.3a4 4 0 0 0-.4-4.8 52.2 52.2 0 0 0-4-4 4 4 0 0 0-4.8-.4L69.2 18a4 4 0 0 1-4 .2c-1.1-.6-2.3-1-3.6-1.5a4 4 0 0 1-2.6-2.9L56.5 3.2zm-6.5 27a19.9 19.9 0 0 1 0 39.7 19.9 19.9 0 1 1 0-39.7z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="MuteVideoButton-icon"><path d="M24.1 24L45.2 1.3a3.7 3.7 0 0 1 4.4-1C51 1.2 52 2.8 52 4.6v91c0 1.8-1 3.4-2.4 4.1-1.5.7-3.2.4-4.4-.9L24.2 76H8a8 8 0 0 1-8-8V32a8 8 0 0 1 8-8h16.1zm51.4-9.3l.5-.7 1.5-1.5a4 4 0 0 1 5.4-.2 49.9 49.9 0 0 1 0 75.3 4 4 0 0 1-5.4-.2A75 75 0 0 1 76 86a4 4 0 0 1 .2-5.8 40 40 0 0 0 9-11.1V69l.1-.2a39.3 39.3 0 0 0 4.4-14v-.3L90 50c0-10.6-4.1-20.2-10.8-27.3l-.1-.2a44 44 0 0 0-1-1H78l-.1-.2a35 35 0 0 0-2-1.8 4 4 0 0 1-.6-.8v-.3a4 4 0 0 1 .1-3.6v-.1zM62.2 27.8l1-1a4 4 0 0 1 5.4-.4l2.5 2.2a30 30 0 0 1-2.7 45v.1a4 4 0 0 1-2.2.7c-1.1 0-2.1-.4-3-1.1l-.9-1-.5-.5a4 4 0 0 1-.6-.8l-.1-.2a4 4 0 0 1 1-5A20 20 0 0 0 70 50a20 20 0 0 0-7.8-15.8A4 4 0 0 1 62 28l.2-.2z"></path></svg>

After

Width:  |  Height:  |  Size: 854 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="UnMuteVideoButton-icon"><path d="M24.1 24L45.2 1.3a3.7 3.7 0 0 1 4.4-1C51 1.2 52 2.8 52 4.6v91c0 1.8-1 3.4-2.4 4.1-1.5.7-3.2.4-4.4-.9L24.2 76H8a8 8 0 0 1-8-8V32a8 8 0 0 1 8-8h16.1zM80 42.7l11.5-11.4a4.4 4.4 0 0 1 6.1 0l1.1 1a4.4 4.4 0 0 1 0 6.2L87.3 50l11.4 11.5a4.4 4.4 0 0 1 0 6.1l-1 1.1a4.4 4.4 0 0 1-6.2 0L80 57.3 68.5 68.7a4.4 4.4 0 0 1-6.1 0l-1.1-1a4.4 4.4 0 0 1 0-6.2L72.7 50 61.3 38.5a4.4 4.4 0 0 1 0-6.1l1-1.1a4.4 4.4 0 0 1 6.2 0L80 42.7z"></path></svg>

After

Width:  |  Height:  |  Size: 620 B

View file

@ -0,0 +1 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.4" class="MuteVideoButton-icon"><path d="M24.1 24L45.2 1.3a3.7 3.7 0 0 1 4.4-1C51 1.2 52 2.8 52 4.6v91c0 1.8-1 3.4-2.4 4.1-1.5.7-3.2.4-4.4-.9L24.2 76H8a8 8 0 0 1-8-8V32a8 8 0 0 1 8-8h16.1zm51.4-9.3l.5-.7 1.5-1.5a4 4 0 0 1 5.4-.2 49.9 49.9 0 0 1 0 75.3 4 4 0 0 1-5.4-.2A75 75 0 0 1 76 86a4 4 0 0 1 .2-5.8 40 40 0 0 0 9-11.1V69l.1-.2a39.3 39.3 0 0 0 4.4-14v-.3L90 50c0-10.6-4.1-20.2-10.8-27.3l-.1-.2a44 44 0 0 0-1-1H78l-.1-.2a35 35 0 0 0-2-1.8 4 4 0 0 1-.6-.8v-.3a4 4 0 0 1 .1-3.6v-.1zM62.2 27.8l1-1a4 4 0 0 1 5.4-.4l2.5 2.2a30 30 0 0 1-2.7 45v.1a4 4 0 0 1-2.2.7c-1.1 0-2.1-.4-3-1.1l-.9-1-.5-.5a4 4 0 0 1-.6-.8l-.1-.2a4 4 0 0 1 1-5A20 20 0 0 0 70 50a20 20 0 0 0-7.8-15.8A4 4 0 0 1 62 28l.2-.2z"></path></svg>

After

Width:  |  Height:  |  Size: 854 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M24 10V2L14 12l10 10v-8c6.6 0 12 5.4 12 12s-5.4 12-12 12-12-5.4-12-12H8c0 8.8 7.2 16 16 16s16-7.2 16-16-7.2-16-16-16zm-2.2 22h-1.7v-6.5l-2 .6v-1.4l3.5-1.3h.2V32zm8.5-3.5c0 .6-.1 1.2-.2 1.6s-.3.8-.6 1.1-.6.5-.9.7-.7.2-1.2.2-.8-.1-1.2-.2-.7-.4-.9-.7-.5-.7-.6-1.1-.2-1-.2-1.6V27c0-.6.1-1.2.2-1.6s.3-.8.6-1.1.6-.5.9-.7.7-.2 1.2-.2.8.1 1.2.2.7.4.9.7.5.7.6 1.1.2 1 .2 1.6v1.5zm-1.6-1.7c0-.4 0-.7-.1-1s-.1-.5-.2-.6-.2-.3-.4-.3-.3-.1-.5-.1-.4 0-.5.1-.3.2-.4.3-.2.4-.2.6-.1.6-.1 1v1.9c0 .4 0 .7.1 1s.1.5.2.6.2.3.4.3.3.1.5.1.4 0 .5-.1.3-.2.4-.3.2-.4.2-.6.1-.6.1-1v-1.9z"/></svg>

After

Width:  |  Height:  |  Size: 640 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M8 26c0 8.8 7.2 16 16 16s16-7.2 16-16h-4c0 6.6-5.4 12-12 12s-12-5.4-12-12 5.4-12 12-12v8l10-10L24 2v8c-8.8 0-16 7.2-16 16zm13.7 6H20v-6.5l-2 .6v-1.4l3.5-1.3h.2V32zm8.5-3.5c0 .6-.1 1.2-.2 1.6s-.3.8-.6 1.1-.6.5-.9.7-.7.2-1.2.2-.8-.1-1.2-.2-.7-.4-.9-.7-.5-.7-.6-1.1-.2-1-.2-1.6V27c0-.6.1-1.2.2-1.6s.3-.8.6-1.1.6-.5.9-.7.7-.2 1.2-.2.8.1 1.2.2.7.4.9.7.5.7.6 1.1.2 1 .2 1.6v1.5zm-1.7-1.7c0-.4 0-.7-.1-1s-.1-.5-.2-.6-.2-.3-.4-.3-.3-.1-.5-.1-.4 0-.5.1-.3.2-.4.3-.2.4-.2.6-.1.6-.1 1v1.9c0 .4 0 .7.1 1s.1.5.2.6.2.3.4.3.3.1.5.1.4 0 .5-.1.3-.2.4-.3.2-.4.2-.6.1-.6.1-1v-1.9z"/></svg>

After

Width:  |  Height:  |  Size: 642 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z"/></svg>

After

Width:  |  Height:  |  Size: 172 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 36l17-12-17-12v24zm20-24v24h4V12h-4z"/></svg>

After

Width:  |  Height:  |  Size: 121 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 38h8V10h-8v28zm16-28v28h8V10h-8z"/></svg>

After

Width:  |  Height:  |  Size: 117 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M16 10v28l22-14z"/></svg>

After

Width:  |  Height:  |  Size: 97 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="-12 -12 48 48"><path d="M26 6H-8v4h34V6zm0-8H-8v4h34v-4zM-8 18h26v-4H-8v4zm30-4v12l10-6-10-6z"/></svg>

After

Width:  |  Height:  |  Size: 174 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 12h4v24h-4zm7 12l17 12V12z"/></svg>

After

Width:  |  Height:  |  Size: 111 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="18px" height="18px"><g><path d="M0,0h24v24H0V0z" fill="none"/><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></g></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M40 8H8c-2.21 0-4 1.79-4 4v24c0 2.21 1.79 4 4 4h32c2.21 0 4-1.79 4-4V12c0-2.21-1.79-4-4-4zM8 24h8v4H8v-4zm20 12H8v-4h20v4zm12 0h-8v-4h8v4zm0-8H20v-4h20v4z"/></svg>

After

Width:  |  Height:  |  Size: 235 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M37 24c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zm-27-6v12h8l10 10V8L18 18h-8z"/></svg>

After

Width:  |  Height:  |  Size: 171 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M33 24c0-3.53-2.04-6.58-5-8.05v4.42l4.91 4.91c.06-.42.09-.85.09-1.28zm5 0c0 1.88-.41 3.65-1.08 5.28l3.03 3.03C41.25 29.82 42 27 42 24c0-8.56-5.99-15.72-14-17.54v4.13c5.78 1.72 10 7.07 10 13.41zM8.55 6L6 8.55 15.45 18H6v12h8l10 10V26.55l8.51 8.51c-1.34 1.03-2.85 1.86-4.51 2.36v4.13a17.94 17.94 0 007.37-3.62L39.45 42 42 39.45l-18-18L8.55 6zM24 8l-4.18 4.18L24 16.36V8z"/></svg>

After

Width:  |  Height:  |  Size: 449 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M6 18v12h8l10 10V8L14 18H6zm27 6c0-3.53-2.04-6.58-5-8.05v16.11c2.96-1.48 5-4.53 5-8.06zM28 6.46v4.13c5.78 1.72 10 7.07 10 13.41s-4.22 11.69-10 13.41v4.13c8.01-1.82 14-8.97 14-17.54S36.01 8.28 28 6.46z"/></svg>

After

Width:  |  Height:  |  Size: 281 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M24 10V2L14 12l10 10v-8c6.6 0 12 5.4 12 12s-5.4 12-12 12-12-5.4-12-12H8c0 8.8 7.2 16 16 16s16-7.2 16-16-7.2-16-16-16zm-2.2 22h-1.7v-6.5l-2 .6v-1.4l3.5-1.3h.2V32zm8.5-3.5c0 .6-.1 1.2-.2 1.6s-.3.8-.6 1.1-.6.5-.9.7-.7.2-1.2.2-.8-.1-1.2-.2-.7-.4-.9-.7-.5-.7-.6-1.1-.2-1-.2-1.6V27c0-.6.1-1.2.2-1.6s.3-.8.6-1.1.6-.5.9-.7.7-.2 1.2-.2.8.1 1.2.2.7.4.9.7.5.7.6 1.1.2 1 .2 1.6v1.5zm-1.6-1.7c0-.4 0-.7-.1-1s-.1-.5-.2-.6-.2-.3-.4-.3-.3-.1-.5-.1-.4 0-.5.1-.3.2-.4.3-.2.4-.2.6-.1.6-.1 1v1.9c0 .4 0 .7.1 1s.1.5.2.6.2.3.4.3.3.1.5.1.4 0 .5-.1.3-.2.4-.3.2-.4.2-.6.1-.6.1-1v-1.9z"/></svg>

After

Width:  |  Height:  |  Size: 640 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M8 26c0 8.8 7.2 16 16 16s16-7.2 16-16h-4c0 6.6-5.4 12-12 12s-12-5.4-12-12 5.4-12 12-12v8l10-10L24 2v8c-8.8 0-16 7.2-16 16zm13.7 6H20v-6.5l-2 .6v-1.4l3.5-1.3h.2V32zm8.5-3.5c0 .6-.1 1.2-.2 1.6s-.3.8-.6 1.1-.6.5-.9.7-.7.2-1.2.2-.8-.1-1.2-.2-.7-.4-.9-.7-.5-.7-.6-1.1-.2-1-.2-1.6V27c0-.6.1-1.2.2-1.6s.3-.8.6-1.1.6-.5.9-.7.7-.2 1.2-.2.8.1 1.2.2.7.4.9.7.5.7.6 1.1.2 1 .2 1.6v1.5zm-1.7-1.7c0-.4 0-.7-.1-1s-.1-.5-.2-.6-.2-.3-.4-.3-.3-.1-.5-.1-.4 0-.5.1-.3.2-.4.3-.2.4-.2.6-.1.6-.1 1v1.9c0 .4 0 .7.1 1s.1.5.2.6.2.3.4.3.3.1.5.1.4 0 .5-.1.3-.2.4-.3.2-.4.2-.6.1-.6.1-1v-1.9z"/></svg>

After

Width:  |  Height:  |  Size: 642 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M14 28h-4v10h10v-4h-6v-6zm-4-8h4v-6h6v-4H10v10zm24 14h-6v4h10V28h-4v6zm-6-24v4h6v6h4V10H28z"/></svg>

After

Width:  |  Height:  |  Size: 172 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 36l17-12-17-12v24zm20-24v24h4V12h-4z"/></svg>

After

Width:  |  Height:  |  Size: 121 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 38h8V10h-8v28zm16-28v28h8V10h-8z"/></svg>

After

Width:  |  Height:  |  Size: 117 B

1
src/qml/icons/YouTube/play.svg Executable file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M16 10v28l22-14z"/></svg>

After

Width:  |  Height:  |  Size: 97 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="-12 -12 48 48"><path d="M26 6H-8v4h34V6zm0-8H-8v4h34v-4zM-8 18h26v-4H-8v4zm30-4v12l10-6-10-6z"/></svg>

After

Width:  |  Height:  |  Size: 174 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48"><path d="M12 12h4v24h-4zm7 12l17 12V12z"/></svg>

After

Width:  |  Height:  |  Size: 111 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24" fill="black" width="18px" height="18px"><g><path d="M0,0h24v24H0V0z" fill="none"/><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></g></svg>

After

Width:  |  Height:  |  Size: 1 KiB

Some files were not shown because too many files have changed in this diff Show more