99 Commits

Author SHA1 Message Date
Rafał Dzięgiel
5e40079480 gluploader: Handle DMABufs directly 2022-05-08 20:02:32 +02:00
Rafał Dzięgiel
4b93d4d132 meson: Cleanup plugin build script 2022-05-08 18:47:13 +02:00
Rafał Dzięgiel
b7b99c20cf meson: Do not go down in lib versioning
Install plugin lib stuff into a dir named "clapper-1.0" instead of "clapper-0.0". Even through our API is still very unstable, we cannot go down with versioning. The sink importers being installed there are not part of public API anyway.
2022-05-08 13:54:55 +02:00
Rafał Dzięgiel
5775738f67 plugin: Fix possible dir enumerator NULL unref
On an unlikely chance that sink was compiled without any importers, iterated dir does not exists thus we try to unref a NULL. Avoid doing that.
2022-05-07 13:43:51 +02:00
Rafał Dzięgiel
7159459160 meson: Cleanup of plugin build script 2022-05-06 21:08:03 +02:00
Rafał Dzięgiel
00650a596f obs: Use "trigger_services" step
Instead "rebuild_package" use "trigger_services" for OBS integration, otherwise the same old package is rebuild again and again.
2022-05-06 18:57:12 +02:00
Rafał Dzięgiel
6a995e2143 Merge pull request #253 from Rafostar/clappersink2
Move away from GtkGLArea
2022-05-06 17:52:56 +02:00
Rafał Dzięgiel
01b3feb213 New Crowdin updates (#247)
* New translations com.github.rafostar.Clapper.pot (Chinese Simplified)

* New translations com.github.rafostar.Clapper.pot (Portuguese)
2022-05-06 17:36:53 +02:00
Rafał Dzięgiel
5b9e7eacba meson: Do not auto build rawimporter when building gluploader
No need to auto build rawimporter if we are building gluploader as it
will be always loaded on startup, but never used.

Skip the build of it when its meson option is set to "auto".
2022-05-06 11:42:47 +02:00
Rafostar
b37ab432d7 player: Use new "clappersink" element
Use brand new clapper video sink for video output. Also add "CLAPPER_USE_LEGACY_SINK" env
to still allow the usage of old video sink if any problems arise.
2022-05-05 11:17:28 +02:00
Rafostar
0410c6e9b5 plugin: Add GL uploader
Add "clappergluploader" module which uses "glupload" and "glcolorconvert" internally,
allowing either uploading RAW system memory into GPU with GL colorspace conversion or
importing DMABufs into GL memory and making a GdkTexture out of them at one go.
2022-05-05 11:17:17 +02:00
Rafostar
3e0a0e0555 plugin: Add GL memory importer
Add "clapperglimporter" and a base class for creating GL importers. This module allows importing GL memory into GdkTexture.
2022-05-05 11:17:04 +02:00
Rafostar
5b7b7085e4 plugin: Add RAW system memory importer
Add "clapperrawimporter" module which allows importing RAW system memory mapped frames
2022-05-05 11:16:44 +02:00
Rafostar
044710f97e plugin: Add clapper GStreamer plugin
Add new GStreamer plugin with custom video sink for Clapper video player.

The main difference is that unlike the old one, this does not operate using `GtkGLArea`
anymore, but processes and displays `GdkTextures` directly through `GtkPicture` widget.
Also this one is installed like any other GStreamer plugin, thus can be used via
`gst-launch-1.0` binary or even by other GTK4 apps if they wish to integrate it.

In order to not depend on GL stuff at build time, this plugin uses seperate GModules
called "importers" in order to import different kind of memories into GdkTexture. This
allows expanding its capabilities further then we were able to do before.
2022-05-05 11:16:34 +02:00
Rafał Dzięgiel
b83d500352 Merge pull request #245 from Rafostar/crowdin_sync
New Crowdin updates
2022-04-11 21:02:50 +02:00
Rafał Dzięgiel
3fadf13125 api: Also check "USE_PLAYBIN3" env
Some mobile distros seem to be under assumption that playbin3 is required to
get HW accel and setting this env by default is good idea. Both are wrong.

Check this env presence to avoid problems as factory will always return
playbin3 if set (even if playbin2 was requested).
2022-04-11 19:44:05 +02:00
Rafał Dzięgiel
f8fe49a809 obs: Add "workflows.yml" file 2022-04-11 18:08:52 +02:00
Rafał Dzięgiel
264f0abb64 New translations com.github.rafostar.Clapper.pot (Dutch) 2022-04-08 18:14:27 +02:00
Rafał Dzięgiel
b330aa1ccd workflow: Increase default timeout time
AArch64 build is taking a lot of time...
2022-04-08 17:27:38 +02:00
Rafał Dzięgiel
de8ecb8f82 flatpak: Do not build pango anymore
GNOME 42 runtime includes version that is new enough
2022-04-08 17:08:32 +02:00
Rafał Dzięgiel
a2bb927502 Update LINGUAS file 2022-04-08 10:08:05 +02:00
Rafał Dzięgiel
df728f383b Merge pull request #237 from Rafostar/crowdin_sync
New Crowdin updates
2022-04-08 10:01:39 +02:00
Rafał Dzięgiel
c80d2e9fd6 New translations com.github.rafostar.Clapper.pot (Turkish) 2022-04-02 03:43:30 +02:00
Rafał Dzięgiel
5a00301935 New translations com.github.rafostar.Clapper.pot (Turkish) 2022-04-02 02:44:03 +02:00
Rafał Dzięgiel
46b5a6df96 New translations com.github.rafostar.Clapper.pot (Japanese) 2022-04-01 11:06:26 +02:00
Rafał Dzięgiel
f771e0320c Merge pull request #240 from Rafostar/pkgs
Update Flatpak, remove RPM specfiles
2022-04-01 11:05:22 +02:00
Rafał Dzięgiel
72a64a41d9 Update README.md 2022-04-01 09:42:06 +02:00
Rafał Dzięgiel
2818d3c91b flatpak: Enable network access for CI GTK4 builds
Make sure CI can satisfy latest GTK4 dependency requirements by allowing it to download libs it needs as fallback projects
2022-04-01 09:27:13 +02:00
Rafał Dzięgiel
ffb481b52b pkgs: Remove RPM build files from git
Clapper is now available in official repos on both Fedora and openSUSE. Considering that, I will not have time to properly maintain them here, so they go away.
2022-04-01 09:27:10 +02:00
Rafał Dzięgiel
c5c289d466 flatpak: Do not build libsoup3 anymore
It is now included in GNOME 42 runtime
2022-04-01 09:27:07 +02:00
Rafał Dzięgiel
3074051b3d flatpak: Build recent GTK 4.6.2 version
We are gonna move into depending on at least 4.6.0, build it for CI to pass
2022-04-01 09:26:58 +02:00
Rafał Dzięgiel
980e1d9e1a flatpak: Update git actions runtime to 42 2022-03-31 16:28:30 +02:00
Rafał Dzięgiel
045e4fc2c4 flatpak: Sync with Flathub 2022-03-31 16:20:47 +02:00
Rafał Dzięgiel
4f27739287 New translations com.github.rafostar.Clapper.pot (Japanese) 2022-03-31 10:08:37 +02:00
Rafał Dzięgiel
52aa7710dc Update README.md 2022-03-30 11:21:01 +02:00
Rafał Dzięgiel
0e49eaec7e New translations com.github.rafostar.Clapper.pot (Arabic) 2022-03-26 14:37:46 +01:00
Rafał Dzięgiel
62a923a3c1 New translations com.github.rafostar.Clapper.pot (Arabic) 2022-03-26 13:39:14 +01:00
Rafał Dzięgiel
f59b937316 New translations com.github.rafostar.Clapper.pot (Arabic) 2022-03-26 12:42:32 +01:00
Rafał Dzięgiel
8c914d0555 New translations com.github.rafostar.Clapper.pot (Arabic) 2022-03-25 22:09:19 +01:00
Rafał Dzięgiel
d2fab16093 New translations com.github.rafostar.Clapper.pot (Arabic) 2022-03-25 20:58:27 +01:00
Rafał Dzięgiel
67e877af0f New translations com.github.rafostar.Clapper.pot (Basque) 2022-03-25 17:45:32 +01:00
Rafał Dzięgiel
c825649a71 New translations com.github.rafostar.Clapper.pot (Basque) 2022-03-25 16:34:21 +01:00
Rafał Dzięgiel
5101fce5a7 Merge pull request #228 from Rafostar/nightly-fix
Flatpak nightly fixes
2022-03-11 00:30:05 +01:00
Rafał Dzięgiel
e0daf8435a flatpak-nightly: Use "--wrap-mode=nodownload" for GStreamer build
We do not have net access during build of GStreamer, nor we want to pull anything, so disable downloads so it will not try
2022-03-10 22:55:31 +01:00
Rafał Dzięgiel
9f18295728 flatpak-nightly: Do not disable GStreamer asserts and checks
We may disable them for stable releases, but should be left to default on nightly
2022-03-10 22:55:27 +01:00
Rafał Dzięgiel
8ce977505e actions: nightly: Manually install SDK extensions via workflow
The github actions Flatpak action only allows to specify single source
for installing missing dependencies. This makes impossible to build
some things from "gnome-nightly" while others from "flathub" repo.

Work around this limitation by manually installing missing SDK
extensions from Flathub prior to using this git action.
2022-03-10 22:55:24 +01:00
Rafał Dzięgiel
8ac839c9aa flatpak-nightly: Disable cloning GStreamer submodules
It seems that now GStreamer ships its testsuite (which we do not need) as a submodule. Do not try to clone it.
2022-03-10 22:55:09 +01:00
Rafał Dzięgiel
8fa2036265 actions: Allow runs on workflow_dispatch 2022-03-10 09:32:11 +01:00
Rafał Dzięgiel
1d5bb1e6aa Merge pull request #227 from Rafostar/nightly-rust
flatpak-nightly: Build dav1d decoder from gst-plugins-rs
2022-03-09 22:09:22 +01:00
Rafał Dzięgiel
9ec87c1b58 flatpak-nightly: Build dav1d decoder from gst-plugins-rs 2022-03-09 20:53:18 +01:00
Rafał Dzięgiel
16c0f8baae Merge pull request #226 from Rafostar/speed-fix
Fix end time calculation with with non-1x speed
2022-03-06 16:52:40 +01:00
Rafał Dzięgiel
c94d21fc53 Fix end time calculation with with non-1x speed 2022-03-06 15:51:06 +01:00
Rafał Dzięgiel
159f96c984 Merge pull request #214 from Rafostar/libsoup3
libsoup3
2022-02-28 16:58:29 +01:00
Rafał Dzięgiel
96ad4fa3db RPM: Require libsoup3 2022-02-17 15:44:45 +01:00
Rafał Dzięgiel
b2e7bef8d4 flatpak: Update git actions runtime to 41
We need to update, so we can build libsoup3. Flathub ver will likely remain as 40 ver to avoid some problems and will be updated directly to 42 later.
2022-02-14 17:37:15 +01:00
Rafał Dzięgiel
40a1dc6960 flatpak: Build libsoup3
Now both Clapper and Gtuber lib require libsoup3, so build it until it will be available in runtime
2022-02-14 17:37:15 +01:00
Rafał Dzięgiel
c4bd604e17 Port to libsoup3
With all apps and modules/plugins porting itself to libsoup3 we also need to do so.

It would appear as a good idea to conditionally import "3.0" and fallback to "2.4"
bindings here, but its not as loaded GStreamer plugins might use libsoup3 already
and we cannot have both libsoup2 and libsoup3 in a single process.
2022-02-14 17:37:15 +01:00
Rafał Dzięgiel
83c0e3b598 Remove unfinished web application
It used Broadway as an easy "reuse the same code into web application" way for me, but Broadway
is not very good for this. This feature should be made using some better dedicated framework for
building websites (so it can work better and support different screen sizes).

All the WebSocket functionality needed for this will remain (and be documented at a later point
when expanded and stable), so if anyone would like to make such remote controlling app, will be
free to do so (outside of Clapper code).
2022-02-14 17:37:15 +01:00
Rafał Dzięgiel
54715023c0 flatpak: Update git actions manifest 2022-02-14 16:36:29 +01:00
Rafał Dzięgiel
95046ceba8 flatpak: Sync with Flathub 2022-02-14 16:06:36 +01:00
Rafał Dzięgiel
b189e24b5e Merge pull request #209 from simonsextras/swdeish-tranlation-nbfreygff
Full Swedish Translation
2022-01-21 12:14:22 +01:00
sastofficial
cb9e2ac979 Update sv.po 2022-01-21 09:35:17 +01:00
sastofficial
545d8826e0 Update sv.po 2022-01-19 13:46:08 +01:00
Rafał Dzięgiel
c2160198b9 Update LINGUAS file 2022-01-19 10:07:09 +01:00
Rafał Dzięgiel
aa7eae2417 New translations com.github.rafostar.Clapper.pot (French) (#208) 2022-01-19 10:00:56 +01:00
Rafał Dzięgiel
3192a32845 Merge pull request #206 from Rafostar/unicode
Use more unicode characters
2022-01-18 18:46:06 +01:00
Rafał Dzięgiel
706c783498 Use unicode U+2215 for division 2022-01-17 22:28:17 +01:00
Rafał Dzięgiel
8ba3ca4af6 Use unicode U+00D7 for multiplication 2022-01-17 21:01:23 +01:00
Rafał Dzięgiel
38efa7ab9f Use unicode U+2236 for time 2022-01-17 20:27:13 +01:00
Rafał Dzięgiel
d19ddbcaca Merge pull request #204 from Rafostar/devel
Misc gstclapper fixes
2022-01-17 11:00:28 +01:00
Rafał Dzięgiel
615f1553fb flatpak: Sync with Flathub 2022-01-17 10:19:29 +01:00
Rafał Dzięgiel
09c9e7560b New Crowdin updates (#201)
* New translations com.github.rafostar.Clapper.pot (Swedish)

* New translations com.github.rafostar.Clapper.pot (Russian)

* New translations com.github.rafostar.Clapper.pot (Chinese Simplified)

* New translations com.github.rafostar.Clapper.pot (Spanish)

* New translations com.github.rafostar.Clapper.pot (Spanish)

* New translations com.github.rafostar.Clapper.pot (Basque)
2022-01-17 08:56:59 +01:00
Rafał Dzięgiel
f40ce756ad Merge pull request #199 from majjejjam/master
change three dots to ellipsis
2022-01-16 14:10:49 +01:00
majjejjam
23d51be185 changed to ellipses in ui/clapper.ui 2022-01-15 18:20:13 +01:00
Rafał Dzięgiel
0db2a4a045 gstclapper: Fix plugin feature leak 2022-01-14 09:28:26 +01:00
Rafostar
469c06d22b gstclapper: Go into NULL state directly when stopped
Instead of waiting another 60 seconds for user, go into NULL state ASAP to free all resources and open devices.
2022-01-09 13:31:37 +01:00
Rafostar
3feaf225b5 gstclapper: Make tags writable before insert
Someone might be holding a copied media info in which case tag
list ref count is not 1. Use gst_tag_list_make_writable to
assure that ref count is 1, so we can insert more tags into the list.
2022-01-09 13:31:29 +01:00
majjejjam
fc8d881efd change three dots to ellipsis 2022-01-08 16:28:07 +01:00
Rafał Dzięgiel
ac76836fd7 Merge pull request #192 from Rafostar/yt-removal
YouTube code removal
2022-01-05 14:27:59 +01:00
Rafał Dzięgiel
f6a1aaf1dc Remove all YouTube code
It has been broken for quite some time. From now on this is gonna be left to handle for GStreamer plugins.
2021-12-22 08:12:55 +01:00
Rafał Dzięgiel
000dca82d9 flatpak: Sync with Flathub 2021-12-20 21:23:31 +01:00
Rafał Dzięgiel
7a508fef39 0.4.1 2021-12-20 11:33:19 +01:00
Rafał Dzięgiel
d465d9f150 Make floating window update its title earlier
Instead of waiting till animation finishes, update the window title right away when changing modes
2021-12-15 16:58:54 +01:00
Rafał Dzięgiel
5e4dfb322c Append "PiP" suffix to window title when in floating mode
We are gonna take advantage of this simple change elsewhere
2021-12-15 12:44:23 +01:00
Rafał Dzięgiel
0c561ab4b3 Also allow enabling gtuber code path via env
Having to use "gtuber" URI scheme might be inconvenient, so also allow to
whitelist it with "GST_PLUGIN_FEATURE_RANK=gtubersrc:300" env
2021-12-10 11:05:00 +01:00
Rafał Dzięgiel
46ce261524 widget: Make sure we have caps before logging them 2021-12-10 11:03:06 +01:00
Rafał Dzięgiel
50aac8cdd8 gstclapper: Merge global tags instead replacing them
There is no guarantee that received later tags also contain values from
earlier ones, as they might come from different element.
Combine them instead while replacing old values with newer ones.
2021-12-02 08:52:35 +01:00
Rafał Dzięgiel
810aea476f Use gtuber lib for URIs with "gtuber" scheme
Take a different code path when URI uses "gtuber" scheme.
This allows testing new WIP lib as an opt-in.
2021-11-29 22:33:53 +01:00
Rafał Dzięgiel
24905f1d60 flatpak: Build gtuber 2021-11-29 22:30:21 +01:00
Rafał Dzięgiel
82e3c9a52f prefs: Add can-swipe-back compat with latest libadwaita
Recent libadwaita has renamed "can-swipe-back" into "can-navigate-back".
Set both in JS code instead of UI file in order to support all libadwaita versions.

Fixes #185
2021-11-29 10:13:06 +01:00
Rafał Dzięgiel
654b8aaf60 prefs: Fix expander rows compat with latest libadwaita 2021-11-19 18:43:01 +01:00
Rafał Dzięgiel
3c0e33e4a4 css: Few override fixes for latest libadwaita 2021-11-19 18:40:34 +01:00
Rafał Dzięgiel
d2df1c3bd8 app: Use Adw.StyleManager to enable dark-theme
Latest libadwaita (for reasons unknown to me) totally ignores/breaks dark theme usage with gtk_application_prefer_dark_theme property. Lets just try using the new Adw.StyleManager without asking questions why.
2021-11-18 22:42:05 +01:00
Rafał Dzięgiel
af24073590 Update LINGUAS file 2021-11-18 15:37:48 +01:00
Rafał Dzięgiel
44cee14eb2 New translations com.github.rafostar.Clapper.pot (Portuguese) (#175) 2021-11-18 15:34:16 +01:00
Rafał Dzięgiel
b853685dd4 gstclapper: Fix decoder stream ID string leak 2021-10-22 13:11:42 +02:00
Rafał Dzięgiel
15461dd38a gstclapper: Fix video/audio decoder change detection
The video/audio decoder changed signal was not working correctly in case of
multiple streams with multiple decoders in single file.

We need to listen to the current-(video/audio) signal, when it changes find
corresponding "input-selector", get stream ID from its active pad and then
find the decoder in the pipeline that handles this stream ID. Similarly for
playbin3, but use stream ID from the "streams-selected" signal.
2021-10-22 11:05:08 +02:00
Rafał Dzięgiel
1c1989bc32 gstclapper: Fix GST_PLUGIN_FEATURE_RANK env usage
We change few default plugin ranks during init to whitelist them, but we do not update
their values from GST_PLUGIN_FEATURE_RANK env afterwards, so do that.
The ENV should be preferred over default config.
2021-10-21 11:21:07 +02:00
Rafał Dzięgiel
e9c9ae073f flatpak-nightly: Enable GPL gstreamer plugins 2021-10-19 10:23:41 +02:00
104 changed files with 6323 additions and 2103 deletions

View File

@@ -1,4 +1,5 @@
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
name: "Flatpak Nightly"
@@ -6,6 +7,7 @@ jobs:
flatpak:
name: "Flatpak"
runs-on: ubuntu-latest
timeout-minutes: 600
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-nightly
options: --privileged
@@ -25,8 +27,12 @@ jobs:
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- name: Prepare Runtime
run: |
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.rust-nightly/${{ matrix.arch }}/21.08
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.llvm13/${{ matrix.arch }}/21.08
- uses: bilelmoussaoui/flatpak-github-actions/flatpak-builder@v4
name: "Build"
name: Build
with:
bundle: com.github.rafostar.Clapper.flatpak
manifest-path: pkgs/flatpak/com.github.rafostar.Clapper-nightly.json

View File

@@ -1,4 +1,5 @@
on:
workflow_dispatch:
push:
branches:
- master
@@ -10,8 +11,9 @@ jobs:
flatpak:
name: "Flatpak"
runs-on: ubuntu-latest
timeout-minutes: 600
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-40
image: bilelmoussaoui/flatpak-github-actions:gnome-42
options: --privileged
strategy:
matrix:

10
.obs/workflows.yml Normal file
View File

@@ -0,0 +1,10 @@
rebuild_master:
steps:
- trigger_services:
project: home:Rafostar
package: clapper
filters:
event: push
branches:
only:
- master

View File

@@ -2,6 +2,8 @@
[![Flatpak](https://github.com/Rafostar/clapper/actions/workflows/flatpak.yml/badge.svg?event=push)](https://github.com/Rafostar/clapper/actions/workflows/flatpak.yml)
[![Flatpak Nightly](https://github.com/Rafostar/clapper/actions/workflows/flatpak-nightly.yml/badge.svg?event=schedule)](https://github.com/Rafostar/clapper/actions/workflows/flatpak-nightly.yml)
[![Crowdin](https://badges.crowdin.net/clapper/localized.svg)](https://crowdin.com/project/clapper)
[![Matrix](https://img.shields.io/matrix/clapper-player:matrix.org?label=matrix)](https://matrix.to/#/#clapper-player:matrix.org)
[![Donate](https://img.shields.io/liberapay/receives/Clapper.svg?logo=liberapay)](https://liberapay.com/Clapper)
A GNOME media player built using [GJS](https://gitlab.gnome.org/GNOME/gjs) with [GTK4](https://www.gtk.org) toolkit.
The media player uses [GStreamer](https://gstreamer.freedesktop.org/) as a media backend and renders everything via [OpenGL](https://www.opengl.org).
@@ -38,17 +40,13 @@ List of patches used in this version can be found [here](https://github.com/Rafo
<img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/>
</a>
## Packages
#### Fedora & openSUSE
Pre-built packages are available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper)).<br>
Those are automatically built on each git commit, and are thus considered unstable.
## Packages in Linux Distributions
[![Packaging status](https://repology.org/badge/vertical-allrepos/clapper.svg)](https://repology.org/project/clapper/versions)
#### Arch Linux
You can get Clapper from the AUR:
* [clapper](https://aur.archlinux.org/packages/clapper) (stable version)
* [clapper-git](https://aur.archlinux.org/packages/clapper-git)
Pre-built RPM packages are also available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper)).<br>
Those are automatically built on each git commit, thus are considered unstable.
## Installation from source code
## Installation from Source Code
```sh
meson builddir --prefix=/usr/local
sudo meson install -C builddir

View File

@@ -6,4 +6,4 @@ imports.package.init({
prefix: '@prefix@',
libdir: '@libdir@',
});
imports.package.run(imports.src.main@ID_POSTFIX@);
imports.package.run(imports.src.main);

View File

@@ -1,25 +1,19 @@
clapper_apps = ['', 'Remote', 'Daemon']
bin_conf = configuration_data()
foreach id_postfix : clapper_apps
app_postfix = (id_postfix != '') ? '.' + id_postfix : ''
bin_conf.set('GJS', find_program('gjs').path())
bin_conf.set('PACKAGE_NAME', meson.project_name())
bin_conf.set('PACKAGE_VERSION', meson.project_version())
bin_conf.set('prefix', get_option('prefix'))
bin_conf.set('libdir', libdir)
bin_conf = configuration_data()
bin_conf.set('GJS', find_program('gjs').path())
bin_conf.set('PACKAGE_NAME', meson.project_name())
bin_conf.set('PACKAGE_VERSION', meson.project_version())
bin_conf.set('ID_POSTFIX', id_postfix)
bin_conf.set('prefix', get_option('prefix'))
bin_conf.set('libdir', libdir)
configure_file(
input: 'com.github.rafostar.Clapper.in',
output: 'com.github.rafostar.Clapper' + app_postfix,
configuration: bin_conf,
install: true,
install_dir: bindir,
install_mode: 'rwxr-xr-x'
)
endforeach
configure_file(
input: 'com.github.rafostar.Clapper.in',
output: 'com.github.rafostar.Clapper',
configuration: bin_conf,
install: true,
install_dir: bindir,
install_mode: 'rwxr-xr-x'
)
clapper_symlink_cmd = 'ln -fs @0@ $DESTDIR@1@'.format(
'com.github.rafostar.Clapper',

View File

@@ -10,6 +10,9 @@ scrolledwindow scrollbar.vertical slider {
}
/* Adwaita is missing osd ListBox */
.clapperplaylist {
background: none;
}
.clapperplaylist row {
border-radius: 5px;
}
@@ -28,9 +31,6 @@ scrolledwindow scrollbar.vertical slider {
margin-left: 2px;
margin-right: 2px;
}
.osd .clapperplaylist {
background: none;
}
.osd .clapperplaylist row image {
-gtk-icon-shadow: none;
}
@@ -223,6 +223,9 @@ scale trough slider {
.fullscreen.tvmode .positionscale marks.bottom {
margin-top: 2px;
}
.fullscreen.tvmode .positionscale trough {
border-radius: 3px;
}
.fullscreen.tvmode .positionscale trough highlight {
border-radius: 3px;
min-height: 20px;

View File

@@ -103,16 +103,6 @@
<summary>Set PlayFlags for playbin</summary>
</key>
<!-- Gtuber -->
<key name="yt-adaptive-enabled" type="b">
<default>false</default>
<summary>Enable to use adaptive streaming</summary>
</key>
<key name="yt-quality-type" type="i">
<default>1</default>
<summary>Max online video quality type</summary>
</key>
<!-- Other -->
<key name="window-size" type="s">
<default>'[800, 490]'</default>

View File

@@ -48,6 +48,34 @@
</screenshot>
</screenshots>
<releases>
<release version="0.4.1" date="2021-12-20">
<description>
<p>Fixes:</p>
<ul>
<li>Compatibility with more recent libadwaita versions</li>
<li>Toggle mute with M button alone</li>
<li>Allow handling YouTube with external GStreamer plugins</li>
<li>Fix catching errors when reading clipboard</li>
<li>Fix missing translator-credits</li>
<li>Fix missing gio-unix-2.0 dep</li>
<li>Fix playback pausing when entering fullscreen with touchscreen</li>
<li>Fix GST_PLUGIN_FEATURE_RANK env usage</li>
<li>Fix video/audio decoder change detection</li>
<li>Merge global video tags instead replacing them</li>
<li>Few other misc bug fixes</li>
</ul>
<p>New translations:</p>
<ul>
<li>Chinese Simplified</li>
<li>Czech</li>
<li>Hungarian</li>
<li>Portuguese</li>
<li>Portuguese, Brazilian</li>
<li>Russian</li>
<li>Spanish</li>
</ul>
</description>
</release>
<release version="0.4.0" date="2021-09-12">
<description>
<p>Changes:</p>

View File

@@ -152,7 +152,7 @@ struct _GstClapper
GstBus *bus;
GstState target_state, current_state;
gboolean is_live;
GSource *tick_source, *ready_timeout_source;
GSource *tick_source;
GstClockTime cached_duration;
gdouble rate;
@@ -253,6 +253,9 @@ static void gst_clapper_audio_info_update (GstClapper * self,
static void gst_clapper_subtitle_info_update (GstClapper * self,
GstClapperStreamInfo * stream_info);
static gboolean find_active_decoder_with_stream_id (GstClapper * self,
GstElementFactoryListType type, const gchar * stream_id);
/* For playbin3 */
static void gst_clapper_streams_info_create_from_collection (GstClapper * self,
GstClapperMediaInfo * media_info, GstStreamCollection * collection);
@@ -1135,44 +1138,6 @@ remove_tick_source (GstClapper * self)
self->tick_source = NULL;
}
static gboolean
ready_timeout_cb (gpointer user_data)
{
GstClapper *self = user_data;
if (self->target_state <= GST_STATE_READY) {
GST_DEBUG_OBJECT (self, "Setting pipeline to NULL state");
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_NULL;
gst_element_set_state (self->playbin, GST_STATE_NULL);
}
return G_SOURCE_REMOVE;
}
static void
add_ready_timeout_source (GstClapper * self)
{
if (self->ready_timeout_source)
return;
self->ready_timeout_source = g_timeout_source_new_seconds (60);
g_source_set_callback (self->ready_timeout_source,
(GSourceFunc) ready_timeout_cb, self, NULL);
g_source_attach (self->ready_timeout_source, self->context);
}
static void
remove_ready_timeout_source (GstClapper * self)
{
if (!self->ready_timeout_source)
return;
g_source_destroy (self->ready_timeout_source);
g_source_unref (self->ready_timeout_source);
self->ready_timeout_source = NULL;
}
typedef struct
{
GstClapper *clapper;
@@ -1217,7 +1182,6 @@ emit_error (GstClapper * self, GError * err)
g_error_free (err);
remove_tick_source (self);
remove_ready_timeout_source (self);
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_NULL;
@@ -1862,6 +1826,17 @@ media_info_update (GstClapper * self, GstClapperMediaInfo * info)
"image_sample: %p", info->title, info->container, info->image_sample);
}
static void
merge_tags (GstTagList **my_tags, GstTagList *tags)
{
if (*my_tags) {
*my_tags = gst_tag_list_make_writable (*my_tags);
gst_tag_list_insert (*my_tags, tags, GST_TAG_MERGE_REPLACE);
} else {
*my_tags = gst_tag_list_ref (tags);
}
}
static void
tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
{
@@ -1877,17 +1852,12 @@ tags_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg, gpointer user_data)
if (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL) {
g_mutex_lock (&self->lock);
if (self->media_info) {
if (self->media_info->tags)
gst_tag_list_unref (self->media_info->tags);
self->media_info->tags = gst_tag_list_ref (tags);
merge_tags (&self->media_info->tags, tags);
media_info_update (self, self->media_info);
g_mutex_unlock (&self->lock);
} else {
if (self->global_tags)
gst_tag_list_unref (self->global_tags);
self->global_tags = gst_tag_list_ref (tags);
g_mutex_unlock (&self->lock);
merge_tags (&self->global_tags, tags);
}
g_mutex_unlock (&self->lock);
}
gst_tag_list_unref (tags);
@@ -2050,6 +2020,7 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
{
GstClapper *self = GST_CLAPPER (user_data);
GstStreamCollection *collection = NULL;
gchar *video_sid, *audio_sid;
guint i, len;
gst_message_parse_streams_selected (msg, &collection);
@@ -2098,7 +2069,22 @@ streams_selected_cb (G_GNUC_UNUSED GstBus * bus, GstMessage * msg,
*current_sid = g_strdup (stream_id);
}
video_sid = g_strdup (self->video_sid);
audio_sid = g_strdup (self->audio_sid);
g_mutex_unlock (&self->lock);
if (video_sid) {
find_active_decoder_with_stream_id (self, GST_ELEMENT_FACTORY_TYPE_DECODER
| GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, video_sid);
g_free (video_sid);
}
if (audio_sid) {
find_active_decoder_with_stream_id (self, GST_ELEMENT_FACTORY_TYPE_DECODER
| GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, audio_sid);
g_free (audio_sid);
}
}
static gboolean
@@ -3009,11 +2995,12 @@ decoder_changed_signal_data_free (DecoderChangedSignalData * data)
static void
emit_decoder_changed (GstClapper * self, gchar * decoder_name,
gboolean is_video)
GstElementFactoryListType type)
{
GstClapperSignalDispatcherFunc func = NULL;
if (is_video) {
if ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) ==
GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) {
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_VIDEO_DECODER_CHANGED], 0, NULL, NULL, NULL) != 0 &&
g_strcmp0 (self->last_vdecoder, decoder_name) != 0) {
@@ -3021,7 +3008,8 @@ emit_decoder_changed (GstClapper * self, gchar * decoder_name,
g_free (self->last_vdecoder);
self->last_vdecoder = g_strdup (decoder_name);
}
} else {
} else if ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) ==
GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) {
if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID,
signals[SIGNAL_AUDIO_DECODER_CHANGED], 0, NULL, NULL, NULL) != 0 &&
g_strcmp0 (self->last_adecoder, decoder_name) != 0) {
@@ -3042,6 +3030,138 @@ emit_decoder_changed (GstClapper * self, gchar * decoder_name,
}
}
static gboolean
iterate_decoder_pads (GstClapper * self, GstElement * element,
const gchar * stream_id, GstElementFactoryListType type)
{
GstIterator *iter;
GValue value = { 0, };
gboolean found = FALSE;
iter = gst_element_iterate_src_pads (element);
while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) {
GstPad *decoder_pad = g_value_get_object (&value);
gchar *decoder_stream_id = gst_pad_get_stream_id (decoder_pad);
GST_DEBUG_OBJECT (self, "Decoder stream: %s", decoder_stream_id);
/* In case of playbin3, pad may not be active yet */
if ((found = (g_strcmp0 (decoder_stream_id, stream_id) == 0
|| (!decoder_stream_id && self->use_playbin3)))) {
GstElementFactory *factory;
gchar *plugin_name;
factory = gst_element_get_factory (element);
plugin_name = gst_object_get_name (GST_OBJECT_CAST (factory));
if (plugin_name) {
GST_DEBUG_OBJECT (self, "Found decoder: %s", plugin_name);
emit_decoder_changed (self, plugin_name, type);
g_free (plugin_name);
}
}
g_free (decoder_stream_id);
g_value_unset (&value);
if (found)
break;
}
gst_iterator_free (iter);
return found;
}
static gboolean
find_active_decoder_with_stream_id (GstClapper * self, GstElementFactoryListType type,
const gchar * stream_id)
{
GstIterator *iter;
GValue value = { 0, };
gboolean found = FALSE;
GST_DEBUG_OBJECT (self, "Searching for decoder with stream: %s", stream_id);
iter = gst_bin_iterate_recurse (GST_BIN (self->playbin));
while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) {
GstElement *element = g_value_get_object (&value);
GstElementFactory *factory = gst_element_get_factory (element);
if (factory && gst_element_factory_list_is_type (factory, type))
found = iterate_decoder_pads (self, element, stream_id, type);
g_value_unset (&value);
if (found)
break;
}
gst_iterator_free (iter);
return found;
}
static void
update_current_decoder (GstClapper *self, GstElementFactoryListType type)
{
GstIterator *iter;
GValue value = { 0, };
iter = gst_bin_iterate_all_by_element_factory_name (
GST_BIN (self->playbin), "input-selector");
while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) {
GstElement *element = g_value_get_object (&value);
GstPad *active_pad;
gboolean found = FALSE;
g_object_get (G_OBJECT (element), "active-pad", &active_pad, NULL);
if (active_pad) {
gchar *stream_id;
stream_id = gst_pad_get_stream_id (active_pad);
gst_object_unref (active_pad);
if (stream_id) {
found = find_active_decoder_with_stream_id (self, type, stream_id);
g_free (stream_id);
}
}
g_value_unset (&value);
if (found)
break;
}
gst_iterator_free (iter);
}
static void
current_video_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
GstClapper * self)
{
GstElementFactoryListType type = GST_ELEMENT_FACTORY_TYPE_DECODER
| GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO;
update_current_decoder (self, type);
}
static void
current_audio_notify_cb (G_GNUC_UNUSED GObject * obj, G_GNUC_UNUSED GParamSpec * pspec,
GstClapper * self)
{
GstElementFactoryListType type = GST_ELEMENT_FACTORY_TYPE_DECODER
| GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO;
update_current_decoder (self, type);
}
static void
element_setup_cb (GstElement * playbin, GstElement * element, GstClapper * self)
{
@@ -3054,13 +3174,6 @@ element_setup_cb (GstElement * playbin, GstElement * element, GstClapper * self)
if (plugin_name) {
GST_INFO_OBJECT (self, "Plugin setup: %s", plugin_name);
if (gst_element_factory_list_is_type (factory,
GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO))
emit_decoder_changed (self, plugin_name, TRUE);
else if (gst_element_factory_list_is_type (factory,
GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO))
emit_decoder_changed (self, plugin_name, FALSE);
/* TODO: Set plugin props */
}
g_free (plugin_name);
@@ -3109,6 +3222,10 @@ gst_clapper_main (gpointer data)
_update_from_env (&self->use_playbin3, "GST_CLAPPER_USE_PLAYBIN3");
/* Takes precedence over `GST_CLAPPER_USE_PLAYBIN3` as it
* influences element factory behavior */
_update_from_env (&self->use_playbin3, "USE_PLAYBIN3");
if (self->use_playbin3) {
GST_DEBUG_OBJECT (self, "playbin3 enabled");
self->playbin = gst_element_factory_make ("playbin3", "playbin3");
@@ -3229,6 +3346,11 @@ gst_clapper_main (gpointer data)
G_CALLBACK (audio_tags_changed_cb), self);
g_signal_connect (self->playbin, "text-tags-changed",
G_CALLBACK (subtitle_tags_changed_cb), self);
g_signal_connect (self->playbin, "notify::current-video",
G_CALLBACK (current_video_notify_cb), self);
g_signal_connect (self->playbin, "notify::current-audio",
G_CALLBACK (current_audio_notify_cb), self);
}
g_signal_connect (self->playbin, "notify::volume",
@@ -3255,7 +3377,6 @@ gst_clapper_main (gpointer data)
gst_object_unref (bus);
remove_tick_source (self);
remove_ready_timeout_source (self);
g_mutex_lock (&self->lock);
if (self->media_info) {
@@ -3324,6 +3445,104 @@ gst_clapper_has_plugin_with_features (const gchar * name)
return ret;
}
static gboolean
parse_feature_name (gchar * str, const gchar ** feature)
{
if (!str)
return FALSE;
g_strstrip (str);
if (str[0] != '\0') {
*feature = str;
return TRUE;
}
return FALSE;
}
static gboolean
parse_feature_rank (gchar * str, GstRank * rank)
{
if (!str)
return FALSE;
g_strstrip (str);
if (g_ascii_isdigit (str[0])) {
unsigned long l;
char *endptr;
l = strtoul (str, &endptr, 10);
if (endptr > str && endptr[0] == 0) {
*rank = (GstRank) l;
} else {
return FALSE;
}
} else if (g_ascii_strcasecmp (str, "NONE") == 0) {
*rank = GST_RANK_NONE;
} else if (g_ascii_strcasecmp (str, "MARGINAL") == 0) {
*rank = GST_RANK_MARGINAL;
} else if (g_ascii_strcasecmp (str, "SECONDARY") == 0) {
*rank = GST_RANK_SECONDARY;
} else if (g_ascii_strcasecmp (str, "PRIMARY") == 0) {
*rank = GST_RANK_PRIMARY;
} else if (g_ascii_strcasecmp (str, "MAX") == 0) {
*rank = (GstRank) G_MAXINT;
} else {
return FALSE;
}
return TRUE;
}
static void
_env_feature_rank_update (void)
{
const gchar *env;
gchar **split, **walk;
env = g_getenv ("GST_PLUGIN_FEATURE_RANK");
if (!env)
return;
split = g_strsplit (env, ",", 0);
for (walk = split; *walk; walk++) {
if (strchr (*walk, ':')) {
gchar **values;
values = g_strsplit (*walk, ":", 2);
if (values[0] && values[1]) {
GstRank rank;
const gchar *name;
if (parse_feature_name (values[0], &name)
&& parse_feature_rank (values[1], &rank)) {
GstPluginFeature *feature;
feature = gst_registry_find_feature (gst_registry_get (), name,
GST_TYPE_ELEMENT_FACTORY);
if (feature) {
GstRank old_rank;
old_rank = gst_plugin_feature_get_rank (feature);
if (old_rank != rank) {
gst_plugin_feature_set_rank (feature, rank);
GST_DEBUG ("Updated rank from env: %i -> %i for %s", old_rank, rank, name);
}
gst_object_unref (feature);
}
}
}
g_strfreev (values);
}
}
g_strfreev (split);
}
static void
gst_clapper_prepare_gstreamer (void)
{
@@ -3346,6 +3565,9 @@ gst_clapper_prepare_gstreamer (void)
gst_clapper_set_feature_rank ("v4l2slvp8dec", GST_RANK_NONE);
}
/* After setting defaults, update them from ENV */
_env_feature_rank_update ();
gst_clapper_gstreamer_prepared = TRUE;
GST_DEBUG ("GStreamer plugins prepared");
}
@@ -3422,7 +3644,6 @@ gst_clapper_play_internal (gpointer user_data)
}
g_mutex_unlock (&self->lock);
remove_ready_timeout_source (self);
self->target_state = GST_STATE_PLAYING;
if (self->current_state < GST_STATE_PAUSED)
@@ -3490,7 +3711,6 @@ gst_clapper_pause_internal (gpointer user_data)
tick_cb (self);
remove_tick_source (self);
remove_ready_timeout_source (self);
self->target_state = GST_STATE_PAUSED;
@@ -3570,13 +3790,11 @@ gst_clapper_stop_internal (GstClapper * self, gboolean transient)
tick_cb (self);
remove_tick_source (self);
add_ready_timeout_source (self);
self->target_state = GST_STATE_NULL;
self->current_state = GST_STATE_READY;
self->current_state = GST_STATE_NULL;
self->is_live = FALSE;
gst_bus_set_flushing (self->bus, TRUE);
gst_element_set_state (self->playbin, GST_STATE_READY);
gst_element_set_state (self->playbin, GST_STATE_NULL);
gst_bus_set_flushing (self->bus, FALSE);
change_state (self, transient && self->app_state != GST_CLAPPER_STATE_STOPPED
? GST_CLAPPER_STATE_BUFFERING : GST_CLAPPER_STATE_STOPPED);

6
lib/gst/meson.build vendored
View File

@@ -1 +1,5 @@
subdir('clapper')
if get_option('lib')
subdir('clapper')
endif
subdir('plugin')

439
lib/gst/plugin/gstclapperimporter.c vendored Normal file
View File

@@ -0,0 +1,439 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperimporter.h"
#include "gstgtkutils.h"
#define GST_CAT_DEFAULT gst_clapper_importer_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_importer_parent_class
G_DEFINE_TYPE (GstClapperImporter, gst_clapper_importer, GST_TYPE_OBJECT);
typedef struct
{
GdkTexture *texture;
GstVideoOverlayRectangle *rectangle;
gint x, y;
guint width, height;
gint index;
gatomicrefcount ref_count;
} GstClapperGdkOverlay;
static GstClapperGdkOverlay *
gst_clapper_gdk_overlay_new (GdkTexture *texture, GstVideoOverlayRectangle *rectangle,
gint x, gint y, guint width, guint height, guint index)
{
GstClapperGdkOverlay *overlay = g_slice_new (GstClapperGdkOverlay);
overlay->texture = g_object_ref (texture);
overlay->rectangle = gst_video_overlay_rectangle_ref (rectangle);
overlay->x = x;
overlay->y = y;
overlay->width = width;
overlay->height = height;
overlay->index = index;
g_atomic_ref_count_init (&overlay->ref_count);
return overlay;
}
static GstClapperGdkOverlay *
gst_clapper_gdk_overlay_ref (GstClapperGdkOverlay *overlay)
{
g_atomic_ref_count_inc (&overlay->ref_count);
return overlay;
}
static void
gst_clapper_gdk_overlay_unref (GstClapperGdkOverlay *overlay)
{
if (g_atomic_ref_count_dec (&overlay->ref_count)) {
GST_TRACE ("Freeing overlay: %" GST_PTR_FORMAT, overlay);
g_object_unref (overlay->texture);
gst_video_overlay_rectangle_unref (overlay->rectangle);
g_slice_free (GstClapperGdkOverlay, overlay);
}
}
static GstBufferPool *
_default_create_pool (GstClapperImporter *self, GstStructure **config)
{
GST_FIXME_OBJECT (self, "Need to create buffer pool");
return NULL;
}
static GdkTexture *
_default_generate_texture (GstClapperImporter *self,
GstBuffer *buffer, GstVideoInfo *v_info)
{
GST_FIXME_OBJECT (self, "GdkTexture generation not implemented");
return NULL;
}
static void
gst_clapper_importer_init (GstClapperImporter *self)
{
gst_video_info_init (&self->pending_v_info);
gst_video_info_init (&self->v_info);
self->pending_overlays = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_clapper_gdk_overlay_unref);
self->overlays = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_clapper_gdk_overlay_unref);
gdk_rgba_parse (&self->bg, "black");
}
static void
gst_clapper_importer_finalize (GObject *object)
{
GstClapperImporter *self = GST_CLAPPER_IMPORTER_CAST (object);
GST_TRACE ("Finalize");
gst_clear_buffer (&self->pending_buffer);
gst_clear_buffer (&self->buffer);
g_ptr_array_unref (self->pending_overlays);
g_ptr_array_unref (self->overlays);
g_clear_object (&self->texture);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_importer_class_init (GstClapperImporterClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimporter", 0,
"Clapper Importer");
gobject_class->finalize = gst_clapper_importer_finalize;
importer_class->create_pool = _default_create_pool;
importer_class->generate_texture = _default_generate_texture;
}
static GstClapperGdkOverlay *
_get_cached_overlay (GPtrArray *overlays, GstVideoOverlayRectangle *rectangle)
{
guint i;
for (i = 0; i < overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (overlays, i);
if (overlay->rectangle == rectangle)
return overlay;
}
return NULL;
}
static gint
_sort_overlays_cb (gconstpointer a, gconstpointer b)
{
GstClapperGdkOverlay *overlay_a, *overlay_b;
overlay_a = *((GstClapperGdkOverlay **) a);
overlay_b = *((GstClapperGdkOverlay **) b);
return (overlay_a->index - overlay_b->index);
}
/*
* Prepares overlays to show with the next rendered buffer.
*
* In order for overlays caching to work correctly, this should be called for
* every received buffer (even if its going to be disgarded), also must be
* called together with pending buffer replacement within a single importer
* locking, to make sure prepared overlays always match the pending buffer.
*/
static void
gst_clapper_importer_prepare_overlays_locked (GstClapperImporter *self)
{
GstVideoOverlayCompositionMeta *comp_meta;
guint num_overlays, i;
if (G_UNLIKELY (!self->pending_buffer)
|| !(comp_meta = gst_buffer_get_video_overlay_composition_meta (self->pending_buffer))) {
guint n_pending = self->pending_overlays->len;
/* Remove all cached overlays if new buffer does not have any */
if (n_pending > 0) {
GST_TRACE ("No overlays in buffer, removing all cached ones");
g_ptr_array_remove_range (self->pending_overlays, 0, n_pending);
}
return;
}
GST_LOG_OBJECT (self, "Preparing overlays...");
/* Mark all old overlays as unused by giving them negative index */
for (i = 0; i < self->pending_overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i);
overlay->index = -1;
}
num_overlays = gst_video_overlay_composition_n_rectangles (comp_meta->overlay);
for (i = 0; i < num_overlays; i++) {
GdkTexture *texture;
GstBuffer *comp_buffer;
GstVideoFrame comp_frame;
GstVideoMeta *v_meta;
GstVideoInfo v_info;
GstVideoOverlayRectangle *rectangle;
GstClapperGdkOverlay *overlay;
GstVideoOverlayFormatFlags flags, alpha_flags = 0;
gint comp_x, comp_y;
guint comp_width, comp_height;
rectangle = gst_video_overlay_composition_get_rectangle (comp_meta->overlay, i);
if ((overlay = _get_cached_overlay (self->pending_overlays, rectangle))) {
overlay->index = i;
GST_TRACE ("Reusing cached overlay: %" GST_PTR_FORMAT, overlay);
continue;
}
if (G_UNLIKELY (!gst_video_overlay_rectangle_get_render_rectangle (rectangle,
&comp_x, &comp_y, &comp_width, &comp_height))) {
GST_WARNING ("Invalid overlay rectangle dimensions: %" GST_PTR_FORMAT, rectangle);
continue;
}
flags = gst_video_overlay_rectangle_get_flags (rectangle);
if (flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA)
alpha_flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA;
comp_buffer = gst_video_overlay_rectangle_get_pixels_unscaled_argb (rectangle, alpha_flags);
/* Update overlay video info from video meta */
if ((v_meta = gst_buffer_get_video_meta (comp_buffer))) {
gst_video_info_set_format (&v_info, v_meta->format, v_meta->width, v_meta->height);
v_info.stride[0] = v_meta->stride[0];
}
if (G_UNLIKELY (!gst_video_frame_map (&comp_frame, &v_info, comp_buffer, GST_MAP_READ)))
return;
if ((texture = gst_video_frame_into_gdk_texture (&comp_frame))) {
overlay = gst_clapper_gdk_overlay_new (texture, rectangle, comp_x, comp_y,
comp_width, comp_height, i);
g_object_unref (texture);
GST_TRACE_OBJECT (self, "Created overlay: %"
GST_PTR_FORMAT ", x: %i, y: %i, width: %u, height: %u",
overlay, overlay->x, overlay->y, overlay->width, overlay->height);
g_ptr_array_insert (self->pending_overlays, i, overlay);
}
gst_video_frame_unmap (&comp_frame);
}
/* Remove all overlays that are not going to be used */
for (i = self->pending_overlays->len; i > 0; i--) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i - 1);
if (overlay->index < 0) {
GST_TRACE ("Removing unused cached overlay: %" GST_PTR_FORMAT, overlay);
g_ptr_array_remove (self->pending_overlays, overlay);
}
}
/* Sort remaining overlays */
if (self->pending_overlays->len > 1) {
GST_LOG_OBJECT (self, "Sorting overlays");
g_ptr_array_sort (self->pending_overlays, (GCompareFunc) _sort_overlays_cb);
}
if (G_UNLIKELY (num_overlays != self->pending_overlays->len)) {
GST_WARNING_OBJECT (self, "Some overlays could not be prepared, %u != %u",
num_overlays, self->pending_overlays->len);
}
GST_LOG_OBJECT (self, "Prepared overlays: %u", self->pending_overlays->len);
}
gboolean
gst_clapper_importer_prepare (GstClapperImporter *self)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
if (importer_class->prepare) {
if (!importer_class->prepare (self))
return FALSE;
}
GST_DEBUG_OBJECT (self, "Importer prepared");
return TRUE;
}
void
gst_clapper_importer_share_data (GstClapperImporter *self, GstClapperImporter *dest)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
if (importer_class->share_data)
importer_class->share_data (self, dest);
}
void
gst_clapper_importer_set_caps (GstClapperImporter *self, GstCaps *caps)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
GST_OBJECT_LOCK (self);
self->has_pending_v_info = gst_video_info_from_caps (&self->pending_v_info, caps);
GST_OBJECT_UNLOCK (self);
if (importer_class->set_caps)
importer_class->set_caps (self, caps);
}
void
gst_clapper_importer_set_buffer (GstClapperImporter *self, GstBuffer *buffer)
{
/* Both overlays and pending buffer must be
* set within a single importer locking */
GST_OBJECT_LOCK (self);
gst_buffer_replace (&self->pending_buffer, buffer);
gst_clapper_importer_prepare_overlays_locked (self);
GST_OBJECT_UNLOCK (self);
}
GstBufferPool *
gst_clapper_importer_create_pool (GstClapperImporter *self, GstStructure **config)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
return importer_class->create_pool (self, config);
}
void
gst_clapper_importer_add_allocation_metas (GstClapperImporter *self, GstQuery *query)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
if (importer_class->add_allocation_metas)
importer_class->add_allocation_metas (self, query);
}
gboolean
gst_clapper_importer_handle_context_query (GstClapperImporter *self,
GstBaseSink *bsink, GstQuery *query)
{
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
if (!importer_class->handle_context_query)
return FALSE;
return importer_class->handle_context_query (self, bsink, query);
}
void
gst_clapper_importer_snapshot (GstClapperImporter *self, GdkSnapshot *snapshot,
gdouble width, gdouble height, gfloat scale_x, gfloat scale_y)
{
guint i;
gboolean buffer_changed;
/* Collect all data that we need to snapshot pending buffer,
* lock ourselves to make sure everything matches */
GST_OBJECT_LOCK (self);
buffer_changed = gst_buffer_replace (&self->buffer, self->pending_buffer);
/* Only replace v_info when buffer changed, this way
* we still use old (correct) v_info when resizing */
if (buffer_changed && self->has_pending_v_info) {
self->v_info = self->pending_v_info;
self->has_pending_v_info = FALSE;
}
/* Ref overlays associated with current buffer */
for (i = 0; i < self->pending_overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i);
g_ptr_array_insert (self->overlays, i, gst_clapper_gdk_overlay_ref (overlay));
}
GST_OBJECT_UNLOCK (self);
/* Draw black BG when no buffer or imported format has alpha */
if (!self->buffer || GST_VIDEO_INFO_HAS_ALPHA (&self->v_info))
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
if (self->buffer) {
if (buffer_changed || !self->texture) {
GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self);
GST_TRACE_OBJECT (self, "Importing %" GST_PTR_FORMAT, self->buffer);
g_clear_object (&self->texture);
self->texture = importer_class->generate_texture (self, self->buffer, &self->v_info);
} else {
GST_TRACE_OBJECT (self, "Reusing texture from %" GST_PTR_FORMAT, self->buffer);
}
if (G_LIKELY (self->texture)) {
gtk_snapshot_append_texture (snapshot, self->texture, &GRAPHENE_RECT_INIT (0, 0, width, height));
for (i = 0; i < self->overlays->len; i++) {
GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i);
gtk_snapshot_append_texture (snapshot, overlay->texture,
&GRAPHENE_RECT_INIT (overlay->x * scale_x, overlay->y * scale_y,
overlay->width * scale_x, overlay->height * scale_y));
}
} else {
GST_ERROR_OBJECT (self, "Failed import of %" GST_PTR_FORMAT, self->buffer);
/* Draw black instead of texture on failure if not drawn already */
if (!GST_VIDEO_INFO_HAS_ALPHA (&self->v_info))
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
}
}
/* Unref all used overlays */
if (self->overlays->len > 0)
g_ptr_array_remove_range (self->overlays, 0, self->overlays->len);
}

102
lib/gst/plugin/gstclapperimporter.h vendored Normal file
View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_IMPORTER (gst_clapper_importer_get_type())
#define GST_IS_CLAPPER_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_IMPORTER))
#define GST_IS_CLAPPER_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_IMPORTER))
#define GST_CLAPPER_IMPORTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporterClass))
#define GST_CLAPPER_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporter))
#define GST_CLAPPER_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporterClass))
#define GST_CLAPPER_IMPORTER_CAST(obj) ((GstClapperImporter *)(obj))
#define GST_CLAPPER_IMPORTER_DEFINE(camel,lower,type) \
G_DEFINE_TYPE (camel, lower, type) \
G_MODULE_EXPORT GstClapperImporter *make_importer (void); \
G_MODULE_EXPORT GstCaps *make_caps (GstRank *rank, GStrv *context_types);
typedef struct _GstClapperImporter GstClapperImporter;
typedef struct _GstClapperImporterClass GstClapperImporterClass;
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperImporter, gst_object_unref)
#endif
struct _GstClapperImporter
{
GstObject parent;
GstBuffer *pending_buffer, *buffer;
GPtrArray *pending_overlays, *overlays;
GstVideoInfo pending_v_info, v_info;
gboolean has_pending_v_info;
GdkTexture *texture;
GdkRGBA bg;
};
struct _GstClapperImporterClass
{
GstObjectClass parent_class;
gboolean (* prepare) (GstClapperImporter *importer);
void (* share_data) (GstClapperImporter *src,
GstClapperImporter *dest);
void (* set_caps) (GstClapperImporter *importer,
GstCaps *caps);
gboolean (* handle_context_query) (GstClapperImporter *importer,
GstBaseSink *bsink,
GstQuery *query);
GstBufferPool * (* create_pool) (GstClapperImporter *importer,
GstStructure **config);
void (* add_allocation_metas) (GstClapperImporter *importer,
GstQuery *query);
GdkTexture * (* generate_texture) (GstClapperImporter *importer,
GstBuffer *buffer,
GstVideoInfo *v_info);
};
GType gst_clapper_importer_get_type (void);
gboolean gst_clapper_importer_prepare (GstClapperImporter *importer);
void gst_clapper_importer_share_data (GstClapperImporter *importer, GstClapperImporter *dest);
gboolean gst_clapper_importer_handle_context_query (GstClapperImporter *importer, GstBaseSink *bsink, GstQuery *query);
GstBufferPool * gst_clapper_importer_create_pool (GstClapperImporter *importer, GstStructure **config);
void gst_clapper_importer_add_allocation_metas (GstClapperImporter *importer, GstQuery *query);
void gst_clapper_importer_set_caps (GstClapperImporter *importer, GstCaps *caps);
void gst_clapper_importer_set_buffer (GstClapperImporter *importer, GstBuffer *buffer);
void gst_clapper_importer_snapshot (GstClapperImporter *importer, GdkSnapshot *snapshot, gdouble width, gdouble height, gfloat scale_x, gfloat scale_y);
G_END_DECLS

View File

@@ -0,0 +1,417 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gmodule.h>
#include "gstclapperimporterloader.h"
#include "gstclapperimporter.h"
#define GST_CAT_DEFAULT gst_clapper_importer_loader_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
typedef GstClapperImporter* (* MakeImporter) (void);
typedef GstCaps* (* MakeCaps) (GstRank *rank, GStrv *context_types);
typedef struct
{
gchar *module_path;
GModule *open_module;
GstCaps *caps;
GstRank rank;
GStrv context_types;
} GstClapperImporterData;
static void
gst_clapper_importer_data_free (GstClapperImporterData *data)
{
g_free (data->module_path);
if (data->open_module)
g_module_close (data->open_module);
gst_clear_caps (&data->caps);
g_strfreev (data->context_types);
g_free (data);
}
static gboolean
_open_importer (GstClapperImporterData *data)
{
g_return_val_if_fail (data && data->module_path, FALSE);
/* Already open */
if (data->open_module)
return TRUE;
GST_DEBUG ("Opening module: %s", data->module_path);
data->open_module = g_module_open (data->module_path, G_MODULE_BIND_LAZY);
if (!data->open_module) {
GST_WARNING ("Could not load importer: %s, reason: %s",
data->module_path, g_module_error ());
return FALSE;
}
GST_DEBUG ("Opened importer module");
/* Make sure module stays loaded. Seems to be needed for
* reusing exported symbols from the same module again */
g_module_make_resident (data->open_module);
return TRUE;
}
static void
_close_importer (GstClapperImporterData *data)
{
if (!data || !data->open_module)
return;
if (G_LIKELY (g_module_close (data->open_module)))
GST_DEBUG ("Closed module: %s", data->module_path);
else
GST_WARNING ("Could not close importer module");
data->open_module = NULL;
}
static GstClapperImporter *
_obtain_importer_internal (GstClapperImporterData *data)
{
MakeImporter make_importer;
GstClapperImporter *importer = NULL;
if (!_open_importer (data))
goto finish;
if (!g_module_symbol (data->open_module, "make_importer", (gpointer *) &make_importer)
|| make_importer == NULL) {
GST_WARNING ("Make function missing in importer");
goto fail;
}
/* Do not close the module, we are gonna continue using it */
if ((importer = make_importer ()))
goto finish;
fail:
_close_importer (data);
finish:
return importer;
}
static GstClapperImporterData *
_fill_importer_data (const gchar *module_path)
{
MakeCaps make_caps;
GstClapperImporterData *data;
data = g_new0 (GstClapperImporterData, 1);
data->module_path = g_strdup (module_path);
data->open_module = g_module_open (data->module_path, G_MODULE_BIND_LAZY);
if (!data->open_module)
goto fail;
if (!g_module_symbol (data->open_module, "make_caps", (gpointer *) &make_caps)
|| make_caps == NULL) {
GST_WARNING ("Make caps function missing in importer");
goto fail;
}
data->caps = make_caps (&data->rank, &data->context_types);
GST_DEBUG ("Caps reading %ssuccessful", data->caps ? "" : "un");
if (!data->caps)
goto fail;
/* Once we obtain importer data, close module afterwards */
_close_importer (data);
return data;
fail:
gst_clapper_importer_data_free (data);
return NULL;
}
static gint
_sort_importers_cb (gconstpointer a, gconstpointer b)
{
GstClapperImporterData *data_a, *data_b;
data_a = *((GstClapperImporterData **) a);
data_b = *((GstClapperImporterData **) b);
return (data_b->rank - data_a->rank);
}
static gpointer
_obtain_available_importers (G_GNUC_UNUSED gpointer data)
{
GPtrArray *importers;
GFile *dir;
GFileEnumerator *dir_enum;
GError *error = NULL;
importers = g_ptr_array_new_with_free_func (
(GDestroyNotify) gst_clapper_importer_data_free);
GST_INFO ("Checking available clapper sink importers");
dir = g_file_new_for_path (CLAPPER_SINK_IMPORTER_PATH);
if ((dir_enum = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error))) {
while (TRUE) {
GFileInfo *info = NULL;
GstClapperImporterData *data;
gchar *module_path;
const gchar *module_name;
if (!g_file_enumerator_iterate (dir_enum, &info,
NULL, NULL, &error) || !info)
break;
module_name = g_file_info_get_name (info);
if (!g_str_has_suffix (module_name, G_MODULE_SUFFIX))
continue;
module_path = g_module_build_path (CLAPPER_SINK_IMPORTER_PATH, module_name);
data = _fill_importer_data (module_path);
g_free (module_path);
if (!data) {
GST_WARNING ("Could not read importer data: %s", module_name);
continue;
}
GST_INFO ("Found importer: %s, caps: %" GST_PTR_FORMAT, module_name, data->caps);
g_ptr_array_add (importers, data);
}
g_object_unref (dir_enum);
}
g_object_unref (dir);
if (error) {
GST_ERROR ("Could not load importer, reason: %s",
(error->message) ? error->message : "unknown");
g_error_free (error);
}
g_ptr_array_sort (importers, (GCompareFunc) _sort_importers_cb);
return importers;
}
static const GPtrArray *
gst_clapper_importer_loader_get_available_importers (void)
{
static GOnce once = G_ONCE_INIT;
g_once (&once, _obtain_available_importers, NULL);
return (const GPtrArray *) once.retval;
}
static GstClapperImporterData *
_find_open_importer_data (const GPtrArray *importers)
{
guint i;
for (i = 0; i < importers->len; i++) {
GstClapperImporterData *data = g_ptr_array_index (importers, i);
if (data->open_module)
return data;
}
return NULL;
}
static GstClapperImporterData *
_get_importer_data_for_caps (const GPtrArray *importers, const GstCaps *caps)
{
guint i;
for (i = 0; i < importers->len; i++) {
GstClapperImporterData *data = g_ptr_array_index (importers, i);
if (!gst_caps_is_always_compatible (caps, data->caps))
continue;
return data;
}
return NULL;
}
static GstClapperImporterData *
_get_importer_data_for_context_type (const GPtrArray *importers, const gchar *context_type)
{
guint i;
for (i = 0; i < importers->len; i++) {
GstClapperImporterData *data = g_ptr_array_index (importers, i);
guint j;
if (!data->context_types)
continue;
for (j = 0; data->context_types[j]; j++) {
if (strcmp (context_type, data->context_types[j]))
continue;
return data;
}
}
return NULL;
}
void
gst_clapper_importer_loader_unload_all (void)
{
const GPtrArray *importers;
guint i;
importers = gst_clapper_importer_loader_get_available_importers ();
GST_TRACE ("Unloading all open modules");
for (i = 0; i < importers->len; i++) {
GstClapperImporterData *data = g_ptr_array_index (importers, i);
_close_importer (data);
}
}
GstPadTemplate *
gst_clapper_importer_loader_make_sink_pad_template (void)
{
const GPtrArray *importers;
GstCaps *sink_caps;
GstPadTemplate *templ;
guint i;
/* This is only called once from sink class init function */
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimporterloader", 0,
"Clapper Importer Loader");
importers = gst_clapper_importer_loader_get_available_importers ();
sink_caps = gst_caps_new_empty ();
for (i = 0; i < importers->len; i++) {
GstClapperImporterData *data = g_ptr_array_index (importers, i);
GstCaps *copied_caps;
copied_caps = gst_caps_copy (data->caps);
gst_caps_append (sink_caps, copied_caps);
}
if (G_UNLIKELY (gst_caps_is_empty (sink_caps)))
gst_caps_append (sink_caps, gst_caps_new_any ());
templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, sink_caps);
gst_caps_unref (sink_caps);
return templ;
}
static gboolean
_find_importer_internal (GstCaps *caps, GstQuery *query, GstClapperImporter **importer)
{
const GPtrArray *importers;
GstClapperImporterData *old_data = NULL, *new_data = NULL;
GstClapperImporter *found_importer = NULL;
importers = gst_clapper_importer_loader_get_available_importers ();
old_data = _find_open_importer_data (importers);
if (caps) {
GST_DEBUG ("Requested importer for caps: %" GST_PTR_FORMAT, caps);
new_data = _get_importer_data_for_caps (importers, caps);
} else if (query) {
const gchar *context_type;
gst_query_parse_context_type (query, &context_type);
GST_DEBUG ("Requested importer for context: %s", context_type);
new_data = _get_importer_data_for_context_type (importers, context_type);
/* In case missing importer for context query, leave the old one.
* We should allow some queries to go through unresponded */
if (!new_data)
new_data = old_data;
}
GST_LOG ("Old importer path: %s, new path: %s",
(old_data != NULL) ? old_data->module_path : NULL,
(new_data != NULL) ? new_data->module_path : NULL);
if (old_data == new_data) {
GST_DEBUG ("No importer change");
if (*importer && caps)
gst_clapper_importer_set_caps (*importer, caps);
return (*importer != NULL);
}
if (new_data) {
found_importer = _obtain_importer_internal (new_data);
if (*importer && found_importer)
gst_clapper_importer_share_data (*importer, found_importer);
}
gst_clear_object (importer);
_close_importer (old_data);
if (found_importer && gst_clapper_importer_prepare (found_importer)) {
if (caps)
gst_clapper_importer_set_caps (found_importer, caps);
*importer = found_importer;
return TRUE;
}
gst_clear_object (&found_importer);
_close_importer (new_data);
return FALSE;
}
gboolean
gst_clapper_importer_loader_find_importer_for_caps (GstCaps *caps, GstClapperImporter **importer)
{
return _find_importer_internal (caps, NULL, importer);
}
gboolean
gst_clapper_importer_loader_find_importer_for_context_query (GstQuery *query, GstClapperImporter **importer)
{
return _find_importer_internal (NULL, query, importer);
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gst.h>
#include "gstclapperimporter.h"
G_BEGIN_DECLS
GstPadTemplate * gst_clapper_importer_loader_make_sink_pad_template (void);
gboolean gst_clapper_importer_loader_find_importer_for_caps (GstCaps *caps, GstClapperImporter **importer);
gboolean gst_clapper_importer_loader_find_importer_for_context_query (GstQuery *query, GstClapperImporter **importer);
void gst_clapper_importer_loader_unload_all (void);
G_END_DECLS

428
lib/gst/plugin/gstclapperpaintable.c vendored Normal file
View File

@@ -0,0 +1,428 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperpaintable.h"
#define DEFAULT_PAR_N 1
#define DEFAULT_PAR_D 1
#define GST_CAT_DEFAULT gst_clapper_paintable_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static void gst_clapper_paintable_iface_init (GdkPaintableInterface *iface);
static void gst_clapper_paintable_dispose (GObject *object);
static void gst_clapper_paintable_finalize (GObject *object);
#define parent_class gst_clapper_paintable_parent_class
G_DEFINE_TYPE_WITH_CODE (GstClapperPaintable, gst_clapper_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gst_clapper_paintable_iface_init));
static void
gst_clapper_paintable_class_init (GstClapperPaintableClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperpaintable", 0,
"Clapper Paintable");
gobject_class->dispose = gst_clapper_paintable_dispose;
gobject_class->finalize = gst_clapper_paintable_finalize;
}
static void
gst_clapper_paintable_init (GstClapperPaintable *self)
{
self->par_n = DEFAULT_PAR_N;
self->par_d = DEFAULT_PAR_D;
self->pixel_aspect = ((gdouble) self->par_d / self->par_n);
g_mutex_init (&self->lock);
gst_video_info_init (&self->v_info);
g_weak_ref_init (&self->widget, NULL);
g_weak_ref_init (&self->importer, NULL);
gdk_rgba_parse (&self->bg, "black");
}
static void
gst_clapper_paintable_dispose (GObject *object)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
GST_CLAPPER_PAINTABLE_LOCK (self);
if (self->draw_id > 0) {
g_source_remove (self->draw_id);
self->draw_id = 0;
}
GST_CLAPPER_PAINTABLE_UNLOCK (self);
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_clapper_paintable_finalize (GObject *object)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object);
GST_TRACE ("Finalize");
g_weak_ref_clear (&self->widget);
g_weak_ref_clear (&self->importer);
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static gboolean
calculate_display_par (GstClapperPaintable *self, const GstVideoInfo *info)
{
gint width, height, par_n, par_d, req_par_n, req_par_d;
gboolean success;
width = GST_VIDEO_INFO_WIDTH (info);
height = GST_VIDEO_INFO_HEIGHT (info);
/* Cannot apply aspect ratio if there is no video */
if (width == 0 || height == 0)
return FALSE;
par_n = GST_VIDEO_INFO_PAR_N (info);
par_d = GST_VIDEO_INFO_PAR_D (info);
req_par_n = self->par_n;
req_par_d = self->par_d;
if (par_n == 0)
par_n = 1;
/* Use defaults if user set zero */
if (req_par_n == 0 || req_par_d == 0) {
req_par_n = DEFAULT_PAR_N;
req_par_d = DEFAULT_PAR_D;
}
GST_LOG_OBJECT (self, "PAR: %u/%u, DAR: %u/%u", par_n, par_d, req_par_n, req_par_d);
if (!(success = gst_video_calculate_display_ratio (&self->display_ratio_num,
&self->display_ratio_den, width, height, par_n, par_d,
req_par_n, req_par_d))) {
GST_ERROR_OBJECT (self, "Could not calculate display ratio values");
}
return success;
}
static void
invalidate_paintable_size_internal (GstClapperPaintable *self)
{
gint video_width, video_height;
guint display_ratio_num, display_ratio_den;
GST_CLAPPER_PAINTABLE_LOCK (self);
video_width = GST_VIDEO_INFO_WIDTH (&self->v_info);
video_height = GST_VIDEO_INFO_HEIGHT (&self->v_info);
display_ratio_num = self->display_ratio_num;
display_ratio_den = self->display_ratio_den;
self->pixel_aspect = ((gdouble) self->par_d / self->par_n);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
if (video_height % display_ratio_den == 0) {
GST_LOG ("Keeping video height");
self->display_width = (guint)
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
self->display_height = video_height;
} else if (video_width % display_ratio_num == 0) {
GST_LOG ("Keeping video width");
self->display_width = video_width;
self->display_height = (guint)
gst_util_uint64_scale_int (video_width, display_ratio_den, display_ratio_num);
} else {
GST_LOG ("Approximating while keeping video height");
self->display_width = (guint)
gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den);
self->display_height = video_height;
}
self->display_aspect_ratio = ((gdouble) self->display_width
/ (gdouble) self->display_height);
GST_DEBUG_OBJECT (self, "Invalidate paintable size, display: %dx%d",
self->display_width, self->display_height);
gdk_paintable_invalidate_size ((GdkPaintable *) self);
}
static gboolean
invalidate_paintable_size_on_main_cb (GstClapperPaintable *self)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
self->draw_id = 0;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
invalidate_paintable_size_internal (self);
return G_SOURCE_REMOVE;
}
static gboolean
update_paintable_on_main_cb (GstClapperPaintable *self)
{
gboolean size_changed;
GST_CLAPPER_PAINTABLE_LOCK (self);
/* Check if we will need to invalidate size */
if ((size_changed = self->pending_resize))
self->pending_resize = FALSE;
self->draw_id = 0;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
if (size_changed)
invalidate_paintable_size_internal (self);
GST_LOG_OBJECT (self, "Invalidate paintable contents");
gdk_paintable_invalidate_contents ((GdkPaintable *) self);
return G_SOURCE_REMOVE;
}
GstClapperPaintable *
gst_clapper_paintable_new (void)
{
return g_object_new (GST_TYPE_CLAPPER_PAINTABLE, NULL);
}
void
gst_clapper_paintable_set_widget (GstClapperPaintable *self, GtkWidget *widget)
{
g_weak_ref_set (&self->widget, widget);
}
void
gst_clapper_paintable_set_importer (GstClapperPaintable *self, GstClapperImporter *importer)
{
g_weak_ref_set (&self->importer, importer);
}
void
gst_clapper_paintable_queue_draw (GstClapperPaintable *self)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
if (self->draw_id > 0) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
GST_TRACE ("Already have pending draw");
return;
}
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) update_paintable_on_main_cb, self, NULL);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
}
gboolean
gst_clapper_paintable_set_video_info (GstClapperPaintable *self, const GstVideoInfo *v_info)
{
GST_CLAPPER_PAINTABLE_LOCK (self);
if (gst_video_info_is_equal (&self->v_info, v_info)) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return TRUE;
}
/* Reject info if values would cause integer overflow */
if (G_UNLIKELY (!calculate_display_par (self, v_info))) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return FALSE;
}
self->pending_resize = TRUE;
self->v_info = *v_info;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return TRUE;
}
void
gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *self,
gint par_n, gint par_d)
{
gboolean success;
GST_CLAPPER_PAINTABLE_LOCK (self);
/* No change */
if (self->par_n == par_n && self->par_d == par_d) {
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
self->par_n = par_n;
self->par_d = par_d;
/* Check if we can accept new values. This will update
* display `ratio_num` and `ratio_den` only when successful */
success = calculate_display_par (self, &self->v_info);
/* If paintable update is queued, wait for it, otherwise invalidate
* size only for change to be applied even when paused */
if (!success || self->draw_id > 0) {
self->pending_resize = success;
GST_CLAPPER_PAINTABLE_UNLOCK (self);
return;
}
self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) invalidate_paintable_size_on_main_cb, self, NULL);
GST_CLAPPER_PAINTABLE_UNLOCK (self);
}
/*
* GdkPaintableInterface
*/
static void
gst_clapper_paintable_snapshot_internal (GstClapperPaintable *self,
GdkSnapshot *snapshot, gdouble width, gdouble height,
gint widget_width, gint widget_height)
{
GstClapperImporter *importer;
gfloat scale_x, scale_y;
GST_LOG_OBJECT (self, "Snapshot");
scale_x = (gfloat) width / self->display_width;
scale_y = (gfloat) height / self->display_height;
/* Apply black borders when keeping aspect ratio */
if (scale_x == scale_y || abs (scale_x - scale_y) <= FLT_EPSILON) {
if (widget_height - height > 0) {
/* XXX: Top uses integer to work with GTK rounding (not going offscreen) */
gint top_bar_height = (widget_height - height) / 2;
gdouble bottom_bar_height = (widget_height - top_bar_height - height);
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, -top_bar_height));
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, height, width, bottom_bar_height));
} else if (widget_width - width > 0) {
gint left_bar_width = (widget_width - width) / 2;
gdouble right_bar_width = (widget_width - left_bar_width - width);
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, -left_bar_width, height));
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (width, 0, right_bar_width, height));
}
}
if ((importer = g_weak_ref_get (&self->importer))) {
gst_clapper_importer_snapshot (importer, snapshot, width, height,
scale_x * self->pixel_aspect, scale_y);
g_object_unref (importer);
} else {
GST_LOG_OBJECT (self, "No texture importer, drawing black");
gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height));
}
}
static void
gst_clapper_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot, gdouble width, gdouble height)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
GtkWidget *widget;
gint widget_width = 0, widget_height = 0;
if ((widget = g_weak_ref_get (&self->widget))) {
gint scale_factor;
scale_factor = gtk_widget_get_scale_factor (widget);
widget_width = gtk_widget_get_width (widget) * scale_factor;
widget_height = gtk_widget_get_height (widget) * scale_factor;
g_object_unref (widget);
}
gst_clapper_paintable_snapshot_internal (self, snapshot,
width, height, widget_width, widget_height);
}
static GdkPaintable *
gst_clapper_paintable_get_current_image (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
GtkSnapshot *snapshot = gtk_snapshot_new ();
/* Snapshot without widget size in order to get
* paintable without black borders */
gst_clapper_paintable_snapshot_internal (self, snapshot,
self->display_width, self->display_height, 0, 0);
return gtk_snapshot_free_to_paintable (snapshot, NULL);
}
static gint
gst_clapper_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_width;
}
static gint
gst_clapper_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_height;
}
static gdouble
gst_clapper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
{
GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable);
return self->display_aspect_ratio;
}
static void
gst_clapper_paintable_iface_init (GdkPaintableInterface *iface)
{
iface->snapshot = gst_clapper_paintable_snapshot;
iface->get_current_image = gst_clapper_paintable_get_current_image;
iface->get_intrinsic_width = gst_clapper_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gst_clapper_paintable_get_intrinsic_height;
iface->get_intrinsic_aspect_ratio = gst_clapper_paintable_get_intrinsic_aspect_ratio;
}

78
lib/gst/plugin/gstclapperpaintable.h vendored Normal file
View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include "gstclapperimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_PAINTABLE (gst_clapper_paintable_get_type())
G_DECLARE_FINAL_TYPE (GstClapperPaintable, gst_clapper_paintable, GST, CLAPPER_PAINTABLE, GObject)
#define GST_CLAPPER_PAINTABLE_CAST(obj) ((GstClapperPaintable *)(obj))
#define GST_CLAPPER_PAINTABLE_GET_LOCK(obj) (&GST_CLAPPER_PAINTABLE_CAST(obj)->lock)
#define GST_CLAPPER_PAINTABLE_LOCK(obj) g_mutex_lock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj))
#define GST_CLAPPER_PAINTABLE_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj))
struct _GstClapperPaintable
{
GObject parent;
GMutex lock;
GstVideoInfo v_info;
GdkRGBA bg;
GWeakRef widget, importer;
/* Sink properties */
gint par_n, par_d;
/* For drawing overlays */
gdouble pixel_aspect;
/* Resize */
gboolean pending_resize;
guint display_ratio_num;
guint display_ratio_den;
/* GdkPaintableInterface */
gint display_width;
gint display_height;
gdouble display_aspect_ratio;
/* Pending draw signal id */
guint draw_id;
};
GstClapperPaintable * gst_clapper_paintable_new (void);
void gst_clapper_paintable_queue_draw (GstClapperPaintable *paintable);
void gst_clapper_paintable_set_widget (GstClapperPaintable *paintable, GtkWidget *widget);
void gst_clapper_paintable_set_importer (GstClapperPaintable *paintable, GstClapperImporter *importer);
gboolean gst_clapper_paintable_set_video_info (GstClapperPaintable *paintable, const GstVideoInfo *v_info);
void gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *paintable, gint par_n, gint par_d);
G_END_DECLS

910
lib/gst/plugin/gstclappersink.c vendored Normal file
View File

@@ -0,0 +1,910 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclappersink.h"
#include "gstclapperimporterloader.h"
#include "gstgtkutils.h"
#define DEFAULT_FORCE_ASPECT_RATIO TRUE
#define DEFAULT_PAR_N 1
#define DEFAULT_PAR_D 1
#define DEFAULT_KEEP_LAST_FRAME FALSE
#define WINDOW_CSS_CLASS_NAME "clappersinkwindow"
enum
{
PROP_0,
PROP_WIDGET,
PROP_FORCE_ASPECT_RATIO,
PROP_PIXEL_ASPECT_RATIO,
PROP_KEEP_LAST_FRAME,
PROP_LAST
};
#define GST_CAT_DEFAULT gst_clapper_sink_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
static void gst_clapper_sink_navigation_interface_init (
GstNavigationInterface *iface);
#define parent_class gst_clapper_sink_parent_class
G_DEFINE_TYPE_WITH_CODE (GstClapperSink, gst_clapper_sink, GST_TYPE_VIDEO_SINK,
G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION,
gst_clapper_sink_navigation_interface_init));
GST_ELEMENT_REGISTER_DEFINE (clappersink, "clappersink", GST_RANK_NONE,
GST_TYPE_CLAPPER_SINK);
static void
window_clear_no_lock (GstClapperSink *self)
{
if (!self->window)
return;
GST_TRACE_OBJECT (self, "Window clear");
if (self->window_destroy_id) {
g_signal_handler_disconnect (self->window, self->window_destroy_id);
self->window_destroy_id = 0;
}
self->window = NULL;
self->presented_window = FALSE;
}
static void
widget_clear_no_lock (GstClapperSink *self)
{
if (!self->widget)
return;
GST_TRACE_OBJECT (self, "Widget clear");
if (self->widget_destroy_id) {
g_signal_handler_disconnect (self->widget, self->widget_destroy_id);
self->widget_destroy_id = 0;
}
g_clear_object (&self->widget);
}
static void
widget_destroy_cb (GtkWidget *widget, GstClapperSink *self)
{
GST_CLAPPER_SINK_LOCK (self);
widget_clear_no_lock (self);
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
window_destroy_cb (GtkWidget *window, GstClapperSink *self)
{
GST_DEBUG_OBJECT (self, "Window destroy");
GST_CLAPPER_SINK_LOCK (self);
widget_clear_no_lock (self);
window_clear_no_lock (self);
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
calculate_stream_coords (GstClapperSink *self, GtkWidget *widget,
gdouble x, gdouble y, gdouble *stream_x, gdouble *stream_y)
{
GstVideoRectangle result;
gint scaled_width, scaled_height, scale_factor;
gint video_width, video_height;
gboolean force_aspect_ratio;
GST_CLAPPER_SINK_LOCK (self);
video_width = GST_VIDEO_INFO_WIDTH (&self->v_info);
video_height = GST_VIDEO_INFO_HEIGHT (&self->v_info);
force_aspect_ratio = self->force_aspect_ratio;
GST_CLAPPER_SINK_UNLOCK (self);
scale_factor = gtk_widget_get_scale_factor (widget);
scaled_width = gtk_widget_get_width (widget) * scale_factor;
scaled_height = gtk_widget_get_height (widget) * scale_factor;
if (force_aspect_ratio) {
GstVideoRectangle src, dst;
src.x = 0;
src.y = 0;
src.w = gdk_paintable_get_intrinsic_width ((GdkPaintable *) self->paintable);
src.h = gdk_paintable_get_intrinsic_height ((GdkPaintable *) self->paintable);
dst.x = 0;
dst.y = 0;
dst.w = scaled_width;
dst.h = scaled_height;
gst_video_center_rect (&src, &dst, &result, TRUE);
} else {
result.x = 0;
result.y = 0;
result.w = scaled_width;
result.h = scaled_height;
}
/* Display coordinates to stream coordinates */
*stream_x = (result.w > 0)
? (x - result.x) / result.w * video_width
: 0;
*stream_y = (result.h > 0)
? (y - result.y) / result.h * video_height
: 0;
/* Clip to stream size */
*stream_x = CLAMP (*stream_x, 0, video_width);
*stream_y = CLAMP (*stream_y, 0, video_height);
GST_LOG ("Transform coords %fx%f => %fx%f", x, y, *stream_x, *stream_y);
}
static void
gst_clapper_sink_widget_motion_event (GtkEventControllerMotion *motion,
gdouble x, gdouble y, GstClapperSink *self)
{
GtkWidget *widget;
gdouble stream_x, stream_y;
gboolean is_inactive;
if (x == self->last_pos_x && y == self->last_pos_y)
return;
GST_OBJECT_LOCK (self);
is_inactive = (GST_STATE (self) < GST_STATE_PLAYING);
GST_OBJECT_UNLOCK (self);
if (is_inactive)
return;
self->last_pos_x = x;
self->last_pos_y = y;
widget = gtk_event_controller_get_widget ((GtkEventController *) motion);
calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y);
GST_LOG ("Event \"mouse-move\", x: %f, y: %f", stream_x, stream_y);
gst_navigation_send_mouse_event ((GstNavigation *) self, "mouse-move",
0, stream_x, stream_y);
}
static void
gst_clapper_sink_widget_button_event (GtkGestureClick *click,
gint n_press, gdouble x, gdouble y, GstClapperSink *self)
{
GtkWidget *widget;
GdkEvent *event;
GdkEventType event_type;
const gchar *event_name;
gdouble stream_x, stream_y;
gboolean is_inactive;
GST_OBJECT_LOCK (self);
is_inactive = (GST_STATE (self) < GST_STATE_PLAYING);
GST_OBJECT_UNLOCK (self);
if (is_inactive)
return;
event = gtk_event_controller_get_current_event ((GtkEventController *) click);
event_type = gdk_event_get_event_type (event);
/* FIXME: Touchscreen handling should probably use new touch events from GStreamer 1.22 */
event_name = (event_type == GDK_BUTTON_PRESS || event_type == GDK_TOUCH_BEGIN)
? "mouse-button-press"
: (event_type == GDK_BUTTON_RELEASE || event_type == GDK_TOUCH_END)
? "mouse-button-release"
: NULL;
/* Can be NULL on touch */
if (!event_name)
return;
widget = gtk_event_controller_get_widget ((GtkEventController *) click);
calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y);
GST_LOG ("Event \"%s\", x: %f, y: %f", event_name, stream_x, stream_y);
/* Gesture is set to handle only primary button, so we do not have to check */
gst_navigation_send_mouse_event ((GstNavigation *) self, event_name,
1, stream_x, stream_y);
}
/* Must call from main thread only with a lock */
static GtkWidget *
gst_clapper_sink_get_widget (GstClapperSink *self)
{
if (G_UNLIKELY (!self->widget)) {
GtkEventController *controller;
GtkGesture *gesture;
/* Make sure GTK is initialized */
if (!gtk_init_check ()) {
GST_ERROR_OBJECT (self, "Could not ensure GTK initialization");
return NULL;
}
self->widget = gtk_picture_new ();
/* Otherwise widget in grid will appear as a 1x1px
* video which might be misleading for users */
gtk_widget_set_hexpand (self->widget, TRUE);
gtk_widget_set_vexpand (self->widget, TRUE);
gtk_widget_set_focusable (self->widget, TRUE);
gtk_widget_set_can_focus (self->widget, TRUE);
controller = gtk_event_controller_motion_new ();
g_signal_connect (controller, "motion",
G_CALLBACK (gst_clapper_sink_widget_motion_event), self);
gtk_widget_add_controller (self->widget, controller);
gesture = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 1);
g_signal_connect (gesture, "pressed",
G_CALLBACK (gst_clapper_sink_widget_button_event), self);
g_signal_connect (gesture, "released",
G_CALLBACK (gst_clapper_sink_widget_button_event), self);
gtk_widget_add_controller (self->widget, GTK_EVENT_CONTROLLER (gesture));
/* TODO: Implement touch events once we depend on GStreamer 1.22 */
/* Take floating ref */
g_object_ref_sink (self->widget);
/* Set widget back pointer */
gst_clapper_paintable_set_widget (self->paintable, self->widget);
/* Set earlier remembered property */
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget),
self->force_aspect_ratio);
gtk_picture_set_paintable (GTK_PICTURE (self->widget), GDK_PAINTABLE (self->paintable));
self->widget_destroy_id = g_signal_connect (self->widget,
"destroy", G_CALLBACK (widget_destroy_cb), self);
}
return self->widget;
}
static GtkWidget *
gst_clapper_sink_obtain_widget (GstClapperSink *self)
{
GtkWidget *widget;
GST_CLAPPER_SINK_LOCK (self);
widget = gst_clapper_sink_get_widget (self);
if (widget)
g_object_ref (widget);
GST_CLAPPER_SINK_UNLOCK (self);
return widget;
}
static void
gst_clapper_sink_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
switch (prop_id) {
case PROP_WIDGET:
if (self->widget) {
g_value_set_object (value, self->widget);
} else {
GtkWidget *widget;
GST_CLAPPER_SINK_UNLOCK (self);
widget = gst_gtk_invoke_on_main ((GThreadFunc) gst_clapper_sink_obtain_widget, self);
GST_CLAPPER_SINK_LOCK (self);
g_value_set_object (value, widget);
g_object_unref (widget);
}
break;
case PROP_FORCE_ASPECT_RATIO:
g_value_set_boolean (value, self->force_aspect_ratio);
break;
case PROP_PIXEL_ASPECT_RATIO:
gst_value_set_fraction (value, self->par_n, self->par_d);
break;
case PROP_KEEP_LAST_FRAME:
g_value_set_boolean (value, self->keep_last_frame);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
gst_clapper_sink_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
switch (prop_id) {
case PROP_FORCE_ASPECT_RATIO:
self->force_aspect_ratio = g_value_get_boolean (value);
if (self->widget) {
gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget),
self->force_aspect_ratio);
}
break;
case PROP_PIXEL_ASPECT_RATIO:
self->par_n = gst_value_get_fraction_numerator (value);
self->par_d = gst_value_get_fraction_denominator (value);
gst_clapper_paintable_set_pixel_aspect_ratio (self->paintable,
self->par_n, self->par_d);
break;
case PROP_KEEP_LAST_FRAME:
self->keep_last_frame = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
GST_CLAPPER_SINK_UNLOCK (self);
}
static void
gst_clapper_sink_navigation_send_event (GstNavigation *navigation,
GstStructure *structure)
{
GstClapperSink *sink = GST_CLAPPER_SINK_CAST (navigation);
GstEvent *event;
GST_TRACE_OBJECT (sink, "Navigation event: %" GST_PTR_FORMAT, structure);
event = gst_event_new_navigation (structure);
if (G_LIKELY (event)) {
GstPad *pad;
pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink));
if (G_LIKELY (pad)) {
if (!gst_pad_send_event (pad, gst_event_ref (event))) {
/* If upstream didn't handle the event we'll post a message with it
* for the application in case it wants to do something with it */
gst_element_post_message (GST_ELEMENT_CAST (sink),
gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event));
}
gst_object_unref (pad);
}
gst_event_unref (event);
}
}
static gboolean
gst_clapper_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GstBufferPool *pool = NULL;
GstCaps *caps;
GstVideoInfo info;
guint size, min_buffers;
gboolean need_pool;
gst_query_parse_allocation (query, &caps, &need_pool);
if (!caps) {
GST_DEBUG_OBJECT (self, "No caps specified");
return FALSE;
}
if (!gst_video_info_from_caps (&info, caps)) {
GST_DEBUG_OBJECT (self, "Invalid caps specified");
return FALSE;
}
/* Normal size of a frame */
size = GST_VIDEO_INFO_SIZE (&info);
/* We keep around current buffer and a pending one */
min_buffers = 3;
if (need_pool) {
GstStructure *config = NULL;
GST_DEBUG_OBJECT (self, "Need to create buffer pool");
GST_CLAPPER_SINK_LOCK (self);
pool = gst_clapper_importer_create_pool (self->importer, &config);
GST_CLAPPER_SINK_UNLOCK (self);
if (pool) {
/* If we did not get config, use default one */
if (!config)
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, caps, size, min_buffers, 0);
if (!gst_buffer_pool_set_config (pool, config)) {
gst_object_unref (pool);
GST_ERROR_OBJECT (self, "Failed to set config");
return FALSE;
}
} else if (config) {
GST_WARNING_OBJECT (self, "Got config without a pool to apply it");
gst_structure_free (config);
}
}
gst_query_add_allocation_pool (query, pool, size, min_buffers, 0);
if (pool)
gst_object_unref (pool);
GST_CLAPPER_SINK_LOCK (self);
gst_clapper_importer_add_allocation_metas (self->importer, query);
GST_CLAPPER_SINK_UNLOCK (self);
return TRUE;
}
static gboolean
gst_clapper_sink_query (GstBaseSink *bsink, GstQuery *query)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gboolean res = FALSE;
GST_CLAPPER_SINK_LOCK (self);
if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT) {
gboolean is_inactive;
GST_OBJECT_LOCK (self);
is_inactive = (GST_STATE (self) < GST_STATE_PAUSED);
GST_OBJECT_UNLOCK (self);
/* Some random context query in the middle of playback
* should not trigger importer replacement */
if (is_inactive)
gst_clapper_importer_loader_find_importer_for_context_query (query, &self->importer);
if (self->importer)
res = gst_clapper_importer_handle_context_query (self->importer, bsink, query);
}
GST_CLAPPER_SINK_UNLOCK (self);
if (res)
return TRUE;
return GST_BASE_SINK_CLASS (parent_class)->query (bsink, query);
}
static gboolean
gst_clapper_sink_start_on_main (GstClapperSink *self)
{
GtkWidget *widget;
GST_CLAPPER_SINK_LOCK (self);
/* Make sure widget is created */
if (!(widget = gst_clapper_sink_get_widget (self))) {
GST_CLAPPER_SINK_UNLOCK (self);
return FALSE;
}
/* When no toplevel window, make our own */
if (G_UNLIKELY (!gtk_widget_get_root (widget) && !self->window)) {
GtkWidget *toplevel, *parent;
GtkCssProvider *provider;
gchar *win_title;
if ((parent = gtk_widget_get_parent (widget))) {
GtkWidget *temp_parent;
while ((temp_parent = gtk_widget_get_parent (parent)))
parent = temp_parent;
}
toplevel = (parent) ? parent : widget;
self->window = (GtkWindow *) gtk_window_new ();
gtk_widget_add_css_class (GTK_WIDGET (self->window), WINDOW_CSS_CLASS_NAME);
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider,
"." WINDOW_CSS_CLASS_NAME " { background: none; }", -1);
gtk_style_context_add_provider_for_display (
gdk_display_get_default (), GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider);
win_title = g_strdup_printf ("Clapper Sink - GTK %u.%u.%u Window",
gtk_get_major_version (),
gtk_get_minor_version (),
gtk_get_micro_version ());
/* Set some common default size, adding stock headerbar height
* to it in order to display 4:3 aspect video widget */
gtk_window_set_default_size (self->window, 640, 480 + 37);
gtk_window_set_title (self->window, win_title);
gtk_window_set_child (self->window, toplevel);
g_free (win_title);
self->window_destroy_id = g_signal_connect (self->window,
"destroy", G_CALLBACK (window_destroy_cb), self);
}
GST_CLAPPER_SINK_UNLOCK (self);
return TRUE;
}
static gboolean
window_present_on_main_idle (GtkWindow *window)
{
GST_INFO ("Presenting window");
gtk_window_present (window);
return G_SOURCE_REMOVE;
}
static gboolean
gst_clapper_sink_start (GstBaseSink *bsink)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GST_INFO_OBJECT (self, "Start");
if (G_UNLIKELY (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
gst_clapper_sink_start_on_main, self)))) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("GtkWidget could not be created"), (NULL));
return FALSE;
}
return TRUE;
}
static gboolean
gst_clapper_sink_stop_on_main (GstClapperSink *self)
{
GtkWindow *window = NULL;
GST_CLAPPER_SINK_LOCK (self);
if (self->window)
window = g_object_ref (self->window);
GST_CLAPPER_SINK_UNLOCK (self);
if (window) {
gtk_window_destroy (window);
g_object_unref (window);
}
return TRUE;
}
static gboolean
gst_clapper_sink_stop (GstBaseSink *bsink)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gboolean has_window;
GST_INFO_OBJECT (self, "Stop");
GST_CLAPPER_SINK_LOCK (self);
has_window = (self->window != NULL);
GST_CLAPPER_SINK_UNLOCK (self);
if (G_UNLIKELY (has_window)) {
return (! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
gst_clapper_sink_stop_on_main, self));
}
return TRUE;
}
static GstStateChangeReturn
gst_clapper_sink_change_state (GstElement *element, GstStateChange transition)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (element);
GST_DEBUG_OBJECT (self, "Changing state: %s => %s",
gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)),
gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition)));
switch (transition) {
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_CLAPPER_SINK_LOCK (self);
if (!self->keep_last_frame && self->importer) {
gst_clapper_importer_set_buffer (self->importer, NULL);
gst_clapper_paintable_queue_draw (self->paintable);
}
GST_CLAPPER_SINK_UNLOCK (self);
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
GST_CLAPPER_SINK_LOCK (self);
if (G_UNLIKELY (self->window && !self->presented_window)) {
g_idle_add_full (G_PRIORITY_DEFAULT,
(GSourceFunc) window_present_on_main_idle,
g_object_ref (self->window), (GDestroyNotify) g_object_unref);
self->presented_window = TRUE;
}
GST_CLAPPER_SINK_UNLOCK (self);
break;
default:
break;
}
return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
}
static void
gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer,
GstClockTime *start, GstClockTime *end)
{
if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
return;
*start = GST_BUFFER_TIMESTAMP (buffer);
if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
*end = *start + GST_BUFFER_DURATION (buffer);
} else {
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
gint fps_n, fps_d;
GST_CLAPPER_SINK_LOCK (self);
fps_n = GST_VIDEO_INFO_FPS_N (&self->v_info);
fps_d = GST_VIDEO_INFO_FPS_D (&self->v_info);
GST_CLAPPER_SINK_UNLOCK (self);
if (fps_n > 0)
*end = *start + gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n);
}
}
static GstCaps *
gst_clapper_sink_get_caps (GstBaseSink *bsink, GstCaps *filter)
{
GstCaps *result, *tmp;
tmp = gst_pad_get_pad_template_caps (GST_BASE_SINK_PAD (bsink));
if (filter) {
GST_DEBUG ("Intersecting with filter caps: %" GST_PTR_FORMAT, filter);
result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (tmp);
} else {
result = tmp;
}
GST_DEBUG ("Returning caps: %" GST_PTR_FORMAT, result);
return result;
}
static gboolean
gst_clapper_sink_set_caps (GstBaseSink *bsink, GstCaps *caps)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink);
GST_INFO_OBJECT (self, "Set caps: %" GST_PTR_FORMAT, caps);
GST_CLAPPER_SINK_LOCK (self);
if (G_UNLIKELY (!self->widget)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Output widget was destroyed"), (NULL));
return FALSE;
}
if (!gst_clapper_importer_loader_find_importer_for_caps (caps, &self->importer)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("No importer for given caps found"), (NULL));
return FALSE;
}
gst_clapper_paintable_set_importer (self->paintable, self->importer);
GST_CLAPPER_SINK_UNLOCK (self);
return GST_BASE_SINK_CLASS (parent_class)->set_caps (bsink, caps);
}
static gboolean
gst_clapper_sink_set_info (GstVideoSink *vsink, GstCaps *caps, const GstVideoInfo *info)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink);
gboolean res;
GST_CLAPPER_SINK_LOCK (self);
self->v_info = *info;
GST_DEBUG_OBJECT (self, "Video info changed");
res = gst_clapper_paintable_set_video_info (self->paintable, info);
GST_CLAPPER_SINK_UNLOCK (self);
return res;
}
static GstFlowReturn
gst_clapper_sink_show_frame (GstVideoSink *vsink, GstBuffer *buffer)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink);
GST_TRACE ("Got %" GST_PTR_FORMAT, buffer);
GST_CLAPPER_SINK_LOCK (self);
if (G_UNLIKELY (!self->widget)) {
GST_CLAPPER_SINK_UNLOCK (self);
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Output widget was destroyed"), (NULL));
return GST_FLOW_ERROR;
}
gst_clapper_importer_set_buffer (self->importer, buffer);
gst_clapper_paintable_queue_draw (self->paintable);
GST_CLAPPER_SINK_UNLOCK (self);
return GST_FLOW_OK;
}
static void
gst_clapper_sink_init (GstClapperSink *self)
{
GObjectClass *gobject_class;
gobject_class = (GObjectClass *) GST_CLAPPER_SINK_GET_CLASS (self);
/* HACK: install here instead of class init to avoid GStreamer
* plugin scanner GObject type conflicts with older GTK versions */
if (!g_object_class_find_property (gobject_class, "widget")) {
g_object_class_install_property (gobject_class, PROP_WIDGET,
g_param_spec_object ("widget", "GTK Widget",
"The GtkWidget to place in the widget hierarchy",
GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO;
self->par_n = DEFAULT_PAR_N;
self->par_d = DEFAULT_PAR_D;
self->keep_last_frame = DEFAULT_KEEP_LAST_FRAME;
g_mutex_init (&self->lock);
gst_video_info_init (&self->v_info);
self->paintable = gst_clapper_paintable_new ();
}
static void
gst_clapper_sink_dispose (GObject *object)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_CLAPPER_SINK_LOCK (self);
window_clear_no_lock (self);
widget_clear_no_lock (self);
g_clear_object (&self->paintable);
gst_clear_object (&self->importer);
GST_CLAPPER_SINK_UNLOCK (self);
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
}
static void
gst_clapper_sink_finalize (GObject *object)
{
GstClapperSink *self = GST_CLAPPER_SINK_CAST (object);
GST_TRACE ("Finalize");
gst_clapper_importer_loader_unload_all ();
g_mutex_clear (&self->lock);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_sink_class_init (GstClapperSinkClass *klass)
{
GstPadTemplate *sink_pad_templ;
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappersink", 0,
"Clapper Sink");
gobject_class->get_property = gst_clapper_sink_get_property;
gobject_class->set_property = gst_clapper_sink_set_property;
gobject_class->dispose = gst_clapper_sink_dispose;
gobject_class->finalize = gst_clapper_sink_finalize;
g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO,
g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio",
"When enabled, scaling will respect original aspect ratio",
DEFAULT_FORCE_ASPECT_RATIO,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO,
gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio",
"The pixel aspect ratio of the device",
DEFAULT_PAR_N, DEFAULT_PAR_D,
G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_KEEP_LAST_FRAME,
g_param_spec_boolean ("keep-last-frame", "Keep last frame",
"Keep showing last video frame after playback instead of black screen",
DEFAULT_KEEP_LAST_FRAME,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstelement_class->change_state = gst_clapper_sink_change_state;
gstbasesink_class->get_caps = gst_clapper_sink_get_caps;
gstbasesink_class->set_caps = gst_clapper_sink_set_caps;
gstbasesink_class->get_times = gst_clapper_sink_get_times;
gstbasesink_class->propose_allocation = gst_clapper_sink_propose_allocation;
gstbasesink_class->query = gst_clapper_sink_query;
gstbasesink_class->start = gst_clapper_sink_start;
gstbasesink_class->stop = gst_clapper_sink_stop;
gstvideosink_class->set_info = gst_clapper_sink_set_info;
gstvideosink_class->show_frame = gst_clapper_sink_show_frame;
gst_element_class_set_metadata (gstelement_class,
"Clapper video sink",
"Sink/Video", "A GTK4 video sink used by Clapper media player",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
sink_pad_templ = gst_clapper_importer_loader_make_sink_pad_template ();
gst_element_class_add_pad_template (gstelement_class, sink_pad_templ);
}
/*
* GstNavigationInterface
*/
static void
gst_clapper_sink_navigation_interface_init (GstNavigationInterface *iface)
{
/* TODO: Port to "send_event_simple" once we depend on GStreamer 1.22 */
iface->send_event = gst_clapper_sink_navigation_send_event;
}

72
lib/gst/plugin/gstclappersink.h vendored Normal file
View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gtk/gtk.h>
#include <gst/gst.h>
#include <gst/video/gstvideosink.h>
#include <gst/video/video.h>
#include "gstclapperpaintable.h"
#include "gstclapperimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_SINK (gst_clapper_sink_get_type())
G_DECLARE_FINAL_TYPE (GstClapperSink, gst_clapper_sink, GST, CLAPPER_SINK, GstVideoSink)
#define GST_CLAPPER_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass))
#define GST_CLAPPER_SINK_CAST(obj) ((GstClapperSink *)(obj))
#define GST_CLAPPER_SINK_GET_LOCK(obj) (&GST_CLAPPER_SINK_CAST(obj)->lock)
#define GST_CLAPPER_SINK_LOCK(obj) g_mutex_lock (GST_CLAPPER_SINK_GET_LOCK(obj))
#define GST_CLAPPER_SINK_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_SINK_GET_LOCK(obj))
struct _GstClapperSink
{
GstVideoSink parent;
GMutex lock;
GstClapperPaintable *paintable;
GstClapperImporter *importer;
GstVideoInfo v_info;
GtkWidget *widget;
GtkWindow *window;
gboolean presented_window;
/* Properties */
gboolean force_aspect_ratio;
gint par_n, par_d;
gboolean keep_last_frame;
/* Position coords */
gdouble last_pos_x;
gdouble last_pos_y;
gulong widget_destroy_id;
gulong window_destroy_id;
};
GST_ELEMENT_REGISTER_DECLARE (clappersink);
G_END_DECLS

37
lib/gst/plugin/gstgdkformats.h vendored Normal file
View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <glib.h>
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
#define GST_GDK_MEMORY_ENDIAN_FORMATS "RGBA64_LE"
#define GST_GDK_GL_TEXTURE_ENDIAN_FORMATS "RGBA64_LE"
#elif G_BYTE_ORDER == G_BIG_ENDIAN
#define GST_GDK_MEMORY_ENDIAN_FORMATS "RGBA64_BE"
#define GST_GDK_GL_TEXTURE_ENDIAN_FORMATS "RGBA64_BE"
#endif
#define GST_GDK_MEMORY_FORMATS \
GST_GDK_MEMORY_ENDIAN_FORMATS ", " \
"ABGR, BGRA, ARGB, RGBA, BGRx, RGBx, BGR, RGB"
/* Formats that `GdkGLTexture` supports */
#define GST_GDK_GL_TEXTURE_FORMATS \
GST_GDK_GL_TEXTURE_ENDIAN_FORMATS ", " \
"RGBA, RGBx, RGB"

132
lib/gst/plugin/gstgtkutils.c vendored Normal file
View File

@@ -0,0 +1,132 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gstgtkutils.h"
struct invoke_context
{
GThreadFunc func;
gpointer data;
GMutex lock;
GCond cond;
gboolean fired;
gpointer res;
};
static gboolean
gst_gtk_invoke_func (struct invoke_context *info)
{
g_mutex_lock (&info->lock);
info->res = info->func (info->data);
info->fired = TRUE;
g_cond_signal (&info->cond);
g_mutex_unlock (&info->lock);
return G_SOURCE_REMOVE;
}
gpointer
gst_gtk_invoke_on_main (GThreadFunc func, gpointer data)
{
GMainContext *main_context = g_main_context_default ();
struct invoke_context info;
g_mutex_init (&info.lock);
g_cond_init (&info.cond);
info.fired = FALSE;
info.func = func;
info.data = data;
g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func,
&info);
g_mutex_lock (&info.lock);
while (!info.fired)
g_cond_wait (&info.cond, &info.lock);
g_mutex_unlock (&info.lock);
g_mutex_clear (&info.lock);
g_cond_clear (&info.cond);
return info.res;
}
/* For use with `GdkMemoryTexture` only! */
GdkMemoryFormat
gst_video_format_to_gdk_memory_format (GstVideoFormat format)
{
switch (format) {
case GST_VIDEO_FORMAT_RGBA64_LE:
case GST_VIDEO_FORMAT_RGBA64_BE:
return GDK_MEMORY_R16G16B16A16_PREMULTIPLIED;
case GST_VIDEO_FORMAT_RGBA:
return GDK_MEMORY_R8G8B8A8;
case GST_VIDEO_FORMAT_BGRA:
return GDK_MEMORY_B8G8R8A8;
case GST_VIDEO_FORMAT_ARGB:
return GDK_MEMORY_A8R8G8B8;
case GST_VIDEO_FORMAT_ABGR:
return GDK_MEMORY_A8B8G8R8;
case GST_VIDEO_FORMAT_RGBx:
return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
case GST_VIDEO_FORMAT_BGRx:
return GDK_MEMORY_B8G8R8A8_PREMULTIPLIED;
case GST_VIDEO_FORMAT_RGB:
return GDK_MEMORY_R8G8B8;
case GST_VIDEO_FORMAT_BGR:
return GDK_MEMORY_B8G8R8;
default:
break;
}
/* This should never happen as long as above switch statement
* is updated when new formats are added to caps */
g_assert_not_reached ();
/* Fallback format */
return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
}
GdkTexture *
gst_video_frame_into_gdk_texture (GstVideoFrame *frame)
{
GdkTexture *texture;
GBytes *bytes;
bytes = g_bytes_new_with_free_func (
GST_VIDEO_FRAME_PLANE_DATA (frame, 0),
GST_VIDEO_FRAME_HEIGHT (frame) * GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0),
(GDestroyNotify) gst_buffer_unref,
gst_buffer_ref (frame->buffer));
texture = gdk_memory_texture_new (
GST_VIDEO_FRAME_WIDTH (frame),
GST_VIDEO_FRAME_HEIGHT (frame),
gst_video_format_to_gdk_memory_format (GST_VIDEO_FRAME_FORMAT (frame)),
bytes,
GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0));
g_bytes_unref (bytes);
return texture;
}

37
lib/gst/plugin/gstgtkutils.h vendored Normal file
View File

@@ -0,0 +1,37 @@
/*
* GStreamer
* Copyright (C) 2015 Matthew Waters <matthew@centricular.com>
* Copyright (C) 2015 Thibault Saunier <tsaunier@gnome.org>
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <glib.h>
#include <gtk/gtk.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data);
GdkMemoryFormat gst_video_format_to_gdk_memory_format (GstVideoFormat format);
GdkTexture * gst_video_frame_into_gdk_texture (GstVideoFrame *frame);
G_END_DECLS

43
lib/gst/plugin/gstplugin.c vendored Normal file
View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gmodule.h>
#include "gstclappersink.h"
static gboolean
plugin_init (GstPlugin *plugin)
{
if (!g_module_supported ())
return FALSE;
gst_plugin_add_dependency_simple (plugin,
NULL, CLAPPER_SINK_IMPORTER_PATH, NULL,
GST_PLUGIN_DEPENDENCY_FLAG_NONE);
return GST_ELEMENT_REGISTER (clappersink, plugin);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
clapper, "Clapper elements", plugin_init, VERSION, "LGPL",
GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)

View File

@@ -0,0 +1,549 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperglbaseimporter.h"
#include "gst/plugin/gstgdkformats.h"
#include "gst/plugin/gstgtkutils.h"
#include <gst/gl/gstglfuncs.h>
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_WAYLAND
#include <gdk/wayland/gdkwayland.h>
#include <gst/gl/wayland/gstgldisplay_wayland.h>
#endif
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11
#include <gdk/x11/gdkx.h>
#endif
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_GLX
#include <gst/gl/x11/gstgldisplay_x11.h>
#endif
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_EGL
#include <gst/gl/egl/gstgldisplay_egl.h>
#endif
#define GST_CAT_DEFAULT gst_clapper_gl_base_importer_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_gl_base_importer_parent_class
G_DEFINE_TYPE (GstClapperGLBaseImporter, gst_clapper_gl_base_importer, GST_TYPE_CLAPPER_IMPORTER);
static GstGLContext *
wrap_current_gl (GstGLDisplay *display, GdkGLAPI gdk_gl_api, GstGLPlatform platform)
{
GstGLAPI gst_gl_api = GST_GL_API_NONE;
switch (gdk_gl_api) {
case GDK_GL_API_GL:
gst_gl_api = GST_GL_API_OPENGL | GST_GL_API_OPENGL3;
break;
case GDK_GL_API_GLES:
gst_gl_api = GST_GL_API_GLES2;
break;
default:
g_assert_not_reached ();
break;
}
if (gst_gl_api != GST_GL_API_NONE) {
guintptr gl_handle;
gst_gl_display_filter_gl_api (display, gst_gl_api);
if ((gl_handle = gst_gl_context_get_current_gl_context (platform)))
return gst_gl_context_new_wrapped (display, gl_handle, platform, gst_gl_api);
}
return NULL;
}
static gboolean
retrieve_gl_context_on_main (GstClapperGLBaseImporter *self)
{
GstClapperGLBaseImporterClass *gl_bi_class = GST_CLAPPER_GL_BASE_IMPORTER_GET_CLASS (self);
GdkDisplay *gdk_display;
GdkGLContext *gdk_context;
GError *error = NULL;
GdkGLAPI gdk_gl_api;
GstGLPlatform platform = GST_GL_PLATFORM_NONE;
gint gl_major = 0, gl_minor = 0;
if (!gtk_init_check ()) {
GST_ERROR_OBJECT (self, "Could not ensure GTK initialization");
return FALSE;
}
/* Make sure we are clean here, otherwise data sharing
* between GL-based importers may lead to leaks */
gst_clear_object (&self->wrapped_context);
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
gdk_display = gdk_display_get_default ();
if (!(gdk_context = gdk_display_create_gl_context (gdk_display, &error))) {
GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s",
error ? error->message : "No error set by Gdk");
g_clear_error (&error);
return FALSE;
}
if (!gl_bi_class->gdk_context_realize (self, gdk_context)) {
GST_ERROR_OBJECT (self, "Could not realize Gdk context: %" GST_PTR_FORMAT,
gdk_context);
g_object_unref (gdk_context);
return FALSE;
}
gdk_gl_api = gdk_gl_context_get_api (gdk_context);
GST_OBJECT_LOCK (self);
self->gdk_context = gdk_context;
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) {
struct wl_display *wayland_display =
gdk_wayland_display_get_wl_display (gdk_display);
self->gst_display = (GstGLDisplay *)
gst_gl_display_wayland_new_with_display (wayland_display);
}
#endif
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display)) {
gpointer display_ptr;
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_EGL
display_ptr = gdk_x11_display_get_egl_display (gdk_display);
if (display_ptr) {
self->gst_display = (GstGLDisplay *)
gst_gl_display_egl_new_with_egl_display (display_ptr);
}
#endif
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_GLX
if (!self->gst_display) {
display_ptr = gdk_x11_display_get_xdisplay (gdk_display);
self->gst_display = (GstGLDisplay *)
gst_gl_display_x11_new_with_display (display_ptr);
}
}
#endif
#endif
/* Fallback to generic display */
if (G_UNLIKELY (!self->gst_display)) {
GST_WARNING_OBJECT (self, "Unknown Gdk display!");
self->gst_display = gst_gl_display_new ();
}
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_WAYLAND
if (GST_IS_GL_DISPLAY_WAYLAND (self->gst_display)) {
platform = GST_GL_PLATFORM_EGL;
GST_INFO_OBJECT (self, "Using EGL on Wayland");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_EGL
if (GST_IS_GL_DISPLAY_EGL (self->gst_display)) {
platform = GST_GL_PLATFORM_EGL;
GST_INFO_OBJECT (self, "Using EGL on x11");
goto have_display;
}
#endif
#if GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_GLX
if (GST_IS_GL_DISPLAY_X11 (self->gst_display)) {
platform = GST_GL_PLATFORM_GLX;
GST_INFO_OBJECT (self, "Using GLX on x11");
goto have_display;
}
#endif
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_OBJECT_UNLOCK (self);
GST_ERROR_OBJECT (self, "Unsupported GL platform");
return FALSE;
have_display:
gdk_gl_context_make_current (self->gdk_context);
self->wrapped_context = wrap_current_gl (self->gst_display, gdk_gl_api, platform);
if (!self->wrapped_context) {
GST_ERROR ("Could not retrieve Gdk OpenGL context");
gdk_gl_context_clear_current ();
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_OBJECT_UNLOCK (self);
return FALSE;
}
GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, self->wrapped_context);
gst_gl_context_activate (self->wrapped_context, TRUE);
if (!gst_gl_context_fill_info (self->wrapped_context, &error)) {
GST_ERROR ("Failed to fill Gdk context info: %s", error->message);
g_clear_error (&error);
gst_gl_context_activate (self->wrapped_context, FALSE);
gst_clear_object (&self->wrapped_context);
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
GST_OBJECT_UNLOCK (self);
return FALSE;
}
gst_gl_context_get_gl_version (self->wrapped_context, &gl_major, &gl_minor);
GST_INFO ("Using OpenGL%s %i.%i", (gdk_gl_api == GDK_GL_API_GLES) ? " ES" : "",
gl_major, gl_minor);
/* Deactivate in both places */
gst_gl_context_activate (self->wrapped_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
return TRUE;
}
static gboolean
retrieve_gst_context (GstClapperGLBaseImporter *self)
{
GstGLDisplay *gst_display = NULL;
GstGLContext *gst_context = NULL;
GError *error = NULL;
GST_OBJECT_LOCK (self);
gst_display = gst_object_ref (self->gst_display);
/* GstGLDisplay operations require display object lock to be held */
GST_OBJECT_LOCK (gst_display);
if (!self->gst_context) {
GST_TRACE_OBJECT (self, "Creating new GstGLContext");
if (!gst_gl_display_create_context (gst_display, self->wrapped_context,
&self->gst_context, &error)) {
GST_WARNING ("Could not create OpenGL context: %s",
error ? error->message : "Unknown");
g_clear_error (&error);
GST_OBJECT_UNLOCK (gst_display);
GST_OBJECT_UNLOCK (self);
return FALSE;
}
}
gst_context = gst_object_ref (self->gst_context);
GST_OBJECT_UNLOCK (self);
gst_gl_display_add_context (gst_display, gst_context);
GST_OBJECT_UNLOCK (gst_display);
gst_object_unref (gst_display);
gst_object_unref (gst_context);
return TRUE;
}
static gboolean
gst_clapper_gl_base_importer_prepare (GstClapperImporter *importer)
{
GstClapperGLBaseImporter *self = GST_CLAPPER_GL_BASE_IMPORTER_CAST (importer);
gboolean need_invoke;
GST_OBJECT_LOCK (self);
need_invoke = (!self->gdk_context || !self->gst_display || !self->wrapped_context);
GST_OBJECT_UNLOCK (self);
if (need_invoke) {
if (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback)
retrieve_gl_context_on_main, self)))
return FALSE;
}
if (!retrieve_gst_context (self))
return FALSE;
if (!GST_CLAPPER_IMPORTER_CLASS (parent_class)->prepare)
return TRUE;
return GST_CLAPPER_IMPORTER_CLASS (parent_class)->prepare (importer);
}
static void
gst_clapper_gl_base_importer_share_data (GstClapperImporter *importer, GstClapperImporter *dest_importer)
{
GstClapperGLBaseImporter *self = GST_CLAPPER_GL_BASE_IMPORTER (importer);
if (GST_IS_CLAPPER_GL_BASE_IMPORTER (dest_importer)) {
GstClapperGLBaseImporter *dest = GST_CLAPPER_GL_BASE_IMPORTER (dest_importer);
GST_OBJECT_LOCK (self);
GST_OBJECT_LOCK (dest);
/* Successfully prepared GL importer should have all three */
if (self->gdk_context && self->gst_display && self->wrapped_context) {
g_clear_object (&dest->gdk_context);
dest->gdk_context = g_object_ref (self->gdk_context);
gst_clear_object (&dest->gst_display);
dest->gst_display = gst_object_ref (self->gst_display);
gst_clear_object (&dest->wrapped_context);
dest->wrapped_context = gst_object_ref (self->wrapped_context);
}
/* This context is not required, we can create it ourselves
* using gst_display and wrapped_context */
if (self->gst_context) {
gst_clear_object (&dest->gst_context);
dest->gst_context = gst_object_ref (self->gst_context);
}
GST_OBJECT_UNLOCK (dest);
GST_OBJECT_UNLOCK (self);
}
if (GST_CLAPPER_IMPORTER_CLASS (parent_class)->share_data)
GST_CLAPPER_IMPORTER_CLASS (parent_class)->share_data (importer, dest_importer);
}
static gboolean
gst_clapper_gl_base_importer_handle_context_query (GstClapperImporter *importer,
GstBaseSink *bsink, GstQuery *query)
{
GstClapperGLBaseImporter *self = GST_CLAPPER_GL_BASE_IMPORTER_CAST (importer);
gboolean res;
GST_OBJECT_LOCK (self);
res = gst_gl_handle_context_query (GST_ELEMENT_CAST (bsink), query,
self->gst_display, self->gst_context, self->wrapped_context);
GST_OBJECT_UNLOCK (self);
return res;
}
static GstBufferPool *
gst_clapper_gl_base_importer_create_pool (GstClapperImporter *importer, GstStructure **config)
{
GstClapperGLBaseImporter *self = GST_CLAPPER_GL_BASE_IMPORTER_CAST (importer);
GstBufferPool *pool;
GST_DEBUG_OBJECT (self, "Creating new GL buffer pool");
GST_OBJECT_LOCK (self);
pool = gst_gl_buffer_pool_new (self->gst_context);
GST_OBJECT_UNLOCK (self);
*config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (*config, GST_BUFFER_POOL_OPTION_VIDEO_META);
gst_buffer_pool_config_add_option (*config, GST_BUFFER_POOL_OPTION_GL_SYNC_META);
return pool;
}
static void
gst_clapper_gl_base_importer_add_allocation_metas (GstClapperImporter *importer, GstQuery *query)
{
GstClapperGLBaseImporter *self = GST_CLAPPER_GL_BASE_IMPORTER_CAST (importer);
gst_query_add_allocation_meta (query, GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
GST_OBJECT_LOCK (self);
if (self->gst_context->gl_vtable->FenceSync)
gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, NULL);
GST_OBJECT_UNLOCK (self);
}
static gboolean
gst_clapper_gl_base_importer_gdk_context_realize (GstClapperGLBaseImporter *self, GdkGLContext *gdk_context)
{
GdkGLAPI allowed_apis;
GError *error = NULL;
const gchar *gl_env;
gboolean success;
GST_DEBUG_OBJECT (self, "Realizing GdkGLContext with default implementation");
/* Use single "GST_GL_API" env to also influence Gdk GL selection */
gl_env = g_getenv ("GST_GL_API");
allowed_apis = (!gl_env || g_str_has_prefix (gl_env, "gles"))
? GDK_GL_API_GLES
: (g_str_has_prefix (gl_env, "opengl"))
? GDK_GL_API_GL
: GDK_GL_API_GL | GDK_GL_API_GLES;
gdk_gl_context_set_allowed_apis (gdk_context, allowed_apis);
if (!(success = gdk_gl_context_realize (gdk_context, &error))) {
GST_WARNING_OBJECT (self, "Could not realize Gdk context with %s: %s",
(allowed_apis & GDK_GL_API_GL) ? "GL" : "GLES", error->message);
g_clear_error (&error);
}
if (!success && !gl_env) {
gdk_gl_context_set_allowed_apis (gdk_context, GDK_GL_API_GL);
if (!(success = gdk_gl_context_realize (gdk_context, &error))) {
GST_WARNING_OBJECT (self, "Could not realize Gdk context with GL: %s", error->message);
g_clear_error (&error);
}
}
return success;
}
static void
gst_clapper_gl_base_importer_init (GstClapperGLBaseImporter *self)
{
}
static void
gst_clapper_gl_base_importer_finalize (GObject *object)
{
GstClapperGLBaseImporter *self = GST_CLAPPER_GL_BASE_IMPORTER_CAST (object);
g_clear_object (&self->gdk_context);
gst_clear_object (&self->gst_display);
gst_clear_object (&self->wrapped_context);
gst_clear_object (&self->gst_context);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_gl_base_importer_class_init (GstClapperGLBaseImporterClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass;
GstClapperGLBaseImporterClass *gl_bi_class = (GstClapperGLBaseImporterClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperglbaseimporter", 0,
"Clapper GL Base Importer");
gobject_class->finalize = gst_clapper_gl_base_importer_finalize;
importer_class->prepare = gst_clapper_gl_base_importer_prepare;
importer_class->share_data = gst_clapper_gl_base_importer_share_data;
importer_class->handle_context_query = gst_clapper_gl_base_importer_handle_context_query;
importer_class->create_pool = gst_clapper_gl_base_importer_create_pool;
importer_class->add_allocation_metas = gst_clapper_gl_base_importer_add_allocation_metas;
gl_bi_class->gdk_context_realize = gst_clapper_gl_base_importer_gdk_context_realize;
}
GstCaps *
gst_clapper_gl_base_importer_make_supported_gdk_gl_caps (void)
{
GstCaps *caps, *tmp;
tmp = gst_caps_from_string (
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
"{ " GST_GDK_GL_TEXTURE_FORMATS " }") ", "
"texture-target = (string) { " GST_GL_TEXTURE_TARGET_2D_STR " }");
caps = gst_caps_copy (tmp);
gst_caps_set_features_simple (caps, gst_caps_features_new (
GST_CAPS_FEATURE_MEMORY_GL_MEMORY,
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, NULL));
gst_caps_append (caps, tmp);
return caps;
}
GStrv
gst_clapper_gl_base_importer_make_gl_context_types (void)
{
GStrv context_types;
GStrvBuilder *builder = g_strv_builder_new ();
g_strv_builder_add (builder, GST_GL_DISPLAY_CONTEXT_TYPE);
g_strv_builder_add (builder, "gst.gl.app_context");
g_strv_builder_add (builder, "gst.gl.local_context");
context_types = g_strv_builder_end (builder);
g_strv_builder_unref (builder);
return context_types;
}
GdkTexture *
gst_clapper_gl_base_importer_make_gl_texture (GstClapperGLBaseImporter *self,
GstBuffer *buffer, GstVideoInfo *v_info)
{
GdkTexture *texture;
GstGLSyncMeta *sync_meta;
GstVideoFrame frame;
if (G_UNLIKELY (!gst_video_frame_map (&frame, v_info, buffer, GST_MAP_READ | GST_MAP_GL))) {
GST_ERROR_OBJECT (self, "Could not map input buffer for reading");
return NULL;
}
GST_OBJECT_LOCK (self);
/* Must have context active here for both sync meta
* and Gdk texture format auto-detection to work */
gdk_gl_context_make_current (self->gdk_context);
gst_gl_context_activate (self->wrapped_context, TRUE);
sync_meta = gst_buffer_get_gl_sync_meta (buffer);
/* Wait for all previous OpenGL commands to complete,
* before we start using the input texture */
if (sync_meta) {
gst_gl_sync_meta_set_sync_point (sync_meta, self->gst_context);
gst_gl_sync_meta_wait (sync_meta, self->wrapped_context);
}
texture = gdk_gl_texture_new (
self->gdk_context,
*(guint *) GST_VIDEO_FRAME_PLANE_DATA (&frame, 0),
GST_VIDEO_FRAME_WIDTH (&frame),
GST_VIDEO_FRAME_HEIGHT (&frame),
(GDestroyNotify) gst_buffer_unref,
gst_buffer_ref (buffer));
gst_gl_context_activate (self->wrapped_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
gst_video_frame_unmap (&frame);
return texture;
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include <gst/gl/gl.h>
#include "gst/plugin/gstclapperimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GL_BASE_IMPORTER (gst_clapper_gl_base_importer_get_type())
#define GST_IS_CLAPPER_GL_BASE_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_GL_BASE_IMPORTER))
#define GST_IS_CLAPPER_GL_BASE_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_GL_BASE_IMPORTER))
#define GST_CLAPPER_GL_BASE_IMPORTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_GL_BASE_IMPORTER, GstClapperGLBaseImporterClass))
#define GST_CLAPPER_GL_BASE_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_GL_BASE_IMPORTER, GstClapperGLBaseImporter))
#define GST_CLAPPER_GL_BASE_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_GL_BASE_IMPORTER, GstClapperGLBaseImporterClass))
#define GST_CLAPPER_GL_BASE_IMPORTER_CAST(obj) ((GstClapperGLBaseImporter *)(obj))
#define GST_CLAPPER_GL_BASE_IMPORTER_HAVE_WAYLAND (GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND))
#define GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11 (GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11))
#define GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_GLX (GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11 && GST_GL_HAVE_PLATFORM_GLX)
#define GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11_EGL (GST_CLAPPER_GL_BASE_IMPORTER_HAVE_X11 && GST_GL_HAVE_PLATFORM_EGL)
typedef struct _GstClapperGLBaseImporter GstClapperGLBaseImporter;
typedef struct _GstClapperGLBaseImporterClass GstClapperGLBaseImporterClass;
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperGLBaseImporter, gst_object_unref)
#endif
struct _GstClapperGLBaseImporter
{
GstClapperImporter parent;
GdkGLContext *gdk_context;
GstGLDisplay *gst_display;
GstGLContext *wrapped_context;
GstGLContext *gst_context;
};
struct _GstClapperGLBaseImporterClass
{
GstClapperImporterClass parent_class;
gboolean (* gdk_context_realize) (GstClapperGLBaseImporter *gl_bi,
GdkGLContext *gdk_context);
};
GType gst_clapper_gl_base_importer_get_type (void);
GstCaps * gst_clapper_gl_base_importer_make_supported_gdk_gl_caps (void);
GStrv gst_clapper_gl_base_importer_make_gl_context_types (void);
GdkTexture * gst_clapper_gl_base_importer_make_gl_texture (GstClapperGLBaseImporter *self, GstBuffer *buffer, GstVideoInfo *v_info);
G_END_DECLS

View File

@@ -0,0 +1,70 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperglimporter.h"
#define GST_CAT_DEFAULT gst_clapper_gl_importer_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_gl_importer_parent_class
GST_CLAPPER_IMPORTER_DEFINE (GstClapperGLImporter, gst_clapper_gl_importer, GST_TYPE_CLAPPER_GL_BASE_IMPORTER);
static GdkTexture *
gst_clapper_gl_importer_generate_texture (GstClapperImporter *importer,
GstBuffer *buffer, GstVideoInfo *v_info)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (importer);
return gst_clapper_gl_base_importer_make_gl_texture (gl_bi, buffer, v_info);
}
static void
gst_clapper_gl_importer_init (GstClapperGLImporter *self)
{
}
static void
gst_clapper_gl_importer_class_init (GstClapperGLImporterClass *klass)
{
GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperglimporter", 0,
"Clapper GL Importer");
importer_class->generate_texture = gst_clapper_gl_importer_generate_texture;
}
GstClapperImporter *
make_importer (void)
{
return g_object_new (GST_TYPE_CLAPPER_GL_IMPORTER, NULL);
}
GstCaps *
make_caps (GstRank *rank, GStrv *context_types)
{
*rank = GST_RANK_SECONDARY;
*context_types = gst_clapper_gl_base_importer_make_gl_context_types ();
return gst_clapper_gl_base_importer_make_supported_gdk_gl_caps ();
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gstclapperglbaseimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GL_IMPORTER (gst_clapper_gl_importer_get_type())
G_DECLARE_FINAL_TYPE (GstClapperGLImporter, gst_clapper_gl_importer, GST, CLAPPER_GL_IMPORTER, GstClapperGLBaseImporter)
#define GST_CLAPPER_GL_IMPORTER_CAST(obj) ((GstClapperGLImporter *)(obj))
struct _GstClapperGLImporter
{
GstClapperGLBaseImporter parent;
};
G_END_DECLS

View File

@@ -0,0 +1,646 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclappergluploader.h"
#include "gst/plugin/gstgtkutils.h"
#include <gst/gl/egl/gsteglimage.h>
#include <gst/allocators/gstdmabuf.h>
static const GLfloat vertices[] = {
1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 1.0f
};
static const GLushort indices[] = {
0, 1, 2, 0, 2, 3
};
/* GTK4 renders things upside down ¯\_(ツ)_/¯ */
static const gfloat vertical_flip_matrix[] = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f,
};
typedef struct
{
GstClapperGLUploader *dmabuf_bi;
GLuint id;
guint width;
guint height;
} GstClapperDmabufTexData;
#define GST_CAT_DEFAULT gst_clapper_gl_uploader_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_gl_uploader_parent_class
GST_CLAPPER_IMPORTER_DEFINE (GstClapperGLUploader, gst_clapper_gl_uploader, GST_TYPE_CLAPPER_GL_BASE_IMPORTER);
static void
gst_clapper_gl_uploader_bind_buffer (GstClapperGLUploader *self)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (self);
const GstGLFuncs *gl = gl_bi->gst_context->gl_vtable;
gl->BindBuffer (GL_ARRAY_BUFFER, self->vertex_buffer);
/* Load the vertex position */
gl->VertexAttribPointer (self->attr_position, 3, GL_FLOAT, GL_FALSE,
5 * sizeof (GLfloat), (void *) 0);
/* Load the texture coordinate */
gl->VertexAttribPointer (self->attr_texture, 2, GL_FLOAT, GL_FALSE,
5 * sizeof (GLfloat), (void *) (3 * sizeof (GLfloat)));
gl->EnableVertexAttribArray (self->attr_position);
gl->EnableVertexAttribArray (self->attr_texture);
}
static void
gst_clapper_gl_uploader_unbind_buffer (GstClapperGLUploader *self)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (self);
const GstGLFuncs *gl = gl_bi->gst_context->gl_vtable;
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
gl->DisableVertexAttribArray (self->attr_position);
gl->DisableVertexAttribArray (self->attr_texture);
}
static gboolean
prepare_dmabuf_support_on_main (GstClapperGLUploader *self)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (self);
GstGLSLStage *frag_stage, *vert_stage;
GError *error = NULL;
gchar *frag_str;
const GstGLFuncs *gl;
GST_OBJECT_LOCK (self);
/* FIXME: Return if already prepared */
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
if (!((vert_stage = gst_glsl_stage_new_with_string (gl_bi->gst_context,
GL_VERTEX_SHADER, GST_GLSL_VERSION_NONE,
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY,
gst_gl_shader_string_vertex_mat4_vertex_transform)))) {
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
GST_OBJECT_UNLOCK (self);
GST_ERROR ("Failed to retrieve vertex shader for texture target");
return FALSE;
}
frag_str = gst_gl_shader_string_fragment_external_oes_get_default (
gl_bi->gst_context, GST_GLSL_VERSION_NONE,
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY);
frag_stage = gst_glsl_stage_new_with_string (gl_bi->gst_context,
GL_FRAGMENT_SHADER, GST_GLSL_VERSION_NONE,
GST_GLSL_PROFILE_ES | GST_GLSL_PROFILE_COMPATIBILITY, frag_str);
g_free (frag_str);
if (!frag_stage) {
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
GST_ERROR ("Failed to retrieve fragment shader for texture target");
return FALSE;
}
if (!((self->shader = gst_gl_shader_new_link_with_stages (gl_bi->gst_context,
&error, vert_stage, frag_stage, NULL)))) {
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
GST_ERROR ("Failed to initialize shader: %s", error->message);
g_clear_error (&error);
return FALSE;
}
self->attr_position =
gst_gl_shader_get_attribute_location (self->shader, "a_position");
self->attr_texture =
gst_gl_shader_get_attribute_location (self->shader, "a_texcoord");
gl = gl_bi->gst_context->gl_vtable;
if (gl->GenVertexArrays) {
gl->GenVertexArrays (1, &self->vao);
gl->BindVertexArray (self->vao);
}
gl->GenBuffers (1, &self->vertex_buffer);
gl->BindBuffer (GL_ARRAY_BUFFER, self->vertex_buffer);
gl->BufferData (GL_ARRAY_BUFFER, 4 * 5 * sizeof (GLfloat), vertices, GL_STATIC_DRAW);
if (gl->GenVertexArrays) {
gst_clapper_gl_uploader_bind_buffer (self);
gl->BindVertexArray (0);
}
gl->BindBuffer (GL_ARRAY_BUFFER, 0);
//self->prepared = TRUE;
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
return TRUE;
}
static void
_tex_data_free (GstClapperDmabufTexData *tex_data)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (tex_data->dmabuf_bi);
if (G_LIKELY (tex_data->id > 0)) {
const GstGLFuncs *gl;
GST_OBJECT_LOCK (gl_bi);
gl = gl_bi->gst_context->gl_vtable;
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
gl->DeleteTextures (1, &tex_data->id);
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (gl_bi);
}
gst_object_unref (tex_data->dmabuf_bi);
g_slice_free (GstClapperDmabufTexData, tex_data);
}
static gboolean
_dmabuf_into_texture (GstClapperGLUploader *self, gint *fds, GstVideoInfo *v_info,
gsize *offsets, GstClapperDmabufTexData *tex_data)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (self);
GstEGLImage *image;
const GstGLFuncs *gl;
image = gst_egl_image_from_dmabuf_direct_target (gl_bi->gst_context,
fds, offsets, v_info, self->gst_tex_target);
/* If HW colorspace conversion failed and there is only one
* plane, we can just make it into single EGLImage as is */
if (!image && GST_VIDEO_INFO_N_PLANES (v_info) == 1)
image = gst_egl_image_from_dmabuf (gl_bi->gst_context,
fds[0], v_info, 0, offsets[0]);
if (!image)
return FALSE;
gl = gl_bi->gst_context->gl_vtable;
gl->GenTextures (1, &tex_data->id);
tex_data->width = GST_VIDEO_INFO_WIDTH (v_info);
tex_data->height = GST_VIDEO_INFO_HEIGHT (v_info);
gl->BindTexture (self->gl_tex_target, tex_data->id);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri (self->gl_tex_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->EGLImageTargetTexture2D (self->gl_tex_target, gst_egl_image_get_image (image));
gl->BindTexture (GL_TEXTURE_2D, 0);
gst_egl_image_unref (image);
return TRUE;
}
static gboolean
_oes_texture_into_2d (GstClapperGLUploader *self, GstClapperDmabufTexData *tex_data)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (self);
GLuint framebuffer, tex_id;
GLenum status;
const GstGLFuncs *gl;
gl = gl_bi->gst_context->gl_vtable;
gl->GenFramebuffers (1, &framebuffer);
gl->BindFramebuffer (GL_FRAMEBUFFER, framebuffer);
gl->GenTextures (1, &tex_id);
gl->BindTexture (GL_TEXTURE_2D, tex_id);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl->TexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl->TexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, tex_data->width, tex_data->height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
gl->FramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, tex_id, 0);
status = gl->CheckFramebufferStatus (GL_FRAMEBUFFER);
if (G_UNLIKELY (status != GL_FRAMEBUFFER_COMPLETE)) {
GST_ERROR ("Invalid framebuffer status: %u", status);
gl->BindTexture (GL_TEXTURE_2D, 0);
gl->DeleteTextures (1, &tex_id);
gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
gl->DeleteFramebuffers (1, &framebuffer);
return FALSE;
}
gl->Viewport (0, 0, tex_data->width, tex_data->height);
gst_gl_shader_use (self->shader);
if (gl->BindVertexArray)
gl->BindVertexArray (self->vao);
gst_clapper_gl_uploader_bind_buffer (self);
gl->ActiveTexture (GL_TEXTURE0);
gl->BindTexture (self->gl_tex_target, tex_data->id);
gst_gl_shader_set_uniform_1i (self->shader, "tex", 0);
gst_gl_shader_set_uniform_matrix_4fv (self->shader,
"u_transformation", 1, FALSE, vertical_flip_matrix);
gl->DrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
if (gl->BindVertexArray)
gl->BindVertexArray (0);
else
gst_clapper_gl_uploader_unbind_buffer (self);
gl->BindTexture (self->gl_tex_target, 0);
/* Replace External OES texture with newly created 2D */
gl->DeleteTextures (1, &tex_data->id);
tex_data->id = tex_id;
gl->BindFramebuffer (GL_FRAMEBUFFER, 0);
gl->DeleteFramebuffers (1, &framebuffer);
return TRUE;
}
static GdkTexture *
dmabuf_into_gdk_texture (GstClapperGLUploader *self, GstVideoInfo *v_info, gint *fds, gsize *offsets)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (self);
GdkTexture *texture = NULL;
GstClapperDmabufTexData *tex_data;
tex_data = g_slice_new (GstClapperDmabufTexData);
tex_data->dmabuf_bi = gst_object_ref (self);
GST_OBJECT_LOCK (self);
gdk_gl_context_make_current (gl_bi->gdk_context);
gst_gl_context_activate (gl_bi->gst_context, TRUE);
if (!_dmabuf_into_texture (self, fds, v_info, offsets, tex_data))
goto finish;
/* GTK4 does not support External OES textures.
* Make it into 2D using framebuffer + shader */
if (self->gst_tex_target == GST_GL_TEXTURE_TARGET_EXTERNAL_OES) {
if (G_UNLIKELY (!_oes_texture_into_2d (self, tex_data)))
goto finish;
}
texture = gdk_gl_texture_new (gl_bi->gdk_context,
tex_data->id,
tex_data->width,
tex_data->height,
(GDestroyNotify) _tex_data_free,
tex_data);
finish:
gst_gl_context_activate (gl_bi->gst_context, FALSE);
gdk_gl_context_clear_current ();
GST_OBJECT_UNLOCK (self);
return texture;
}
static gboolean
verify_dmabuf_memory (GstBuffer *buffer, GstVideoInfo *info, gint *fds, gsize *offsets)
{
guint i, n_planes = GST_VIDEO_INFO_N_PLANES (info);
for (i = 0; i < n_planes; i++) {
GstMemory *memory;
gsize plane_size, mem_skip;
guint mem_idx, length;
plane_size = gst_gl_get_plane_data_size (info, NULL, i);
if (!gst_buffer_find_memory (buffer,
GST_VIDEO_INFO_PLANE_OFFSET (info, i),
plane_size, &mem_idx, &length, &mem_skip)) {
GST_DEBUG ("Could not find memory %u", i);
return FALSE;
}
/* We cannot have more then one DMABuf per plane */
if (length != 1) {
GST_DEBUG ("Data for plane %u spans %u memories", i, length);
return FALSE;
}
memory = gst_buffer_peek_memory (buffer, mem_idx);
offsets[i] = memory->offset + mem_skip;
fds[i] = gst_dmabuf_memory_get_fd (memory);
}
return TRUE;
}
static void
_update_elements_caps_locked (GstClapperGLUploader *self, GstCaps *upload_sink_caps)
{
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (self);
GstCaps *upload_src_caps, *color_sink_caps, *color_src_caps, *gdk_sink_caps;
GST_INFO_OBJECT (self, "Input caps: %" GST_PTR_FORMAT, upload_sink_caps);
upload_src_caps = gst_gl_upload_transform_caps (self->upload, gl_bi->gst_context,
GST_PAD_SINK, upload_sink_caps, NULL);
upload_src_caps = gst_caps_fixate (upload_src_caps);
GST_INFO_OBJECT (self, "GLUpload caps: %" GST_PTR_FORMAT, upload_src_caps);
gst_gl_upload_set_caps (self->upload, upload_sink_caps, upload_src_caps);
gdk_sink_caps = gst_clapper_gl_base_importer_make_supported_gdk_gl_caps ();
color_sink_caps = gst_gl_color_convert_transform_caps (gl_bi->gst_context,
GST_PAD_SRC, upload_src_caps, gdk_sink_caps);
gst_caps_unref (gdk_sink_caps);
/* Second caps arg is transfer-full */
color_src_caps = gst_gl_color_convert_fixate_caps (gl_bi->gst_context,
GST_PAD_SINK, upload_src_caps, color_sink_caps);
GST_INFO_OBJECT (self, "GLColorConvert caps: %" GST_PTR_FORMAT, color_src_caps);
gst_gl_color_convert_set_caps (self->color_convert, upload_src_caps, color_src_caps);
self->has_pending_v_info = gst_video_info_from_caps (&self->pending_v_info, color_src_caps);
gst_caps_unref (upload_src_caps);
gst_caps_unref (color_src_caps);
}
static void
gst_clapper_gl_uploader_set_caps (GstClapperImporter *importer, GstCaps *caps)
{
GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (importer);
GST_OBJECT_LOCK (self);
_update_elements_caps_locked (self, caps);
GST_OBJECT_UNLOCK (self);
}
static void
_uploader_reconfigure_locked (GstClapperGLUploader *self)
{
GstCaps *in_caps = NULL;
GST_DEBUG_OBJECT (self, "Reconfiguring upload");
gst_gl_upload_get_caps (self->upload, &in_caps, NULL);
if (G_LIKELY (in_caps)) {
_update_elements_caps_locked (self, in_caps);
gst_caps_unref (in_caps);
}
}
static gboolean
gst_clapper_gl_uploader_prepare (GstClapperImporter *importer)
{
gboolean res = GST_CLAPPER_IMPORTER_CLASS (parent_class)->prepare (importer);
if (res) {
GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (importer);
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (importer);
GST_OBJECT_LOCK (self);
if (!self->upload)
self->upload = gst_gl_upload_new (gl_bi->gst_context);
if (!self->color_convert)
self->color_convert = gst_gl_color_convert_new (gl_bi->gst_context);
GST_OBJECT_UNLOCK (self);
if (!(! !gst_gtk_invoke_on_main (
(GThreadFunc) (GCallback) prepare_dmabuf_support_on_main, self))) {
GST_WARNING_OBJECT (self, "Could not prepare DMABuf support");
/* FIXME: Continue to allow using glupload/cc as fallback */
return FALSE;
}
}
return res;
}
static GstBuffer *
_upload_perform_locked (GstClapperGLUploader *self, GstBuffer *buffer)
{
GstBuffer *upload_buf = NULL;
GstGLUploadReturn ret;
ret = gst_gl_upload_perform_with_buffer (self->upload, buffer, &upload_buf);
if (G_UNLIKELY (ret != GST_GL_UPLOAD_DONE)) {
switch (ret) {
case GST_GL_UPLOAD_RECONFIGURE:
_uploader_reconfigure_locked (self);
/* Retry with the same buffer after reconfiguring */
return _upload_perform_locked (self, buffer);
default:
GST_ERROR_OBJECT (self, "Could not upload input buffer, returned: %i", ret);
break;
}
}
return upload_buf;
}
static GdkTexture *
gst_clapper_gl_uploader_generate_texture (GstClapperImporter *importer,
GstBuffer *buffer, GstVideoInfo *v_info)
{
GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (importer);
GstClapperGLBaseImporter *gl_bi = GST_CLAPPER_GL_BASE_IMPORTER_CAST (importer);
GstBuffer *upload_buf, *color_buf;
GstVideoMeta *meta;
GdkTexture *texture;
/* XXX: We both upload and perform color conversion here, thus we skip
* upload for buffers that are not going to be shown and gain more free
* CPU time to prepare the next one. Improves performance on weak HW. */
if ((meta = gst_buffer_get_video_meta (buffer))) {
guint i;
GST_VIDEO_INFO_WIDTH (v_info) = meta->width;
GST_VIDEO_INFO_HEIGHT (v_info) = meta->height;
for (i = 0; i < meta->n_planes; i++) {
GST_VIDEO_INFO_PLANE_OFFSET (v_info, i) = meta->offset[i];
GST_VIDEO_INFO_PLANE_STRIDE (v_info, i) = meta->stride[i];
}
}
/* FIXME: if can do dmabuf and seems like we have dmabuf here */
{
gint fds[GST_VIDEO_MAX_PLANES];
gsize offsets[GST_VIDEO_MAX_PLANES];
if (verify_dmabuf_memory (buffer, v_info, fds, offsets)) {
if ((texture = dmabuf_into_gdk_texture (self, v_info, fds, offsets))) {
GST_TRACE_OBJECT (self, "Got texture from DMABuf, skipping upload of %" GST_PTR_FORMAT, buffer);
goto done;
}
}
}
GST_LOG_OBJECT (self, "Uploading %" GST_PTR_FORMAT, buffer);
GST_OBJECT_LOCK (self);
upload_buf = _upload_perform_locked (self, buffer);
if (G_UNLIKELY (!upload_buf)) {
GST_ERROR_OBJECT (self, "Could not perform upload on input buffer");
GST_OBJECT_UNLOCK (self);
return NULL;
}
GST_LOG_OBJECT (self, "Uploaded into %" GST_PTR_FORMAT, upload_buf);
color_buf = gst_gl_color_convert_perform (self->color_convert, upload_buf);
gst_buffer_unref (upload_buf);
/* Use video info associated with converted buffer */
if (self->has_pending_v_info) {
self->v_info = self->pending_v_info;
self->has_pending_v_info = FALSE;
}
GST_OBJECT_UNLOCK (self);
if (G_UNLIKELY (!color_buf)) {
GST_ERROR_OBJECT (self, "Could not perform color conversion on input buffer");
return NULL;
}
GST_LOG_OBJECT (self, "Color converted into %" GST_PTR_FORMAT, color_buf);
texture = gst_clapper_gl_base_importer_make_gl_texture (gl_bi, color_buf, &self->v_info);
gst_buffer_unref (color_buf);
done:
return texture;
}
static void
gst_clapper_gl_uploader_init (GstClapperGLUploader *self)
{
gst_video_info_init (&self->pending_v_info);
gst_video_info_init (&self->v_info);
self->gst_tex_target = GST_GL_TEXTURE_TARGET_EXTERNAL_OES;
self->gl_tex_target = gst_gl_texture_target_to_gl (self->gst_tex_target);
}
static void
gst_clapper_gl_uploader_finalize (GObject *object)
{
GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (object);
gst_clear_object (&self->upload);
gst_clear_object (&self->color_convert);
gst_clear_object (&self->shader);
GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}
static void
gst_clapper_gl_uploader_class_init (GstClapperGLUploaderClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergluploader", 0,
"Clapper GL Uploader");
gobject_class->finalize = gst_clapper_gl_uploader_finalize;
importer_class->prepare = gst_clapper_gl_uploader_prepare;
importer_class->set_caps = gst_clapper_gl_uploader_set_caps;
importer_class->generate_texture = gst_clapper_gl_uploader_generate_texture;
}
GstClapperImporter *
make_importer (void)
{
return g_object_new (GST_TYPE_CLAPPER_GL_UPLOADER, NULL);
}
GstCaps *
make_caps (GstRank *rank, GStrv *context_types)
{
*rank = GST_RANK_MARGINAL + 1;
*context_types = gst_clapper_gl_base_importer_make_gl_context_types ();
return gst_gl_upload_get_input_template_caps ();
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gstclapperglbaseimporter.h"
#include <gst/gl/gstglfuncs.h>
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_GL_UPLOADER (gst_clapper_gl_uploader_get_type())
G_DECLARE_FINAL_TYPE (GstClapperGLUploader, gst_clapper_gl_uploader, GST, CLAPPER_GL_UPLOADER, GstClapperGLBaseImporter)
#define GST_CLAPPER_GL_UPLOADER_CAST(obj) ((GstClapperGLUploader *)(obj))
struct _GstClapperGLUploader
{
GstClapperGLBaseImporter parent;
GstGLUpload *upload;
GstGLColorConvert *color_convert;
GstVideoInfo pending_v_info, v_info;
gboolean has_pending_v_info;
/* DMABuf fast-path */
GstGLTextureTarget gst_tex_target;
guint gl_tex_target;
GstGLShader *shader;
GLuint vao;
GLuint vertex_buffer;
GLint attr_position;
GLint attr_texture;
};
G_END_DECLS

View File

@@ -0,0 +1,111 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstclapperrawimporter.h"
#include "gst/plugin/gstgtkutils.h"
#include "gst/plugin/gstgdkformats.h"
#define GST_CAT_DEFAULT gst_clapper_raw_importer_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define parent_class gst_clapper_raw_importer_parent_class
GST_CLAPPER_IMPORTER_DEFINE (GstClapperRawImporter, gst_clapper_raw_importer, GST_TYPE_CLAPPER_IMPORTER);
static GstBufferPool *
gst_clapper_raw_importer_create_pool (GstClapperImporter *importer, GstStructure **config)
{
GstClapperRawImporter *self = GST_CLAPPER_RAW_IMPORTER_CAST (importer);
GstBufferPool *pool;
GST_DEBUG_OBJECT (self, "Creating new buffer pool");
pool = gst_video_buffer_pool_new ();
*config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_add_option (*config, GST_BUFFER_POOL_OPTION_VIDEO_META);
return pool;
}
static void
gst_clapper_raw_importer_add_allocation_metas (GstClapperImporter *importer, GstQuery *query)
{
gst_query_add_allocation_meta (query, GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL);
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
}
static GdkTexture *
gst_clapper_raw_importer_generate_texture (GstClapperImporter *importer,
GstBuffer *buffer, GstVideoInfo *v_info)
{
GdkTexture *texture;
GstVideoFrame frame;
if (G_UNLIKELY (!gst_video_frame_map (&frame, v_info, buffer, GST_MAP_READ))) {
GST_ERROR_OBJECT (importer, "Could not map input buffer for reading");
return NULL;
}
texture = gst_video_frame_into_gdk_texture (&frame);
gst_video_frame_unmap (&frame);
return texture;
}
static void
gst_clapper_raw_importer_init (GstClapperRawImporter *self)
{
}
static void
gst_clapper_raw_importer_class_init (GstClapperRawImporterClass *klass)
{
GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperrawimporter", 0,
"Clapper RAW Importer");
importer_class->create_pool = gst_clapper_raw_importer_create_pool;
importer_class->add_allocation_metas = gst_clapper_raw_importer_add_allocation_metas;
importer_class->generate_texture = gst_clapper_raw_importer_generate_texture;
}
GstClapperImporter *
make_importer (void)
{
return g_object_new (GST_TYPE_CLAPPER_RAW_IMPORTER, NULL);
}
GstCaps *
make_caps (GstRank *rank, GStrv *context_types)
{
*rank = GST_RANK_MARGINAL;
return gst_caps_from_string (
GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY ", "
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION,
"{ " GST_GDK_MEMORY_FORMATS " }")
"; "
GST_VIDEO_CAPS_MAKE (
"{ " GST_GDK_MEMORY_FORMATS " }"));
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 Rafał Dzięgiel <rafostar.github@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#pragma once
#include "gst/plugin/gstclapperimporter.h"
G_BEGIN_DECLS
#define GST_TYPE_CLAPPER_RAW_IMPORTER (gst_clapper_raw_importer_get_type())
G_DECLARE_FINAL_TYPE (GstClapperRawImporter, gst_clapper_raw_importer, GST, CLAPPER_RAW_IMPORTER, GstClapperImporter)
#define GST_CLAPPER_RAW_IMPORTER_CAST(obj) ((GstClapperRawImporter *)(obj))
struct _GstClapperRawImporter
{
GstClapperImporter parent;
};
G_END_DECLS

146
lib/gst/plugin/importers/meson.build vendored Normal file
View File

@@ -0,0 +1,146 @@
gst_clapper_gl_base_importer_dep = dependency('', required: false)
all_importers = [
'glimporter',
'gluploader',
'rawimporter',
]
build_glbase = (
not get_option('glimporter').disabled()
or not get_option('gluploader').disabled()
)
gl_support_required = (
get_option('glimporter').enabled()
or get_option('gluploader').enabled()
)
# We cannot build any importers without sink that they depend on
if not gst_clapper_sink_dep.found()
foreach imp : all_importers
if get_option(imp).enabled()
error('"@0@" option was enabled, but it requires building gstreamer plugin'.format(imp))
endif
endforeach
endif
gst_plugin_gl_base_deps = [gst_clapper_sink_dep, gstgl_dep, gstglproto_dep]
have_gtk_gl_windowing = false
if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx)
gtk_x11_dep = dependency('gtk4-x11', required: false)
if gtk_x11_dep.found()
gst_plugin_gl_base_deps += gtk_x11_dep
if gst_gl_have_platform_glx
gst_plugin_gl_base_deps += gstglx11_dep
endif
have_gtk_gl_windowing = true
endif
endif
if gst_gl_have_window_wayland and gst_gl_have_platform_egl
gtk_wayland_dep = dependency('gtk4-wayland', required: false)
if gtk_wayland_dep.found()
gst_plugin_gl_base_deps += [gtk_wayland_dep, gstglwayland_dep]
have_gtk_gl_windowing = true
endif
endif
if gl_support_required and not have_gtk_gl_windowing
error('GL-based importer was enabled, but support for current GL windowing is missing')
endif
if gst_gl_have_platform_egl
gst_plugin_gl_base_deps += gstglegl_dep
endif
foreach dep : gst_plugin_gl_base_deps
if not dep.found()
if gl_support_required
error('GL-based importer was enabled, but required dependencies were not found')
endif
build_glbase = false
endif
endforeach
if build_glbase
gst_clapper_gl_base_importer_dep = declare_dependency(
link_with: library('gstclapperglbaseimporter',
'gstclapperglbaseimporter.c',
c_args: gst_clapper_plugin_args,
include_directories: configinc,
dependencies: gst_plugin_gl_base_deps,
version: libversion,
install: true,
),
include_directories: configinc,
dependencies: gst_plugin_gl_base_deps,
)
endif
build_glimporter = (
not get_option('glimporter').disabled()
and gst_clapper_gl_base_importer_dep.found()
)
if build_glimporter
library(
'gstclapperglimporter',
'gstclapperglimporter.c',
dependencies: gst_clapper_gl_base_importer_dep,
include_directories: configinc,
c_args: gst_clapper_plugin_args,
install: true,
install_dir: gst_clapper_importers_libdir,
)
endif
build_gluploader = (
not get_option('gluploader').disabled()
and gst_clapper_gl_base_importer_dep.found()
)
gluploader_deps = [
gst_clapper_gl_base_importer_dep,
gstallocators_dep,
]
foreach dep : gluploader_deps
if not dep.found()
if get_option('gluploader').enabled()
error('GL uploader was enabled, but required dependencies were not found')
endif
build_gluploader = false
endif
endforeach
if build_gluploader
library(
'gstclappergluploader',
'gstclappergluploader.c',
dependencies: gluploader_deps,
include_directories: configinc,
c_args: gst_clapper_plugin_args,
install: true,
install_dir: gst_clapper_importers_libdir,
)
endif
# No need to auto build rawimporter if we are building gluploader
build_rawimporter = (
not get_option('rawimporter').disabled()
and (not build_gluploader or get_option('rawimporter').enabled())
and gst_clapper_sink_dep.found()
)
if build_rawimporter
library(
'gstclapperrawimporter',
'gstclapperrawimporter.c',
dependencies: gst_clapper_sink_dep,
include_directories: configinc,
c_args: gst_clapper_plugin_args,
install: true,
install_dir: gst_clapper_importers_libdir,
)
endif

63
lib/gst/plugin/meson.build vendored Normal file
View File

@@ -0,0 +1,63 @@
gst_plugins_libdir = join_paths(prefix, libdir, 'gstreamer-1.0')
gst_clapper_plugin_args = [
'-DHAVE_CONFIG_H',
'-DGST_USE_UNSTABLE_API',
]
gst_clapper_sink_dep = dependency('', required: false)
gtk4_dep = dependency('gtk4', version: '>=4.6.0', required: false)
gmodule_dep = dependency('gmodule-2.0',
version: glib_req,
required: false,
fallback: ['glib', 'libgmodule_dep'],
)
gst_clapper_plugin_deps = [
gtk4_dep,
gst_dep,
gstbase_dep,
gstvideo_dep,
gmodule_dep,
]
build_gst_plugin = not get_option('gst-plugin').disabled()
foreach dep : gst_clapper_plugin_deps
if not dep.found()
if get_option('gst-plugin').enabled()
error('GStreamer plugin was enabled, but required dependencies were not found')
endif
build_gst_plugin = false
endif
endforeach
if get_option('default_library') == 'static'
gst_clapper_plugin_args += ['-DGST_STATIC_COMPILATION']
endif
gst_clapper_plugin_sources = [
'gstclappersink.c',
'gstclapperpaintable.c',
'gstgtkutils.c',
'gstplugin.c',
'gstclapperimporter.c',
'gstclapperimporterloader.c',
]
if build_gst_plugin
gst_clapper_sink_dep = declare_dependency(
link_with: library('gstclapper',
gst_clapper_plugin_sources,
c_args: gst_clapper_plugin_args,
include_directories: configinc,
dependencies: gst_clapper_plugin_deps,
install: true,
install_dir: gst_plugins_libdir,
),
include_directories: configinc,
dependencies: gst_clapper_plugin_deps,
)
endif
subdir('importers')

26
lib/meson.build vendored
View File

@@ -1,5 +1,5 @@
glib_req = '>= 2.56.0'
gst_req = '>= 1.18.0'
glib_req = '>= 2.68.0'
gst_req = '>= 1.20.0'
api_version = '1.0'
libversion = meson.project_version()
@@ -42,10 +42,6 @@ endif
# Symbol visibility
if cc.get_id() == 'msvc'
export_define = '__declspec(dllexport) extern'
elif cc.has_argument('-fvisibility=hidden')
add_project_arguments('-fvisibility=hidden', language: 'c')
add_project_arguments('-fvisibility=hidden', language: 'cpp')
export_define = 'extern __attribute__ ((visibility ("default")))'
else
export_define = 'extern'
endif
@@ -132,7 +128,7 @@ cdata.set('SIZEOF_SHORT', cc.sizeof('short'))
cdata.set('SIZEOF_VOIDP', cc.sizeof('void*'))
cdata.set_quoted('VERSION', libversion)
cdata.set_quoted('PACKAGE', 'gst-plugins-clapper')
cdata.set_quoted('PACKAGE', 'clapper')
cdata.set_quoted('PACKAGE_VERSION', libversion)
cdata.set_quoted('PACKAGE_BUGREPORT', 'https://github.com/Rafostar/clapper/issues/new')
cdata.set_quoted('PACKAGE_NAME', 'GStreamer Clapper Libs')
@@ -184,7 +180,7 @@ foreach extra_arg : warning_flags
endif
endforeach
cdata.set_quoted('GST_PACKAGE_NAME', 'GStreamer Plugins Clapper')
cdata.set_quoted('GST_PACKAGE_NAME', 'gst-plugin-clapper')
cdata.set_quoted('GST_PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper')
# Mandatory GST deps
@@ -200,6 +196,8 @@ gsttag_dep = dependency('gstreamer-tag-1.0', version: gst_req,
fallback: ['gst-plugins-base', 'tag_dep'])
gstvideo_dep = dependency('gstreamer-video-1.0', version: gst_req,
fallback: ['gst-plugins-base', 'video_dep'])
gstallocators_dep = dependency('gstreamer-allocators-1.0', version: gst_req,
fallback : ['gst-plugins-base', 'allocators_dep'])
# GStreamer OpenGL
gstgl_dep = dependency('gstreamer-gl-1.0', version: gst_req,
@@ -251,21 +249,21 @@ giounix_dep = dependency('gio-unix-2.0', version: glib_req, fallback: ['glib', '
cdata.set('DISABLE_ORC', 1)
cdata.set('GST_ENABLE_EXTRA_CHECKS', get_option('devel-checks'))
cdata.set_quoted('GST_PACKAGE_RELEASE_DATETIME', 'Unknown')
configinc = include_directories('.')
libsinc = include_directories('gst')
gir = find_program('g-ir-scanner', required: true)
if not gir.found()
error('Clapper requires GI bindings to be compiled')
endif
gir = find_program('g-ir-scanner')
gir_init_section = ['--add-init-section=extern void gst_init(gint*,gchar**);' + \
'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \
'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \
'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \
'gst_init(NULL,NULL);', '--quiet'
]
gst_clapper_plugin_libdir = join_paths(get_option('prefix'), libdir, 'clapper-@0@'.format(api_version), 'gst', 'plugin')
gst_clapper_importers_libdir = join_paths(gst_clapper_plugin_libdir, 'importers')
cdata.set_quoted('CLAPPER_SINK_IMPORTER_PATH', gst_clapper_importers_libdir)
subdir('gst')
configure_file(output: 'config.h', configuration: cdata)

View File

@@ -1,5 +1,5 @@
project('com.github.rafostar.Clapper', 'c', 'cpp',
version: '0.4.0',
version: '0.4.1',
meson_version: '>= 0.50.0',
license: 'GPL-3.0-or-later',
default_options: [
@@ -19,9 +19,7 @@ datadir = join_paths(get_option('prefix'), get_option('datadir'))
pkglibdir = join_paths(libdir, meson.project_name())
pkgdatadir = join_paths(datadir, meson.project_name())
if get_option('lib')
subdir('lib')
endif
subdir('lib')
if get_option('player')
subdir('bin')

View File

@@ -8,6 +8,28 @@ option('lib',
value: true,
description: 'Build GstClapper lib'
)
option('gst-plugin',
type: 'feature',
value: 'enabled',
description: 'Build GStreamer plugin (includes GTK video sink element)'
)
option('glimporter',
type: 'feature',
value: 'auto',
description: 'Build GL memory importer for clappersink'
)
option('gluploader',
type: 'feature',
value: 'auto',
description: 'Build GL uploader for clappersink'
)
option('rawimporter',
type: 'feature',
value: 'auto',
description: 'Build RAW system memory importer for clappersink'
)
option('devel-checks',
type: 'boolean',
value: false,

View File

@@ -3,6 +3,10 @@
"runtime": "org.gnome.Platform",
"runtime-version": "master",
"sdk": "org.gnome.Sdk",
"sdk-extensions": [
"org.freedesktop.Sdk.Extension.rust-nightly",
"org.freedesktop.Sdk.Extension.llvm13"
],
"command": "com.github.rafostar.Clapper",
"finish-args": [
"--share=ipc",
@@ -19,6 +23,10 @@
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0",
"--env=GST_VAAPI_ALL_DRIVERS=1"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-nightly/bin:/usr/lib/sdk/llvm13/bin",
"prepend-ld-library-path": "/usr/lib/sdk/llvm13/lib"
},
"modules": [
"flathub/shared-modules/gudev/gudev.json",
"flathub/lib/libsass.json",
@@ -33,6 +41,7 @@
"flathub/lib/libass.json",
"flathub/lib/ffmpeg.json",
"testing/gstreamer.json",
"testing/gst-plugins-rs.json",
"testing/gtuber.json",
{
"name": "clapper",

View File

@@ -1,7 +1,7 @@
{
"app-id": "com.github.rafostar.Clapper",
"runtime": "org.gnome.Platform",
"runtime-version": "40",
"runtime-version": "42",
"sdk": "org.gnome.Sdk",
"command": "com.github.rafostar.Clapper",
"finish-args": [
@@ -21,7 +21,6 @@
],
"modules": [
"flathub/shared-modules/gudev/gudev.json",
"flathub/lib/pango.json",
"flathub/lib/libsass.json",
"flathub/lib/sassc.json",
"flathub/lib/liba52.json",
@@ -34,13 +33,7 @@
"flathub/lib/ffmpeg.json",
"flathub/lib/uchardet.json",
"flathub/gstreamer-1.0/gstreamer.json",
"flathub/gstreamer-1.0/gst-plugins-base.json",
"flathub/gstreamer-1.0/gst-plugins-good.json",
"flathub/gstreamer-1.0/gst-plugins-bad.json",
"flathub/gstreamer-1.0/gst-plugins-ugly.json",
"flathub/gstreamer-1.0/gst-libav.json",
"flathub/gstreamer-1.0/gstreamer-vaapi.json",
"flathub/lib/gtk4.json",
"testing/gtk4.json",
"flathub/lib/libadwaita.json",
"testing/gtuber.json",
{

View File

@@ -0,0 +1,23 @@
{
"name": "gst-plugins-rs",
"buildsystem": "simple",
"build-options": {
"build-args": [
"--share=network"
],
"env": {
"CARGO_HOME": "/run/build/gst-plugins-rs/cargo"
}
},
"sources": [
{
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git",
"branch": "main"
}
],
"build-commands": [
"cargo install cargo-c",
"cargo cinstall --prefix=/app -p gst-plugin-dav1d"
]
}

View File

@@ -2,7 +2,7 @@
"name": "gstreamer",
"buildsystem": "meson",
"config-opts": [
"--wrap-mode=nofallback",
"--wrap-mode=nodownload",
"-Dbase=enabled",
"-Dgood=enabled",
@@ -23,28 +23,15 @@
"-Dintrospection=enabled",
"-Ddoc=disabled",
"-Dgtk_doc=disabled",
"-Dgpl=enabled",
"-Dgstreamer:benchmarks=disabled",
"-Dgstreamer:gobject-cast-checks=disabled",
"-Dgstreamer:glib-asserts=disabled",
"-Dgstreamer:glib-checks=disabled",
"-Dgstreamer:extra-checks=disabled",
"-Dgst-plugins-base:gobject-cast-checks=disabled",
"-Dgst-plugins-base:glib-asserts=disabled",
"-Dgst-plugins-base:glib-checks=disabled",
"-Dgst-plugins-base:gl_api=opengl,gles2",
"-Dgst-plugins-base:gl_platform=egl,glx",
"-Dgst-plugins-good:gobject-cast-checks=disabled",
"-Dgst-plugins-good:glib-asserts=disabled",
"-Dgst-plugins-good:glib-checks=disabled",
"-Dgst-plugins-good:gtk3=disabled",
"-Dgst-plugins-bad:gobject-cast-checks=disabled",
"-Dgst-plugins-bad:glib-asserts=disabled",
"-Dgst-plugins-bad:glib-checks=disabled",
"-Dgst-plugins-bad:extra-checks=disabled",
"-Dgst-plugins-bad:vulkan=disabled",
"-Dgst-plugins-bad:webrtc=disabled",
"-Dgst-plugins-bad:wasapi=disabled",
@@ -56,16 +43,14 @@
"-Dgst-plugins-bad:v4l2codecs=enabled",
"-Dgst-plugins-bad:va=enabled",
"-Dgst-plugins-ugly:gobject-cast-checks=disabled",
"-Dgst-plugins-ugly:glib-asserts=disabled",
"-Dgst-plugins-ugly:glib-checks=disabled",
"-Dgst-plugins-ugly:mpeg2dec=enabled"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git",
"branch": "main"
"branch": "main",
"disable-submodules": true
}
]
}

View File

@@ -0,0 +1,31 @@
From b413ee2c7d458c7005d3d3d1da8822cd86893ac0 Mon Sep 17 00:00:00 2001
From: Rafostar <40623528+Rafostar@users.noreply.github.com>
Date: Fri, 4 Dec 2020 19:25:34 +0100
Subject: [PATCH] popover: Call unrealize on hide
When popover is shown "realize" method is called which creates a new
surface for popup. Unfortunately this causes performance drop on Wayland until that
surface is destroyed what happens inside "unrealize" method during popover destruction.
This commit changes default behavior in a way that surface will be destroyed
when popover is closed and app will ragain the performance it lost when
popover was shown.
---
gtk/gtkpopover.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/gtk/gtkpopover.c b/gtk/gtkpopover.c
index 504dcd6cc1..a7a764d483 100644
--- a/gtk/gtkpopover.c
+++ b/gtk/gtkpopover.c
@@ -951,6 +951,7 @@ gtk_popover_hide (GtkWidget *widget)
gtk_popover_set_mnemonics_visible (GTK_POPOVER (widget), FALSE);
_gtk_widget_set_visible_flag (widget, FALSE);
+ gtk_widget_unrealize (widget);
gtk_widget_unmap (widget);
g_signal_emit (widget, signals[CLOSED], 0);
}
--
GitLab

View File

@@ -0,0 +1,37 @@
{
"name": "gtk",
"buildsystem": "meson",
"build-options": {
"build-args": [
"--share=network"
]
},
"config-opts": [
"--buildtype=release",
"-Dwin32-backend=false",
"-Dmacos-backend=false",
"-Dmedia-ffmpeg=disabled",
"-Dprint-cups=disabled",
"-Dintrospection=enabled",
"-Ddemos=false",
"-Dbuild-examples=false",
"-Dbuild-tests=false"
],
"cleanup": [
"/bin/gtk4-builder-tool",
"/bin/gtk4-encode-symbolic-svg"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/gtk.git",
"tag": "4.6.2",
"commit": "aec7ca82007dbe07faee6be084d20758ebac2b91"
},
{
"type": "patch",
"path": "gtk4-popover-unrealize.patch"
}
]
}

View File

@@ -2,7 +2,9 @@
"name": "gtuber",
"buildsystem": "meson",
"config-opts": [
"-Dvapi=disabled"
"-Dintrospection=disabled",
"-Dvapi=disabled",
"-Dgst-gtuber=enabled"
],
"cleanup": [
"/include",

3
pkgs/rpm/.gitignore vendored
View File

@@ -1,3 +0,0 @@
.osc/
clapper/
.lock

View File

@@ -1 +0,0 @@
addFilter("explicit-lib-dependency")

View File

@@ -1,184 +0,0 @@
#
# spec file for package clapper
#
# Copyright (C) 2020 sp1rit
# Copyright (C) 2020-21 Rafostar
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
%define debug_package %{nil}
%global appname com.github.rafostar.Clapper
%global gst_version 1.18.0
%global gtk4_version 4.0.0
%global meson_version 0.50
%global glib2_version 2.56.0
Name: clapper
Version: 0.4.0
Release: 1%{?dist}
Summary: Simple and modern GNOME media player
License: GPL-3.0
URL: https://github.com/Rafostar/clapper
BuildRoot: %{_builddir}/%{name}-%{version}-build
Source0: _service
BuildRequires: meson >= %{meson_version}
BuildRequires: gtk4-devel >= %{gtk4_version}
BuildRequires: glib2-devel >= %{glib2_version}
BuildRequires: gobject-introspection-devel
BuildRequires: gjs
BuildRequires: gcc-c++
BuildRequires: desktop-file-utils
BuildRequires: hicolor-icon-theme
Requires: gjs
Requires: gtk4 >= %{gtk4_version}
Requires: libadwaita
Requires: hicolor-icon-theme
%if 0%{?suse_version}
# SUSE recommends group tag, while Fedora discourages their use
Group: Productivity/Multimedia/Video/Players
BuildRequires: update-desktop-files
BuildRequires: gstreamer-devel >= %{gst_version}
BuildRequires: gstreamer-plugins-base-devel >= %{gst_version}
BuildRequires: Mesa-libGLESv2-devel
BuildRequires: Mesa-libGLESv3-devel
Requires: gstreamer >= %{gst_version}
Requires: gstreamer-plugins-base >= %{gst_version}
Requires: gstreamer-plugins-good >= %{gst_version}
Requires: gstreamer-plugins-bad >= %{gst_version}
# Popular video decoders
Recommends: gstreamer-plugins-libav >= %{gst_version}
# CD Playback
Suggests: gstreamer-plugins-ugly
# Intel/AMD video acceleration
Suggests: gstreamer-plugins-vaapi
%else
BuildRequires: glibc-all-langpacks
BuildRequires: gstreamer1-devel >= %{gst_version}
BuildRequires: gstreamer1-plugins-base-devel >= %{gst_version}
BuildRequires: mesa-libGL-devel
BuildRequires: mesa-libGLES-devel
BuildRequires: mesa-libGLU-devel
BuildRequires: mesa-libEGL-devel
Requires: gstreamer1 >= %{gst_version}
Requires: gstreamer1-plugins-base >= %{gst_version}
Requires: gstreamer1-plugins-good >= %{gst_version}
Requires: gstreamer1-plugins-bad-free >= %{gst_version}
# ASS subtitles (assrender)
Recommends: gstreamer1-plugins-bad-free-extras >= %{gst_version}
# CD Playback
Suggests: gstreamer1-plugins-ugly-free
# Intel/AMD video acceleration
Suggests: gstreamer1-vaapi
%endif
%description
A GNOME media player built using GJS with GTK4 toolkit and powered by GStreamer with OpenGL rendering.
%prep
%setup -q -n %{_sourcedir}/%{name}-%{version} -T -D
%build
%meson
%meson_build
%install
%meson_install
%if 0%{?suse_version}
%suse_update_desktop_file %{appname}
%endif
%check
desktop-file-validate %{buildroot}%{_datadir}/applications/*.desktop
%files
%license COPYING
%doc README.md
%{_bindir}/%{appname}*
%{_bindir}/clapper
%{_datadir}/%{appname}/
%{_datadir}/icons/hicolor/*/apps/*.svg
%{_datadir}/glib-2.0/schemas/%{appname}.gschema.xml
%{_datadir}/mime/packages/%{appname}.xml
%{_datadir}/applications/*.desktop
%{_datadir}/metainfo/*.metainfo.xml
%{_datadir}/gir-1.0/GstClapper-1.0.gir
%{_datadir}/locale/*/LC_MESSAGES/%{appname}.mo
%{_libdir}/%{appname}/
%changelog
* Sun Sep 12 2021 Rafostar <rafostar.github@gmail.com> - 0.4.0-1
- New version
* Thu Aug 26 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-4
- Install translations
* Thu Aug 26 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-3
- Install clapper symlink
* Mon Aug 23 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-2
- Require libadwaita
* Fri Jun 18 2021 Rafostar <rafostar.github@gmail.com> - 0.3.0-1
- New version
* Mon Apr 19 2021 Rafostar <rafostar.github@gmail.com> - 0.2.1-1
- New version
* Tue Apr 13 2021 Rafostar <rafostar.github@gmail.com> - 0.2.0-1
- New version
* Fri Feb 26 2021 Rafostar <rafostar.github@gmail.com> - 0.1.0-1
- New version
* Sun Feb 7 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-10
- Install gstclapper libs to app named subdirectory
* Fri Feb 5 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-9
- Update build with gstclapper libs support
* Thu Jan 21 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-8
- Use metainfo instead of deprecated appdata
* Mon Jan 18 2021 Rafostar <rafostar.github@gmail.com> - 0.0.0-7
- Remove gjs-1.0 files
* Sun Dec 20 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-6
- Include additional app binaries
* Sat Oct 31 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-5
- Added metainfo
* Sun Oct 25 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-4
- Added gschema
* Wed Oct 14 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-3
- Update to GTK4
* Sat Sep 19 22:02:00 CEST 2020 sp1rit - 0.0.0-2
- Added suse_update_desktop_file macro for SuSE packages
* Fri Sep 18 2020 Rafostar <rafostar.github@gmail.com> - 0.0.0-1
- Initial package

View File

@@ -1 +1 @@
ca cs de es hu it nl pl pt_BR ru zh_CN
ar ca cs de es eu fr hu it ja nl pl pt pt_BR ru sv tr zh_CN

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

222
po/ar.po
View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 15:25\n"
"PO-Revision-Date: 2022-03-26 13:37\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
@@ -18,442 +18,442 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr ""
msgid "Open Files"
msgstr "فتح الملفات…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr ""
msgid "Open URI"
msgstr "فتح عنوان URL…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
msgstr ""
msgstr "الإعدادات"
#: ui/clapper.ui:20
msgid "Shortcuts"
msgstr ""
msgstr "الاختصارات"
#: ui/clapper.ui:26
msgid "About Clapper"
msgstr ""
msgstr "حول Clapper"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "السرعة"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
msgid "Normal"
msgstr ""
msgstr "عادي"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr ""
msgstr "عام"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr ""
msgstr "إظهار الاختصارات"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr ""
msgstr "تبديل ملء الشاشة"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
msgstr ""
msgstr "نقرة مزدوجة"
#: ui/help-overlay.ui:26
msgid "Leave fullscreen"
msgstr ""
msgstr "مغادرة ملء الشاشة"
#: ui/help-overlay.ui:32
msgid "Reveal OSD (fullscreen only)"
msgstr ""
msgstr "إظهار المعلومات (ملء الشاشة فقط)"
#: ui/help-overlay.ui:33
msgid "Tap"
msgstr ""
msgstr "نقرة"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr ""
msgstr "خروج"
#: ui/help-overlay.ui:47
msgid "Media"
msgstr ""
msgstr "الوسائط"
#: ui/help-overlay.ui:50
msgid "Open files"
msgstr ""
msgstr "افتح ملفًا"
#: ui/help-overlay.ui:56 src/dialogs.js:137
msgid "Open URI"
msgstr ""
msgstr "فتح عنوان URL"
#: ui/help-overlay.ui:64
msgid "Playlist"
msgstr ""
msgstr "قوائم التشغيل"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr ""
msgstr "المحتوى التالي"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
msgstr ""
msgstr "نقر مزدوج (الجانب الأيمن)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr ""
msgstr "المحتوى السابق"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
msgstr ""
msgstr "نقر مزدوج (الجانب الأيسر)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr ""
msgstr "تغيير وضع التكرار"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr ""
msgstr "التصدير إلى مِلَفّ"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr ""
msgstr "المشغل"
#: ui/help-overlay.ui:98
msgid "Toggle play"
msgstr ""
msgstr "بَدْءّ / إيقاف"
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr ""
msgstr "اضغط مطولاً | انقر بزر الفائرة الأيمن"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr ""
msgstr "التقدم للأمام"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
msgstr ""
msgstr "مرر لليمين"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr ""
msgstr "الرجوع للوراء"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
msgstr ""
msgstr "مرر لليسار"
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr ""
msgstr "رفع مستوى الصوت"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr ""
msgstr "مرر لأعلى"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr ""
msgstr "خفض مستوى الصوت"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr ""
msgstr "مرر لأسفل"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "كتم الصوت"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr ""
msgstr "الفصل التالي"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr ""
msgstr "الفصل السابق"
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
msgstr ""
msgstr "برامج فك التشفير"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr ""
msgstr "العودة إلى الإعدادات"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr ""
msgstr "السلوك"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
msgstr ""
msgstr "ملء الشاشة تلقائياً"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"
msgstr ""
msgstr "أدخل ملء الشاشة عند استبدال قائمة التشغيل باستثناء الوضع العائم"
#: ui/preferences-window.ui:26
msgid "Ask to resume recent media"
msgstr ""
msgstr "اطلب استئناف الوسائط السابقة"
#: ui/preferences-window.ui:32
msgid "Float on all workspaces"
msgstr ""
msgstr "عائم في جميع مساحات العمل"
#: ui/preferences-window.ui:33
msgid "This option only works on GNOME"
msgstr ""
msgstr "هذا الخِيار يعمل فقط على GNOME"
#: ui/preferences-window.ui:39
msgid "After playback"
msgstr ""
msgstr "بعد انتهاء"
#: ui/preferences-window.ui:44
msgid "Do nothing"
msgstr ""
msgstr "لا تفعل شيئًا"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr ""
msgstr "تجميد الأخر لقطة"
#: ui/preferences-window.ui:46
msgid "Close the app"
msgstr ""
msgstr "أغلق التطبيق"
#: ui/preferences-window.ui:56
msgid "Volume"
msgstr ""
msgstr "الصوت"
#: ui/preferences-window.ui:59
msgid "Custom initial value"
msgstr ""
msgstr "قيمة الافتراضية مخصصة"
#: ui/preferences-window.ui:60
msgid "Set custom volume at startup instead of restoring it"
msgstr ""
msgstr "تعيين مستوى صوت مخصص عند بَدْء التشغيل بدلاً من إعادته"
#: ui/preferences-window.ui:64
msgid "Volume percentage"
msgstr ""
msgstr "حجم الصوت"
#: ui/preferences-window.ui:75
msgid "Seeking"
msgstr ""
msgstr "الوضع"
#: ui/preferences-window.ui:78
msgid "Mode"
msgstr ""
msgstr "النمط"
#: ui/preferences-window.ui:84
msgid "Accurate"
msgstr ""
msgstr "دَقيق"
#: ui/preferences-window.ui:85
msgid "Fast"
msgstr ""
msgstr "سريع"
#: ui/preferences-window.ui:93
msgid "Unit"
msgstr ""
msgstr "الوحدة"
#: ui/preferences-window.ui:98
msgid "Second"
msgstr ""
msgstr "ثواني"
#: ui/preferences-window.ui:99
msgid "Minute"
msgstr ""
msgstr "دقائق"
#: ui/preferences-window.ui:100
msgid "Percentage"
msgstr ""
msgstr "النسبة المئوية"
#: ui/preferences-window.ui:108
msgid "Value"
msgstr ""
msgstr "القيمة"
#: ui/preferences-window.ui:123
msgid "Audio"
msgstr ""
msgstr "الصوت"
#: ui/preferences-window.ui:126
msgid "Offset in milliseconds"
msgstr ""
msgstr "إزاحة بالمللي ثانية"
#: ui/preferences-window.ui:133
msgid "Only native audio formats"
msgstr ""
msgstr "تنسيقات الصوت الأصلية فقط"
#: ui/preferences-window.ui:141
msgid "Subtitles"
msgstr ""
msgstr "التَّرْجَمَةً"
#: ui/preferences-window.ui:144
msgid "Default font"
msgstr ""
msgstr "الخط الافتراضي"
#: ui/preferences-window.ui:154
msgid "Network"
msgstr ""
msgstr "الشبكة"
#: ui/preferences-window.ui:158
msgid "Client"
msgstr ""
msgstr "العميل"
#: ui/preferences-window.ui:161
msgid "Progressive download buffering"
msgstr ""
msgstr "التخزين المؤقت للتنزيل بالتدريج"
#: ui/preferences-window.ui:169
msgid "Server"
msgstr ""
msgstr "الخادم"
#: ui/preferences-window.ui:172
msgid "Control player remotely"
msgstr ""
msgstr "التحكم بالوسائط عن بعد"
#: ui/preferences-window.ui:176
msgid "Listening port"
msgstr ""
msgstr "منفذ الاستماع"
#: ui/preferences-window.ui:183
msgid "Run web application in background"
msgstr ""
msgstr "تشغيل تطبيق الويب في الخلفية"
#: ui/preferences-window.ui:184
msgid "Requires GTK compiled with Broadway backend"
msgstr ""
msgstr "يتطلب GTK مجمعة مع Broadway backend"
#: ui/preferences-window.ui:190
msgid "Web application port"
msgstr ""
msgstr "منفذ تطبيق الويب"
#: ui/preferences-window.ui:204
msgid "Prefer adaptive streaming"
msgstr ""
msgstr "تفضيل البث التكيفي"
#: ui/preferences-window.ui:210
msgid "Max quality"
msgstr ""
msgstr "الأعلى جودة"
#: ui/preferences-window.ui:228
msgid "Tweaks"
msgstr ""
msgstr "تعديلات"
#: ui/preferences-window.ui:232
msgid "Appearance"
msgstr ""
msgstr "إعدادات المظهر"
#: ui/preferences-window.ui:235
msgid "Dark theme"
msgstr ""
msgstr "المظهر الداكن"
#: ui/preferences-window.ui:241
msgid "Render window shadows"
msgstr ""
msgstr "عرض ظلال النافذة"
#: ui/preferences-window.ui:242
msgid "Disable to increase performance when windowed"
msgstr ""
msgstr "تعطيل لزيادة الأداء عند وضع النافذة"
#: ui/preferences-window.ui:253
msgid "Plugin ranking"
msgstr ""
msgstr "أعدادات الإضافات"
#: ui/preferences-window.ui:254
msgid "Alter default ranks of GStreamer plugins"
msgstr ""
msgstr "تغيير الأعدادات الافتراضية للأضافات GStreamer"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
msgstr ""
msgstr "استخدام playbin3"
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
msgid "Requires player restart"
msgstr ""
msgstr "يتطلب إعادة التشغيل"
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
msgid "Experimental"
msgstr ""
msgstr "تجريبية"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr ""
msgstr "استخدام PipeWire لإخراج الصوت"
#: src/buttons.js:201
#, javascript-format
msgid "Decoder: %s"
msgstr ""
msgstr "فك التشفير: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr ""
msgstr "أدخل أو الصق URI هنا"
#: src/dialogs.js:157
msgid "Cancel"
msgstr ""
msgstr "إلغاء"
#: src/dialogs.js:158
msgid "Open"
msgstr ""
msgstr "فتح"
#: src/dialogs.js:226
msgid "Title"
msgstr ""
msgstr "العنوان"
#: src/dialogs.js:227
msgid "Completed"
msgstr ""
msgstr "تم مشاهدة"
#: src/dialogs.js:235
msgid "Resume playback?"
msgstr ""
msgstr "استئناف التشغيل؟"
#: src/dialogs.js:289
#, javascript-format
msgid "GTK version: %s"
msgstr ""
msgstr "إصدار GTK: %s"
#: src/dialogs.js:290
#, javascript-format
msgid "Adwaita version: %s"
msgstr ""
msgstr "إصدار Adwaita: %s"
#: src/dialogs.js:291
#, javascript-format
msgid "GStreamer version: %s"
msgstr ""
msgstr "إصدار GStreamer: %s"
#: src/dialogs.js:292
#, javascript-format
msgid "GJS version: %s"
msgstr ""
msgstr "إصدار GJS: %s"
#: src/dialogs.js:300
msgid "A GNOME media player powered by GStreamer"
msgstr ""
msgstr "مشغل وسائط GNOME مدعوم من GStreamer"
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
msgid "translator-credits"
msgstr ""
msgstr "Yousef Fawaz"
#: src/revealers.js:170
#, javascript-format
msgid "Ends at: %s"
msgstr ""
msgstr "ينتهي في: %s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
msgstr ""
msgstr "غير محدّد"
#: src/widget.js:243
msgid "Channels"
msgstr ""
msgstr "قنوات"
#: src/widget.js:261
msgid "Disabled"
msgstr ""
msgstr "مُعطّل"

View File

@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Obre fitxers..."
msgid "Open Files"
msgstr "Obre fitxers"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "Obre l'URI..."
msgid "Open URI"
msgstr "Obre l'URI"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"

View File

@@ -18,11 +18,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Otevřít soubory..."
msgid "Open Files"
msgstr "Otevřít soubory"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "Otevřít URI..."
msgid "Open URI"
msgstr "Otevřít URI"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Dateien öffnen..."
msgid "Open Files"
msgstr "Dateien öffnen"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "Webquelle öffnen..."
msgid "Open URI"
msgstr "Webquelle öffnen"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 15:25\n"
"PO-Revision-Date: 2022-01-16 16:58\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Abrir archivos"
msgid "Open Files"
msgstr "Abrir archivos"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "Abrir URI"
msgid "Open URI"
msgstr "Abrir URI"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
@@ -76,7 +76,7 @@ msgstr "Tocar"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr "Abandonar"
msgstr "Renunciar"
#: ui/help-overlay.ui:47
msgid "Media"
@@ -96,7 +96,7 @@ msgstr "Lista de reproducción"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr "Artículo siguiente"
msgstr "Elemento siguiente"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
@@ -104,7 +104,7 @@ msgstr "Tocar doble (lado derecho)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr "Artículo anterior"
msgstr "Elemento anterior"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
@@ -112,7 +112,7 @@ msgstr "Tocar doble (lado izquierdo)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr "Cambiar a modo repetición"
msgstr "Cambiar a modo repetir"
#: ui/help-overlay.ui:87
msgid "Export to file"
@@ -132,7 +132,7 @@ msgstr "Pulsación larga | Clic derecho"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr "Buscar adelante "
msgstr "Buscar siguiente"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
@@ -140,7 +140,7 @@ msgstr "Deslizar a derecha | Desplazar a derecha"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr "Buscar a atrás"
msgstr "Buscar anterior"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
@@ -168,11 +168,11 @@ msgstr "Fijar a mudo"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr "Siguiente capítulo"
msgstr "Capítulo siguiente"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr "Anterior capítulo"
msgstr "Capítulo anterior"
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
@@ -184,7 +184,7 @@ msgstr "Regresar a preferencias"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr "Comportamiento"
msgstr "Configuraciones"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
@@ -192,7 +192,7 @@ msgstr "Pantalla completa automática"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"
msgstr "Fijar a pantalla completa cuando se reemplaza la lista de reproducción, excepto el modo flotante"
msgstr "Entra a pantalla completa cuando se reemplaza la lista de reproducción, excepto en modo flotante"
#: ui/preferences-window.ui:26
msgid "Ask to resume recent media"
@@ -216,7 +216,7 @@ msgstr "Nada por hacer"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr "Congelar el último fotograma"
msgstr "Detener último fotograma"
#: ui/preferences-window.ui:46
msgid "Close the app"
@@ -232,7 +232,7 @@ msgstr "Valor inicial personalizado"
#: ui/preferences-window.ui:60
msgid "Set custom volume at startup instead of restoring it"
msgstr "Establecer un volumen personalizado al inicio en lugar de restaurarlo"
msgstr "Establece un volumen personalizado al inicio en lugar de restaurarlo"
#: ui/preferences-window.ui:64
msgid "Volume percentage"
@@ -292,7 +292,7 @@ msgstr "Subtítulos"
#: ui/preferences-window.ui:144
msgid "Default font"
msgstr "Fuente predeterminada"
msgstr "Fuente por defecto"
#: ui/preferences-window.ui:154
msgid "Network"
@@ -356,7 +356,7 @@ msgstr "Renderizar sombras de ventana"
#: ui/preferences-window.ui:242
msgid "Disable to increase performance when windowed"
msgstr "Desactivar para aumentar el rendimiento cuando se abre en ventana"
msgstr "Deshabilitado aumenta el rendimiento cuando se abre en ventana"
#: ui/preferences-window.ui:253
msgid "Plugin ranking"
@@ -364,7 +364,7 @@ msgstr "Rango de enchufes"
#: ui/preferences-window.ui:254
msgid "Alter default ranks of GStreamer plugins"
msgstr "Alterar los rangos predeterminados de los enchufes de GStreamer"
msgstr "Altera los rangos predeterminados de los enchufes de GStreamer"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
@@ -380,7 +380,7 @@ msgstr "Experimental"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr "Usar PipeWire para la salida de audio"
msgstr "Usar PipeWire"
#: src/buttons.js:201
#, javascript-format
@@ -389,7 +389,7 @@ msgstr "Decodificador: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr "Intoducir la URI aquí"
msgstr "Introducir la URI"
#: src/dialogs.js:157
msgid "Cancel"
@@ -455,5 +455,5 @@ msgstr "Canales"
#: src/widget.js:261
msgid "Disabled"
msgstr "Deshabilitado"
msgstr "Deshabilitar"

459
po/eu.po Normal file
View File

@@ -0,0 +1,459 @@
msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2022-03-25 16:45\n"
"Last-Translator: \n"
"Language-Team: Basque\n"
"Language: eu_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: clapper\n"
"X-Crowdin-Project-ID: 473374\n"
"X-Crowdin-Language: eu\n"
"X-Crowdin-File: /master/po/com.github.rafostar.Clapper.pot\n"
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files…"
msgstr "Artxiboak ireki…"
#: ui/clapper.ui:10
msgid "Open URI…"
msgstr "Ireki URI…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
msgstr "Hobespenak"
#: ui/clapper.ui:20
msgid "Shortcuts"
msgstr "Lasterbideak"
#: ui/clapper.ui:26
msgid "About Clapper"
msgstr "Clapperi buruz"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr "Abiadura"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
msgid "Normal"
msgstr "Normala"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr "Orokorra"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr "Erakutsi lasterbideak"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr "Aktibatu pantaila osoa"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
msgstr "Ukitu bikoitza / Bikoitza klik"
#: ui/help-overlay.ui:26
msgid "Leave fullscreen"
msgstr "Pantaila osoa utzi"
#: ui/help-overlay.ui:32
msgid "Reveal OSD (fullscreen only)"
msgstr "OSD errebelatu (pantaila osoan bakarrik)"
#: ui/help-overlay.ui:33
msgid "Tap"
msgstr "Ukitu"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr "Irten"
#: ui/help-overlay.ui:47
msgid "Media"
msgstr "Media"
#: ui/help-overlay.ui:50
msgid "Open files"
msgstr "Artxiboak ireki"
#: ui/help-overlay.ui:56 src/dialogs.js:137
msgid "Open URI"
msgstr "Ireki URI"
#: ui/help-overlay.ui:64
msgid "Playlist"
msgstr "Erreprodukzio-zerrenda"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr "Hurrengo elementua"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
msgstr "Ukitu bikoitza (eskuineko aldea)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr "Aurreko elementua"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
msgstr "Ukitu bikoitza (ezkerreko aldea)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr "Errepikapen modua aldatu"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr "Esportatu fitxategira"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr "Erreprodukzioa"
#: ui/help-overlay.ui:98
msgid "Toggle play"
msgstr "Erreprodukzioa aktibatu"
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr "Pultsazio luzea / Eskuineko klik"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr "Aurrerapena bilatu"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
msgstr "Eskuinera irristatu / Eskuinera joan"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr "Atzerantz bilatu"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
msgstr "Ezkerrera irristatu / Ezkerrera mugitu"
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr "Bolumena igo"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr "Gorantz irristatu / Gorantz mugitu"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr "Bolumena jeitsi"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr "Beherantz irristatu / Beherantz mugitu"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr "Aktibatu isiltasuna"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr "Hurrengo kapitulua"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr "Aurreko kapitulua"
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
msgstr "Deskodetzaileak"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr "Itzuli ezarpenetara"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr "Portaera"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
msgstr "Pantaila osoa automatikoa"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"
msgstr "Pantaila osoan sartu erreprodukzio-zerrenda ordezten denean, flotatzeko modua izan ezik"
#: ui/preferences-window.ui:26
msgid "Ask to resume recent media"
msgstr "Azkenaldiko media berriro ekiteko eskatzea"
#: ui/preferences-window.ui:32
msgid "Float on all workspaces"
msgstr "Laneko espazio guztietan flotatzea"
#: ui/preferences-window.ui:33
msgid "This option only works on GNOME"
msgstr "Aukera honek GNOMEn bakarrik funtzionatzen du"
#: ui/preferences-window.ui:39
msgid "After playback"
msgstr "Erreprodukzioaren ondoren"
#: ui/preferences-window.ui:44
msgid "Do nothing"
msgstr "Ez egin ezer"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr "Izoztu azken fotograma"
#: ui/preferences-window.ui:46
msgid "Close the app"
msgstr "Aplikazioa itxi"
#: ui/preferences-window.ui:56
msgid "Volume"
msgstr "Bolumena"
#: ui/preferences-window.ui:59
msgid "Custom initial value"
msgstr "Hasierako balio pertsonalizatua"
#: ui/preferences-window.ui:60
msgid "Set custom volume at startup instead of restoring it"
msgstr "Hasieran bolumen pertsonalizatua ezartzea, lehengoratu beharrean"
#: ui/preferences-window.ui:64
msgid "Volume percentage"
msgstr "Bolumenaren ehunekoa"
#: ui/preferences-window.ui:75
msgid "Seeking"
msgstr "Bilaketa"
#: ui/preferences-window.ui:78
msgid "Mode"
msgstr "Modua"
#: ui/preferences-window.ui:84
msgid "Accurate"
msgstr "Zehatza"
#: ui/preferences-window.ui:85
msgid "Fast"
msgstr "Azkarra"
#: ui/preferences-window.ui:93
msgid "Unit"
msgstr "Unitatea"
#: ui/preferences-window.ui:98
msgid "Second"
msgstr "Segundu"
#: ui/preferences-window.ui:99
msgid "Minute"
msgstr "Minutu"
#: ui/preferences-window.ui:100
msgid "Percentage"
msgstr "Ehunekoa"
#: ui/preferences-window.ui:108
msgid "Value"
msgstr "Balioa"
#: ui/preferences-window.ui:123
msgid "Audio"
msgstr "Audioa"
#: ui/preferences-window.ui:126
msgid "Offset in milliseconds"
msgstr "Desplazamendua milisegundotan"
#: ui/preferences-window.ui:133
msgid "Only native audio formats"
msgstr "Jatorrizko audio-formatuak bakarrik"
#: ui/preferences-window.ui:141
msgid "Subtitles"
msgstr "Azpitituluak"
#: ui/preferences-window.ui:144
msgid "Default font"
msgstr "Hizki lehenetsia"
#: ui/preferences-window.ui:154
msgid "Network"
msgstr "Sarea"
#: ui/preferences-window.ui:158
msgid "Client"
msgstr "Bezeroa"
#: ui/preferences-window.ui:161
msgid "Progressive download buffering"
msgstr "Deskargak pixkanaka bufferizatzea"
#: ui/preferences-window.ui:169
msgid "Server"
msgstr "Zerbitzaria"
#: ui/preferences-window.ui:172
msgid "Control player remotely"
msgstr "Urrutiko erreproduzitzailea kontrolatzea"
#: ui/preferences-window.ui:176
msgid "Listening port"
msgstr "Entzuteko ataka"
#: ui/preferences-window.ui:183
msgid "Run web application in background"
msgstr "Web-aplikazioa bigarren planoan exekutatzea"
#: ui/preferences-window.ui:184
msgid "Requires GTK compiled with Broadway backend"
msgstr "GTK Broadwayko backend-arekin konpilatzea eskatzen du"
#: ui/preferences-window.ui:190
msgid "Web application port"
msgstr "Web aplikazioaren ataka"
#: ui/preferences-window.ui:204
msgid "Prefer adaptive streaming"
msgstr "Transmisio moldagarria lehenestea"
#: ui/preferences-window.ui:210
msgid "Max quality"
msgstr "Gehieneko kalitatea"
#: ui/preferences-window.ui:228
msgid "Tweaks"
msgstr "Ukituak"
#: ui/preferences-window.ui:232
msgid "Appearance"
msgstr "Itxura"
#: ui/preferences-window.ui:235
msgid "Dark theme"
msgstr "Gai iluna"
#: ui/preferences-window.ui:241
msgid "Render window shadows"
msgstr "Leihoetako itzalak errenderizatzea"
#: ui/preferences-window.ui:242
msgid "Disable to increase performance when windowed"
msgstr "Desaktibatu leihoa erabiltzen denean errendimendua handitzeko"
#: ui/preferences-window.ui:253
msgid "Plugin ranking"
msgstr "Pluginen sailkapena"
#: ui/preferences-window.ui:254
msgid "Alter default ranks of GStreamer plugins"
msgstr "Aldatu GStreamer-en pluginen lehenetsitako mailak"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
msgstr "Playbin3 erabili"
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
msgid "Requires player restart"
msgstr "Berrabiaraztea eskatzen du"
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
msgid "Experimental"
msgstr "Esperimentala"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr "Erabili PipeWire audio-irteerarako"
#: src/buttons.js:201
#, javascript-format
msgid "Decoder: %s"
msgstr "Deskodetzailea: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr "Sartu edo utzi URIa erortzen hemen"
#: src/dialogs.js:157
msgid "Cancel"
msgstr "Ezeztatu"
#: src/dialogs.js:158
msgid "Open"
msgstr "Ireki"
#: src/dialogs.js:226
msgid "Title"
msgstr "Titulua"
#: src/dialogs.js:227
msgid "Completed"
msgstr "Osatuta"
#: src/dialogs.js:235
msgid "Resume playback?"
msgstr "Jarraitu erreprodukzioarekin?"
#: src/dialogs.js:289
#, javascript-format
msgid "GTK version: %s"
msgstr "GTK bertsioa: %s"
#: src/dialogs.js:290
#, javascript-format
msgid "Adwaita version: %s"
msgstr "Adwaitaren bertsioa: %s"
#: src/dialogs.js:291
#, javascript-format
msgid "GStreamer version: %s"
msgstr "GStreamer-en bertsioa: %s"
#: src/dialogs.js:292
#, javascript-format
msgid "GJS version: %s"
msgstr "GJS bertsioa: %s"
#: src/dialogs.js:300
msgid "A GNOME media player powered by GStreamer"
msgstr "GNOMEren multimedia erreproduzitzaile bat GStreamer-ekin"
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
msgid "translator-credits"
msgstr "Sergio Varela (@IngrownMink4)"
#: src/revealers.js:170
#, javascript-format
msgid "Ends at: %s"
msgstr "Amaiera: %s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
msgstr "Zehaztugabea"
#: src/widget.js:243
msgid "Channels"
msgstr "Kanalak"
#: src/widget.js:261
msgid "Disabled"
msgstr "Desgaituta"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

222
po/fr.po
View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 15:25\n"
"PO-Revision-Date: 2022-01-18 20:57\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -18,442 +18,442 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr ""
msgid "Open Files"
msgstr "Ouvrir un fichier…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr ""
msgid "Open URI"
msgstr "Ouvrir une URL…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
msgstr ""
msgstr "Préférences"
#: ui/clapper.ui:20
msgid "Shortcuts"
msgstr ""
msgstr "Raccourcis clavier"
#: ui/clapper.ui:26
msgid "About Clapper"
msgstr ""
msgstr "Á propos de Clapper"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "Vitesse"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
msgid "Normal"
msgstr ""
msgstr "Normal"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr ""
msgstr "Général"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr ""
msgstr "Montrer les raccourcis"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr ""
msgstr "Basculer en plein écran"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
msgstr ""
msgstr "Tapoter/cliquer deux fois"
#: ui/help-overlay.ui:26
msgid "Leave fullscreen"
msgstr ""
msgstr "Sortir du plein écran"
#: ui/help-overlay.ui:32
msgid "Reveal OSD (fullscreen only)"
msgstr ""
msgstr "Afficher les commandes de lecture (seulement en plein écran)"
#: ui/help-overlay.ui:33
msgid "Tap"
msgstr ""
msgstr "Taper"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr ""
msgstr "Quitter"
#: ui/help-overlay.ui:47
msgid "Media"
msgstr ""
msgstr "Média"
#: ui/help-overlay.ui:50
msgid "Open files"
msgstr ""
msgstr "Ouvrir un fichier"
#: ui/help-overlay.ui:56 src/dialogs.js:137
msgid "Open URI"
msgstr ""
msgstr "Ouvrir une URL"
#: ui/help-overlay.ui:64
msgid "Playlist"
msgstr ""
msgstr "Playlist"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr ""
msgstr "Prochain média"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
msgstr ""
msgstr "Tapoter deux fois (côté droit)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr ""
msgstr "Média précédent"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
msgstr ""
msgstr "Tapoter deux fois (côté gauche)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr ""
msgstr "Changer le mode de répétition"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr ""
msgstr "Exporter vers un fichier"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr ""
msgstr "Lecture"
#: ui/help-overlay.ui:98
msgid "Toggle play"
msgstr ""
msgstr "Relancer/stopper la lecture"
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr ""
msgstr "Longue pression | Clic droit"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr ""
msgstr "Avancer dans la lecture"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
msgstr ""
msgstr "Glisser/Faire défiler vers la droite"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr ""
msgstr "Reculer dans la lecture"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
msgstr ""
msgstr "Glisser/Faire défiler vers la gauche"
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr ""
msgstr "Augmenter le volume"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr ""
msgstr "Glisser/Défiler vers le haut"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr ""
msgstr "Baisser le volume"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr ""
msgstr "Glisser/Défiler vers le bas"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "Basculer le mode silencieux"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr ""
msgstr "Prochain chapitre"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr ""
msgstr "Chapitre précédent"
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
msgstr ""
msgstr "Décodeurs"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr ""
msgstr "Retourner aux préférences"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr ""
msgstr "Comportement"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
msgstr ""
msgstr "Lecture automatique en plein écran"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"
msgstr ""
msgstr "Basculer en plein écran quand la playlisyt est remplacée sauf si le mode flottant est activé"
#: ui/preferences-window.ui:26
msgid "Ask to resume recent media"
msgstr ""
msgstr "Demander pour reprendre à la position des médias récents"
#: ui/preferences-window.ui:32
msgid "Float on all workspaces"
msgstr ""
msgstr "Flotter sur tous les bureaux virtuels"
#: ui/preferences-window.ui:33
msgid "This option only works on GNOME"
msgstr ""
msgstr "Cette option ne marche qu'avec GNOME"
#: ui/preferences-window.ui:39
msgid "After playback"
msgstr ""
msgstr "Après la lecture"
#: ui/preferences-window.ui:44
msgid "Do nothing"
msgstr ""
msgstr "Ne rien faire"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr ""
msgstr "Geler la dernière image"
#: ui/preferences-window.ui:46
msgid "Close the app"
msgstr ""
msgstr "Fermer l'application"
#: ui/preferences-window.ui:56
msgid "Volume"
msgstr ""
msgstr "Volume"
#: ui/preferences-window.ui:59
msgid "Custom initial value"
msgstr ""
msgstr "Valeur initiale personnalisée"
#: ui/preferences-window.ui:60
msgid "Set custom volume at startup instead of restoring it"
msgstr ""
msgstr "Régler une valeur personnalisée du volume au démarrage au lien de restorer la valeur précédente"
#: ui/preferences-window.ui:64
msgid "Volume percentage"
msgstr ""
msgstr "Pourcentage du volume"
#: ui/preferences-window.ui:75
msgid "Seeking"
msgstr ""
msgstr "Avancement"
#: ui/preferences-window.ui:78
msgid "Mode"
msgstr ""
msgstr "Comportement"
#: ui/preferences-window.ui:84
msgid "Accurate"
msgstr ""
msgstr "Précis"
#: ui/preferences-window.ui:85
msgid "Fast"
msgstr ""
msgstr "Rapide"
#: ui/preferences-window.ui:93
msgid "Unit"
msgstr ""
msgstr "Unité des sauts"
#: ui/preferences-window.ui:98
msgid "Second"
msgstr ""
msgstr "Seconde"
#: ui/preferences-window.ui:99
msgid "Minute"
msgstr ""
msgstr "Minute"
#: ui/preferences-window.ui:100
msgid "Percentage"
msgstr ""
msgstr "Pourcentage"
#: ui/preferences-window.ui:108
msgid "Value"
msgstr ""
msgstr "Longueur du saut"
#: ui/preferences-window.ui:123
msgid "Audio"
msgstr ""
msgstr "Audio"
#: ui/preferences-window.ui:126
msgid "Offset in milliseconds"
msgstr ""
msgstr "Décalage en millisecondes"
#: ui/preferences-window.ui:133
msgid "Only native audio formats"
msgstr ""
msgstr "Seulement des formats audios natifs"
#: ui/preferences-window.ui:141
msgid "Subtitles"
msgstr ""
msgstr "Sous-titres"
#: ui/preferences-window.ui:144
msgid "Default font"
msgstr ""
msgstr "Police par défaut"
#: ui/preferences-window.ui:154
msgid "Network"
msgstr ""
msgstr "Réseau"
#: ui/preferences-window.ui:158
msgid "Client"
msgstr ""
msgstr "Client"
#: ui/preferences-window.ui:161
msgid "Progressive download buffering"
msgstr ""
msgstr "Téléchargement progressif dans le tampon"
#: ui/preferences-window.ui:169
msgid "Server"
msgstr ""
msgstr "Serveur"
#: ui/preferences-window.ui:172
msgid "Control player remotely"
msgstr ""
msgstr "Controler le lecteur à distance"
#: ui/preferences-window.ui:176
msgid "Listening port"
msgstr ""
msgstr "Écouter sur le port"
#: ui/preferences-window.ui:183
msgid "Run web application in background"
msgstr ""
msgstr "Lancer l'application web en arrière plan"
#: ui/preferences-window.ui:184
msgid "Requires GTK compiled with Broadway backend"
msgstr ""
msgstr "Requiert GTK compilé avec l'interface Broadway"
#: ui/preferences-window.ui:190
msgid "Web application port"
msgstr ""
msgstr "Port de l'application web"
#: ui/preferences-window.ui:204
msgid "Prefer adaptive streaming"
msgstr ""
msgstr "Préférer le streaming adaptatif"
#: ui/preferences-window.ui:210
msgid "Max quality"
msgstr ""
msgstr "Qualité maximale"
#: ui/preferences-window.ui:228
msgid "Tweaks"
msgstr ""
msgstr "Réglages"
#: ui/preferences-window.ui:232
msgid "Appearance"
msgstr ""
msgstr "Apparence"
#: ui/preferences-window.ui:235
msgid "Dark theme"
msgstr ""
msgstr "Thème sombre"
#: ui/preferences-window.ui:241
msgid "Render window shadows"
msgstr ""
msgstr "Afficher les ombres de la fenêtre"
#: ui/preferences-window.ui:242
msgid "Disable to increase performance when windowed"
msgstr ""
msgstr "Désactiver pour améliorer les performances quand fenêtré"
#: ui/preferences-window.ui:253
msgid "Plugin ranking"
msgstr ""
msgstr "Liste des plugins"
#: ui/preferences-window.ui:254
msgid "Alter default ranks of GStreamer plugins"
msgstr ""
msgstr "Changer les options par défaut de plugins GStreamer"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
msgstr ""
msgstr "Utiliser playbin3"
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
msgid "Requires player restart"
msgstr ""
msgstr "Requiert le redémarrage du lecteur"
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
msgid "Experimental"
msgstr ""
msgstr "Expérimental"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr ""
msgstr "Utiliser PipeWire pour la sortie audio"
#: src/buttons.js:201
#, javascript-format
msgid "Decoder: %s"
msgstr ""
msgstr "Décodeur: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr ""
msgstr "Entrer ou déposer une URL ici"
#: src/dialogs.js:157
msgid "Cancel"
msgstr ""
msgstr "Annuler"
#: src/dialogs.js:158
msgid "Open"
msgstr ""
msgstr "Ouvrir"
#: src/dialogs.js:226
msgid "Title"
msgstr ""
msgstr "Titre"
#: src/dialogs.js:227
msgid "Completed"
msgstr ""
msgstr "Terminé"
#: src/dialogs.js:235
msgid "Resume playback?"
msgstr ""
msgstr "Reprendre la lecture?"
#: src/dialogs.js:289
#, javascript-format
msgid "GTK version: %s"
msgstr ""
msgstr "Version de GTK: %s"
#: src/dialogs.js:290
#, javascript-format
msgid "Adwaita version: %s"
msgstr ""
msgstr "Version d'Adwaita: %s"
#: src/dialogs.js:291
#, javascript-format
msgid "GStreamer version: %s"
msgstr ""
msgstr "Version de GStreamer: %s"
#: src/dialogs.js:292
#, javascript-format
msgid "GJS version: %s"
msgstr ""
msgstr "Version de GJS: %s"
#: src/dialogs.js:300
msgid "A GNOME media player powered by GStreamer"
msgstr ""
msgstr "Un lecteur multimédia pour GNOME propulsé par GStreamer"
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
msgid "translator-credits"
msgstr ""
msgstr "Robin Verdenal-Tallieux"
#: src/revealers.js:170
#, javascript-format
msgid "Ends at: %s"
msgstr ""
msgstr "Finit à: %s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
msgstr ""
msgstr "Indéterminé"
#: src/widget.js:243
msgid "Channels"
msgstr ""
msgstr "Chaines"
#: src/widget.js:261
msgid "Disabled"
msgstr ""
msgstr "Désactivé"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Fájlok megnyitása..."
msgid "Open Files"
msgstr "Fájlok megnyitása"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "URI megnyitása..."
msgid "Open URI"
msgstr "URI megnyitása"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"

View File

@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Apri i File..."
msgid "Open Files"
msgstr "Apri i File"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "Apri URI..."
msgid "Open URI"
msgstr "Apri URI"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"

222
po/ja.po
View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 15:25\n"
"PO-Revision-Date: 2022-04-01 09:06\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
@@ -18,442 +18,442 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr ""
msgid "Open Files"
msgstr "ファイルを開く…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr ""
msgid "Open URI"
msgstr "URIを開く…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
msgstr ""
msgstr "設定"
#: ui/clapper.ui:20
msgid "Shortcuts"
msgstr ""
msgstr "ショートカット"
#: ui/clapper.ui:26
msgid "About Clapper"
msgstr ""
msgstr "Clapperについて"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "速度"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
msgid "Normal"
msgstr ""
msgstr "標準"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr ""
msgstr "一般"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr ""
msgstr "ショートカットを表示する"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr ""
msgstr "フルスクリーン切り替え"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
msgstr ""
msgstr "ダブルタップ | ダブルクリック"
#: ui/help-overlay.ui:26
msgid "Leave fullscreen"
msgstr ""
msgstr "フルスクリーンを終了"
#: ui/help-overlay.ui:32
msgid "Reveal OSD (fullscreen only)"
msgstr ""
msgstr "OSDを表示 (全画面表示の場合のみ)"
#: ui/help-overlay.ui:33
msgid "Tap"
msgstr ""
msgstr "タップ"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr ""
msgstr "終了"
#: ui/help-overlay.ui:47
msgid "Media"
msgstr ""
msgstr "メディア"
#: ui/help-overlay.ui:50
msgid "Open files"
msgstr ""
msgstr "ファイルを開く"
#: ui/help-overlay.ui:56 src/dialogs.js:137
msgid "Open URI"
msgstr ""
msgstr "URIを開く"
#: ui/help-overlay.ui:64
msgid "Playlist"
msgstr ""
msgstr "プレイリスト"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr ""
msgstr "次の項目"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
msgstr ""
msgstr "ダブルタップ(右端)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr ""
msgstr "前の項目"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
msgstr ""
msgstr "ダブルタップ(左端)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr ""
msgstr "リピートモードを変更"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr ""
msgstr "ファイルへエクスポート"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr ""
msgstr "プレイバック"
#: ui/help-overlay.ui:98
msgid "Toggle play"
msgstr ""
msgstr "一時停止・再生"
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr ""
msgstr "Long press | Right click"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr ""
msgstr "早送り"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
msgstr ""
msgstr "右スワイプ | 右スクロール"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr ""
msgstr "巻き戻し"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
msgstr ""
msgstr "左スワイプ | 左スクロール"
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr ""
msgstr "音量を上げる"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr ""
msgstr "上スワイプ | 上スクロール"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr ""
msgstr "音量を下げる"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr ""
msgstr "下スワイプ | 下スクロール"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "ミュート切り替え"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr ""
msgstr "次のチャプターへ"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr ""
msgstr "前のチャプターへ\""
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
msgstr ""
msgstr "デコーダー"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr ""
msgstr "設定へ戻る"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr ""
msgstr "挙動"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
msgstr ""
msgstr "自動的にフルスクリーンに切り替える"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"
msgstr ""
msgstr "フローティングモードを除き、プレイリストを入れ替えた場合に自動的にフルスクリーンにする"
#: ui/preferences-window.ui:26
msgid "Ask to resume recent media"
msgstr ""
msgstr "前回再生したメディアを続きから再生するか尋ねる"
#: ui/preferences-window.ui:32
msgid "Float on all workspaces"
msgstr ""
msgstr "すべてのワークスペースにフロートを配置する"
#: ui/preferences-window.ui:33
msgid "This option only works on GNOME"
msgstr ""
msgstr "このオプションはGnomeでのみ機能します"
#: ui/preferences-window.ui:39
msgid "After playback"
msgstr ""
msgstr "再生終了後"
#: ui/preferences-window.ui:44
msgid "Do nothing"
msgstr ""
msgstr "何もしない"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr ""
msgstr "最後のフレームで停止する"
#: ui/preferences-window.ui:46
msgid "Close the app"
msgstr ""
msgstr "アプリを閉じる"
#: ui/preferences-window.ui:56
msgid "Volume"
msgstr ""
msgstr "音量"
#: ui/preferences-window.ui:59
msgid "Custom initial value"
msgstr ""
msgstr "初期音量を設定する"
#: ui/preferences-window.ui:60
msgid "Set custom volume at startup instead of restoring it"
msgstr ""
msgstr "前回使用した音量を使用するのではなく、起動時に音量をカスタム値に設定します"
#: ui/preferences-window.ui:64
msgid "Volume percentage"
msgstr ""
msgstr "音量(%)"
#: ui/preferences-window.ui:75
msgid "Seeking"
msgstr ""
msgstr "シーキング"
#: ui/preferences-window.ui:78
msgid "Mode"
msgstr ""
msgstr "モード"
#: ui/preferences-window.ui:84
msgid "Accurate"
msgstr ""
msgstr "精密"
#: ui/preferences-window.ui:85
msgid "Fast"
msgstr ""
msgstr "高速"
#: ui/preferences-window.ui:93
msgid "Unit"
msgstr ""
msgstr "ユニット"
#: ui/preferences-window.ui:98
msgid "Second"
msgstr ""
msgstr ""
#: ui/preferences-window.ui:99
msgid "Minute"
msgstr ""
msgstr ""
#: ui/preferences-window.ui:100
msgid "Percentage"
msgstr ""
msgstr "パーセント"
#: ui/preferences-window.ui:108
msgid "Value"
msgstr ""
msgstr ""
#: ui/preferences-window.ui:123
msgid "Audio"
msgstr ""
msgstr "オ−ディオ"
#: ui/preferences-window.ui:126
msgid "Offset in milliseconds"
msgstr ""
msgstr "オフセット(ミリ秒)"
#: ui/preferences-window.ui:133
msgid "Only native audio formats"
msgstr ""
msgstr "ネイティブオーディオフォーマットのみを使用する"
#: ui/preferences-window.ui:141
msgid "Subtitles"
msgstr ""
msgstr "字幕"
#: ui/preferences-window.ui:144
msgid "Default font"
msgstr ""
msgstr "既定のフォント"
#: ui/preferences-window.ui:154
msgid "Network"
msgstr ""
msgstr "ネットワーク"
#: ui/preferences-window.ui:158
msgid "Client"
msgstr ""
msgstr "クライアント"
#: ui/preferences-window.ui:161
msgid "Progressive download buffering"
msgstr ""
msgstr "プログレッシブなバッファリングを使用する"
#: ui/preferences-window.ui:169
msgid "Server"
msgstr ""
msgstr "サーバー"
#: ui/preferences-window.ui:172
msgid "Control player remotely"
msgstr ""
msgstr "プレーヤーをリモートでコントロールする"
#: ui/preferences-window.ui:176
msgid "Listening port"
msgstr ""
msgstr "待ち受けポート"
#: ui/preferences-window.ui:183
msgid "Run web application in background"
msgstr ""
msgstr "Webアプリケーションをバックグラウンドで実行する"
#: ui/preferences-window.ui:184
msgid "Requires GTK compiled with Broadway backend"
msgstr ""
msgstr "Broadwayバックエンドでコンパイルされた GTK が必要です"
#: ui/preferences-window.ui:190
msgid "Web application port"
msgstr ""
msgstr "Webアプリケーションのポート"
#: ui/preferences-window.ui:204
msgid "Prefer adaptive streaming"
msgstr ""
msgstr "アダプティブストリーミングを優先する"
#: ui/preferences-window.ui:210
msgid "Max quality"
msgstr ""
msgstr "Max quality"
#: ui/preferences-window.ui:228
msgid "Tweaks"
msgstr ""
msgstr "詳細設定"
#: ui/preferences-window.ui:232
msgid "Appearance"
msgstr ""
msgstr "外観"
#: ui/preferences-window.ui:235
msgid "Dark theme"
msgstr ""
msgstr "ダークテーマ"
#: ui/preferences-window.ui:241
msgid "Render window shadows"
msgstr ""
msgstr "ウィンドウの影をレンダリングする"
#: ui/preferences-window.ui:242
msgid "Disable to increase performance when windowed"
msgstr ""
msgstr "無効化するとウィンドウモードでのパフォーマンスが向上します"
#: ui/preferences-window.ui:253
msgid "Plugin ranking"
msgstr ""
msgstr "プライグインの優先順位"
#: ui/preferences-window.ui:254
msgid "Alter default ranks of GStreamer plugins"
msgstr ""
msgstr "GStreamerプラグインの優先順位を変更します"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
msgstr ""
msgstr "playbin3を使用"
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
msgid "Requires player restart"
msgstr ""
msgstr "プレイヤーの再起動が必要です"
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
msgid "Experimental"
msgstr ""
msgstr "試験運用機能"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr ""
msgstr "オーディオ出力にPipeWireを使用する"
#: src/buttons.js:201
#, javascript-format
msgid "Decoder: %s"
msgstr ""
msgstr "デコーダー: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr ""
msgstr "ここにURIを入力またはドロップ"
#: src/dialogs.js:157
msgid "Cancel"
msgstr ""
msgstr "キャンセル"
#: src/dialogs.js:158
msgid "Open"
msgstr ""
msgstr "開く"
#: src/dialogs.js:226
msgid "Title"
msgstr ""
msgstr "タイトル"
#: src/dialogs.js:227
msgid "Completed"
msgstr ""
msgstr "再生済み"
#: src/dialogs.js:235
msgid "Resume playback?"
msgstr ""
msgstr "前回の続きから再生しますか?"
#: src/dialogs.js:289
#, javascript-format
msgid "GTK version: %s"
msgstr ""
msgstr "GTKバージョン: %s"
#: src/dialogs.js:290
#, javascript-format
msgid "Adwaita version: %s"
msgstr ""
msgstr "Adwaitaバージョン: %s"
#: src/dialogs.js:291
#, javascript-format
msgid "GStreamer version: %s"
msgstr ""
msgstr "GStreamer バージョン: %s"
#: src/dialogs.js:292
#, javascript-format
msgid "GJS version: %s"
msgstr ""
msgstr "GJS バージョン: %s"
#: src/dialogs.js:300
msgid "A GNOME media player powered by GStreamer"
msgstr ""
msgstr "GStreamerを使用したGNOME向けのメディアプレーヤー"
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
msgid "translator-credits"
msgstr ""
msgstr "翻訳者"
#: src/revealers.js:170
#, javascript-format
msgid "Ends at: %s"
msgstr ""
msgstr "再生終了時刻: %s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
msgstr ""
msgstr "不明"
#: src/widget.js:243
msgid "Channels"
msgstr ""
msgstr "チャンネル"
#: src/widget.js:261
msgid "Disabled"
msgstr ""
msgstr "Disabled"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 16:21\n"
"PO-Revision-Date: 2022-04-08 16:14\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr "Bestanden openen…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr "URI openen…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
@@ -39,7 +39,7 @@ msgstr "Over Clapper"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "Snelheid"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
@@ -164,7 +164,7 @@ msgstr "Omlaag vegen/scrollen"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "Geluid dempen"
#: ui/help-overlay.ui:139
msgid "Next chapter"
@@ -368,72 +368,72 @@ msgstr "Pas de standaardvolgorde van GStreamer-plug-ins aan"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
msgstr ""
msgstr "playbin3 gebruiken"
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
msgid "Requires player restart"
msgstr ""
msgstr "Herstart de speler om de wijziging toe te passen"
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
msgid "Experimental"
msgstr ""
msgstr "Experimenteel"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr ""
msgstr "PipeWire gebruiken voor audio-uitvoer"
#: src/buttons.js:201
#, javascript-format
msgid "Decoder: %s"
msgstr ""
msgstr "Decoder: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr ""
msgstr "Voer een uri in of sleep een uri hierheen"
#: src/dialogs.js:157
msgid "Cancel"
msgstr ""
msgstr "Annuleren"
#: src/dialogs.js:158
msgid "Open"
msgstr ""
msgstr "Openen"
#: src/dialogs.js:226
msgid "Title"
msgstr ""
msgstr "Titel"
#: src/dialogs.js:227
msgid "Completed"
msgstr ""
msgstr "Voltooid"
#: src/dialogs.js:235
msgid "Resume playback?"
msgstr ""
msgstr "Afspelen hervatten?"
#: src/dialogs.js:289
#, javascript-format
msgid "GTK version: %s"
msgstr ""
msgstr "GTK versie: %s"
#: src/dialogs.js:290
#, javascript-format
msgid "Adwaita version: %s"
msgstr ""
msgstr "Adwaita-versie: %s"
#: src/dialogs.js:291
#, javascript-format
msgid "GStreamer version: %s"
msgstr ""
msgstr "GStreamer-versie: %s"
#: src/dialogs.js:292
#, javascript-format
msgid "GJS version: %s"
msgstr ""
msgstr "GJS-versie: %s"
#: src/dialogs.js:300
msgid "A GNOME media player powered by GStreamer"
msgstr ""
msgstr "Een door GStreamer aangedreven GNOME-mediaspeler"
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
@@ -447,13 +447,13 @@ msgstr "Eindigt op: %s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
msgstr ""
msgstr "onbekend"
#: src/widget.js:243
msgid "Channels"
msgstr ""
msgstr "Kanalen"
#: src/widget.js:261
msgid "Disabled"
msgstr ""
msgstr "Uitgeschakeld"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Otwórz pliki..."
msgid "Open Files"
msgstr "Otwórz pliki"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "Otwórz URI..."
msgid "Open URI"
msgstr "Otwórz URI"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"

222
po/pt.po
View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 15:25\n"
"PO-Revision-Date: 2022-05-01 18:43\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
@@ -18,442 +18,442 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr ""
msgid "Open Files"
msgstr "Abrir ficheiros…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr ""
msgid "Open URI"
msgstr "Abrir URI…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
msgstr ""
msgstr "Preferências"
#: ui/clapper.ui:20
msgid "Shortcuts"
msgstr ""
msgstr "Atalhos"
#: ui/clapper.ui:26
msgid "About Clapper"
msgstr ""
msgstr "Sobre o Clapper"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "Velocidade"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
msgid "Normal"
msgstr ""
msgstr "Predefinido"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr ""
msgstr "Geral"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr ""
msgstr "Mostrar atalhos"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr ""
msgstr "Mudar modo de ecrã"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
msgstr ""
msgstr "Toque duplo duplo Clique duplo"
#: ui/help-overlay.ui:26
msgid "Leave fullscreen"
msgstr ""
msgstr "Sair do modo de ecrã completo"
#: ui/help-overlay.ui:32
msgid "Reveal OSD (fullscreen only)"
msgstr ""
msgstr "Revelar OSD (apenas em tela cheia)"
#: ui/help-overlay.ui:33
msgid "Tap"
msgstr ""
msgstr "Tocar"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr ""
msgstr "Sair"
#: ui/help-overlay.ui:47
msgid "Media"
msgstr ""
msgstr "Multimédia"
#: ui/help-overlay.ui:50
msgid "Open files"
msgstr ""
msgstr "Abrir ficheiro"
#: ui/help-overlay.ui:56 src/dialogs.js:137
msgid "Open URI"
msgstr ""
msgstr "Abrir URI"
#: ui/help-overlay.ui:64
msgid "Playlist"
msgstr ""
msgstr "Lista de reprodução"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr ""
msgstr "Próximo item"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
msgstr ""
msgstr "Toque duplo (lado direito)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr ""
msgstr "Item anterior"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
msgstr ""
msgstr "Toque duplo (lado esquerdo)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr ""
msgstr "Alterar modo de repetição"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr ""
msgstr "Exportar para ficheiro"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr ""
msgstr "Reproduzir"
#: ui/help-overlay.ui:98
msgid "Toggle play"
msgstr ""
msgstr "Alternar reprodução"
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr ""
msgstr "Toque longo | Clique com o botão direito"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr ""
msgstr "Procurar para a frente"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
msgstr ""
msgstr "Deslizar para a direita | Deslocar para direita"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr ""
msgstr "Procurar para trás"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
msgstr ""
msgstr "Deslizar para a esquerda | Deslocar para a esquerda"
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr ""
msgstr "Aumentar o volume"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr ""
msgstr "Deslizar para cima | Deslocar para cima"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr ""
msgstr "Diminuir o volume"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr ""
msgstr "Deslizar para baixo | Deslocar para baixo"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "Ativar/Desativar Som"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr ""
msgstr "Capítulo seguinte"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr ""
msgstr "Capítulo anterior"
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
msgstr ""
msgstr "Descodificadores"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr ""
msgstr "Voltar para as preferências"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr ""
msgstr "Comportamento"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
msgstr ""
msgstr "Ecrã inteiro automático"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"
msgstr ""
msgstr "Entrar em ecrã inteiro quando a lista de reprodução é substituída, excepto no modo flutuante"
#: ui/preferences-window.ui:26
msgid "Ask to resume recent media"
msgstr ""
msgstr "Pedir para retomar o ficheiro recente"
#: ui/preferences-window.ui:32
msgid "Float on all workspaces"
msgstr ""
msgstr "Flutuar em todas as áreas de trabalho"
#: ui/preferences-window.ui:33
msgid "This option only works on GNOME"
msgstr ""
msgstr "Esta opção apenas funciona no GNOME"
#: ui/preferences-window.ui:39
msgid "After playback"
msgstr ""
msgstr "Após reprodução"
#: ui/preferences-window.ui:44
msgid "Do nothing"
msgstr ""
msgstr "Não fazer nada"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr ""
msgstr "Suster o fotograma"
#: ui/preferences-window.ui:46
msgid "Close the app"
msgstr ""
msgstr "Fechar a aplicação"
#: ui/preferences-window.ui:56
msgid "Volume"
msgstr ""
msgstr "Volume"
#: ui/preferences-window.ui:59
msgid "Custom initial value"
msgstr ""
msgstr "Valor inicial personalizado"
#: ui/preferences-window.ui:60
msgid "Set custom volume at startup instead of restoring it"
msgstr ""
msgstr "Definir volume personalizado no arranque ao invés de restaurá-lo"
#: ui/preferences-window.ui:64
msgid "Volume percentage"
msgstr ""
msgstr "Percentagem de volume"
#: ui/preferences-window.ui:75
msgid "Seeking"
msgstr ""
msgstr "Procurando"
#: ui/preferences-window.ui:78
msgid "Mode"
msgstr ""
msgstr "Modo"
#: ui/preferences-window.ui:84
msgid "Accurate"
msgstr ""
msgstr "Preciso"
#: ui/preferences-window.ui:85
msgid "Fast"
msgstr ""
msgstr "Rápido"
#: ui/preferences-window.ui:93
msgid "Unit"
msgstr ""
msgstr "Unidade"
#: ui/preferences-window.ui:98
msgid "Second"
msgstr ""
msgstr "Segundo"
#: ui/preferences-window.ui:99
msgid "Minute"
msgstr ""
msgstr "Minuto"
#: ui/preferences-window.ui:100
msgid "Percentage"
msgstr ""
msgstr "Percentagem"
#: ui/preferences-window.ui:108
msgid "Value"
msgstr ""
msgstr "Valor"
#: ui/preferences-window.ui:123
msgid "Audio"
msgstr ""
msgstr "Áudio"
#: ui/preferences-window.ui:126
msgid "Offset in milliseconds"
msgstr ""
msgstr "Deslocamento em milissegundos"
#: ui/preferences-window.ui:133
msgid "Only native audio formats"
msgstr ""
msgstr "Apenas formatos de áudio nativos"
#: ui/preferences-window.ui:141
msgid "Subtitles"
msgstr ""
msgstr "Legendas"
#: ui/preferences-window.ui:144
msgid "Default font"
msgstr ""
msgstr "Tipo de letra predefinida"
#: ui/preferences-window.ui:154
msgid "Network"
msgstr ""
msgstr "Rede"
#: ui/preferences-window.ui:158
msgid "Client"
msgstr ""
msgstr "Cliente"
#: ui/preferences-window.ui:161
msgid "Progressive download buffering"
msgstr ""
msgstr "Buffering de transferência progressiva"
#: ui/preferences-window.ui:169
msgid "Server"
msgstr ""
msgstr "Servidor"
#: ui/preferences-window.ui:172
msgid "Control player remotely"
msgstr ""
msgstr "Controlar reprodutor remotamente"
#: ui/preferences-window.ui:176
msgid "Listening port"
msgstr ""
msgstr "Porta de escuta"
#: ui/preferences-window.ui:183
msgid "Run web application in background"
msgstr ""
msgstr "Executar aplicação web em segundo plano"
#: ui/preferences-window.ui:184
msgid "Requires GTK compiled with Broadway backend"
msgstr ""
msgstr "Requer GTK compilado com backend Broadway"
#: ui/preferences-window.ui:190
msgid "Web application port"
msgstr ""
msgstr "Porta de aplicação Web"
#: ui/preferences-window.ui:204
msgid "Prefer adaptive streaming"
msgstr ""
msgstr "Preferir transmissão adaptável"
#: ui/preferences-window.ui:210
msgid "Max quality"
msgstr ""
msgstr "Qualidade máxima"
#: ui/preferences-window.ui:228
msgid "Tweaks"
msgstr ""
msgstr "Ajustes"
#: ui/preferences-window.ui:232
msgid "Appearance"
msgstr ""
msgstr "Aparência"
#: ui/preferences-window.ui:235
msgid "Dark theme"
msgstr ""
msgstr "Tema escuro"
#: ui/preferences-window.ui:241
msgid "Render window shadows"
msgstr ""
msgstr "Renderizar sombras das janelas"
#: ui/preferences-window.ui:242
msgid "Disable to increase performance when windowed"
msgstr ""
msgstr "Desativar para aumentar o desempenho quando em janela"
#: ui/preferences-window.ui:253
msgid "Plugin ranking"
msgstr ""
msgstr "Classificação do plugin"
#: ui/preferences-window.ui:254
msgid "Alter default ranks of GStreamer plugins"
msgstr ""
msgstr "Altera as classificações predefinidas dos plugins GStreamer"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
msgstr ""
msgstr "Usar playbin3"
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
msgid "Requires player restart"
msgstr ""
msgstr "Requer o reinício do reprodutor"
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
msgid "Experimental"
msgstr ""
msgstr "Experimental"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr ""
msgstr "Usar o PipeWire para a saída áudio"
#: src/buttons.js:201
#, javascript-format
msgid "Decoder: %s"
msgstr ""
msgstr "Descodificador: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr ""
msgstr "Introduzir ou largar o URI aqui"
#: src/dialogs.js:157
msgid "Cancel"
msgstr ""
msgstr "Cancelar"
#: src/dialogs.js:158
msgid "Open"
msgstr ""
msgstr "Abrir"
#: src/dialogs.js:226
msgid "Title"
msgstr ""
msgstr "Título"
#: src/dialogs.js:227
msgid "Completed"
msgstr ""
msgstr "Concluído"
#: src/dialogs.js:235
msgid "Resume playback?"
msgstr ""
msgstr "Retomar a reprodução?"
#: src/dialogs.js:289
#, javascript-format
msgid "GTK version: %s"
msgstr ""
msgstr "Versão do GTK: %s"
#: src/dialogs.js:290
#, javascript-format
msgid "Adwaita version: %s"
msgstr ""
msgstr "Versão do Adwaita: %s"
#: src/dialogs.js:291
#, javascript-format
msgid "GStreamer version: %s"
msgstr ""
msgstr "Versão do GStreamer: %s"
#: src/dialogs.js:292
#, javascript-format
msgid "GJS version: %s"
msgstr ""
msgstr "Versão do GJS: %s"
#: src/dialogs.js:300
msgid "A GNOME media player powered by GStreamer"
msgstr ""
msgstr "Um reprodutor multimédia GNOME desenvolvido por GStreamer"
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
msgid "translator-credits"
msgstr ""
msgstr "Hugo Carvalho <hugokarvalho@hotmail.com>"
#: src/revealers.js:170
#, javascript-format
msgid "Ends at: %s"
msgstr ""
msgstr "Termina às: %s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
msgstr ""
msgstr "Indeterminado"
#: src/widget.js:243
msgid "Channels"
msgstr ""
msgstr "Canais"
#: src/widget.js:261
msgid "Disabled"
msgstr ""
msgstr "Desativado"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr "Abrir Arquivos"
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr "Abrir URI"
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-17 08:56\n"
"PO-Revision-Date: 2022-01-16 14:15\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -18,12 +18,12 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr "Открыть файлы ..."
msgid "Open Files"
msgstr "Открыть файлы"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr "Открыть URI..."
msgid "Open URI"
msgstr "Открыть URI"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

222
po/sv.po
View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 15:24\n"
"PO-Revision-Date: 2022-01-16 14:15\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
@@ -18,442 +18,442 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr ""
msgid "Open Files"
msgstr "Öppna filer…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr ""
msgid "Open URI"
msgstr "Öppna URL…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
msgstr ""
msgstr "Inställningar"
#: ui/clapper.ui:20
msgid "Shortcuts"
msgstr ""
msgstr "Tangentbordsgenvägar"
#: ui/clapper.ui:26
msgid "About Clapper"
msgstr ""
msgstr "Om Clapper"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "Hastighet"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
msgid "Normal"
msgstr ""
msgstr "Normal"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr ""
msgstr "Allmänt"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr ""
msgstr "Visa tangentbordsgenvägar"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr ""
msgstr "Växla helskärmsläge"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
msgstr ""
msgstr "Dubbeltryck | Dubbelklicka"
#: ui/help-overlay.ui:26
msgid "Leave fullscreen"
msgstr ""
msgstr "Lämna helskärmsläge"
#: ui/help-overlay.ui:32
msgid "Reveal OSD (fullscreen only)"
msgstr ""
msgstr "Visa OSD (endast helskärmsläge)"
#: ui/help-overlay.ui:33
msgid "Tap"
msgstr ""
msgstr "Tryck"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr ""
msgstr "Avsluta"
#: ui/help-overlay.ui:47
msgid "Media"
msgstr ""
msgstr "Media"
#: ui/help-overlay.ui:50
msgid "Open files"
msgstr ""
msgstr "Öppna filer"
#: ui/help-overlay.ui:56 src/dialogs.js:137
msgid "Open URI"
msgstr ""
msgstr "Öppna URI"
#: ui/help-overlay.ui:64
msgid "Playlist"
msgstr ""
msgstr "Spellista"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr ""
msgstr "Nästa föremål"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
msgstr ""
msgstr "Dubbeltryck (höger sida)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr ""
msgstr "Föregående föremål"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
msgstr ""
msgstr "Dubbeltryck (vänster sida)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr ""
msgstr "Ändra upprepningsläge"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr ""
msgstr "Exportera till fil"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr ""
msgstr "Uppspelning"
#: ui/help-overlay.ui:98
msgid "Toggle play"
msgstr ""
msgstr "Spela/pausa"
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr ""
msgstr "Långtryck | Högerklicka"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr ""
msgstr "Spola framåt"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
msgstr ""
msgstr "Svep höger | Skrolla höger"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr ""
msgstr "Spola bakåt"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
msgstr ""
msgstr "Svep vänster | Skrolla vänster"
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr ""
msgstr "Höj volymen"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr ""
msgstr "Svep uppåt | Skrolla uppåt"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr ""
msgstr "Sänk volymen"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr ""
msgstr "Svep nedåt | Skrolla nedåt"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "Växla ljudet på/av"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr ""
msgstr "Nästa kapitel"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr ""
msgstr "Föregående kapitel"
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
msgstr ""
msgstr "Avkodare"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr ""
msgstr "Återgå till inställningarna"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr ""
msgstr "Beteende"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
msgstr ""
msgstr "Automatiskt helskärmsläge"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"
msgstr ""
msgstr "Växla till fullskärmsläge när en spellista ersätts, förutom i flytande läge"
#: ui/preferences-window.ui:26
msgid "Ask to resume recent media"
msgstr ""
msgstr "Be att återuppta senaste media"
#: ui/preferences-window.ui:32
msgid "Float on all workspaces"
msgstr ""
msgstr "Flyt på alla arbetsytor"
#: ui/preferences-window.ui:33
msgid "This option only works on GNOME"
msgstr ""
msgstr "Det här alternativet fungerar endast i GNOME"
#: ui/preferences-window.ui:39
msgid "After playback"
msgstr ""
msgstr "Efter uppspelning"
#: ui/preferences-window.ui:44
msgid "Do nothing"
msgstr ""
msgstr "Gör ingenting"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr ""
msgstr "Frys sista bildruta"
#: ui/preferences-window.ui:46
msgid "Close the app"
msgstr ""
msgstr "Stäng appen"
#: ui/preferences-window.ui:56
msgid "Volume"
msgstr ""
msgstr "Volym"
#: ui/preferences-window.ui:59
msgid "Custom initial value"
msgstr ""
msgstr "Anpassat startvärde"
#: ui/preferences-window.ui:60
msgid "Set custom volume at startup instead of restoring it"
msgstr ""
msgstr "Ställ in anpassad volym vid start istället för att återställa den"
#: ui/preferences-window.ui:64
msgid "Volume percentage"
msgstr ""
msgstr "Volymprocent"
#: ui/preferences-window.ui:75
msgid "Seeking"
msgstr ""
msgstr "Spolning"
#: ui/preferences-window.ui:78
msgid "Mode"
msgstr ""
msgstr "Läge"
#: ui/preferences-window.ui:84
msgid "Accurate"
msgstr ""
msgstr "Riktig"
#: ui/preferences-window.ui:85
msgid "Fast"
msgstr ""
msgstr "Snabbt"
#: ui/preferences-window.ui:93
msgid "Unit"
msgstr ""
msgstr "Enhet"
#: ui/preferences-window.ui:98
msgid "Second"
msgstr ""
msgstr "Sekund"
#: ui/preferences-window.ui:99
msgid "Minute"
msgstr ""
msgstr "Minut"
#: ui/preferences-window.ui:100
msgid "Percentage"
msgstr ""
msgstr "Procent"
#: ui/preferences-window.ui:108
msgid "Value"
msgstr ""
msgstr "Värde"
#: ui/preferences-window.ui:123
msgid "Audio"
msgstr ""
msgstr "Ljud"
#: ui/preferences-window.ui:126
msgid "Offset in milliseconds"
msgstr ""
msgstr "Förskjutning i millisekunder"
#: ui/preferences-window.ui:133
msgid "Only native audio formats"
msgstr ""
msgstr "Endast inbyggda ljudformat"
#: ui/preferences-window.ui:141
msgid "Subtitles"
msgstr ""
msgstr "Undertexter"
#: ui/preferences-window.ui:144
msgid "Default font"
msgstr ""
msgstr "Standardteckensnitt"
#: ui/preferences-window.ui:154
msgid "Network"
msgstr ""
msgstr "Nätverk"
#: ui/preferences-window.ui:158
msgid "Client"
msgstr ""
msgstr "Klient"
#: ui/preferences-window.ui:161
msgid "Progressive download buffering"
msgstr ""
msgstr "Progressiv nedladdningsbuffert"
#: ui/preferences-window.ui:169
msgid "Server"
msgstr ""
msgstr "Server"
#: ui/preferences-window.ui:172
msgid "Control player remotely"
msgstr ""
msgstr "Fjärrstyra spelaren"
#: ui/preferences-window.ui:176
msgid "Listening port"
msgstr ""
msgstr "Lyssningsport"
#: ui/preferences-window.ui:183
msgid "Run web application in background"
msgstr ""
msgstr "Kör webbprogram i bakgrunden"
#: ui/preferences-window.ui:184
msgid "Requires GTK compiled with Broadway backend"
msgstr ""
msgstr "Kräver GTK kompilerad med Broadway backend"
#: ui/preferences-window.ui:190
msgid "Web application port"
msgstr ""
msgstr "Port för webbprogram"
#: ui/preferences-window.ui:204
msgid "Prefer adaptive streaming"
msgstr ""
msgstr "Föredra adaptiv streaming"
#: ui/preferences-window.ui:210
msgid "Max quality"
msgstr ""
msgstr "Max kvalitet"
#: ui/preferences-window.ui:228
msgid "Tweaks"
msgstr ""
msgstr "Tweaks"
#: ui/preferences-window.ui:232
msgid "Appearance"
msgstr ""
msgstr "Utseende"
#: ui/preferences-window.ui:235
msgid "Dark theme"
msgstr ""
msgstr "Mörkt Tema"
#: ui/preferences-window.ui:241
msgid "Render window shadows"
msgstr ""
msgstr "Rendera fönsterskuggor"
#: ui/preferences-window.ui:242
msgid "Disable to increase performance when windowed"
msgstr ""
msgstr "Inaktivera för att öka prestanda när fönsterläge är på"
#: ui/preferences-window.ui:253
msgid "Plugin ranking"
msgstr ""
msgstr "Rangordning av plugin"
#: ui/preferences-window.ui:254
msgid "Alter default ranks of GStreamer plugins"
msgstr ""
msgstr "Ändra standardrankningar för GStreamer-plugins"
#: ui/preferences-window.ui:259
msgid "Use playbin3"
msgstr ""
msgstr "Använd playbin3"
#: ui/preferences-window.ui:260 ui/preferences-window.ui:269
msgid "Requires player restart"
msgstr ""
msgstr "Kräver omstart av spelaren"
#: ui/preferences-window.ui:262 ui/preferences-window.ui:271
msgid "Experimental"
msgstr ""
msgstr "Exprimentalt"
#: ui/preferences-window.ui:268
msgid "Use PipeWire for audio output"
msgstr ""
msgstr "Använd PipeWire för Ljudutgång"
#: src/buttons.js:201
#, javascript-format
msgid "Decoder: %s"
msgstr ""
msgstr "Avkodare: %s"
#: src/dialogs.js:152
msgid "Enter or drop URI here"
msgstr ""
msgstr "Ange eller släpp URI här"
#: src/dialogs.js:157
msgid "Cancel"
msgstr ""
msgstr "Avbryt"
#: src/dialogs.js:158
msgid "Open"
msgstr ""
msgstr "Öppna"
#: src/dialogs.js:226
msgid "Title"
msgstr ""
msgstr "Titel"
#: src/dialogs.js:227
msgid "Completed"
msgstr ""
msgstr "Klar"
#: src/dialogs.js:235
msgid "Resume playback?"
msgstr ""
msgstr "Återuppta uppspelningen?"
#: src/dialogs.js:289
#, javascript-format
msgid "GTK version: %s"
msgstr ""
msgstr "GTK version: %s"
#: src/dialogs.js:290
#, javascript-format
msgid "Adwaita version: %s"
msgstr ""
msgstr "Adwaita version: %s"
#: src/dialogs.js:291
#, javascript-format
msgid "GStreamer version: %s"
msgstr ""
msgstr "GStreamer version: %s"
#: src/dialogs.js:292
#, javascript-format
msgid "GJS version: %s"
msgstr ""
msgstr "GJS version: %s"
#: src/dialogs.js:300
msgid "A GNOME media player powered by GStreamer"
msgstr ""
msgstr "En media spelare för GNOME som drivs av GStreamer"
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
msgid "translator-credits"
msgstr ""
msgstr "SA ST (sastofficial)"
#: src/revealers.js:170
#, javascript-format
msgid "Ends at: %s"
msgstr ""
msgstr "Slutar vid: %s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
msgstr ""
msgstr "Obestämd"
#: src/widget.js:243
msgid "Channels"
msgstr ""
msgstr "Kanaler"
#: src/widget.js:261
msgid "Disabled"
msgstr ""
msgstr "Avstängd"

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-14 15:24\n"
"PO-Revision-Date: 2022-04-02 01:43\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
@@ -18,177 +18,177 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgstr ""
msgid "Open Files"
msgstr "Dosya Aç…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgstr ""
msgid "Open URI"
msgstr "URL Aç…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
msgid "Preferences"
msgstr ""
msgstr "Tercihler"
#: ui/clapper.ui:20
msgid "Shortcuts"
msgstr ""
msgstr "Kısayollar"
#: ui/clapper.ui:26
msgid "About Clapper"
msgstr ""
msgstr "Clapper Hakkında"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "Hız"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
msgid "Normal"
msgstr ""
msgstr "Normal"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr ""
msgstr "Genel"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr ""
msgstr "Kısayolları göster"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr ""
msgstr "Tam ekran"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
msgstr ""
msgstr "Çift dokun | Çift tıkla"
#: ui/help-overlay.ui:26
msgid "Leave fullscreen"
msgstr ""
msgstr "Tam ekrandan çık"
#: ui/help-overlay.ui:32
msgid "Reveal OSD (fullscreen only)"
msgstr ""
msgstr "Pencere Düğmelerini Göster (yalnızca tam ekran)"
#: ui/help-overlay.ui:33
msgid "Tap"
msgstr ""
msgstr "Dokun"
#: ui/help-overlay.ui:39
msgid "Quit"
msgstr ""
msgstr "Çık"
#: ui/help-overlay.ui:47
msgid "Media"
msgstr ""
msgstr "Ortam"
#: ui/help-overlay.ui:50
msgid "Open files"
msgstr ""
msgstr "Dosyaları"
#: ui/help-overlay.ui:56 src/dialogs.js:137
msgid "Open URI"
msgstr ""
msgstr "URL Aç"
#: ui/help-overlay.ui:64
msgid "Playlist"
msgstr ""
msgstr "Oynatma listesi"
#: ui/help-overlay.ui:67
msgid "Next item"
msgstr ""
msgstr "Sonraki öğe"
#: ui/help-overlay.ui:68
msgid "Double tap (right side)"
msgstr ""
msgstr "Çift tıkla (sağ tarafa)"
#: ui/help-overlay.ui:74
msgid "Previous item"
msgstr ""
msgstr "Önceki öğe"
#: ui/help-overlay.ui:75
msgid "Double tap (left side)"
msgstr ""
msgstr "Çift tıkla (sol tarafa)"
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr ""
msgstr "Tekrarlı oynatma modunu değiştir"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr ""
msgstr "Dosyaya aktar"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr ""
msgstr "Oynatma"
#: ui/help-overlay.ui:98
msgid "Toggle play"
msgstr ""
msgstr "Oynatmayı başlat"
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr ""
msgstr "Uzun bas | Sağ tıkla"
#: ui/help-overlay.ui:105
msgid "Seek forward"
msgstr ""
msgstr "İleri sar"
#: ui/help-overlay.ui:106
msgid "Swipe right | Scroll right"
msgstr ""
msgstr "Sağa sürükle | Sağa kaydır"
#: ui/help-overlay.ui:112
msgid "Seek backward"
msgstr ""
msgstr "Geri sar"
#: ui/help-overlay.ui:113
msgid "Swipe left | Scroll left"
msgstr ""
msgstr "Sola sürükle | Sola kaydır"
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr ""
msgstr "Sesi artır"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr ""
msgstr "Yukarı sürükle | Yukarı kaydır"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr ""
msgstr "Sesi kıs"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr ""
msgstr "Aşağı sürükle | Aşağı kaydır"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "Sesi kapat"
#: ui/help-overlay.ui:139
msgid "Next chapter"
msgstr ""
msgstr "Sonraki parça"
#: ui/help-overlay.ui:145
msgid "Previous chapter"
msgstr ""
msgstr "Önceki parça"
#: ui/preferences-plugin-ranking-subpage.ui:11
msgid "Decoders"
msgstr ""
msgstr "Çözücüler"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr ""
msgstr "Tercihlere geri dön"
#: ui/preferences-window.ui:16
msgid "Behavior"
msgstr ""
msgstr "Davranış"
#: ui/preferences-window.ui:19
msgid "Auto fullscreen"
msgstr ""
msgstr "Otomatik tam ekran"
#: ui/preferences-window.ui:20
msgid "Enter fullscreen when playlist is replaced except floating mode"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-14 16:35+0200\n"
"PO-Revision-Date: 2021-09-24 14:11\n"
"PO-Revision-Date: 2022-04-20 18:10\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr "打开文件…"
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr "打开 URI…"
#: ui/clapper.ui:16 ui/preferences-window.ui:4
@@ -39,7 +39,7 @@ msgstr "关于 Claper"
#: ui/elapsed-time-button.ui:27
msgid "Speed"
msgstr ""
msgstr "速度"
#: ui/elapsed-time-button.ui:41 ui/preferences-window.ui:83
#: ui/preferences-window.ui:215
@@ -48,15 +48,15 @@ msgstr "一般"
#: ui/help-overlay.ui:10 ui/preferences-window.ui:12
msgid "General"
msgstr ""
msgstr "常规​​​​​"
#: ui/help-overlay.ui:13
msgid "Show shortcuts"
msgstr ""
msgstr "显示快捷键"
#: ui/help-overlay.ui:19
msgid "Toggle fullscreen"
msgstr ""
msgstr "切换全屏"
#: ui/help-overlay.ui:20
msgid "Double tap | Double click"
@@ -112,15 +112,15 @@ msgstr ""
#: ui/help-overlay.ui:81
msgid "Change repeat mode"
msgstr ""
msgstr "更改循环模式"
#: ui/help-overlay.ui:87
msgid "Export to file"
msgstr ""
msgstr "导出至文件"
#: ui/help-overlay.ui:95 ui/preferences-window.ui:119
msgid "Playback"
msgstr ""
msgstr "播放"
#: ui/help-overlay.ui:98
msgid "Toggle play"
@@ -128,7 +128,7 @@ msgstr ""
#: ui/help-overlay.ui:99
msgid "Long press | Right click"
msgstr ""
msgstr "长按 | 右键点击"
#: ui/help-overlay.ui:105
msgid "Seek forward"
@@ -148,23 +148,23 @@ msgstr ""
#: ui/help-overlay.ui:119
msgid "Volume up"
msgstr ""
msgstr "提高音量"
#: ui/help-overlay.ui:120
msgid "Swipe up | Scroll up"
msgstr ""
msgstr "向上滑动 | 向上滚动"
#: ui/help-overlay.ui:126
msgid "Volume down"
msgstr ""
msgstr "降低音量"
#: ui/help-overlay.ui:127
msgid "Swipe down | Scroll down"
msgstr ""
msgstr "向下滑动 | 向下滚动"
#: ui/help-overlay.ui:133
msgid "Toggle mute"
msgstr ""
msgstr "切换静音"
#: ui/help-overlay.ui:139
msgid "Next chapter"
@@ -180,7 +180,7 @@ msgstr "解码器"
#: ui/preferences-plugin-ranking-subpage.ui:18
msgid "Return to the preferences"
msgstr ""
msgstr "返回首选项"
#: ui/preferences-window.ui:16
msgid "Behavior"
@@ -208,19 +208,19 @@ msgstr "此选项仅适用于 GNOME"
#: ui/preferences-window.ui:39
msgid "After playback"
msgstr ""
msgstr "播放结束后"
#: ui/preferences-window.ui:44
msgid "Do nothing"
msgstr ""
msgstr "不执行任何操作"
#: ui/preferences-window.ui:45
msgid "Freeze last frame"
msgstr ""
msgstr "停留在最后一帧"
#: ui/preferences-window.ui:46
msgid "Close the app"
msgstr ""
msgstr "关闭应用程序"
#: ui/preferences-window.ui:56
msgid "Volume"
@@ -240,7 +240,7 @@ msgstr "音量百分比"
#: ui/preferences-window.ui:75
msgid "Seeking"
msgstr ""
msgstr "定位播放"
#: ui/preferences-window.ui:78
msgid "Mode"
@@ -308,7 +308,7 @@ msgstr ""
#: ui/preferences-window.ui:169
msgid "Server"
msgstr ""
msgstr "服务器"
#: ui/preferences-window.ui:172
msgid "Control player remotely"
@@ -328,7 +328,7 @@ msgstr ""
#: ui/preferences-window.ui:190
msgid "Web application port"
msgstr ""
msgstr "Web 应用程序端口"
#: ui/preferences-window.ui:204
msgid "Prefer adaptive streaming"
@@ -340,7 +340,7 @@ msgstr ""
#: ui/preferences-window.ui:228
msgid "Tweaks"
msgstr "调"
msgstr "调"
#: ui/preferences-window.ui:232
msgid "Appearance"
@@ -438,12 +438,12 @@ msgstr ""
#. TRANSLATORS: Put your name(s) here for credits or leave untranslated
#: src/dialogs.js:305
msgid "translator-credits"
msgstr ""
msgstr "刘韬"
#: src/revealers.js:170
#, javascript-format
msgid "Ends at: %s"
msgstr ""
msgstr "结束于:%s"
#: src/widget.js:227 src/widget.js:236 src/widget.js:242 src/widget.js:248
msgid "Undetermined"
@@ -455,5 +455,5 @@ msgstr "声道"
#: src/widget.js:261
msgid "Disabled"
msgstr ""
msgstr "禁用"

View File

@@ -18,11 +18,11 @@ msgstr ""
"X-Crowdin-File-ID: 31\n"
#: ui/clapper.ui:6
msgid "Open Files..."
msgid "Open Files"
msgstr ""
#: ui/clapper.ui:10
msgid "Open URI..."
msgid "Open URI"
msgstr ""
#: ui/clapper.ui:16 ui/preferences-window.ui:4

View File

@@ -96,13 +96,6 @@ class ClapperAppBase extends Gtk.Application
if(accels)
this.set_accels_for_action(`app.${name}`, accels);
}
const gtkSettings = Gtk.Settings.get_default();
settings.bind(
'dark-theme', gtkSettings,
'gtk-application-prefer-dark-theme',
Gio.SettingsBindFlags.GET
);
this.doneFirstActivate = true;
}
});

View File

@@ -1,25 +0,0 @@
const { GObject } = imports.gi;
const { AppBase } = imports.src.appBase;
const { HeaderBarRemote } = imports.src.headerbarRemote;
const { WidgetRemote } = imports.src.widgetRemote;
var AppRemote = GObject.registerClass({
GTypeName: 'ClapperAppRemote',
},
class ClapperAppRemote extends AppBase
{
vfunc_startup()
{
super.vfunc_startup();
const window = this.active_window;
const clapperWidget = new WidgetRemote();
window.set_child(clapperWidget);
const headerBar = new HeaderBarRemote();
window.set_titlebar(headerBar);
window.maximize();
}
});

View File

@@ -165,7 +165,7 @@ class ClapperElapsedTimeButton extends PopoverButtonBase
setInitialState()
{
this.label = '00:00/00:00';
this.label = '00000000';
}
setFullscreenMode(isFullscreen, isMobileMonitor)

6
src/controls.js vendored
View File

@@ -7,8 +7,6 @@ const Revealers = imports.src.revealers;
const { debug } = Debug;
const { settings } = Misc;
const INITIAL_ELAPSED = '00:00/00:00';
var Controls = GObject.registerClass({
GTypeName: 'ClapperControls',
},
@@ -29,7 +27,7 @@ class ClapperControls extends Gtk.Box
this.isMobile = false;
this.showHours = false;
this.durationFormatted = '00:00';
this.durationFormatted = '0000';
this.revealersArr = [];
this.chapters = null;
@@ -148,7 +146,7 @@ class ClapperControls extends Gtk.Box
value = value || 0;
const elapsed = Misc.getFormattedTime(value, this.showHours)
+ '/' + this.durationFormatted;
+ '' + this.durationFormatted;
this.elapsedButton.label = elapsed;
}

View File

@@ -1,70 +0,0 @@
const { Gio, GLib, GObject } = imports.gi;
const Debug = imports.src.debug;
const { debug } = Debug;
var Daemon = GObject.registerClass({
GTypeName: 'ClapperDaemon',
},
class ClapperDaemon extends Gio.SubprocessLauncher
{
_init()
{
const port = ARGV[0] || 8080;
/* FIXME: show output when debugging is on */
const flags = Gio.SubprocessFlags.STDOUT_SILENCE
| Gio.SubprocessFlags.STDERR_SILENCE;
super._init({ flags });
this.errMsg = 'exited with error or was forced to close';
this.loop = GLib.MainLoop.new(null, false);
this.broadwayd = this.spawnv(['gtk4-broadwayd', '--port=' + port]);
this.broadwayd.wait_async(null, this._onBroadwaydClosed.bind(this));
this.setenv('GDK_BACKEND', 'broadway', true);
const remoteApp = this.spawnv(['com.github.rafostar.Clapper.Remote']);
remoteApp.wait_async(null, this._onRemoteClosed.bind(this));
this.loop.run();
}
_checkProcResult(proc, result)
{
let hadError = false;
try {
hadError = proc.wait_finish(result);
}
catch(err) {
debug(err);
}
return hadError;
}
_onBroadwaydClosed(proc, result)
{
const hadError = this._checkProcResult(proc, result);
if(hadError)
debug(`broadwayd ${this.errMsg}`);
this.broadwayd = null;
this.loop.quit();
}
_onRemoteClosed(proc, result)
{
const hadError = this._checkProcResult(proc, result);
if(hadError)
debug(`remote app ${this.errMsg}`);
if(this.broadwayd)
this.broadwayd.force_exit();
}
});

View File

@@ -1,165 +0,0 @@
const Debug = imports.src.debug;
const FileOps = imports.src.fileOps;
const Misc = imports.src.misc;
const { debug } = Debug;
function generateDash(dashInfo)
{
debug('generating dash');
const bufferSec = Math.min(4, dashInfo.duration);
const dash = [
`<?xml version="1.0" encoding="UTF-8"?>`,
`<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
` xmlns="urn:mpeg:dash:schema:mpd:2011"`,
` xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd"`,
` type="static"`,
` mediaPresentationDuration="PT${dashInfo.duration}S"`,
` minBufferTime="PT${bufferSec}S"`,
` profiles="urn:mpeg:dash:profile:isoff-on-demand:2011">`,
` <Period>`
];
for(let adaptation of dashInfo.adaptations)
dash.push(_addAdaptationSet(adaptation));
dash.push(
` </Period>`,
`</MPD>`
);
debug('dash generated');
return dash.join('\n');
}
function _addAdaptationSet(streamsArr)
{
/* We just need it for adaptation type,
* so any stream will do */
const { mimeInfo } = streamsArr[0];
const adaptArr = [
`contentType="${mimeInfo.content}"`,
`mimeType="${mimeInfo.type}"`,
`subsegmentAlignment="true"`,
`subsegmentStartsWithSAP="1"`,
];
const widthArr = [];
const heightArr = [];
const fpsArr = [];
const representations = [];
for(let stream of streamsArr) {
/* No point parsing if no URL */
if(!stream.url)
continue;
if(stream.width && stream.height) {
widthArr.push(stream.width);
heightArr.push(stream.height);
}
if(stream.fps)
fpsArr.push(stream.fps);
representations.push(_getStreamRepresentation(stream));
}
if(widthArr.length && heightArr.length) {
const maxWidth = Math.max.apply(null, widthArr);
const maxHeight = Math.max.apply(null, heightArr);
const par = _getPar(maxWidth, maxHeight);
adaptArr.push(`maxWidth="${maxWidth}"`);
adaptArr.push(`maxHeight="${maxHeight}"`);
adaptArr.push(`par="${par}"`);
}
if(fpsArr.length) {
const maxFps = Math.max.apply(null, fpsArr);
adaptArr.push(`maxFrameRate="${maxFps}"`);
}
const adaptationSet = [
` <AdaptationSet ${adaptArr.join(' ')}>`,
representations.join('\n'),
` </AdaptationSet>`
];
return adaptationSet.join('\n');
}
function _getStreamRepresentation(stream)
{
const repOptsArr = [
`id="${stream.itag}"`,
`codecs="${stream.mimeInfo.codecs}"`,
`bandwidth="${stream.bitrate}"`,
];
if(stream.width && stream.height) {
repOptsArr.push(`width="${stream.width}"`);
repOptsArr.push(`height="${stream.height}"`);
repOptsArr.push(`sar="1:1"`);
}
if(stream.fps)
repOptsArr.push(`frameRate="${stream.fps}"`);
const repArr = [
` <Representation ${repOptsArr.join(' ')}>`,
];
if(stream.audioChannels) {
const audioConfArr = [
`schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"`,
`value="${stream.audioChannels}"`,
];
repArr.push(` <AudioChannelConfiguration ${audioConfArr.join(' ')}/>`);
}
repArr.push(
` <BaseURL>${stream.url}</BaseURL>`
);
if(stream.indexRange) {
const segRange = `${stream.indexRange.start}-${stream.indexRange.end}`;
repArr.push(
` <SegmentBase indexRange="${segRange}">`
);
if(stream.initRange) {
const initRange = `${stream.initRange.start}-${stream.initRange.end}`;
repArr.push(
` <Initialization range="${initRange}"/>`
);
}
repArr.push(
` </SegmentBase>`
);
}
repArr.push(
` </Representation>`
);
return repArr.join('\n');
}
function _getPar(width, height)
{
const gcd = _getGCD(width, height);
width /= gcd;
height /= gcd;
return `${width}:${height}`;
}
function _getGCD(width, height)
{
return (height)
? _getGCD(height, width % height)
: width;
}

View File

@@ -14,13 +14,11 @@ const clapperDebugger = new Debug.Debugger('Clapper', {
}),
high_precision: true,
});
var enabled = (
clapperDebugger.enabled = (
clapperDebugger.enabled
|| G_DEBUG_ENV != null
&& G_DEBUG_ENV.includes('Clapper')
);
clapperDebugger.enabled = enabled;
function _logStructured(debuggerName, msg, level)
{
@@ -34,11 +32,15 @@ function _logStructured(debuggerName, msg, level)
function _debug(debuggerName, msg)
{
if(msg.message) {
_logStructured(debuggerName, msg.message,
_logStructured(
debuggerName,
msg.message,
GLib.LogLevelFlags.LEVEL_CRITICAL
);
return;
}
clapperDebugger.debug(msg);
}
@@ -51,8 +53,3 @@ function warn(msg)
{
_logStructured('Clapper', msg, GLib.LogLevelFlags.LEVEL_WARNING);
}
function message(msg)
{
_logStructured('Clapper', msg, GLib.LogLevelFlags.LEVEL_MESSAGE);
}

View File

@@ -1,297 +0,0 @@
const { Gio, GstClapper } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const FileOps = imports.src.fileOps;
const Gtuber = Misc.tryImport('Gtuber');
const { debug, warn } = Debug;
const { settings } = Misc;
const best = {
video: null,
audio: null,
video_audio: null,
};
const codecPairs = [];
const qualityType = {
0: 30, // normal
1: 60, // hfr
};
var isAvailable = (Gtuber != null);
var cancellable = null;
let client = null;
function resetBestStreams()
{
best.video = null;
best.audio = null;
best.video_audio = null;
}
function isStreamAllowed(stream, opts)
{
const vcodec = stream.video_codec;
const acodec = stream.audio_codec;
if(
vcodec
&& (!vcodec.startsWith(opts.vcodec)
|| (stream.height < 240 || stream.height > opts.height)
|| stream.fps > qualityType[opts.quality])
) {
return false;
}
if(
acodec
&& (!acodec.startsWith(opts.acodec))
) {
return false;
}
return (vcodec != null || acodec != null);
}
function updateBestStreams(streams, opts)
{
for(let stream of streams) {
if(!isStreamAllowed(stream, opts))
continue;
const type = (stream.video_codec && stream.audio_codec)
? 'video_audio'
: (stream.video_codec)
? 'video'
: 'audio';
if(!best[type] || best[type].bitrate < stream.bitrate)
best[type] = stream;
}
}
function _streamFilter(opts, stream)
{
switch(stream) {
case best.video:
return (best.audio != null || best.video_audio == null);
case best.audio:
return (best.video != null || best.video_audio == null);
case best.video_audio:
return (best.video == null || best.audio == null);
default:
return (opts.adaptive)
? isStreamAllowed(stream, opts)
: false;
}
}
function generateManifest(info, opts)
{
const gen = new Gtuber.ManifestGenerator({
pretty: Debug.enabled,
});
gen.set_media_info(info);
gen.set_filter_func(_streamFilter.bind(this, opts));
debug('trying to get manifest');
for(let pair of codecPairs) {
opts.vcodec = pair[0];
opts.acodec = pair[1];
/* Find best streams among adaptive ones */
if (!opts.adaptive)
updateBestStreams(info.get_adaptive_streams(), opts);
const data = gen.to_data();
/* Release our ref */
if (!opts.adaptive)
resetBestStreams();
if(data) {
debug('got manifest');
return data;
}
}
debug('manifest not generated');
return null;
}
function getBestCombinedUri(info, opts)
{
const streams = info.get_streams();
debug('searching for best combined URI');
for(let pair of codecPairs) {
opts.vcodec = pair[0];
opts.acodec = pair[1];
/* Find best non-adaptive stream */
updateBestStreams(streams, opts);
const bestUri = (best.video_audio)
? best.video_audio.get_uri()
: (best.audio)
? best.audio.get_uri()
: (best.video)
? best.video.get_uri()
: null;
/* Release our ref */
resetBestStreams();
if(bestUri) {
debug('got best possible URI');
return bestUri;
}
}
/* If still nothing find stream by height */
for(let stream of streams) {
const height = stream.get_height();
if(!height || height > opts.height)
continue;
if(!best.video_audio || best.video_audio.height < stream.height)
best.video_audio = stream;
}
const anyUri = (best.video_audio)
? best.video_audio.get_uri()
: null;
/* Release our ref */
resetBestStreams();
if (anyUri)
debug('got any URI');
return anyUri;
}
async function _parseMediaInfoAsync(info, player)
{
const resp = {
uri: null,
title: info.title,
};
const { root } = player.widget;
const surface = root.get_surface();
const monitor = root.display.get_monitor_at_surface(surface);
const opts = {
width: monitor.geometry.width * monitor.scale_factor,
height: monitor.geometry.height * monitor.scale_factor,
vcodec: null,
acodec: null,
quality: settings.get_int('yt-quality-type'),
adaptive: settings.get_boolean('yt-adaptive-enabled'),
};
if(info.has_adaptive_streams) {
const data = generateManifest(info, opts);
if(data) {
const manifestFile = await FileOps.saveFilePromise(
'tmp', null, 'manifest', data
).catch(debug);
if(!manifestFile)
throw new Error('Gtuber: no manifest file was generated');
resp.uri = manifestFile.get_uri();
return resp;
}
}
resp.uri = getBestCombinedUri(info, opts);
if(!resp.uri)
throw new Error("Gtuber: no compatible stream found");
return resp;
}
function _createClient(player)
{
client = new Gtuber.Client();
debug('created new gtuber client');
/* TODO: config based on what HW supports */
//codecPairs.push(['vp9', 'opus']);
codecPairs.push(['avc', 'mp4a']);
}
function mightHandleUri(uri)
{
const unsupported = [
'file', 'fd', 'dvd', 'cdda',
'dvb', 'v4l2', 'gs'
];
return !unsupported.includes(Misc.getUriProtocol(uri));
}
function cancelFetching()
{
if(cancellable && !cancellable.is_cancelled())
cancellable.cancel();
}
function parseUriPromise(uri, player)
{
return new Promise((resolve, reject) => {
if(!client) {
if(!isAvailable) {
debug('gtuber is not installed');
return resolve({ uri, title: null });
}
_createClient(player);
}
/* Stop to show reaction and restore internet bandwidth */
if(player.state !== GstClapper.ClapperState.STOPPED)
player.stop();
cancellable = new Gio.Cancellable();
debug('gtuber is fetching media info...');
client.fetch_media_info_async(uri, cancellable, (client, task) => {
cancellable = null;
let info = null;
try {
info = client.fetch_media_info_finish(task);
debug('gtuber successfully fetched media info');
}
catch(err) {
const taskCancellable = task.get_cancellable();
if(taskCancellable.is_cancelled())
return reject(err);
const gtuberNoPlugin = (
err.domain === Gtuber.ClientError.quark()
&& err.code === Gtuber.ClientError.NO_PLUGIN
);
if(!gtuberNoPlugin)
return reject(err);
warn(`Gtuber: ${err.message}, trying URI as is...`);
/* Allow handling URI as is via GStreamer plugins */
return resolve({ uri, title: null });
}
_parseMediaInfoAsync(info, player)
.then(resp => resolve(resp))
.catch(err => reject(err));
});
});
}

View File

@@ -1,22 +0,0 @@
const { GObject } = imports.gi;
const { HeaderBarBase } = imports.src.headerbarBase;
var HeaderBarRemote = GObject.registerClass({
GTypeName: 'ClapperHeaderBarRemote',
},
class ClapperHeaderBarRemote extends HeaderBarBase
{
_init()
{
super._init();
this.extraButtonsBox.visible = false;
}
_onWindowButtonActivate(action)
{
if(action === 'toggle-maximized')
action = 'toggle_maximized';
this.root.child.sendWs(action);
}
});

View File

@@ -1,7 +1,6 @@
imports.gi.versions.Gdk = '4.0';
imports.gi.versions.Gtk = '4.0';
imports.gi.versions.Soup = '2.4';
imports.gi.versions.Gtuber = '0.0';
imports.gi.versions.Soup = '3.0';
pkg.initGettext();
pkg.initFormat();

View File

@@ -1,6 +0,0 @@
const { Daemon } = imports.src.daemon;
function main()
{
new Daemon();
}

View File

@@ -1,20 +0,0 @@
imports.gi.versions.Gdk = '4.0';
imports.gi.versions.Gtk = '4.0';
imports.gi.versions.Soup = '2.4';
imports.gi.versions.Gtuber = '0.0';
pkg.initGettext();
const Misc = imports.src.misc;
Misc.appId += '.Remote';
const { Gtk, Adw } = imports.gi;
const { AppRemote } = imports.src.appRemote;
function main(argv)
{
Gtk.init();
Adw.init();
new AppRemote().run(argv);
}

View File

@@ -1,8 +1,7 @@
const { Gio, GLib, Gdk, Gtk } = imports.gi;
const Debug = imports.src.debug;
const { debug, message } = Debug;
const failedImports = [];
const { debug } = Debug;
var appName = 'Clapper';
var appId = 'com.github.rafostar.Clapper';
@@ -29,23 +28,6 @@ const subsKeys = Object.keys(subsTitles);
let inhibitCookie;
function tryImport(libName)
{
let lib = null;
try {
lib = imports.gi[libName];
}
catch(err) {
if(!failedImports.includes(libName)) {
failedImports.push(libName);
message(err.message);
}
}
return lib;
}
function getResourceUri(path)
{
const res = `file://${pkg.pkgdatadir}/${path}`;
@@ -179,8 +161,8 @@ function getFormattedTime(time, showHours)
time -= minutes * 60;
const seconds = ('0' + Math.floor(time)).slice(-2);
const parsed = (hours) ? `${hours}:` : '';
return parsed + `${minutes}:${seconds}`;
const parsed = (hours) ? `${hours}` : '';
return parsed + `${minutes}${seconds}`;
}
function parsePlaylistFiles(filesArray)

View File

@@ -1,10 +1,8 @@
const { Gdk, Gio, GObject, Gst, GstClapper, Gtk } = imports.gi;
const { Adw, Gdk, Gio, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi;
const ByteArray = imports.byteArray;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const Gtuber = imports.src.gtuber;
const { PlaylistWidget } = imports.src.playlist;
const { WebApp } = imports.src.webApp;
const { debug, warn } = Debug;
const { settings } = Misc;
@@ -18,14 +16,28 @@ class ClapperPlayer extends GstClapper.Clapper
{
_init()
{
const gtk4plugin = new GstClapper.ClapperGtk4Plugin();
const glsinkbin = Gst.ElementFactory.make('glsinkbin', null);
glsinkbin.sink = gtk4plugin.video_sink;
let vsink = null;
const use_legacy_sink = GLib.getenv('CLAPPER_USE_LEGACY_SINK');
if(!use_legacy_sink || use_legacy_sink != '1') {
vsink = Gst.ElementFactory.make('clappersink', null);
this.clappersink = vsink;
}
if(!vsink) {
vsink = Gst.ElementFactory.make('glsinkbin', null);
const gtk4plugin = new GstClapper.ClapperGtk4Plugin();
warn('using legacy video sink');
this.clappersink = gtk4plugin.video_sink;
vsink.sink = this.clappersink;
}
super._init({
signal_dispatcher: new GstClapper.ClapperGMainContextSignalDispatcher(),
video_renderer: new GstClapper.ClapperVideoOverlayVideoRenderer({
video_sink: glsinkbin,
video_sink: vsink,
}),
mpris: new GstClapper.ClapperMpris({
own_name: `org.mpris.MediaPlayer2.${Misc.appName}`,
@@ -38,18 +50,16 @@ class ClapperPlayer extends GstClapper.Clapper
use_pipewire: settings.get_boolean('use-pipewire'),
});
this.widget = gtk4plugin.video_sink.widget;
this.widget = this.clappersink.widget;
this.widget.add_css_class('videowidget');
this.visualization_enabled = false;
this.webserver = null;
this.webapp = null;
this.playlistWidget = new PlaylistWidget();
this.seekDone = true;
this.needsFastSeekRestore = false;
this.customVideoTitle = null;
this.windowMapped = false;
this.quitOnStop = false;
@@ -73,6 +83,7 @@ class ClapperPlayer extends GstClapper.Clapper
set_and_bind_settings()
{
const settingsToSet = [
'dark-theme',
'after-playback',
'seeking-mode',
'audio-offset',
@@ -140,31 +151,19 @@ class ClapperPlayer extends GstClapper.Clapper
set_uri(uri)
{
this.customVideoTitle = null;
Gtuber.cancelFetching();
if(Misc.getUriProtocol(uri) === 'file') {
const file = Misc.getFileFromLocalUri(uri);
if(!file) {
if(!this.playlistWidget.nextTrack())
debug('set media reached end of playlist');
if(Gtuber.mightHandleUri(uri)) {
Gtuber.parseUriPromise(uri, this)
.then(res => {
this.customVideoTitle = res.title;
super.set_uri(res.uri);
})
.catch(debug);
return;
}
if(uri.endsWith('.claps')) {
this.load_playlist_file(file);
return;
}
const file = Misc.getFileFromLocalUri(uri);
if(!file) {
if(!this.playlistWidget.nextTrack())
debug('set media reached end of playlist');
return;
}
if(uri.endsWith('.claps')) {
this.load_playlist_file(file);
return;
return;
}
}
super.set_uri(uri);
@@ -630,7 +629,7 @@ class ClapperPlayer extends GstClapper.Clapper
switch(key) {
case 'after-playback':
this.widget.keep_last_frame = (settings.get_int(key) === 1);
this.clappersink.keep_last_frame = (settings.get_int(key) === 1);
break;
case 'seeking-mode':
switch(settings.get_int(key)) {
@@ -645,6 +644,19 @@ class ClapperPlayer extends GstClapper.Clapper
break;
}
break;
case 'dark-theme':
/* TODO: Remove libadwaita alpha2 compat someday */
if (Adw.StyleManager != null) {
const styleManager = Adw.StyleManager.get_default();
styleManager.color_scheme = (settings.get_boolean(key))
? Adw.ColorScheme.FORCE_DARK
: Adw.ColorScheme.FORCE_LIGHT;
}
else {
const gtkSettings = Gtk.Settings.get_default();
gtkSettings.gtk_application_prefer_dark_theme = settings.get_boolean(key);
}
break;
case 'render-shadows':
root = this.widget.get_root();
if(!root) break;
@@ -675,7 +687,6 @@ class ClapperPlayer extends GstClapper.Clapper
debug(`changed play flags: ${initialFlags} -> ${settingsFlags}`);
break;
case 'webserver-enabled':
case 'webapp-enabled':
const webserverEnabled = settings.get_boolean('webserver-enabled');
if(webserverEnabled) {
@@ -690,22 +701,8 @@ class ClapperPlayer extends GstClapper.Clapper
this.webserver.passMsgData = this.receiveWs.bind(this);
}
this.webserver.startListening();
const webappEnabled = settings.get_boolean('webapp-enabled');
if(!this.webapp && !webappEnabled)
break;
if(webappEnabled) {
if(!this.webapp)
this.webapp = new WebApp();
this.webapp.startDaemonApp(settings.get_int('webapp-port'));
}
}
else if(this.webserver) {
/* remote app will close when connection is lost
* which will cause the daemon to close too */
this.webserver.stopListening();
}
break;

View File

@@ -1,51 +0,0 @@
const { GObject } = imports.gi;
const { WebClient } = imports.src.webClient;
var ClapperState = {
STOPPED: 0,
BUFFERING: 1,
PAUSED: 2,
PLAYING: 3,
};
var PlayerRemote = GObject.registerClass({
GTypeName: 'ClapperPlayerRemote',
},
class ClapperPlayerRemote extends GObject.Object
{
_init()
{
super._init();
this.webclient = new WebClient();
}
set_playlist(playlist)
{
const uris = [];
/* We can not send GioFiles via WebSocket */
for(let source of playlist)
uris.push(this._getSourceUri(source));
this.webclient.sendMessage({
action: 'set_playlist',
value: uris
});
}
set_subtitles(source)
{
this.webclient.sendMessage({
action: 'set_subtitles',
value: this._getSourceUri(source)
});
}
_getSourceUri(source)
{
return (source.get_uri != null)
? source.get_uri()
: source;
}
});

View File

@@ -1,7 +1,6 @@
const { Adw, GObject, Gio, Gst, Gtk } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const Gtuber = imports.src.gtuber;
const { debug } = Debug;
const { settings } = Misc;
@@ -441,8 +440,15 @@ class ClapperPrefsPluginExpander extends Adw.ExpanderRow
const featuresNames = Object.keys(pluginsData[this.title]);
debug(`Adding ${featuresNames.length} features to the list of plugin: ${this.title}`);
for(let featureObj of pluginsData[this.title])
this.add(new PrefsPluginFeature(featureObj));
for(let featureObj of pluginsData[this.title]) {
const prefsPluginFeature = new PrefsPluginFeature(featureObj);
/* TODO: Remove old libadwaita compat */
if(this.add_row)
this.add_row(prefsPluginFeature);
else
this.add(prefsPluginFeature);
}
}
});
@@ -538,7 +544,6 @@ class ClapperPrefsPluginRankingSubpage extends Gtk.Box
var PrefsWindow = GObject.registerClass({
GTypeName: 'ClapperPrefsWindow',
Template: Misc.getResourceUri('ui/preferences-window.ui'),
InternalChildren: ['gtuber_group'],
},
class ClapperPrefsWindow extends Adw.PreferencesWindow
{
@@ -548,7 +553,11 @@ class ClapperPrefsWindow extends Adw.PreferencesWindow
transient_for: window,
});
this._gtuber_group.visible = Gtuber.isAvailable;
/* FIXME: old libadwaita compat, should be
* normally in prefs UI file */
this.can_swipe_back = true;
this.can_navigate_back = true;
this.show();
}
});

View File

@@ -59,8 +59,8 @@ class ClapperRevealerTop extends CustomRevealer
const initTime = GLib.DateTime.new_now_local().format('%X');
this.timeFormat = (initTime.length > 8)
? '%I:%M %p'
: '%H:%M';
? '%I%M %p'
: '%H%M';
this.mediaTitle = new Gtk.Label({
ellipsize: Pango.EllipsizeMode.END,
@@ -321,6 +321,8 @@ class ClapperControlsRevealer extends Gtk.Revealer
const isStick = (isFloating && settings.get_boolean('floating-stick'));
DBus.shellWindowEval('stick', isStick);
this.root.child.refreshWindowTitle(this.root.title);
}
_onControlsRevealed()

View File

@@ -1,51 +0,0 @@
const { Gio, GObject } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const { debug } = Debug;
var WebApp = GObject.registerClass({
GTypeName: 'ClapperWebApp',
},
class ClapperWebApp extends Gio.SubprocessLauncher
{
_init()
{
const flags = Gio.SubprocessFlags.STDOUT_SILENCE
| Gio.SubprocessFlags.STDERR_SILENCE;
super._init({ flags });
this.daemonApp = null;
}
startDaemonApp(port)
{
if(this.daemonApp)
return;
this.daemonApp = this.spawnv([Misc.appId + '.Daemon', String(port)]);
this.daemonApp.wait_async(null, this._onDaemonClosed.bind(this));
debug('daemon app started');
}
_onDaemonClosed(proc, result)
{
let hadError;
try {
hadError = proc.wait_finish(result);
}
catch(err) {
debug(err);
}
this.daemonApp = null;
if(hadError)
debug('daemon app exited with error or was forced to close');
debug('daemon app closed');
}
});

View File

@@ -1,90 +0,0 @@
const { Gio, GObject, Soup } = imports.gi;
const Debug = imports.src.debug;
const Misc = imports.src.misc;
const WebHelpers = imports.src.webHelpers;
const { debug } = Debug;
const { settings } = Misc;
var WebClient = GObject.registerClass({
GTypeName: 'ClapperWebClient',
},
class ClapperWebClient extends Soup.Session
{
_init(port)
{
super._init({
timeout: 3,
use_thread_context: true,
});
this.wsConn = null;
this.connectWebsocket();
}
connectWebsocket()
{
if(this.wsConn)
return;
const port = settings.get_int('webserver-port');
const message = Soup.Message.new('GET', `ws://127.0.0.1:${port}/websocket`);
this.websocket_connect_async(message, null, null, null, this._onWsConnect.bind(this));
debug('connecting WebSocket to Clapper app');
}
sendMessage(data)
{
if(
!this.wsConn
|| this.wsConn.state !== Soup.WebsocketState.OPEN
)
return;
this.wsConn.send_text(JSON.stringify(data));
}
passMsgData(action, value)
{
}
_onWsConnect(session, result)
{
let connection = null;
try {
connection = this.websocket_connect_finish(result);
}
catch(err) {
debug(err);
}
if(!connection)
return this.passMsgData('close');
connection.connect('message', this._onWsMessage.bind(this));
connection.connect('closed', this._onWsClosed.bind(this));
this.wsConn = connection;
debug('successfully connected WebSocket');
}
_onWsMessage(connection, dataType, bytes)
{
const [success, parsedMsg] = WebHelpers.parseData(dataType, bytes);
if(success)
this.passMsgData(parsedMsg.action, parsedMsg.value);
}
_onWsClosed(connection)
{
debug('closed WebSocket connection');
this.wsConn = null;
this.passMsgData('close');
}
});

View File

@@ -107,7 +107,7 @@ class ClapperWebServer extends Soup.Server
this.remove_handler('/');
}
_onWsConnection(server, connection)
_onWsConnection(server, msg, path, connection)
{
debug('new WebSocket connection');

View File

@@ -224,7 +224,7 @@ class ClapperWidget extends Gtk.Grid
case GstClapper.ClapperVideoInfo:
type = 'video';
codec = info.get_codec() || _('Undetermined');
text = `${codec}, ${info.get_width()}x${info.get_height()}`;
text = `${codec}, ${info.get_width()}×${info.get_height()}`;
let fps = info.get_framerate();
fps = Number((fps[0] / fps[1]).toFixed(2));
if(fps)
@@ -277,7 +277,8 @@ class ClapperWidget extends Gtk.Grid
if(currStream && type !== 'subtitle') {
const caps = currStream.get_caps();
debug(`${type} caps: ${caps.to_string()}`);
if (caps)
debug(`${type} caps: ${caps.to_string()}`);
}
if(type === 'video') {
const isShowVis = (
@@ -307,21 +308,31 @@ class ClapperWidget extends Gtk.Grid
updateTitle(mediaInfo)
{
let title = this.player.customVideoTitle;
if(!title)
title = mediaInfo.get_title();
let title = mediaInfo.get_title();
if(!title) {
const item = this.player.playlistWidget.getActiveRow();
title = item.filename;
}
this.root.title = title;
this.refreshWindowTitle(title);
this.revealerTop.title = title;
this.revealerTop.showTitle = true;
}
refreshWindowTitle(title)
{
const isFloating = !this.controlsRevealer.reveal_child;
const pipSuffix = ' - PiP';
const hasPipSuffix = title.endsWith(pipSuffix);
this.root.title = (isFloating && !hasPipSuffix)
? title + pipSuffix
: (!isFloating && hasPipSuffix)
? title.substring(0, title.length - pipSuffix.length)
: title;
}
updateTime()
{
if(
@@ -334,7 +345,8 @@ class ClapperWidget extends Gtk.Grid
const currTime = GLib.DateTime.new_now_local();
const endTime = currTime.add_seconds(
this.controls.positionAdjustment.get_upper() - this.controls.currentPosition
(this.controls.positionAdjustment.get_upper() - this.controls.currentPosition)
/ this.controls.elapsedButton.speedScale.get_value()
);
const nextUpdate = this.revealerTop.setTimes(currTime, endTime, this.isSeekable);

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