42 Commits

Author SHA1 Message Date
Rafał Dzięgiel
9fd87dbbb9 clapper: Rename enhancersrc -> extractablesrc
Since this element only uses enhancers of "extractable" type
2025-05-21 17:42:15 +02:00
Rafał Dzięgiel
ca15f4760a clapper: Cleanup cached harvests periodically 2025-05-21 17:42:08 +02:00
Rafał Dzięgiel
6ddb53252a clapper: Implement harvest caching
Using recently added local cache functionality, store harvests
that have expiration date. With this, next time the same URI is
selected for playback we can read it from cache, skipping loading
of any enhancer plugins and doing network requests.

This also works nicely with Clapper discoverer feature.
Making queued items be fetched and cached ahead of playback.
2025-05-20 18:56:28 +02:00
Rafał Dzięgiel
b30d53d8ce clapper: Add ability to set harvest expiration date 2025-05-20 17:18:20 +02:00
Rafał Dzięgiel
1527873bcc clapper: Fix missing unref of mapped file
In case where enhancer plugin version did not match
an unref was missing of read file.
2025-05-19 20:42:32 +02:00
Rafał Dzięgiel
e23f2acb3e meson: Remove unused "config.h" variable 2025-05-18 13:24:51 +02:00
Rafał Dzięgiel
1dfcb218ac meson: Bump min required libadwaita version
Since URI dialog was ported to use AdwAlertDialog,
a minimal Adw version has to be changed to 1.5.
2025-05-17 18:55:31 +02:00
Rafał Dzięgiel
b05f0f2b30 meson: Remove GIR init section
Clapper GStreamer plugin is not part of GObject Introspection,
so no need to init GStreamer when compiling bindings.
2025-05-17 18:20:33 +02:00
Rafał Dzięgiel
dad0d46196 Merge pull request #546 from Rafostar/configurable-enhancers
Make Clapper Enhancers configurable
2025-05-16 18:30:05 +02:00
Rafał Dzięgiel
e34f729f62 clapper: Move basic functions into separate files
Avoid including whole "clapper.h" internally.
Faster recompilation when changes are done.
2025-05-16 17:02:29 +02:00
Rafał Dzięgiel
a97e7d1a96 clapper: Avoid using hardcoded API name in enhancers loader on win32 2025-05-16 17:02:10 +02:00
Rafał Dzięgiel
c6c4fe309b clapper: Implement data cache
Add data cache functions and use them to store enhancer data into local cache file.
This way we can restore all properties and interfaces used in enhancer without
creating its instance. This avoids loading interpreters like Python at init time
making startup a lot faster.
2025-05-16 17:02:07 +02:00
Rafał Dzięgiel
3ef6e9694a clapper-app: Add enhancers to preferences window
Browse, read info and configure Clapper enhancer plugins from preferences window
2025-05-16 17:02:03 +02:00
Rafał Dzięgiel
d951be7a56 clapper: Remove usage of "X-Interfaces" in enhancer data
At this point its sole remaining place where its used is one debug message.
For this reason remove it. With this, enhancer plugin files no longer need to
have this in them unless they want to keep Clapper 0.8 compat (otherwise unused).
2025-05-16 17:01:59 +02:00
Rafał Dzięgiel
98fdd7c58b clapper: Introduce "ClapperEnhancerProxy" objects
Add support for configuring Clapper Enhancers. In order to do that,
introduce enhancer proxy object that act as intermediary between
player and enhancer plugin.

We cannot give direct access to enhancer instances from code, since
they are managed (created and destroyed) by player as/when needed.
Also due to some interpreted languages not working with multiple
threads. Instead, give proxy objects that will store each enhancer
configuration to be applied when an enhancer instance is created.

With this, implementations also gain ability to browse available
enhancers, see what they support and change their properties.

Enhancers are now also assigned to player, instead of being only global.
This allows to configure them separately on a per player instance basis.

Writing configurable enhancers is super easy too, as all plugin has
to do is install standard GParamSpec properties to its class with a
corresponding gschema file (for global props only) and its done.
2025-05-16 17:01:51 +02:00
Rafał Dzięgiel
147d94088c Merge pull request #548 from ximion/master
Use modern appstream, instead of appstream-util for validation
2025-05-03 15:42:28 +02:00
Matthias Klumpp
c7790d9f7b Use modern appstream, instead of appstream-util for validation 2025-05-03 01:56:53 +02:00
Rafał Dzięgiel
f5731957dc New Crowdin updates (#524)
* New translations clapper-app.pot (Chinese Simplified)

* New translations clapper-app.pot (Chinese Simplified)

* New translations clapper-app.pot (Romanian)

* New translations clapper-app.pot (French)

* New translations clapper-app.pot (Spanish)

* New translations clapper-app.pot (Afrikaans)

* New translations clapper-app.pot (Arabic)

* New translations clapper-app.pot (Catalan)

* New translations clapper-app.pot (Czech)

* New translations clapper-app.pot (Danish)

* New translations clapper-app.pot (German)

* New translations clapper-app.pot (Greek)

* New translations clapper-app.pot (Basque)

* New translations clapper-app.pot (Finnish)

* New translations clapper-app.pot (Hebrew)

* New translations clapper-app.pot (Hungarian)

* New translations clapper-app.pot (Italian)

* New translations clapper-app.pot (Japanese)

* New translations clapper-app.pot (Korean)

* New translations clapper-app.pot (Lithuanian)

* New translations clapper-app.pot (Dutch)

* New translations clapper-app.pot (Norwegian)

* New translations clapper-app.pot (Polish)

* New translations clapper-app.pot (Portuguese)

* New translations clapper-app.pot (Russian)

* New translations clapper-app.pot (Slovak)

* New translations clapper-app.pot (Serbian (Cyrillic))

* New translations clapper-app.pot (Swedish)

* New translations clapper-app.pot (Turkish)

* New translations clapper-app.pot (Ukrainian)

* New translations clapper-app.pot (Chinese Simplified)

* New translations clapper-app.pot (Chinese Traditional)

* New translations clapper-app.pot (Vietnamese)

* New translations clapper-app.pot (Portuguese, Brazilian)

* New translations clapper-app.pot (Persian)

* New translations clapper-app.pot (Croatian)

* New translations clapper-app.pot (Hindi)

* New translations clapper-app.pot (Asturian)

* New translations clapper-app.pot (Occitan)

* New translations clapper-app.pot (Persian)

* New translations clapper-app.pot (Portuguese, Brazilian)

* New translations clapper-app.pot (Finnish)

* New translations clapper-app.pot (Russian)

* New translations clapper-gtk.pot (Russian)

* New translations clapper-app.pot (French)

* New translations clapper-app.pot (Portuguese)

* New translations clapper-app.pot (German)
2025-04-27 15:20:33 +02:00
Rafał Dzięgiel
2e3c98d9b4 Merge pull request #530 from Rafostar/pipeline-preview-launcher
Ability to show GStreamer pipeline
2025-04-27 13:04:04 +02:00
Rafał Dzięgiel
d09824fb1a clapper-app: Show error dialog on pipeline preview failure 2025-04-26 21:25:13 +02:00
Rafał Dzięgiel
366315077e clapper-app: Use user data dir for storing pipeline exports
System tmp directory does not work within containers such as Flatpak
for our usage with file launcher, so make our own temp in app data dir
2025-04-26 18:33:02 +02:00
Rafał Dzięgiel
a75184a471 flatpak: Build graphviz
Needed for pipeline preview app functionality
2025-04-26 18:32:59 +02:00
Rafał Dzięgiel
5bd6ba8948 meson: Add "pipeline-preview" to printed summary 2025-04-26 18:32:57 +02:00
Rafał Dzięgiel
160dbce265 clapper-app: Add "show pipeline" key shortcut to help overlay 2025-04-26 18:32:55 +02:00
Rafał Dzięgiel
224326857b clapper-app: Create pipeline preview file async 2025-04-26 18:32:42 +02:00
Rafał Dzięgiel
f9b31c51a7 Merge pull request #538 from fritteli/fix-typo-in-german-translation
chore: Fix typo in german translation
2025-04-11 20:11:27 +02:00
Manuel Friedli
be1de8bc77 chore: Fix typo in german translation 2025-04-04 23:06:42 +02:00
Rafał Dzięgiel
b8a4a90b6c clapper-app: Delete temp dir on exit
Pipeline preview creates temp files, so delete whole directory
recursively on app exit. Since these files are used through
launcher/portal with another app, we do not know when user finished
using them, thus we cannot detect and remove them earlier.
2025-02-18 18:30:09 +01:00
Rafał Dzięgiel
d73f33785d clapper-app: Fix a file path in POTFILES
Since #479 "window" was renamed to "dialog".
We also need to update POTFILES accordingly.
2025-02-16 17:49:11 +01:00
Rafał Dzięgiel
dca8fbd336 clapper-app: Add GStreamer pipeline preview
Allow to preview GStreamer pipeline while playing content.
This makes it easier to check what is used underneath.
2025-02-16 17:36:05 +01:00
Rafał Dzięgiel
681d5b8699 clapper: Add a function to get pipeline graph data 2025-02-16 17:23:34 +01:00
Rafał Dzięgiel
009f272f64 Merge pull request #479 from geopjr-forks/fix/adw-1-6-deprecations
clapper-app: Fix libadwaita 1.6 deprecations
2025-02-16 13:22:54 +01:00
Evan Paterakis
a5bc1b5578 clapper-app: set uri-dialog's content width to 420
Co-authored-by: Rafał Dzięgiel <40623528+Rafostar@users.noreply.github.com>
2025-02-15 19:17:04 +02:00
Rafał Dzięgiel
49471882c1 clapper-app: Update POT file 2025-02-15 12:48:42 +01:00
Evan Paterakis
069be30225 clapper-app: fix adw 1.6 deprecations 2025-02-12 17:49:22 +02:00
Rafał Dzięgiel
5f83d93374 Merge pull request #525 from abhijeeth-babu/patch-1
Remove "Music" from Clapper's keywords to lower its priority as a music player
2025-02-10 19:59:51 +01:00
Rafał Dzięgiel
d0a9122833 Merge pull request #528 from Rafostar/ci-arm64
workflows: Build natively on arm64
2025-02-10 19:26:16 +01:00
Rafał Dzięgiel
c29b8871a3 workflows: Build natively on arm64
Replace QEMU with native arm64 runners, now that they are available
2025-02-10 18:21:37 +01:00
Rafał Dzięgiel
d4d0f9e95e flatpak: Sync with Flathub 2025-02-08 12:48:37 +01:00
Rafał Dzięgiel
291c6e35f2 Back to development 2025-02-08 11:48:12 +01:00
Abhijeeth Babu
69bace7150 Remove other unneeded keywords (Album, GNOME, Clapper) from desktop file 2025-01-27 22:57:01 +05:30
Abhijeeth Babu
401e33b1c0 Remove "Music" from Clapper's keywords to lower its priority as a music player
This PR removes the keyword "Music" from Clapper's `.desktop` file to lower its priority as a music player in desktop search results. This ensures that Clapper is not prominently suggested when searching for music-related applications, while retaining its functionality as a video player.

### Changes:
- Removed "Music" from the `Keywords` field in the `.desktop` file.

This change does not affect Clapper's ability to play audio files but adjusts its visibility in application search results.
2025-01-25 22:10:10 +05:30
103 changed files with 5585 additions and 1513 deletions

View File

@@ -6,32 +6,32 @@ name: "Flatpak Nightly"
jobs:
flatpak:
name: "Flatpak"
runs-on: ubuntu-latest
timeout-minutes: 600
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-nightly
options: --privileged
strategy:
matrix:
arch: [x86_64, aarch64]
os: [ubuntu-24.04, ubuntu-24.04-arm]
include:
- os: ubuntu-24.04
arch: x86_64
- os: ubuntu-24.04-arm
arch: aarch64
fail-fast: false
runs-on: ${{ matrix.os }}
container:
image: registry.fedoraproject.org/fedora
options: --privileged
steps:
- name: Prepare
run: |
dnf install -y git git-lfs dbus-daemon flatpak flatpak-builder libappstream-glib ccache zstd xorg-x11-server-Xvfb
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.rust-stable/${{ matrix.arch }}/24.08
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.llvm18/${{ matrix.arch }}/24.08
flatpak --system install -y --noninteractive flathub org.flatpak.Builder
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Docker
run: |
dnf -y install docker
- name: Setup QEMU
id: qemu
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Prepare Runtime
run: |
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.rust-stable/${{ matrix.arch }}/24.08
flatpak --system install -y --noninteractive flathub org.freedesktop.Sdk.Extension.llvm18/${{ matrix.arch }}/24.08
- name: Build
uses: flatpak/flatpak-github-actions/flatpak-builder@master
with:

View File

@@ -10,28 +10,29 @@ name: "Flatpak"
jobs:
flatpak:
name: "Flatpak"
runs-on: ubuntu-latest
timeout-minutes: 600
container:
image: bilelmoussaoui/flatpak-github-actions:gnome-47
options: --privileged
strategy:
matrix:
arch: [x86_64, aarch64]
os: [ubuntu-24.04, ubuntu-24.04-arm]
include:
- os: ubuntu-24.04
arch: x86_64
- os: ubuntu-24.04-arm
arch: aarch64
fail-fast: false
runs-on: ${{ matrix.os }}
container:
image: registry.fedoraproject.org/fedora
options: --privileged
steps:
- name: Prepare
run: |
dnf install -y git git-lfs dbus-daemon flatpak flatpak-builder libappstream-glib ccache zstd xorg-x11-server-Xvfb
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --system install -y --noninteractive flathub org.flatpak.Builder
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Docker
run: |
dnf -y install docker
- name: Setup QEMU
id: qemu
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- name: Build
uses: flatpak/flatpak-github-actions/flatpak-builder@master
with:

View File

@@ -1,5 +1,5 @@
project('clapper', 'c',
version: '0.8.0',
version: '0.9.0',
meson_version: '>= 0.64.0',
license: 'LGPL-2.1-or-later AND GPL-3.0-or-later', # LGPL-2.1+ for libs and gst-plugin, GPL-3.0+ for app
default_options: [
@@ -11,7 +11,7 @@ project('clapper', 'c',
glib_req = '>= 2.76.0'
gst_req = '>= 1.24.0'
gtk4_req = '>= 4.10.0'
adw_req = '>= 1.4.0'
adw_req = '>= 1.5.0'
clapper_version = meson.project_version().split('-')[0]
version_array = clapper_version.split('.')
@@ -89,6 +89,12 @@ libadwaita_dep = dependency('libadwaita-1',
peas_dep = dependency('libpeas-2',
required: false,
)
cgraph_dep = dependency('libcgraph',
required: false,
)
gvc_dep = dependency('libgvc',
required: false,
)
cc = meson.get_compiler('c')
libm = cc.find_library('m', required: false)
@@ -160,3 +166,8 @@ if build_clapper
summary(name, clapper_available_features.contains(name) ? 'Yes' : 'No', section: 'Features')
endforeach
endif
if build_clapperapp
foreach name : clapperapp_possible_functionalities
summary(name, clapperapp_available_functionalities.contains(name) ? 'Yes' : 'No', section: 'Functionalities')
endforeach
endif

View File

@@ -41,6 +41,11 @@ option('enhancers-loader',
value: 'enabled',
description: 'Ability to load libpeas based plugins that enhance capabilities'
)
option('pipeline-preview',
type: 'feature',
value: 'auto',
description: 'Ability to preview GStreamer pipeline in clapper-app'
)
# Features
option('discoverer',

View File

@@ -14,6 +14,13 @@
"add-ld-path": ".",
"no-autodownload": false,
"autodelete": false
},
"com.github.rafostar.Clapper.Enhancers": {
"versions": "master;test;stable",
"directory": "extensions/clapper/enhancers",
"add-ld-path": "lib",
"no-autodownload": false,
"autodelete": false
}
},
"command": "clapper",
@@ -31,7 +38,9 @@
"--filesystem=xdg-run/gvfsd",
"--own-name=org.mpris.MediaPlayer2.Clapper.*",
"--talk-name=org.gtk.vfs.*",
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0"
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0",
"--env=CLAPPER_ENHANCERS_PATH=/app/extensions/clapper/enhancers/plugins",
"--env=PYTHONPATH=/app/extensions/clapper/enhancers/python/site-packages"
],
"build-options": {
"append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm18/bin",
@@ -53,11 +62,11 @@
"flathub/lib/libdvdnav.json",
"flathub/lib/libass.json",
"flathub/lib/libmicrodns.json",
"flathub/lib/libpeas.json",
"testing/dav1d.json",
"testing/gstreamer.json",
"testing/gst-plugins-rs.json",
"testing/yt-dlp.json",
"testing/libpeas.json",
"testing/graphviz.json",
{
"name": "clapper",
"buildsystem": "meson",
@@ -67,11 +76,11 @@
"path": "../../."
}
]
},
"testing/clapper-enhancers.json"
}
],
"cleanup-commands": [
"mkdir -p /app/lib/ffmpeg",
"mkdir -p /app/extensions/clapper/enhancers",
"ln -s /lib/$FLATPAK_ARCH-linux-*/gstreamer-1.0/libgstpipewire.so /app/lib/gstreamer-1.0/"
]
}

View File

@@ -10,6 +10,13 @@
"add-ld-path": ".",
"no-autodownload": false,
"autodelete": false
},
"com.github.rafostar.Clapper.Enhancers": {
"versions": "master;test;stable",
"directory": "extensions/clapper/enhancers",
"add-ld-path": "lib",
"no-autodownload": false,
"autodelete": false
}
},
"command": "clapper",
@@ -27,7 +34,9 @@
"--filesystem=xdg-run/gvfsd",
"--own-name=org.mpris.MediaPlayer2.Clapper.*",
"--talk-name=org.gtk.vfs.*",
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0"
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0",
"--env=CLAPPER_ENHANCERS_PATH=/app/extensions/clapper/enhancers/plugins",
"--env=PYTHONPATH=/app/extensions/clapper/enhancers/python/site-packages"
],
"modules": [
"flathub/shared-modules/gudev/gudev.json",
@@ -40,9 +49,9 @@
"flathub/lib/libass.json",
"flathub/lib/uchardet.json",
"flathub/lib/libmicrodns.json",
"testing/gstreamer_stable.json",
"testing/yt-dlp.json",
"testing/libpeas.json",
"flathub/lib/libpeas.json",
"flathub/gstreamer-1.0/gstreamer.json",
"testing/graphviz.json",
{
"name": "clapper",
"buildsystem": "meson",
@@ -52,11 +61,11 @@
"path": "../../."
}
]
},
"testing/clapper-enhancers.json"
}
],
"cleanup-commands": [
"mkdir -p /app/lib/ffmpeg",
"mkdir -p /app/extensions/clapper/enhancers",
"ln -s /lib/$FLATPAK_ARCH-linux-*/gstreamer-1.0/libgstpipewire.so /app/lib/gstreamer-1.0/"
]
}

View File

@@ -1,14 +0,0 @@
{
"name": "clapper-enhancers",
"buildsystem": "meson",
"config-opts": [
"-Dauto_features=enabled"
],
"sources": [
{
"type": "git",
"url": "https://github.com/Rafostar/clapper-enhancers.git",
"branch": "main"
}
]
}

View File

@@ -0,0 +1,38 @@
{
"name": "graphviz",
"buildsystem": "autotools",
"config-opts": [
"--without-x",
"--without-gtk",
"--without-gtkgl",
"--without-gtkglext",
"--without-gdk",
"--without-gdk-pixbuf",
"--without-qt",
"--disable-static",
"--disable-sharp",
"--disable-go",
"--disable-guile",
"--disable-java",
"--disable-javascript",
"--disable-lua",
"--disable-perl",
"--disable-php",
"--disable-python",
"--disable-r",
"--disable-ruby",
"--disable-tcl"
],
"cleanup": [
"/bin",
"/share/man",
"/share/graphviz"
],
"sources": [
{
"type": "archive",
"url": "https://gitlab.com/api/v4/projects/4207231/packages/generic/graphviz-releases/12.2.1/graphviz-12.2.1.tar.gz",
"sha256": "242bc18942eebda6db4039f108f387ec97856fc91ba47f21e89341c34b554df8"
}
]
}

View File

@@ -1,99 +0,0 @@
{
"name": "gstreamer",
"buildsystem": "meson",
"config-opts": [
"--buildtype=release",
"--wrap-mode=nodownload",
"-Dbase=enabled",
"-Dgood=enabled",
"-Dbad=enabled",
"-Dugly=enabled",
"-Dlibav=enabled",
"-Dvaapi=enabled",
"-Dsharp=disabled",
"-Drs=disabled",
"-Dpython=disabled",
"-Ddevtools=disabled",
"-Dges=disabled",
"-Drtsp_server=disabled",
"-Dgst-examples=disabled",
"-Dqt5=disabled",
"-Dtests=disabled",
"-Dexamples=disabled",
"-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",
"-Dgst-plugins-bad:wasapi2=disabled",
"-Dgst-plugins-bad:winks=disabled",
"-Dgst-plugins-bad:winscreencap=disabled",
"-Dgst-plugins-bad:assrender=enabled",
"-Dgst-plugins-bad:nvcodec=enabled",
"-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",
"tag": "1.24.9",
"commit": "b309f90bfde36e6d175b70bfa0c941f2829dd6a5",
"disable-submodules": true
},
{
"type": "patch",
"path": "../flathub/gstreamer-1.0/gst-libav-stop-caching-codecs.patch"
},
{
"type": "patch",
"path": "../flathub/gstreamer-1.0/gst-plugins-base-autodetect-subtitle-text-encoding.patch"
},
{
"type": "patch",
"path": "../flathub/gstreamer-1.0/gst-plugins-good-matroska-fix-attachments-detection.patch"
},
{
"type": "patch",
"path": "../flathub/gstreamer-1.0/gst-plugins-good-dashdemux2-play-last-subfragment.patch"
},
{
"type": "patch",
"path": "../flathub/gstreamer-1.0/gst-plugins-bad-dashdemux-sidx-range-download.patch"
},
{
"type": "patch",
"path": "../flathub/gstreamer-1.0/gst-plugins-bad-dashdemux-improve-initial-representation-selection.patch"
}
]
}

View File

@@ -1,19 +0,0 @@
{
"name": "yt-dlp",
"buildsystem": "simple",
"build-commands": [
"pip3 install -v --root-user-action=ignore --no-deps --prefix=/app *.whl"
],
"sources": [
{
"type": "file",
"url": "https://files.pythonhosted.org/packages/bb/68/548f9819b41d53561d4f3d39588111cf39993c066b6e5300b4ae118eb2e6/yt_dlp-2024.10.22-py3-none-any.whl",
"sha256": "ba166602ebe22a220e4dc1ead45bf00eb469ed812b22f4fb8bb54734f9b02084",
"x-checker-data": {
"type": "pypi",
"name": "yt-dlp",
"packagetype": "bdist_wheel"
}
}
]
}

View File

@@ -22,29 +22,24 @@
#include <clapper/clapper.h>
#include <adwaita.h>
#include "clapper-app-about-window.h"
#include "clapper-app-about-dialog.h"
GtkWidget *
clapper_app_about_window_new (GtkApplication *gtk_app)
clapper_app_about_dialog_new (void)
{
AdwAboutWindow *about;
GtkWindow *window;
AdwAboutDialog *about;
GString *string;
gchar *gst_ver, *debug_info;
about = ADW_ABOUT_WINDOW (adw_about_window_new_from_appdata (
about = ADW_ABOUT_DIALOG (adw_about_dialog_new_from_appdata (
CLAPPER_APP_RESOURCE_PREFIX "/data/metainfo/" CLAPPER_APP_ID ".metainfo.xml",
NULL));
window = gtk_application_get_active_window (gtk_app);
gtk_window_set_modal (GTK_WINDOW (about), TRUE);
gtk_window_set_transient_for (GTK_WINDOW (about), window);
/* Also show development versions */
adw_about_window_set_version (about, CLAPPER_VERSION_S);
adw_about_dialog_set_version (about, CLAPPER_VERSION_S);
/* TRANSLATORS: Put your name(s) here for credits or leave untranslated */
adw_about_window_set_translator_credits (about, _("translator-credits"));
adw_about_dialog_set_translator_credits (about, _("translator-credits"));
string = g_string_new (NULL);
@@ -66,7 +61,7 @@ clapper_app_about_window_new (GtkApplication *gtk_app)
g_free (gst_ver);
debug_info = g_string_free_and_steal (string);
adw_about_window_set_debug_info (about, debug_info);
adw_about_dialog_set_debug_info (about, debug_info);
g_free (debug_info);
return GTK_WIDGET (about);

View File

@@ -24,6 +24,6 @@
G_BEGIN_DECLS
G_GNUC_INTERNAL
GtkWidget * clapper_app_about_window_new (GtkApplication *gtk_app);
GtkWidget * clapper_app_about_dialog_new (void);
G_END_DECLS

View File

@@ -28,7 +28,7 @@
#include "clapper-app-uri-dialog.h"
#include "clapper-app-info-window.h"
#include "clapper-app-preferences-window.h"
#include "clapper-app-about-window.h"
#include "clapper-app-about-dialog.h"
#include "clapper-app-utils.h"
#define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01)
@@ -41,6 +41,7 @@ struct _ClapperAppApplication
GtkApplication parent;
GSettings *settings;
GCancellable *cancellable;
gboolean need_init_state;
};
@@ -61,6 +62,12 @@ struct ClapperPluginData
struct ClapperPluginFeatureData features[10];
};
typedef struct
{
ClapperAppApplication *app;
guint id;
} ClapperAppWindowData;
typedef struct
{
const gchar *action;
@@ -317,6 +324,19 @@ _assemble_initial_state (GtkWindow *window)
g_object_unref (builder);
}
static void
_show_error_dialog (GError *error, GtkWindow *parent)
{
AdwDialog *dialog;
dialog = adw_alert_dialog_new ("Error", error->message);
adw_alert_dialog_add_response (ADW_ALERT_DIALOG (dialog), "close", _("Close"));
adw_alert_dialog_set_default_response (ADW_ALERT_DIALOG (dialog), "close");
adw_alert_dialog_set_close_response (ADW_ALERT_DIALOG (dialog), "close");
adw_dialog_present (dialog, GTK_WIDGET (parent));
}
static void
add_files (GSimpleAction *action, GVariant *param, gpointer user_data)
{
@@ -395,14 +415,112 @@ show_info (GSimpleAction *action, GVariant *param, gpointer user_data)
gtk_window_present (GTK_WINDOW (info_window));
}
static void
_launch_pipeline_cb (GtkFileLauncher *launcher,
GAsyncResult *res, ClapperAppWindowData *win_data)
{
GError *error = NULL;
if (!gtk_file_launcher_launch_finish (launcher, res, &error)) {
if (error->domain != GTK_DIALOG_ERROR || error->code != GTK_DIALOG_ERROR_DISMISSED) {
GtkWindow *window;
GST_ERROR ("Could not launch pipeline preview, reason: %s",
GST_STR_NULL (error->message));
if ((window = gtk_application_get_window_by_id (
GTK_APPLICATION (win_data->app), win_data->id)))
_show_error_dialog (error, window);
}
g_error_free (error);
}
g_free (win_data);
}
static void
_show_pipeline_cb (GObject *source G_GNUC_UNUSED,
GAsyncResult *res, ClapperAppWindowData *win_data)
{
GTask *task = G_TASK (res);
GtkWindow *window;
GFile *svg_file;
GError *error = NULL;
svg_file = (GFile *) g_task_propagate_pointer (task, &error);
window = gtk_application_get_window_by_id (
GTK_APPLICATION (win_data->app), win_data->id);
if (error) {
if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_CANCELLED) {
GST_ERROR ("Could not create pipeline graph file, reason: %s",
GST_STR_NULL (error->message));
if (window)
_show_error_dialog (error, window);
}
g_error_free (error);
g_free (win_data);
return;
}
if (window) {
GtkFileLauncher *launcher = gtk_file_launcher_new (svg_file);
#if GTK_CHECK_VERSION(4,12,0)
gtk_file_launcher_set_always_ask (launcher, TRUE);
#endif
gtk_file_launcher_launch (launcher, window, NULL,
(GAsyncReadyCallback) _launch_pipeline_cb, win_data);
g_object_unref (launcher);
} else {
g_free (win_data);
}
g_object_unref (svg_file);
}
static void
show_pipeline (GSimpleAction *action, GVariant *param, gpointer user_data)
{
ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (user_data);
GtkApplication *gtk_app = GTK_APPLICATION (self);
GtkWindow *window;
ClapperAppWindowData *win_data;
window = gtk_application_get_active_window (gtk_app);
while (window && !CLAPPER_APP_IS_WINDOW (window))
window = gtk_window_get_transient_for (window);
if (G_UNLIKELY (window == NULL))
return;
if (self->cancellable) {
g_cancellable_cancel (self->cancellable);
g_object_unref (self->cancellable);
}
self->cancellable = g_cancellable_new ();
win_data = g_new (ClapperAppWindowData, 1);
win_data->app = self;
win_data->id = gtk_application_window_get_id (GTK_APPLICATION_WINDOW (window));
clapper_app_utils_create_pipeline_svg_file_async (
clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)),
self->cancellable, (GAsyncReadyCallback) _show_pipeline_cb, win_data);
}
static void
show_about (GSimpleAction *action, GVariant *param, gpointer user_data)
{
GtkApplication *gtk_app = GTK_APPLICATION (user_data);
GtkWidget *about_window;
GtkWindow *window;
GtkWidget *about_dialog;
about_window = clapper_app_about_window_new (gtk_app);
gtk_window_present (GTK_WINDOW (about_window));
window = gtk_application_get_active_window (gtk_app);
about_dialog = clapper_app_about_dialog_new ();
adw_dialog_present (ADW_DIALOG (about_dialog), GTK_WIDGET (window));
}
GApplication *
@@ -712,6 +830,7 @@ clapper_app_application_constructed (GObject *object)
{ "clear-queue", clear_queue, NULL, NULL, NULL },
{ "new-window", new_window, NULL, NULL, NULL },
{ "info", show_info, NULL, NULL, NULL },
{ "pipeline", show_pipeline, NULL, NULL, NULL },
{ "preferences", show_preferences, NULL, NULL, NULL },
{ "about", show_about, NULL, NULL, NULL },
};
@@ -720,6 +839,7 @@ clapper_app_application_constructed (GObject *object)
{ "app.add-uri", { "<Control>u", NULL, NULL }},
{ "app.new-window", { "<Control>n", NULL, NULL }},
{ "app.info", { "<Control>i", NULL, NULL }},
{ "app.pipeline", { "<Control><Shift>p", NULL, NULL }},
{ "app.preferences", { "<Control>comma", NULL, NULL }},
{ "app.about", { "F1", NULL, NULL }},
{ "win.toggle-fullscreen", { "F11", "f", NULL }},
@@ -754,6 +874,19 @@ clapper_app_application_constructed (GObject *object)
G_OBJECT_CLASS (parent_class)->constructed (object);
}
static void
clapper_app_application_dispose (GObject *object)
{
ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (object);
if (self->cancellable) {
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_app_application_finalize (GObject *object)
{
@@ -777,6 +910,7 @@ clapper_app_application_class_init (ClapperAppApplicationClass *klass)
"Clapper App Application");
gobject_class->constructed = clapper_app_application_constructed;
gobject_class->dispose = clapper_app_application_dispose;
gobject_class->finalize = clapper_app_application_finalize;
gtk_application_class->window_removed = clapper_app_application_window_removed;

View File

@@ -19,6 +19,7 @@
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
G_BEGIN_DECLS

View File

@@ -72,6 +72,43 @@ _open_subtitles_cb (GtkFileDialog *dialog, GAsyncResult *result, ClapperMediaIte
gst_object_unref (item); // Borrowed reference
}
static void
_on_select_file_dir_finish (GFile *file, AdwActionRow *action_row, GError *error)
{
if (G_LIKELY (error == NULL)) {
gchar *path = g_file_get_path (file);
adw_action_row_set_subtitle (action_row, path);
g_free (path);
} else {
if (error->domain != GTK_DIALOG_ERROR || error->code != GTK_DIALOG_ERROR_DISMISSED) {
g_printerr ("Error: %s\n",
(error->message) ? error->message : "Could not open file dialog");
}
g_error_free (error);
}
g_clear_object (&file);
g_object_unref (action_row); // Borrowed reference
}
static void
_select_file_cb (GtkFileDialog *dialog, GAsyncResult *result, AdwActionRow *action_row)
{
GError *error = NULL;
GFile *file = gtk_file_dialog_open_finish (dialog, result, &error);
_on_select_file_dir_finish (file, action_row, error);
}
static void
_select_dir_cb (GtkFileDialog *dialog, GAsyncResult *result, AdwActionRow *action_row)
{
GError *error = NULL;
GFile *file = gtk_file_dialog_select_folder_finish (dialog, result, &error);
_on_select_file_dir_finish (file, action_row, error);
}
static void
_dialog_add_mime_types (GtkFileDialog *dialog, const gchar *filter_name,
const gchar *const *mime_types)
@@ -144,3 +181,35 @@ clapper_app_file_dialog_open_subtitles (GtkApplication *gtk_app, ClapperMediaIte
g_object_unref (dialog);
}
void
clapper_app_file_dialog_select_prefs_file (GtkApplication *gtk_app, AdwActionRow *action_row)
{
GtkWindow *window = gtk_application_get_active_window (gtk_app);
GtkFileDialog *dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_modal (dialog, TRUE);
gtk_file_dialog_set_title (dialog, "Select File");
gtk_file_dialog_open (dialog, window, NULL,
(GAsyncReadyCallback) _select_file_cb,
g_object_ref (action_row));
g_object_unref (dialog);
}
void
clapper_app_file_dialog_select_prefs_dir (GtkApplication *gtk_app, AdwActionRow *action_row)
{
GtkWindow *window = gtk_application_get_active_window (gtk_app);
GtkFileDialog *dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_modal (dialog, TRUE);
gtk_file_dialog_set_title (dialog, "Select Folder");
gtk_file_dialog_select_folder (dialog, window, NULL,
(GAsyncReadyCallback) _select_dir_cb,
g_object_ref (action_row));
g_object_unref (dialog);
}

View File

@@ -19,6 +19,7 @@
#include <glib.h>
#include <gtk/gtk.h>
#include <adwaita.h>
#include <clapper/clapper.h>
G_BEGIN_DECLS
@@ -29,4 +30,10 @@ void clapper_app_file_dialog_open_files (GtkApplication *gtk_app);
G_GNUC_INTERNAL
void clapper_app_file_dialog_open_subtitles (GtkApplication *gtk_app, ClapperMediaItem *item);
G_GNUC_INTERNAL
void clapper_app_file_dialog_select_prefs_file (GtkApplication *gtk_app, AdwActionRow *action_row);
G_GNUC_INTERNAL
void clapper_app_file_dialog_select_prefs_dir (GtkApplication *gtk_app, AdwActionRow *action_row);
G_END_DECLS

View File

@@ -36,6 +36,8 @@ struct _ClapperAppInfoWindow
GtkWidget *astreams_list;
GtkWidget *sstreams_list;
GtkWidget *pipeline_button;
ClapperPlayer *player;
};
@@ -174,6 +176,10 @@ clapper_app_info_window_init (ClapperAppInfoWindow *self)
gtk_widget_remove_css_class (self->vstreams_list, "view");
gtk_widget_remove_css_class (self->astreams_list, "view");
gtk_widget_remove_css_class (self->sstreams_list, "view");
#ifdef HAVE_GRAPHVIZ
gtk_widget_set_visible (self->pipeline_button, TRUE);
#endif
}
static void
@@ -256,6 +262,7 @@ clapper_app_info_window_class_init (ClapperAppInfoWindowClass *klass)
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, vstreams_list);
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, astreams_list);
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, sstreams_list);
gtk_widget_class_bind_template_child (widget_class, ClapperAppInfoWindow, pipeline_button);
gtk_widget_class_bind_template_callback (widget_class, media_duration_closure);
gtk_widget_class_bind_template_callback (widget_class, playback_element_name_closure);

View File

@@ -23,6 +23,7 @@
#include "clapper-app-preferences-window.h"
#include "clapper-app-application.h"
#include "clapper-app-file-dialog.h"
#include "clapper-app-utils.h"
#define GST_CAT_DEFAULT clapper_app_preferences_window_debug
@@ -41,6 +42,14 @@ struct _ClapperAppPreferencesWindow
AdwSpinRow *subtitle_offset_spin_row;
GtkFontDialogButton *font_dialog_button;
GtkStack *enhancers_stack;
GtkWidget *browse_enhancers_page;
GtkWidget *no_enhancers_page;
AdwNavigationPage *enhancers_subpage;
AdwComboRow *enhancers_combo_row;
AdwPreferencesGroup *enhancer_config_group;
AdwNavigationPage *plugins_subpage;
AdwComboRow *plugins_combo_row;
AdwComboRow *features_combo_row;
@@ -48,6 +57,8 @@ struct _ClapperAppPreferencesWindow
GSettings *settings;
GList *enhancer_pspec_rows;
GList *features;
GtkStringList *plugins_list;
@@ -67,6 +78,19 @@ typedef struct
gboolean updated;
} ClapperAppPreferencesIterRanksData;
typedef struct
{
GSettings *settings;
const gchar *key;
} ClapperAppPreferencesResetData;
typedef struct
{
GSettings *settings;
GParamSpec *pspec;
guint flag;
} ClapperAppPreferencesFlagMapData;
enum
{
PROP_0,
@@ -76,6 +100,347 @@ enum
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static void
_flag_map_data_free (ClapperAppPreferencesFlagMapData *data)
{
GST_TRACE ("Destroying flag map data: %p", data);
g_object_unref (data->settings);
g_free (data);
}
static void
_reset_button_closure (ClapperAppPreferencesResetData *data, GClosure *closure)
{
GST_TRACE ("Destroying reset button data: %p", data);
g_object_unref (data->settings);
g_free (data);
}
static void
_reset_button_clicked_cb (GtkButton *button, ClapperAppPreferencesResetData *data)
{
g_settings_reset (data->settings, data->key);
}
static void
file_selection_row_activated_cb (AdwActionRow *action_row, GParamSpec *pspec)
{
GtkApplication *gtk_app;
GtkWidget *window;
if (!(window = gtk_widget_get_ancestor (GTK_WIDGET (action_row), GTK_TYPE_WINDOW))) {
GST_ERROR ("Could not get a hold of parent window");
return;
}
gtk_app = gtk_window_get_application (GTK_WINDOW (window));
if (pspec->flags & CLAPPER_ENHANCER_PARAM_FILEPATH)
clapper_app_file_dialog_select_prefs_file (gtk_app, action_row);
else
clapper_app_file_dialog_select_prefs_dir (gtk_app, action_row);
}
static gboolean
_get_enum_mapping (GValue *value, GVariant *variant, GParamSpec *pspec)
{
GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
const gchar *selected_str = g_variant_get_string (variant, NULL);
guint i, selected = 0;
for (i = 0; i < enum_class->n_values; ++i) {
if (g_strcmp0 (selected_str, enum_class->values[i].value_nick) == 0) {
selected = i;
break;
}
}
g_value_set_uint (value, selected);
return TRUE;
}
static GVariant *
_set_enum_mapping (GValue *value, GVariantType *exp_type, GParamSpec *pspec)
{
GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (pspec->value_type));
guint selected = g_value_get_uint (value);
if (G_UNLIKELY (selected == GTK_INVALID_LIST_POSITION))
selected = 0;
return g_variant_new_string (enum_class->values[selected].value_nick);
}
static gboolean
_get_flag_mapping (GValue *value, GVariant *variant, ClapperAppPreferencesFlagMapData *data)
{
guint flags = g_settings_get_flags (data->settings, data->pspec->name);
g_value_set_boolean (value, (flags & data->flag));
return TRUE;
}
static GVariant *
_set_flag_mapping (GValue *value, GVariantType *exp_type, ClapperAppPreferencesFlagMapData *data)
{
GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (data->pspec->value_type));
GStrvBuilder *builder;
GVariant *variant;
gchar **strv;
gboolean active = g_value_get_boolean (value);
guint i, flags = g_settings_get_flags (data->settings, data->pspec->name);
if (active)
flags |= data->flag;
else
flags &= ~(data->flag);
builder = g_strv_builder_new ();
for (i = 0; i < flags_class->n_values; ++i) {
if (flags & flags_class->values[i].value)
g_strv_builder_add (builder, flags_class->values[i].value_nick);
}
strv = g_strv_builder_end (builder);
g_strv_builder_unref (builder);
variant = g_variant_new_strv ((const gchar *const *) strv, -1);
g_strfreev (strv);
return variant;
}
static gboolean
_add_enhancer_config_row (ClapperAppPreferencesWindow *self, GParamSpec *pspec,
GSettings *enhancer_settings)
{
GtkWidget *row = NULL, *reset_button;
ClapperAppPreferencesResetData *reset_data;
const gchar *bind_prop = NULL;
gboolean is_enum = FALSE, is_flags = FALSE;
switch (pspec->value_type) {
case G_TYPE_BOOLEAN:{
row = adw_switch_row_new ();
break;
}
case G_TYPE_INT:{
GParamSpecInt *p = (GParamSpecInt *) pspec;
row = adw_spin_row_new_with_range (p->minimum, p->maximum, 1);
break;
}
case G_TYPE_UINT:{
GParamSpecUInt *p = (GParamSpecUInt *) pspec;
row = adw_spin_row_new_with_range (p->minimum, p->maximum, 1);
break;
}
case G_TYPE_DOUBLE:{
GParamSpecDouble *p = (GParamSpecDouble *) pspec;
row = adw_spin_row_new_with_range (p->minimum, p->maximum, 0.25);
break;
}
case G_TYPE_STRING:{
if (pspec->flags & (CLAPPER_ENHANCER_PARAM_FILEPATH | CLAPPER_ENHANCER_PARAM_DIRPATH)) {
GtkWidget *image;
image = gtk_image_new_from_icon_name ("document-open-symbolic");
gtk_widget_set_margin_end (image, 10); // matches other rows
row = adw_action_row_new ();
adw_action_row_add_suffix (ADW_ACTION_ROW (row), image);
adw_action_row_set_activatable_widget (ADW_ACTION_ROW (row), image);
g_signal_connect (row, "activated",
G_CALLBACK (file_selection_row_activated_cb), pspec);
} else {
row = adw_entry_row_new ();
}
break;
}
default:{
if ((is_enum = G_IS_PARAM_SPEC_ENUM (pspec))) {
GtkExpression *expression;
AdwEnumListModel *enum_model;
row = adw_combo_row_new ();
expression = gtk_property_expression_new (ADW_TYPE_ENUM_LIST_ITEM, NULL, "nick");
adw_combo_row_set_expression (ADW_COMBO_ROW (row), expression);
enum_model = adw_enum_list_model_new (pspec->value_type);
adw_combo_row_set_model (ADW_COMBO_ROW (row), G_LIST_MODEL (enum_model));
gtk_expression_unref (expression);
g_object_unref (enum_model);
break;
} else if ((is_flags = G_IS_PARAM_SPEC_FLAGS (pspec))) {
GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (pspec->value_type));
guint i;
row = adw_expander_row_new ();
for (i = 0; i < flags_class->n_values; ++i) {
GtkWidget *flag_row = adw_switch_row_new ();
ClapperAppPreferencesFlagMapData *fm_data;
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (flag_row),
flags_class->values[i].value_nick);
fm_data = g_new (ClapperAppPreferencesFlagMapData, 1);
fm_data->settings = g_object_ref (enhancer_settings);
fm_data->pspec = pspec;
fm_data->flag = flags_class->values[i].value;
GST_TRACE ("Created flag map data: %p", fm_data);
g_settings_bind_with_mapping (enhancer_settings, pspec->name, flag_row,
"active", G_SETTINGS_BIND_DEFAULT,
(GSettingsBindGetMapping) _get_flag_mapping,
(GSettingsBindSetMapping) _set_flag_mapping,
fm_data, (GDestroyNotify) _flag_map_data_free);
adw_expander_row_add_row (ADW_EXPANDER_ROW (row), flag_row);
}
break;
}
g_warning ("Unsupported enhancer \"%s\" property type: %s",
pspec->name, g_type_name (pspec->value_type));
return FALSE;
}
}
reset_button = gtk_button_new_from_icon_name ("view-refresh-symbolic");
gtk_widget_set_tooltip_text (reset_button, _("Restore default"));
gtk_widget_set_halign (reset_button, GTK_ALIGN_CENTER);
gtk_widget_set_valign (reset_button, GTK_ALIGN_CENTER);
gtk_widget_add_css_class (reset_button, "circular");
gtk_widget_set_tooltip_text (row, g_param_spec_get_blurb (pspec));
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), g_param_spec_get_nick (pspec));
if (ADW_IS_SWITCH_ROW (row)) {
bind_prop = "active";
} else if (ADW_IS_SPIN_ROW (row)) {
bind_prop = "value";
adw_spin_row_set_numeric (ADW_SPIN_ROW (row), TRUE);
} else if (ADW_IS_ENTRY_ROW (row)) {
bind_prop = "text";
} else if (ADW_IS_COMBO_ROW (row)) {
bind_prop = "selected";
} else if (ADW_IS_ACTION_ROW (row)) {
bind_prop = "subtitle";
} else if (!is_flags) { // In case of flags we bind individual widgets
g_assert_not_reached ();
return FALSE;
}
if (ADW_IS_ENTRY_ROW (row))
adw_entry_row_add_prefix (ADW_ENTRY_ROW (row), reset_button);
else if (ADW_IS_ACTION_ROW (row))
adw_action_row_add_prefix (ADW_ACTION_ROW (row), reset_button);
else if (ADW_IS_EXPANDER_ROW (row))
adw_expander_row_add_prefix (ADW_EXPANDER_ROW (row), reset_button);
if (is_enum) {
g_settings_bind_with_mapping (enhancer_settings, pspec->name, row,
bind_prop, G_SETTINGS_BIND_DEFAULT,
(GSettingsBindGetMapping) _get_enum_mapping,
(GSettingsBindSetMapping) _set_enum_mapping,
pspec, NULL);
} else if (!is_flags) {
g_settings_bind (enhancer_settings, pspec->name, row,
bind_prop, G_SETTINGS_BIND_DEFAULT);
}
reset_data = g_new (ClapperAppPreferencesResetData, 1);
reset_data->settings = g_object_ref (enhancer_settings);
reset_data->key = pspec->name;
GST_TRACE ("Created reset button data: %p", reset_data);
g_signal_connect_data (reset_button, "clicked",
G_CALLBACK (_reset_button_clicked_cb), reset_data,
(GClosureNotify) _reset_button_closure, G_CONNECT_DEFAULT);
adw_preferences_group_add (self->enhancer_config_group, row);
self->enhancer_pspec_rows = g_list_append (self->enhancer_pspec_rows, row);
return TRUE;
}
static void
selected_enhancer_changed_cb (AdwComboRow *combo_row,
GParamSpec *pspec G_GNUC_UNUSED, ClapperAppPreferencesWindow *self)
{
guint selected = adw_combo_row_get_selected (combo_row);
/* Remove old rows */
if (self->enhancer_pspec_rows) {
GList *el;
for (el = self->enhancer_pspec_rows; el; el = g_list_next (el))
adw_preferences_group_remove (self->enhancer_config_group, GTK_WIDGET (el->data));
g_clear_list (&self->enhancer_pspec_rows, NULL);
}
/* Add new rows */
if (selected != GTK_INVALID_LIST_POSITION) {
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, selected);
GParamSpec **pspecs;
guint n_pspecs;
gboolean has_props = FALSE;
if ((pspecs = clapper_enhancer_proxy_get_target_properties (proxy, &n_pspecs))) {
GSettings *enhancer_settings = NULL;
guint i;
for (i = 0; i < n_pspecs; ++i) {
if (pspecs[i]->flags & CLAPPER_ENHANCER_PARAM_GLOBAL) {
if (!enhancer_settings)
enhancer_settings = clapper_enhancer_proxy_get_settings (proxy);
if (enhancer_settings)
has_props |= _add_enhancer_config_row (self, pspecs[i], enhancer_settings);
}
}
g_clear_object (&enhancer_settings);
}
if (!has_props) {
GtkWidget *row = adw_action_row_new ();
adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), _("No configurable properties"));
adw_preferences_group_add (self->enhancer_config_group, row);
self->enhancer_pspec_rows = g_list_append (self->enhancer_pspec_rows, row);
}
}
}
static void
enhancers_config_activated_cb (AdwActionRow *action_row, ClapperAppPreferencesWindow *self)
{
/* If no model set yet */
if (!adw_combo_row_get_model (self->enhancers_combo_row)) {
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
adw_combo_row_set_model (self->enhancers_combo_row, G_LIST_MODEL (proxies));
adw_combo_row_set_selected (self->enhancers_combo_row, GTK_INVALID_LIST_POSITION);
GST_DEBUG ("Populated names combo row in enhancers subpage");
if (clapper_enhancer_proxy_list_get_n_proxies (proxies) > 0)
gtk_stack_set_visible_child (self->enhancers_stack, self->browse_enhancers_page);
else
gtk_stack_set_visible_child (self->enhancers_stack, self->no_enhancers_page);
}
adw_preferences_window_push_subpage (ADW_PREFERENCES_WINDOW (self), self->enhancers_subpage);
}
/* Sort by plugin name and if the same, sort by element name */
static gint
_compare_plugins_cb (gconstpointer ptr_a, gconstpointer ptr_b)
@@ -301,6 +666,12 @@ _make_plugin_features_string_list (ClapperAppPreferencesWindow *self, const gcha
return features_list;
}
static gboolean
list_has_selection_closure (ClapperAppPreferencesWindow *self, guint selected)
{
return (selected != GTK_INVALID_LIST_POSITION);
}
static GtkStringList *
ranking_features_model_closure (ClapperAppPreferencesWindow *self, GtkStringObject *string_obj)
{
@@ -543,6 +914,7 @@ clapper_app_preferences_window_finalize (GObject *object)
g_object_unref (self->settings);
g_clear_list (&self->enhancer_pspec_rows, NULL);
g_clear_object (&self->plugins_list);
if (self->features)
@@ -598,6 +970,14 @@ clapper_app_preferences_window_class_init (ClapperAppPreferencesWindowClass *kla
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, subtitle_offset_spin_row);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, font_dialog_button);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_stack);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, browse_enhancers_page);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, no_enhancers_page);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_subpage);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancers_combo_row);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, enhancer_config_group);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, plugins_subpage);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, plugins_combo_row);
gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, features_combo_row);
@@ -605,9 +985,13 @@ clapper_app_preferences_window_class_init (ClapperAppPreferencesWindowClass *kla
gtk_widget_class_bind_template_callback (widget_class, seek_method_name_closure);
gtk_widget_class_bind_template_callback (widget_class, enhancers_config_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, selected_enhancer_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, plugin_ranking_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, plugin_ranking_unrealize_cb);
gtk_widget_class_bind_template_callback (widget_class, list_has_selection_closure);
gtk_widget_class_bind_template_callback (widget_class, ranking_features_model_closure);
gtk_widget_class_bind_template_callback (widget_class, add_override_button_sensitive_closure);
gtk_widget_class_bind_template_callback (widget_class, add_override_button_clicked_cb);

View File

@@ -25,30 +25,28 @@
#include "clapper-app-utils.h"
static void
_entry_text_changed_cb (GtkEntry *entry,
GParamSpec *pspec G_GNUC_UNUSED, AdwMessageDialog *dialog)
_entry_text_changed_cb (AdwEntryRow *entry,
GParamSpec *pspec G_GNUC_UNUSED, AdwAlertDialog *dialog)
{
GtkEntryBuffer *buffer = gtk_entry_get_buffer (entry);
guint text_length = gtk_entry_buffer_get_length (buffer);
const gchar *text = gtk_editable_get_text (GTK_EDITABLE (entry));
gboolean enabled = FALSE;
if (text_length > 0) {
const gchar *text = gtk_entry_buffer_get_text (buffer);
enabled = (text && gst_uri_is_valid (text));
if (text && *text != '\0') {
enabled = gst_uri_is_valid (text);
}
adw_message_dialog_set_response_enabled (dialog, "add", enabled);
adw_alert_dialog_set_response_enabled (dialog, "add", enabled);
}
static void
_open_uri_cb (AdwMessageDialog *dialog, GAsyncResult *result, GtkApplication *gtk_app)
_open_uri_cb (AdwAlertDialog *dialog, GAsyncResult *result, GtkApplication *gtk_app)
{
const gchar *response = adw_message_dialog_choose_finish (dialog, result);
const gchar *response = adw_alert_dialog_choose_finish (dialog, result);
if (strcmp (response, "add") == 0) {
GtkWidget *extra_child = adw_message_dialog_get_extra_child (dialog);
GtkEntryBuffer *buffer = gtk_entry_get_buffer (GTK_ENTRY (extra_child));
const gchar *text = gtk_entry_buffer_get_text (buffer);
GtkWidget *extra_child = adw_alert_dialog_get_extra_child (dialog);
AdwEntryRow *entry_row = ADW_ENTRY_ROW (gtk_list_box_get_row_at_index (GTK_LIST_BOX (extra_child), 0));
const gchar *text = gtk_editable_get_text (GTK_EDITABLE (entry_row));
GFile **files = NULL;
gint n_files = 0;
@@ -60,16 +58,15 @@ _open_uri_cb (AdwMessageDialog *dialog, GAsyncResult *result, GtkApplication *gt
}
static void
_read_text_cb (GdkClipboard *clipboard, GAsyncResult *result, GtkWidget *extra_child)
_read_text_cb (GdkClipboard *clipboard, GAsyncResult *result, GtkWidget *entry_row)
{
GtkEntry *entry = GTK_ENTRY (extra_child);
GError *error = NULL;
gchar *text = gdk_clipboard_read_text_finish (clipboard, result, &error);
if (G_LIKELY (error == NULL)) {
if (gst_uri_is_valid (text)) {
gtk_editable_set_text (GTK_EDITABLE (entry), text);
gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
gtk_editable_set_text (GTK_EDITABLE (entry_row), text);
gtk_editable_select_region (GTK_EDITABLE (entry_row), 0, -1);
}
} else {
/* Common error when clipboard is empty or has unsupported content.
@@ -89,30 +86,28 @@ clapper_app_uri_dialog_open_uri (GtkApplication *gtk_app)
{
GtkWindow *window = gtk_application_get_active_window (gtk_app);
GtkBuilder *builder;
AdwMessageDialog *dialog;
GtkWidget *extra_child;
AdwAlertDialog *dialog;
GtkWidget *entry_row;
GdkDisplay *display;
builder = gtk_builder_new_from_resource (
CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-uri-dialog.ui");
dialog = ADW_MESSAGE_DIALOG (gtk_builder_get_object (builder, "dialog"));
gtk_window_set_transient_for (GTK_WINDOW (dialog), window);
dialog = ADW_ALERT_DIALOG (gtk_builder_get_object (builder, "dialog"));
entry_row = GTK_WIDGET (gtk_builder_get_object (builder, "entry_row"));
extra_child = adw_message_dialog_get_extra_child (dialog);
g_signal_connect (GTK_ENTRY (extra_child), "notify::text",
g_signal_connect (GTK_EDITABLE (entry_row), "notify::text",
G_CALLBACK (_entry_text_changed_cb), dialog);
if ((display = gdk_display_get_default ())) {
GdkClipboard *clipboard = gdk_display_get_clipboard (display);
gdk_clipboard_read_text_async (clipboard, NULL,
(GAsyncReadyCallback) _read_text_cb, extra_child);
(GAsyncReadyCallback) _read_text_cb, entry_row);
}
/* NOTE: Dialog will automatically unref itself after response */
adw_message_dialog_choose (dialog, NULL,
adw_alert_dialog_choose (dialog, GTK_WIDGET (window), NULL,
(GAsyncReadyCallback) _open_uri_cb,
gtk_app);

View File

@@ -23,6 +23,11 @@
#include "clapper-app-utils.h"
#include "clapper-app-media-item-box.h"
#ifdef HAVE_GRAPHVIZ
#include <graphviz/cgraph.h>
#include <graphviz/gvc.h>
#endif
#ifdef G_OS_WIN32
#include <windows.h>
#ifdef HAVE_WIN_PROCESS_THREADS_API
@@ -527,3 +532,178 @@ clapper_app_utils_make_element (const gchar *string)
return gst_element_factory_make (string, NULL);
}
/*
* _get_tmp_dir:
* @subdir: (nullable): an optional subdirectory
*
* Returns: (transfer full): a newly constructed #GFile
*/
static inline GFile *
_get_tmp_dir (const gchar *subdir)
{
/* XXX: System tmp directory does not work within containers such as Flatpak
* for our usage with file launcher, so make our own temp in app data dir */
return g_file_new_build_filename (
g_get_user_data_dir (), CLAPPER_APP_ID, "tmp", subdir, NULL);
}
#ifdef HAVE_GRAPHVIZ
static GFile *
_create_tmp_subdir (const gchar *subdir, GCancellable *cancellable, GError **error)
{
GFile *tmp_dir;
GError *my_error = NULL;
tmp_dir = _get_tmp_dir (subdir);
if (!g_file_make_directory_with_parents (tmp_dir, cancellable, &my_error)) {
if (my_error->domain != G_IO_ERROR || my_error->code != G_IO_ERROR_EXISTS) {
*error = g_error_copy (my_error);
g_clear_object (&tmp_dir); // return NULL
}
g_error_free (my_error);
}
return tmp_dir;
}
#endif
static void
_create_pipeline_svg_file_in_thread (GTask *task, GObject *source G_GNUC_UNUSED,
ClapperPlayer *player, GCancellable *cancellable)
{
GFile *tmp_file = NULL;
GError *error = NULL;
#ifdef HAVE_GRAPHVIZ
GFile *tmp_subdir;
Agraph_t *graph;
GVC_t *gvc;
gchar *path, *template = NULL, *dot_data = NULL, *img_data = NULL;
gint fd;
guint size = 0;
if (!(tmp_subdir = _create_tmp_subdir ("pipelines", cancellable, &error)))
goto finish;
path = g_file_get_path (tmp_subdir);
g_object_unref (tmp_subdir);
template = g_build_filename (path, "pipeline-XXXXXX.svg", NULL);
g_free (path);
fd = g_mkstemp (template); // Modifies template to actual filename
if (G_UNLIKELY (fd == -1)) {
g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"Could not open temp file for writing");
goto finish;
}
dot_data = clapper_player_make_pipeline_graph (player, GST_DEBUG_GRAPH_SHOW_ALL);
if (g_cancellable_is_cancelled (cancellable))
goto close_and_finish;
graph = agmemread (dot_data);
gvc = gvContext ();
gvLayout (gvc, graph, "dot");
gvRenderData (gvc, graph, "svg", &img_data, &size);
agclose (graph);
gvFreeContext (gvc);
if (g_cancellable_is_cancelled (cancellable))
goto close_and_finish;
if (write (fd, img_data, size) != -1) {
tmp_file = g_file_new_for_path (template);
} else {
g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"Could not write data to temp file");
}
close_and_finish:
/* Always close the file IO */
if (G_UNLIKELY (close (fd) == -1))
GST_ERROR ("Could not close temp file!");
finish:
g_free (template);
g_free (dot_data);
g_free (img_data);
#else
g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"Cannot create graph file when compiled without Graphviz");
#endif
if (tmp_file)
g_task_return_pointer (task, tmp_file, (GDestroyNotify) g_object_unref);
else
g_task_return_error (task, error);
}
void
clapper_app_utils_create_pipeline_svg_file_async (ClapperPlayer *player,
GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
GTask *task;
task = g_task_new (NULL, cancellable, callback, user_data);
g_task_set_task_data (task, gst_object_ref (player), (GDestroyNotify) gst_object_unref);
g_task_run_in_thread (task, (GTaskThreadFunc) _create_pipeline_svg_file_in_thread);
g_object_unref (task);
}
static gboolean
_delete_dir_recursive (GFile *dir, GError **error)
{
GFileEnumerator *dir_enum;
if ((dir_enum = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, error))) {
while (TRUE) {
GFileInfo *info = NULL;
GFile *child = NULL;
if (!g_file_enumerator_iterate (dir_enum, &info,
&child, NULL, error) || !info)
break;
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
if (!_delete_dir_recursive (child, error))
break;
} else if (!g_file_delete (child, NULL, error)) {
break;
}
}
g_object_unref (dir_enum);
}
if (*error != NULL)
return FALSE;
return g_file_delete (dir, NULL, error);
}
void
clapper_app_utils_delete_tmp_dir (void)
{
GFile *tmp_dir = _get_tmp_dir (NULL);
GError *error = NULL;
if (!_delete_dir_recursive (tmp_dir, &error)) {
if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_NOT_FOUND) {
GST_ERROR ("Could not remove temp dir, reason: %s",
GST_STR_NULL (error->message));
}
g_error_free (error);
}
g_object_unref (tmp_dir);
}

View File

@@ -84,4 +84,10 @@ void clapper_app_utils_iterate_plugin_feature_ranks (GSettings *settings, Clappe
G_GNUC_INTERNAL
GstElement * clapper_app_utils_make_element (const gchar *string);
G_GNUC_INTERNAL
void clapper_app_utils_create_pipeline_svg_file_async (ClapperPlayer *player, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
G_GNUC_INTERNAL
void clapper_app_utils_delete_tmp_dir (void);
G_END_DECLS

View File

@@ -21,10 +21,10 @@ window.info .subcontent streamlist preferencesgroup {
window.preferences .subcontent {
margin: 16px;
}
window.preferences .pluginssubpage .subcontent popover {
window.preferences .configsubpage .subcontent popover {
min-width: 264px;
}
window.preferences .pluginssubpage .subcontent button.pill {
window.preferences .configsubpage .subcontent button.pill {
margin-top: 8px;
margin-bottom: 8px;
}

View File

@@ -12,7 +12,7 @@ StartupNotify=true
Terminal=false
Type=Application
# Translators: Search terms to find this application. Do NOT translate the semicolons!
Keywords=Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;
Keywords=Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;
# Translators: Do NOT translate or transliterate this text (these are enum types)!
X-Purism-FormFactor=Workstation;Mobile;
Actions=new-window;

View File

@@ -1,10 +1,11 @@
appstream_util = find_program('appstream-util', required: false)
if appstream_util.found()
appstream_cli = find_program('appstreamcli', required: false)
if appstream_cli.found()
test('Validate appstream file',
appstream_util,
appstream_cli,
args: [
'validate-relax',
'--nonet',
'validate',
'--no-net',
'--explain',
join_paths(meson.current_source_dir(), 'metainfo', 'com.github.rafostar.Clapper.metainfo.xml'),
]
)

View File

@@ -74,5 +74,7 @@ main (gint argc, gchar **argv)
clapper_app_utils_win_hi_res_clock_stop (resolution);
#endif
clapper_app_utils_delete_tmp_dir ();
return status;
}

View File

@@ -67,7 +67,7 @@ configure_file(
)
clapperapp_sources = [
'clapper-app-about-window.c',
'clapper-app-about-dialog.c',
'clapper-app-application.c',
'clapper-app-file-dialog.c',
'clapper-app-headerbar.c',
@@ -94,6 +94,23 @@ clapperapp_c_args = [
'-DGST_USE_UNSTABLE_API',
]
clapperapp_possible_functionalities = [
'pipeline-preview',
]
clapperapp_available_functionalities = []
pp_option = get_option('pipeline-preview')
if not pp_option.disabled()
if cgraph_dep.found() and gvc_dep.found()
clapperapp_c_args += ['-DHAVE_GRAPHVIZ']
clapperapp_deps += [cgraph_dep, gvc_dep]
clapperapp_available_functionalities += 'pipeline-preview'
elif pp_option.enabled()
error('pipeline-preview option was enabled, but required dependencies were not found')
endif
endif
is_windows = ['windows'].contains(host_machine.system())
if is_windows

View File

@@ -10,7 +10,7 @@ src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui
src/bin/clapper-app/ui/clapper-app-uri-dialog.ui
src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui
src/bin/clapper-app/clapper-app-about-window.c
src/bin/clapper-app/clapper-app-about-dialog.c
src/bin/clapper-app/clapper-app-application.c
src/bin/clapper-app/clapper-app-info-window.c
src/bin/clapper-app/clapper-app-list-item-utils.c

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Afrikaans\n"
"Language: af_ZA\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Arabic\n"
"Language: ar_SA\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "عادي"
msgid "Fast"
msgstr "سريع"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Asturian\n"
"Language: ast_ES\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normal"
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Catalan\n"
"Language: ca_ES\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Ràpid"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Sense progressió"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Consecutiu"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Repeteix l'ítem"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Carrusel"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Aleatori"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper-app\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -32,9 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid ""
"Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;"
"Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -537,23 +535,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Czech\n"
"Language: cs_CZ\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normální"
msgid "Fast"
msgstr "Rychlý"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Žádný postup"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Po sobě"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Opakovat položku"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Opakovat frontu"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Náhodně"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Language: da_DK\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-13 18:01\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-04-11 18:29\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Language: de_DE\n"
@@ -32,8 +32,8 @@ msgstr "Gebe Videos uns Musik wieder"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Video;Bewegtbildproduktion;Film;Clip;Serie;Wiedergabe;Wiedergabeliste;DVD;TV;Fernsehen;Platte;Album;Musik;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -374,7 +374,7 @@ msgstr "Versatz zwischen der Untertitel- und Videospur (in Sekunden)"
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118
msgid "Default font"
msgstr "Standartschriftart"
msgstr "Standardschriftart"
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119
msgid "Text font used for subtitles when media does not explicitly specify one"
@@ -530,23 +530,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Schnell"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Kein Fortschritt"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Fortlaufend"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Wiederholen"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Kreislauf"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Zufallswiedergabe"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"Language: el_GR\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 17:41\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Language: es_ES\n"
@@ -32,8 +32,8 @@ msgstr "Reproduce videos y música"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Vídeo;Película;Film;Clip;Serie;Reproductor;Playlist;DVD;TV;Disco;Álbum;Música;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -531,23 +531,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Rápido"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Sin progresión"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Consecutivo"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Repetir elemento"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Carrusel"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Aleatorio"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Basque\n"
"Language: eu_ES\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normala"
msgid "Fast"
msgstr "Azkarra"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-13 15:41\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 13:14\n"
"Last-Translator: \n"
"Language-Team: Persian\n"
"Language: fa_IR\n"
@@ -32,8 +32,8 @@ msgstr "پخش ویدیو و آهنگ"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;ویدیو;فیلم;کلیپ;سریتال;پخش;تلویزیون;آلبوم;آهنگ;گنوم;کلاکت;کلپر;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;ویدیو;فیلم;کلیپ;سریال;نمایش;پخش;دی‌وی‌دی;تلویزیون;دیسک;"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -531,23 +531,23 @@ msgstr "معمولی"
msgid "Fast"
msgstr "تند"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "بدون پیشرفت"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "متوالی"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "تکرار مورد"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "چرخ فلک"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "بر زدن"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-12 13:02\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-17 08:37\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"Language: fi_FI\n"
@@ -20,7 +20,7 @@ msgstr ""
#. Translators: Do NOT translate app name!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4
msgid "Clapper"
msgstr ""
msgstr "Clapper"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5
msgid "Multimedia Player"
@@ -32,8 +32,8 @@ msgstr "Toista videoita ja musiikkia"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;elokuva;sarja;soittolista;levy;albumi;musiikki;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;elokuva;leffa;sarja;soittolista;soitin;"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -530,23 +530,23 @@ msgstr "Normaali"
msgid "Fast"
msgstr "Nopea"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Ei etenemistä"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Perättäinen"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Toista kohde"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Karuselli"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Sekoita"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-18 09:13\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-27 20:49\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Language: fr_FR\n"
@@ -32,8 +32,8 @@ msgstr "Lire des vidéos et de la musique"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Vidéo;Movie;Film;Clip;Séries;Player;Lecteur;Playlist;DVD;TV;Disque;Album;Musique;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr "Vidéo;Film;Clip;Série;Lecteur;Playlist;DVD;TV;Disque;"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -531,23 +531,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Rapide"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Aucune progression"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Chronologique"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Répéter lélement"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Carrousel"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Aléatoire"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Hebrew\n"
"Language: he_IL\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "רגילה"
msgid "Fast"
msgstr "מהיר"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"Language: hi_IN\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "साधारण"
msgid "Fast"
msgstr "तेज"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "प्रगति नहीं"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "लगातार"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "मद दोहराएं"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "कैरोसेल"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "शफल"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Croatian\n"
"Language: hr_HR\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normalna"
msgid "Fast"
msgstr "Brzo"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Language: hu_HU\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normál"
msgid "Fast"
msgstr "Gyors"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Ne haladjon"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Folyamatos"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Listaelem ismétlése"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Körkörös"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Véletlenszerű"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Language: it_IT\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normale"
msgid "Fast"
msgstr "Veloce"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Language: ja_JP\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "標準"
msgid "Fast"
msgstr "高速"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Language: ko_KR\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Lithuanian\n"
"Language: lt_LT\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Numatyta"
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Language: nl_NL\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normaal"
msgid "Fast"
msgstr "Snel"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Norwegian\n"
"Language: no_NO\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Rask"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Ingen progresjon"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Konsekutiv"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Gjenta element"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Gjenta kø"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Omstokking"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Occitan\n"
"Language: oc_FR\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr "Rapid"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Cap de progression"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Consecutiu"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Repetir lelement"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Vira-vira"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Aleatòri"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-19 14:18\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Language: pl_PL\n"
@@ -32,8 +32,8 @@ msgstr "Odtwarzaj filmy i muzykę"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Wideo;Film;Clip;Serie;Odtwarzacz;Playlista;DVD;TV;CD;Album;Muzyka;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -530,23 +530,23 @@ msgstr "Normalny"
msgid "Fast"
msgstr "Szybki"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Brak progresji"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Po kolei"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Powtarzaj rzecz"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Karuzela"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Losowo"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-14 14:46\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-04-01 09:11\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Language: pt_PT\n"
@@ -32,8 +32,8 @@ msgstr "Reproduzir vídeos e música"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Vídeo;Filme;Clip;Leitor;Reprodutor;Playlist;Lista de reprodução;DVD;TV;Disco;Música;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr "Vídeo;Filme;Filme;Clipe;Série;Leitor;Reprodutor;Lista de reprodução;DVD;TV;Disco;"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -530,23 +530,23 @@ msgstr "Predefinido"
msgid "Fast"
msgstr "Rápido"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Sem progressão"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Consecutiva"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Repetir item"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Carrossel"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Aleatório"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-12 03:17\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-16 04:37\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"Language: pt_BR\n"
@@ -32,8 +32,8 @@ msgstr "Reproduza vídeos e músicas"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Vídeo;Filme;Clipe;Série;Show;Reprodutor;Player;Playlist;Lista;DVD;TV;Disco;Álbum;Música;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Vídeo;Filme;Clipe;Série;Reprodutor;Lista;Reprodução;Disco;"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -531,23 +531,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Rápido"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Sem progressão"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Consecutiva"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Repetir item"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Repetir tudo"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Aleatório"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-20 20:06\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:10\n"
"Last-Translator: \n"
"Language-Team: Romanian\n"
"Language: ro_RO\n"
@@ -32,7 +32,7 @@ msgstr "Redă videoclipuri și muzică"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-14 21:16\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-25 20:33\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -20,25 +20,25 @@ msgstr ""
#. Translators: Do NOT translate app name!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4
msgid "Clapper"
msgstr ""
msgstr "Clapper"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5
msgid "Multimedia Player"
msgstr ""
msgstr "Проигрыватель мультимедиа"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6
msgid "Play videos and music"
msgstr ""
msgstr "Воспроизведение видео и музыки"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr ""
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr "Видео;Фильм;Фильм;Клип;Серий;Плейлист;DVD;ТВ;Диск;"
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
msgid "New Window"
msgstr ""
msgstr "Новое Окно"
#: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:14
#: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:14
@@ -90,7 +90,7 @@ msgstr "Общие"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13
msgid "New window"
msgstr ""
msgstr "Новое окно"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19
msgid "Open preferences"
@@ -110,15 +110,15 @@ msgstr "Двойное нажатие | Двойной клик"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38
msgid "Unfullscreen"
msgstr ""
msgstr "Выйти из полноэкранного режима"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44
msgid "Auto window resize"
msgstr ""
msgstr "Автоматически изменять размер окна"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45
msgid "Middle click"
msgstr ""
msgstr "Клик колёсиком"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51
msgid "Quit"
@@ -468,19 +468,19 @@ msgstr "Игорь Дятлов"
#: src/bin/clapper-app/clapper-app-application.c:696
msgid "Create a new window"
msgstr ""
msgstr "Создать новое окно"
#: src/bin/clapper-app/clapper-app-application.c:697
msgid "Add media to queue in primary application instance"
msgstr ""
msgstr "Добавить контент в очередь в основной экземпляр приложения"
#: src/bin/clapper-app/clapper-app-application.c:698
msgid "Audio volume to set (0 - 2.0 range)"
msgstr ""
msgstr "Установить громкость звука (диапазон 0 - 2,0)"
#: src/bin/clapper-app/clapper-app-application.c:699
msgid "Playback speed to set (0.05 - 2.0 range)"
msgstr ""
msgstr "Установить скорость воспроизведения (диапазон 0,05 - 2,0)"
#: src/bin/clapper-app/clapper-app-application.c:700
msgid "Initial bitrate for adaptive streaming"
@@ -492,7 +492,7 @@ msgstr ""
#: src/bin/clapper-app/clapper-app-application.c:702
msgid "Set window to be fullscreen"
msgstr ""
msgstr "Запустить в полноэкранном окне"
#: src/bin/clapper-app/clapper-app-application.c:703
msgid "Video filter to use (\"none\" to disable)"
@@ -530,23 +530,23 @@ msgstr "Обычная"
msgid "Fast"
msgstr "Быстрый"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "Нет прогрессии"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Последовательно"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Повторить элемент"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Повтор очереди"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Случайно"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Slovak\n"
"Language: sk_SK\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normálne"
msgid "Fast"
msgstr "Rýchle"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Serbian (Cyrillic)\n"
"Language: sr_SP\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Language: sv_SE\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Snabbt"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 22:52\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Language: tr_TR\n"
@@ -32,8 +32,8 @@ msgstr "Videoları ve müzikleri oynat"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgstr "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;Vidyo;Klip;Dizi;Oynatıcı;Oynatma Listesi;Çalma Listesi;Disk;Albüm;Müzik;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
@@ -530,23 +530,23 @@ msgstr "Normal"
msgid "Fast"
msgstr "Hızlı"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "İlerleme yok"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "Ardaşık"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "Ögeyi Yinele"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "Karosel"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "Karıştır"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"Language: uk_UA\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Vietnamese\n"
"Language: vi_VN\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-19 10:03\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Language: zh_CN\n"
@@ -32,13 +32,13 @@ msgstr "播放视频和音乐"
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
#: src/bin/clapper-app/ui/clapper-app-headerbar.ui:183
msgid "New Window"
msgstr ""
msgstr "新建窗口"
#: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:14
#: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:14
@@ -90,7 +90,7 @@ msgstr "常规​​​​​"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13
msgid "New window"
msgstr ""
msgstr "新建窗口"
#: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19
msgid "Open preferences"
@@ -285,7 +285,7 @@ msgstr "过滤器"
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:182
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:223
msgid "Sink"
msgstr "Sink"
msgstr "接收器"
#: src/bin/clapper-app/ui/clapper-app-info-window.ui:196
msgid "Audio Playback"
@@ -309,11 +309,11 @@ msgstr "添加媒体到播放队列即可开始"
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15
msgid "Seeking"
msgstr "定位播放"
msgstr "快进/快退"
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18
msgid "Method"
msgstr "快进/快退方式"
msgstr "方式"
#: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19
msgid "A preferred method used for seeking"
@@ -465,7 +465,8 @@ msgstr "像素格式"
#: src/bin/clapper-app/clapper-app-about-window.c:47
msgid "translator-credits"
msgstr "刘韬\n"
"Tommy He"
"Tommy He\n"
"demo_007"
#: src/bin/clapper-app/clapper-app-application.c:696
msgid "Create a new window"
@@ -473,11 +474,11 @@ msgstr "新建一个窗口"
#: src/bin/clapper-app/clapper-app-application.c:697
msgid "Add media to queue in primary application instance"
msgstr ""
msgstr "向主应用程序实例播放列队添加媒体"
#: src/bin/clapper-app/clapper-app-application.c:698
msgid "Audio volume to set (0 - 2.0 range)"
msgstr "设置音量 (范围 0 - 2.0)"
msgstr "设置音量(范围 0 - 2.0)"
#: src/bin/clapper-app/clapper-app-application.c:699
msgid "Playback speed to set (0.05 - 2.0 range)"
@@ -485,31 +486,31 @@ msgstr "设置播放速度(范围 0.05 - 2.0)"
#: src/bin/clapper-app/clapper-app-application.c:700
msgid "Initial bitrate for adaptive streaming"
msgstr ""
msgstr "自适性流的初始比特率"
#: src/bin/clapper-app/clapper-app-application.c:701
msgid "Initial queue progression mode (0=none, 1=consecutive, 2=repeat-item, 3=carousel, 4=shuffle)"
msgstr ""
msgstr "初始播放列队模式 (0=单集, 1=连续, 2=单机循环, 3=队列循环, 4=随机)"
#: src/bin/clapper-app/clapper-app-application.c:702
msgid "Set window to be fullscreen"
msgstr "设置窗口全屏"
msgstr "窗口全屏"
#: src/bin/clapper-app/clapper-app-application.c:703
msgid "Video filter to use (\"none\" to disable)"
msgstr ""
msgstr "视频过滤器(\"none\"为禁用)"
#: src/bin/clapper-app/clapper-app-application.c:704
msgid "Audio filter to use (\"none\" to disable)"
msgstr ""
msgstr "音频过滤器(\"none\"为禁用)"
#: src/bin/clapper-app/clapper-app-application.c:705
msgid "Video sink to use"
msgstr ""
msgstr "视频接收器"
#: src/bin/clapper-app/clapper-app-application.c:706
msgid "Audio sink to use"
msgstr ""
msgstr "音频接收器"
#: src/bin/clapper-app/clapper-app-info-window.c:86
msgid "Hardware"
@@ -531,23 +532,23 @@ msgstr "标准"
msgid "Fast"
msgstr "快速"
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr "单集"
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr "连续"
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr "单集循环"
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr "队列循环"
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr "随机"

View File

@@ -2,8 +2,8 @@ msgid ""
msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-11 15:22+0100\n"
"PO-Revision-Date: 2025-01-11 14:36\n"
"POT-Creation-Date: 2025-02-15 12:47+0100\n"
"PO-Revision-Date: 2025-02-15 12:11\n"
"Last-Translator: \n"
"Language-Team: Chinese Traditional\n"
"Language: zh_TW\n"
@@ -32,7 +32,7 @@ msgstr ""
#. Translators: Search terms to find this application. Do NOT translate the semicolons!
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:16
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;Album;Music;GNOME;Clapper;"
msgid "Video;Movie;Film;Clip;Series;Player;Playlist;DVD;TV;Disc;"
msgstr ""
#: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:22
@@ -530,23 +530,23 @@ msgstr ""
msgid "Fast"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:90
#: src/bin/clapper-app/clapper-app-utils.c:207
msgid "No progression"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:91
#: src/bin/clapper-app/clapper-app-utils.c:208
msgid "Consecutive"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:92
#: src/bin/clapper-app/clapper-app-utils.c:209
msgid "Repeat item"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:93
#: src/bin/clapper-app/clapper-app-utils.c:210
msgid "Carousel"
msgstr ""
#: src/bin/clapper-app/clapper-app-utils.c:94
#: src/bin/clapper-app/clapper-app-utils.c:211
msgid "Shuffle"
msgstr ""

View File

@@ -26,6 +26,12 @@
<property name="accelerator">&lt;Ctrl&gt;question</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="title" translatable="yes">Show pipeline</property>
<property name="accelerator">&lt;Ctrl&gt;&lt;Shift&gt;p</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="title" translatable="yes">Toggle fullscreen</property>

View File

@@ -232,6 +232,17 @@
</child>
</object>
</child>
<child>
<object class="GtkButton" id="pipeline_button">
<property name="halign">center</property>
<property name="label" translatable="yes">Show Pipeline</property>
<property name="action-name">app.pipeline</property>
<property name="visible">false</property>
<style>
<class name="pill"/>
</style>
</object>
</child>
</object>
</child>
</object>

View File

@@ -136,6 +136,24 @@
<object class="AdwPreferencesPage">
<property name="title" translatable="yes">Tweaks</property>
<property name="icon-name">applications-engineering-symbolic</property>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="no">Clapper</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Enhancers</property>
<property name="subtitle" translatable="yes">Browse and configure properties of available enhancers</property>
<property name="activatable">true</property>
<signal name="activated" handler="enhancers_config_activated_cb"/>
<child>
<object class="GtkImage">
<property name="icon_name">go-next-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="no">GStreamer</property>
@@ -160,6 +178,124 @@
<class name="preferences"/>
</style>
</template>
<object class="AdwNavigationPage" id="enhancers_subpage">
<property name="title" translatable="yes">Clapper Enhancers</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar"/>
</child>
<property name="content">
<object class="GtkScrolledWindow">
<child>
<object class="AdwClamp">
<style>
<class name="subcontent"/>
</style>
<child>
<object class="GtkStack" id="enhancers_stack">
<child>
<object class="GtkBox" id="browse_enhancers_page">
<property name="orientation">vertical</property>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Available enhancers</property>
<property name="description" translatable="yes">Select an enhancer plugin to view its information and properties to configure.</property>
<child>
<object class="AdwComboRow" id="enhancers_combo_row">
<property name="title" translatable="yes">Enhancer</property>
<property name="enable-search">true</property>
<property name="expression">
<lookup type="ClapperEnhancerProxy" name="friendly-name"/>
</property>
<signal name="notify::selected" handler="selected_enhancer_changed_cb"/>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<binding name="visible">
<closure type="gboolean" function="list_has_selection_closure">
<lookup name="selected">enhancers_combo_row</lookup>
</closure>
</binding>
<child>
<object class="AdwPreferencesGroup">
<property name="title" translatable="yes">Information</property>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Module</property>
<binding name="subtitle">
<lookup name="module-name" type="ClapperEnhancerProxy">
<lookup name="selected-item">enhancers_combo_row</lookup>
</lookup>
</binding>
<style>
<class name="property"/>
</style>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Description</property>
<binding name="subtitle">
<lookup name="description" type="ClapperEnhancerProxy">
<lookup name="selected-item">enhancers_combo_row</lookup>
</lookup>
</binding>
<style>
<class name="property"/>
</style>
</object>
</child>
<child>
<object class="AdwActionRow">
<property name="title" translatable="yes">Version</property>
<binding name="subtitle">
<lookup name="version" type="ClapperEnhancerProxy">
<lookup name="selected-item">enhancers_combo_row</lookup>
</lookup>
</binding>
<style>
<class name="property"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="AdwPreferencesGroup" id="enhancer_config_group">
<property name="title" translatable="yes">Properties</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="AdwStatusPage" id="no_enhancers_page">
<property name="vexpand">true</property>
<property name="hexpand">true</property>
<property name="icon-name">edit-find-symbolic</property>
<property name="title" translatable="yes">No Clapper Enhancers Found</property>
<property name="description" translatable="yes">Install some to add more cool functionalities to the player!</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
<signal name="unrealize" handler="plugin_ranking_unrealize_cb"/>
<style>
<class name="configsubpage"/>
</style>
</object>
<object class="AdwNavigationPage" id="plugins_subpage">
<property name="title" translatable="yes">Plugin Ranking</property>
<property name="child">
@@ -240,7 +376,7 @@
</property>
<signal name="unrealize" handler="plugin_ranking_unrealize_cb"/>
<style>
<class name="pluginssubpage"/>
<class name="configsubpage"/>
</style>
</object>
</interface>

View File

@@ -1,21 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface domain="clapper-app">
<object class="AdwMessageDialog" id="dialog">
<property name="modal">true</property>
<property name="width-request">420</property>
<object class="AdwAlertDialog" id="dialog">
<property name="heading" translatable="yes">Add URI</property>
<property name="body" translatable="yes">Insert an URI to be added to playback queue</property>
<property name="close-response">cancel</property>
<property name="default-response">add</property>
<property name="follows-content-size">false</property>
<property name="content-width">420</property>
<property name="extra-child">
<object class="GtkEntry">
<property name="halign">fill</property>
<property name="valign">center</property>
<property name="hexpand">true</property>
<property name="activates-default">true</property>
<property name="truncate-multiline">true</property>
<property name="input-purpose">url</property>
<property name="placeholder-text" translatable="yes">Enter or drop URI here</property>
<object class="GtkListBox">
<property name="selection-mode">none</property>
<style>
<class name="boxed-list"/>
</style>
<child>
<object class="AdwEntryRow" id="entry_row">
<property name="halign">fill</property>
<property name="valign">center</property>
<property name="hexpand">true</property>
<property name="activates-default">true</property>
<property name="input-purpose">url</property>
<property name="title" translatable="yes">Enter or drop URI here</property>
</object>
</child>
</object>
</property>
<responses>

View File

@@ -176,7 +176,6 @@ if build_gir
clappergtk_enums,
],
extra_args: [
gir_init_section,
'--quiet',
'--warn-all',
'-DCLAPPER_GTK_COMPILATION',

View File

@@ -3,7 +3,7 @@ msgstr ""
"Project-Id-Version: clapper\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-07 19:03+0100\n"
"PO-Revision-Date: 2025-01-07 18:32\n"
"PO-Revision-Date: 2025-02-25 20:33\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Language: ru_RU\n"
@@ -50,11 +50,11 @@ msgstr "Сброс"
#: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25
msgid "External video output"
msgstr ""
msgstr "Внешний источник видео"
#: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37
msgid "Used video sink cannot be embedded within application window"
msgstr ""
msgstr "Используемый видеосигнал не может быть встроен в окно приложения"
#: src/lib/clapper-gtk/clapper-gtk-status.c:79
msgid "Unplayable Content"

View File

@@ -1,5 +1,5 @@
/* Clapper Playback Library
* Copyright (C) 2024 Rafał Dzięgiel <rafostar.github@gmail.com>
* Copyright (C) 2025 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 Lesser General Public
@@ -22,17 +22,22 @@
#include <gst/gst.h>
#include <gst/pbutils/pbutils.h>
#include "clapper.h"
#include "clapper-basic-functions.h"
#include "clapper-cache-private.h"
#include "clapper-utils-private.h"
#include "clapper-playbin-bus-private.h"
#include "clapper-app-bus-private.h"
#include "clapper-features-bus-private.h"
#include "clapper-enhancer-proxy-list-private.h"
#include "gst/clapper-plugin-private.h"
#include "clapper-functionalities-availability.h"
#if CLAPPER_WITH_ENHANCERS_LOADER
#include "clapper-enhancers-loader-private.h"
#endif
static ClapperEnhancerProxyList *_proxies = NULL;
static gboolean is_initialized = FALSE;
static GMutex init_lock;
@@ -46,13 +51,16 @@ clapper_init_check_internal (int *argc, char **argv[])
gst_pb_utils_init ();
clapper_cache_initialize ();
clapper_utils_initialize ();
clapper_playbin_bus_initialize ();
clapper_app_bus_initialize ();
clapper_features_bus_initialize ();
_proxies = clapper_enhancer_proxy_list_new_named ("global-proxy-list");
#if CLAPPER_WITH_ENHANCERS_LOADER
clapper_enhancers_loader_initialize ();
clapper_enhancers_loader_initialize (_proxies);
#endif
gst_plugin_register_static (
@@ -149,18 +157,75 @@ clapper_init_check (int *argc, char **argv[])
* Returns: whether a plausible enhancer was found.
*
* Since: 0.8
*
* Deprecated: 0.10: Use list of enhancer proxies from [func@Clapper.get_global_enhancer_proxies] or
* [property@Clapper.Player:enhancer-proxies] and check if any proxy matches your search criteria.
*/
gboolean
clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name)
{
gboolean success = FALSE;
gboolean is_https;
guint i, n_proxies;
g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface_type), FALSE);
g_return_val_if_fail (scheme != NULL, FALSE);
#if CLAPPER_WITH_ENHANCERS_LOADER
success = clapper_enhancers_loader_check (iface_type, scheme, host, name);
#endif
if (host) {
/* Strip common subdomains, so plugins do not
* have to list all combinations */
if (g_str_has_prefix (host, "www."))
host += 4;
else if (g_str_has_prefix (host, "m."))
host += 2;
}
return success;
/* Whether "http(s)" scheme is used */
is_https = (g_str_has_prefix (scheme, "http")
&& (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0')));
if (!host && is_https)
return FALSE;
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (_proxies);
for (i = 0; i < n_proxies; ++i) {
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (_proxies, i);
if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type)
&& clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme)
&& (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) {
if (name)
*name = clapper_enhancer_proxy_get_friendly_name (proxy);
return TRUE;
}
}
return FALSE;
}
/**
* clapper_get_global_enhancer_proxies:
*
* Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects.
*
* This returns a global list of enhancer proxy objects. You can use it to inspect
* available enhancers without creating a new player instance.
*
* Remember to initialize Clapper library before using this function.
*
* Only enhancer properties with [flags@Clapper.EnhancerParamFlags.GLOBAL] flag can be
* set on proxies in this list. These are meant to be set ONLY by users, not applications
* as they carry over to all player instances (possibly including other apps). Applications
* should instead be changing properties with [flags@Clapper.EnhancerParamFlags.LOCAL] flag
* set from individual proxy lists from [property@Clapper.Player:enhancer-proxies] which
* will affect only that single player instance given list belongs to.
*
* Returns: (transfer none): a global #ClapperEnhancerProxyList of enhancer proxies.
*
* Since: 0.10
*/
ClapperEnhancerProxyList *
clapper_get_global_enhancer_proxies (void)
{
return _proxies;
}

View File

@@ -0,0 +1,46 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <clapper/clapper-visibility.h>
#include <clapper/clapper-enhancer-proxy-list.h>
G_BEGIN_DECLS
CLAPPER_API
void clapper_init (int *argc, char **argv[]);
CLAPPER_API
gboolean clapper_init_check (int *argc, char **argv[]);
CLAPPER_DEPRECATED
gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name);
CLAPPER_API
ClapperEnhancerProxyList * clapper_get_global_enhancer_proxies (void);
G_END_DECLS

View File

@@ -0,0 +1,108 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 <glib-object.h>
G_BEGIN_DECLS
G_GNUC_INTERNAL
void clapper_cache_initialize (void);
G_GNUC_INTERNAL
gboolean clapper_cache_is_disabled (void);
G_GNUC_INTERNAL
GMappedFile * clapper_cache_open (const gchar *filename, const gchar **data, GError **error);
G_GNUC_INTERNAL
gboolean clapper_cache_read_boolean (const gchar **data);
G_GNUC_INTERNAL
gint clapper_cache_read_int (const gchar **data);
G_GNUC_INTERNAL
guint clapper_cache_read_uint (const gchar **data);
G_GNUC_INTERNAL
gint64 clapper_cache_read_int64 (const gchar **data);
G_GNUC_INTERNAL
gdouble clapper_cache_read_double (const gchar **data);
G_GNUC_INTERNAL
const gchar * clapper_cache_read_string (const gchar **data);
G_GNUC_INTERNAL
const guint8 * clapper_cache_read_data (const gchar **data, gsize *size);
G_GNUC_INTERNAL
GType clapper_cache_read_enum (const gchar **data);
G_GNUC_INTERNAL
GType clapper_cache_read_flags (const gchar **data);
G_GNUC_INTERNAL
GType clapper_cache_read_iface (const gchar **data);
G_GNUC_INTERNAL
GParamSpec * clapper_cache_read_pspec (const gchar **data);
G_GNUC_INTERNAL
GByteArray * clapper_cache_create (void);
G_GNUC_INTERNAL
void clapper_cache_store_boolean (GByteArray *bytes, gboolean val);
G_GNUC_INTERNAL
void clapper_cache_store_int (GByteArray *bytes, gint val);
G_GNUC_INTERNAL
void clapper_cache_store_uint (GByteArray *bytes, guint val);
G_GNUC_INTERNAL
void clapper_cache_store_int64 (GByteArray *bytes, gint64 val);
G_GNUC_INTERNAL
void clapper_cache_store_double (GByteArray *bytes, gdouble val);
G_GNUC_INTERNAL
void clapper_cache_store_string (GByteArray *bytes, const gchar *val);
G_GNUC_INTERNAL
void clapper_cache_store_data (GByteArray *bytes, const guint8 *val, gsize val_size);
G_GNUC_INTERNAL
void clapper_cache_store_enum (GByteArray *bytes, GType enum_type);
G_GNUC_INTERNAL
void clapper_cache_store_flags (GByteArray *bytes, GType flags_type);
G_GNUC_INTERNAL
gboolean clapper_cache_store_iface (GByteArray *bytes, GType iface);
G_GNUC_INTERNAL
gboolean clapper_cache_store_pspec (GByteArray *bytes, GParamSpec *pspec);
G_GNUC_INTERNAL
gboolean clapper_cache_write (const gchar *filename, GByteArray *bytes, GError **error);
G_END_DECLS

View File

@@ -0,0 +1,536 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "clapper-cache-private.h"
#include "clapper-version.h"
#include "clapper-extractable.h"
#define CLAPPER_CACHE_HEADER "CLAPPER"
typedef enum
{
CLAPPER_CACHE_IFACE_EXTRACTABLE = 1,
} ClapperCacheIfaces;
static GArray *enum_registry = NULL;
static GArray *flags_registry = NULL;
static gboolean cache_disabled = FALSE;
void
clapper_cache_initialize (void)
{
const gchar *env = g_getenv ("CLAPPER_DISABLE_CACHE");
if (G_LIKELY (!env || !g_str_has_prefix (env, "1"))) {
enum_registry = g_array_new (FALSE, TRUE, sizeof (GEnumValue *));
flags_registry = g_array_new (FALSE, TRUE, sizeof (GFlagsValue *));
} else {
cache_disabled = TRUE;
}
}
gboolean
clapper_cache_is_disabled (void)
{
return cache_disabled;
}
GMappedFile *
clapper_cache_open (const gchar *filename, const gchar **data, GError **error)
{
GMappedFile *file;
if (G_UNLIKELY (cache_disabled))
return NULL;
if (!(file = g_mapped_file_new (filename, FALSE, error)))
return NULL;
if (G_UNLIKELY (g_mapped_file_get_length (file) == 0)) {
g_mapped_file_unref (file);
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"File is empty");
return NULL;
}
*data = g_mapped_file_get_contents (file);
/* Header name check */
if (G_UNLIKELY (g_strcmp0 (*data, CLAPPER_CACHE_HEADER) != 0)) {
g_mapped_file_unref (file);
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"Invalid file header");
return NULL;
}
*data += strlen (*data) + 1;
/* Header version check */
if (clapper_cache_read_uint (data) != CLAPPER_VERSION_HEX) {
g_mapped_file_unref (file);
/* Just different version, so no error set */
return NULL;
}
return file;
}
inline gboolean
clapper_cache_read_boolean (const gchar **data)
{
gboolean val = *(const gboolean *) *data;
*data += sizeof (gboolean);
return val;
}
inline gint
clapper_cache_read_int (const gchar **data)
{
gint val = *(const gint *) *data;
*data += sizeof (gint);
return val;
}
inline guint
clapper_cache_read_uint (const gchar **data)
{
guint val = *(const guint *) *data;
*data += sizeof (guint);
return val;
}
inline gint64
clapper_cache_read_int64 (const gchar **data)
{
gint64 val = *(const gint64 *) *data;
*data += sizeof (gint64);
return val;
}
inline gdouble
clapper_cache_read_double (const gchar **data)
{
gdouble val = *(const gdouble *) *data;
*data += sizeof (gdouble);
return val;
}
inline const gchar *
clapper_cache_read_string (const gchar **data)
{
const gboolean is_null = clapper_cache_read_boolean (data);
const gchar *str = NULL;
if (!is_null) {
str = *data;
*data += strlen (str) + 1;
}
return str;
}
inline const guint8 *
clapper_cache_read_data (const gchar **data, gsize *size)
{
const guint8 *val = NULL;
*size = *(const gsize *) *data;
*data += sizeof (gsize);
if (G_LIKELY (*size > 0)) {
val = (const guint8 *) *data;
*data += *size;
}
return val;
}
inline GType
clapper_cache_read_enum (const gchar **data)
{
GType type;
const gchar *enum_name;
guint i, n_values;
enum_name = clapper_cache_read_string (data);
n_values = clapper_cache_read_uint (data);
/* If not registered yet */
if ((type = g_type_from_name (enum_name)) == 0) {
GEnumValue *values = g_new0 (GEnumValue, n_values + 1);
for (i = 0; i < n_values; ++i) {
values[i].value = clapper_cache_read_int (data);
values[i].value_name = g_intern_string (clapper_cache_read_string (data));
values[i].value_nick = g_intern_string (clapper_cache_read_string (data));
}
g_array_append_val (enum_registry, values); // store statically
type = g_enum_register_static (g_intern_string (enum_name),
g_array_index (enum_registry, GEnumValue *, enum_registry->len - 1));
} else {
/* Skip over data */
for (i = 0; i < n_values; ++i) {
clapper_cache_read_int (data); // value
clapper_cache_read_string (data); // value_name
clapper_cache_read_string (data); // value_nick
}
}
return type;
}
inline GType
clapper_cache_read_flags (const gchar **data)
{
GType type;
const gchar *flags_name;
guint i, n_values;
flags_name = clapper_cache_read_string (data);
n_values = clapper_cache_read_uint (data);
/* If not registered yet */
if ((type = g_type_from_name (flags_name)) == 0) {
GFlagsValue *values = g_new0 (GFlagsValue, n_values + 1);
for (i = 0; i < n_values; ++i) {
values[i].value = clapper_cache_read_int (data);
values[i].value_name = g_intern_string (clapper_cache_read_string (data));
values[i].value_nick = g_intern_string (clapper_cache_read_string (data));
}
g_array_append_val (flags_registry, values); // store statically
type = g_flags_register_static (g_intern_string (flags_name),
g_array_index (flags_registry, GFlagsValue *, flags_registry->len - 1));
} else {
/* Skip over data */
for (i = 0; i < n_values; ++i) {
clapper_cache_read_int (data); // value
clapper_cache_read_string (data); // value_name
clapper_cache_read_string (data); // value_nick
}
}
return type;
}
GType
clapper_cache_read_iface (const gchar **data)
{
gint iface_id = clapper_cache_read_int (data);
switch (iface_id) {
case CLAPPER_CACHE_IFACE_EXTRACTABLE:
return CLAPPER_TYPE_EXTRACTABLE;
default:
return 0;
}
}
GParamSpec *
clapper_cache_read_pspec (const gchar **data)
{
GParamSpec *pspec;
GType value_type;
const gchar *name, *nick, *blurb;
GParamFlags flags;
value_type = *(const GType *) *data;
*data += sizeof (GType);
name = clapper_cache_read_string (data);
nick = clapper_cache_read_string (data);
blurb = clapper_cache_read_string (data);
flags = *(const GParamFlags *) *data;
*data += sizeof (GParamFlags);
/* NOTE: C does not guarantee order in which function arguments
* are evaluated, so read into variables and then create pspec */
switch (value_type) {
case G_TYPE_BOOLEAN:
pspec = g_param_spec_boolean (name, nick, blurb,
clapper_cache_read_boolean (data), flags);
break;
case G_TYPE_INT:{
gint minimum = clapper_cache_read_int (data);
gint maximum = clapper_cache_read_int (data);
gint default_value = clapper_cache_read_int (data);
pspec = g_param_spec_int (name, nick, blurb,
minimum, maximum, default_value, flags);
break;
}
case G_TYPE_UINT:{
guint minimum = clapper_cache_read_uint (data);
guint maximum = clapper_cache_read_uint (data);
guint default_value = clapper_cache_read_uint (data);
pspec = g_param_spec_uint (name, nick, blurb,
minimum, maximum, default_value, flags);
break;
}
case G_TYPE_DOUBLE:{
gdouble minimum = clapper_cache_read_double (data);
gdouble maximum = clapper_cache_read_double (data);
gdouble default_value = clapper_cache_read_double (data);
pspec = g_param_spec_double (name, nick, blurb,
minimum, maximum, default_value, flags);
break;
}
case G_TYPE_STRING:
pspec = g_param_spec_string (name, nick, blurb,
clapper_cache_read_string (data), flags);
break;
case G_TYPE_ENUM:{
GType enum_type = clapper_cache_read_enum (data);
gint default_value = clapper_cache_read_int (data);
pspec = g_param_spec_enum (name, nick, blurb,
enum_type, default_value, flags);
break;
}
case G_TYPE_FLAGS:{
GType flags_type = clapper_cache_read_flags (data);
guint default_value = clapper_cache_read_uint (data);
pspec = g_param_spec_flags (name, nick, blurb,
flags_type, default_value, flags);
break;
}
default:
return NULL;
}
return g_param_spec_ref_sink (pspec);
}
GByteArray *
clapper_cache_create (void)
{
GByteArray *bytes;
if (G_UNLIKELY (cache_disabled))
return NULL;
bytes = g_byte_array_new ();
/* NOTE: We do not store whether string is NULL here, since it never is */
g_byte_array_append (bytes, (const guint8 *) CLAPPER_CACHE_HEADER, 8); // 7 + 1
clapper_cache_store_uint (bytes, CLAPPER_VERSION_HEX);
return bytes;
}
inline void
clapper_cache_store_boolean (GByteArray *bytes, gboolean val)
{
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gboolean));
}
inline void
clapper_cache_store_int (GByteArray *bytes, gint val)
{
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gint));
}
inline void
clapper_cache_store_uint (GByteArray *bytes, guint val)
{
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (guint));
}
inline void
clapper_cache_store_int64 (GByteArray *bytes, gint64 val)
{
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gint64));
}
inline void
clapper_cache_store_double (GByteArray *bytes, gdouble val)
{
g_byte_array_append (bytes, (const guint8 *) &val, sizeof (gdouble));
}
inline void
clapper_cache_store_string (GByteArray *bytes, const gchar *val)
{
/* Distinguish empty string from NULL */
const gboolean is_null = (val == NULL);
clapper_cache_store_boolean (bytes, is_null);
if (!is_null)
g_byte_array_append (bytes, (const guint8 *) val, strlen (val) + 1);
}
inline void
clapper_cache_store_data (GByteArray *bytes, const guint8 *val, gsize val_size)
{
g_byte_array_append (bytes, (const guint8 *) &val_size, sizeof (gsize));
if (G_LIKELY (val_size > 0))
g_byte_array_append (bytes, val, val_size);
}
inline void
clapper_cache_store_enum (GByteArray *bytes, GType enum_type)
{
GEnumClass *enum_class = G_ENUM_CLASS (g_type_class_peek (enum_type));
guint i;
clapper_cache_store_string (bytes, g_type_name (enum_type));
clapper_cache_store_uint (bytes, enum_class->n_values);
for (i = 0; i < enum_class->n_values; ++i) {
clapper_cache_store_int (bytes, enum_class->values[i].value);
clapper_cache_store_string (bytes, enum_class->values[i].value_name);
clapper_cache_store_string (bytes, enum_class->values[i].value_nick);
}
}
inline void
clapper_cache_store_flags (GByteArray *bytes, GType flags_type)
{
GFlagsClass *flags_class = G_FLAGS_CLASS (g_type_class_peek (flags_type));
guint i;
clapper_cache_store_string (bytes, g_type_name (flags_type));
clapper_cache_store_uint (bytes, flags_class->n_values);
for (i = 0; i < flags_class->n_values; ++i) {
clapper_cache_store_int (bytes, flags_class->values[i].value);
clapper_cache_store_string (bytes, flags_class->values[i].value_name);
clapper_cache_store_string (bytes, flags_class->values[i].value_nick);
}
}
gboolean
clapper_cache_store_iface (GByteArray *bytes, GType iface)
{
gint iface_id = 0;
if (iface == CLAPPER_TYPE_EXTRACTABLE)
iface_id = CLAPPER_CACHE_IFACE_EXTRACTABLE;
else
return FALSE;
clapper_cache_store_int (bytes, iface_id);
return TRUE;
}
gboolean
clapper_cache_store_pspec (GByteArray *bytes, GParamSpec *pspec)
{
GParamFlags flags;
const gboolean is_enum = G_IS_PARAM_SPEC_ENUM (pspec);
const gboolean is_flags = (!is_enum && G_IS_PARAM_SPEC_FLAGS (pspec));
if (is_enum) {
GType enum_type = G_TYPE_ENUM;
g_byte_array_append (bytes, (const guint8 *) &enum_type, sizeof (GType));
} else if (is_flags) {
GType flags_type = G_TYPE_FLAGS;
g_byte_array_append (bytes, (const guint8 *) &flags_type, sizeof (GType));
} else {
g_byte_array_append (bytes, (const guint8 *) &pspec->value_type, sizeof (GType));
}
clapper_cache_store_string (bytes, g_param_spec_get_name (pspec));
clapper_cache_store_string (bytes, g_param_spec_get_nick (pspec));
clapper_cache_store_string (bytes, g_param_spec_get_blurb (pspec));
flags = pspec->flags;
flags &= ~G_PARAM_STATIC_STRINGS; // Data read from cache is never static
g_byte_array_append (bytes, (const guint8 *) &flags, sizeof (GParamFlags));
switch (pspec->value_type) {
case G_TYPE_BOOLEAN:{
GParamSpecBoolean *p = (GParamSpecBoolean *) pspec;
clapper_cache_store_boolean (bytes, p->default_value);
break;
}
case G_TYPE_INT:{
GParamSpecInt *p = (GParamSpecInt *) pspec;
clapper_cache_store_int (bytes, p->minimum);
clapper_cache_store_int (bytes, p->maximum);
clapper_cache_store_int (bytes, p->default_value);
break;
}
case G_TYPE_UINT:{
GParamSpecUInt *p = (GParamSpecUInt *) pspec;
clapper_cache_store_uint (bytes, p->minimum);
clapper_cache_store_uint (bytes, p->maximum);
clapper_cache_store_uint (bytes, p->default_value);
break;
}
case G_TYPE_DOUBLE:{
GParamSpecDouble *p = (GParamSpecDouble *) pspec;
clapper_cache_store_double (bytes, p->minimum);
clapper_cache_store_double (bytes, p->maximum);
clapper_cache_store_double (bytes, p->default_value);
break;
}
case G_TYPE_STRING:{
GParamSpecString *p = (GParamSpecString *) pspec;
clapper_cache_store_string (bytes, p->default_value);
break;
}
default:{
if (is_enum) {
GParamSpecEnum *p = (GParamSpecEnum *) pspec;
clapper_cache_store_enum (bytes, pspec->value_type);
clapper_cache_store_int (bytes, p->default_value);
break;
} else if (is_flags) {
GParamSpecFlags *p = (GParamSpecFlags *) pspec;
clapper_cache_store_flags (bytes, pspec->value_type);
clapper_cache_store_uint (bytes, p->default_value);
break;
}
return FALSE;
}
}
return TRUE;
}
gboolean
clapper_cache_write (const gchar *filename, GByteArray *bytes, GError **error)
{
gchar *dirname = g_path_get_dirname (filename);
gboolean has_dir;
has_dir = (g_mkdir_with_parents (dirname, 0755) == 0);
g_free (dirname);
if (!has_dir) {
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"Could not create directory to store cache content");
return FALSE;
}
/* Using "g_file_set_contents" to replace file atomically */
return g_file_set_contents (filename, (const gchar *) bytes->data, bytes->len, error);
}

View File

@@ -0,0 +1,41 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "clapper-enhancer-proxy-list.h"
#include "clapper-enhancer-proxy.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperEnhancerProxyList * clapper_enhancer_proxy_list_new_named (const gchar *name);
G_GNUC_INTERNAL
void clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *list, ClapperEnhancerProxy *proxy);
G_GNUC_INTERNAL
void clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *list);
G_GNUC_INTERNAL
void clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *list);
G_END_DECLS

View File

@@ -0,0 +1,329 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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.
*/
/**
* ClapperEnhancerProxyList:
*
* A list of enhancer proxies.
*
* Since: 0.10
*/
#include <gio/gio.h>
#include "clapper-basic-functions.h"
#include "clapper-enhancer-proxy-list-private.h"
#include "clapper-enhancer-proxy-private.h"
#define GST_CAT_DEFAULT clapper_enhancer_proxy_list_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperEnhancerProxyList
{
GstObject parent;
GPtrArray *proxies;
};
enum
{
PROP_0,
PROP_N_PROXIES,
PROP_LAST
};
static void clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface);
#define parent_class clapper_enhancer_proxy_list_parent_class
G_DEFINE_TYPE_WITH_CODE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, GST_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_enhancer_proxy_list_model_iface_init));
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static GType
clapper_enhancer_proxy_list_model_get_item_type (GListModel *model)
{
return CLAPPER_TYPE_ENHANCER_PROXY;
}
static guint
clapper_enhancer_proxy_list_model_get_n_items (GListModel *model)
{
return CLAPPER_ENHANCER_PROXY_LIST_CAST (model)->proxies->len;
}
static gpointer
clapper_enhancer_proxy_list_model_get_item (GListModel *model, guint index)
{
ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (model);
ClapperEnhancerProxy *proxy = NULL;
if (G_LIKELY (index < self->proxies->len))
proxy = gst_object_ref (g_ptr_array_index (self->proxies, index));
return proxy;
}
static void
clapper_enhancer_proxy_list_model_iface_init (GListModelInterface *iface)
{
iface->get_item_type = clapper_enhancer_proxy_list_model_get_item_type;
iface->get_n_items = clapper_enhancer_proxy_list_model_get_n_items;
iface->get_item = clapper_enhancer_proxy_list_model_get_item;
}
/*
* clapper_enhancer_proxy_list_new_named:
* @name: (nullable): name of the #GstObject
*
* Returns: (transfer full): a new #ClapperEnhancerProxyList instance
*/
ClapperEnhancerProxyList *
clapper_enhancer_proxy_list_new_named (const gchar *name)
{
ClapperEnhancerProxyList *list;
list = g_object_new (CLAPPER_TYPE_ENHANCER_PROXY_LIST,
"name", name, NULL);
gst_object_ref_sink (list);
return list;
}
void
clapper_enhancer_proxy_list_take_proxy (ClapperEnhancerProxyList *self, ClapperEnhancerProxy *proxy)
{
g_ptr_array_add (self->proxies, proxy);
gst_object_set_parent (GST_OBJECT_CAST (proxy), GST_OBJECT_CAST (self));
}
/*
* clapper_enhancer_proxy_list_fill_from_global_proxies:
*
* Fill list with unconfigured proxies from global proxies list.
*/
void
clapper_enhancer_proxy_list_fill_from_global_proxies (ClapperEnhancerProxyList *self)
{
ClapperEnhancerProxyList *global_list = clapper_get_global_enhancer_proxies ();
static guint _list_id = 0;
guint i;
for (i = 0; i < global_list->proxies->len; ++i) {
ClapperEnhancerProxy *proxy, *proxy_copy;
gchar obj_name[64];
proxy = clapper_enhancer_proxy_list_peek_proxy (global_list, i);
/* Name newly created proxy, very useful for debugging. Keep index per
* list, so it will be the same as the player that proxy belongs to. */
g_snprintf (obj_name, sizeof (obj_name), "%s-proxy%u",
clapper_enhancer_proxy_get_friendly_name (proxy), _list_id);
proxy_copy = clapper_enhancer_proxy_copy (proxy, obj_name);
clapper_enhancer_proxy_list_take_proxy (self, proxy_copy);
}
_list_id++;
}
static gint
_sort_values_by_name (ClapperEnhancerProxy *proxy_a, ClapperEnhancerProxy *proxy_b)
{
return g_ascii_strcasecmp (
clapper_enhancer_proxy_get_friendly_name (proxy_a),
clapper_enhancer_proxy_get_friendly_name (proxy_b));
}
/*
* clapper_enhancer_proxy_list_sort:
*
* Sort all list elements by enhancer friendly name.
*/
void
clapper_enhancer_proxy_list_sort (ClapperEnhancerProxyList *self)
{
g_ptr_array_sort_values (self->proxies, (GCompareFunc) _sort_values_by_name);
}
/**
* clapper_enhancer_proxy_list_get_proxy:
* @list: a #ClapperEnhancerProxyList
* @index: an enhancer proxy index
*
* Get the #ClapperEnhancerProxy at index.
*
* This behaves the same as [method@Gio.ListModel.get_item], and is here
* for code uniformity and convenience to avoid type casting by user.
*
* Returns: (transfer full) (nullable): The #ClapperEnhancerProxy at @index.
*
* Since: 0.10
*/
ClapperEnhancerProxy *
clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *self, guint index)
{
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL);
return g_list_model_get_item (G_LIST_MODEL (self), index);
}
/**
* clapper_enhancer_proxy_list_peek_proxy: (skip)
* @list: a #ClapperEnhancerProxyList
* @index: an enhancer proxy index
*
* Get the #ClapperEnhancerProxy at index.
*
* Similar to [method@Clapper.EnhancerProxyList.get_proxy], but does not take
* a new reference on proxy.
*
* Proxies in a list are only removed when a [class@Clapper.Player] instance
* they originate from is destroyed, so do not use returned object afterwards
* unless you take an additional reference on it.
*
* Returns: (transfer none) (nullable): The #ClapperEnhancerProxy at @index.
*
* Since: 0.10
*/
ClapperEnhancerProxy *
clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *self, guint index)
{
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL);
return g_ptr_array_index (self->proxies, index);
}
/**
* clapper_enhancer_proxy_list_get_proxy_by_module:
* @list: a #ClapperEnhancerProxyList
* @module_name: an enhancer module name
*
* Get the #ClapperEnhancerProxy by module name as defined in its plugin file.
*
* A convenience function to find a #ClapperEnhancerProxy by its unique
* module name in the list.
*
* Returns: (transfer full) (nullable): The #ClapperEnhancerProxy with requested module name.
*
* Since: 0.10
*/
ClapperEnhancerProxy *
clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *self, const gchar *module_name)
{
guint i;
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), NULL);
g_return_val_if_fail (module_name != NULL, NULL);
for (i = 0; i < self->proxies->len; ++i) {
ClapperEnhancerProxy *proxy = g_ptr_array_index (self->proxies, i);
if (strcmp (clapper_enhancer_proxy_get_module_name (proxy), module_name) == 0)
return gst_object_ref (proxy);
}
return NULL;
}
/**
* clapper_enhancer_proxy_list_get_n_proxies:
* @list: a #ClapperEnhancerProxyList
*
* Get the number of proxies in #ClapperEnhancerProxyList.
*
* This behaves the same as [method@Gio.ListModel.get_n_items], and is here
* for code uniformity and convenience to avoid type casting by user.
*
* Returns: The number of proxies in #ClapperEnhancerProxyList.
*
* Since: 0.10
*/
guint
clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *self)
{
g_return_val_if_fail (CLAPPER_IS_ENHANCER_PROXY_LIST (self), 0);
return g_list_model_get_n_items (G_LIST_MODEL (self));
}
static void
_proxy_remove_func (ClapperEnhancerProxy *proxy)
{
gst_object_unparent (GST_OBJECT_CAST (proxy));
gst_object_unref (proxy);
}
static void
clapper_enhancer_proxy_list_init (ClapperEnhancerProxyList *self)
{
self->proxies = g_ptr_array_new_with_free_func ((GDestroyNotify) _proxy_remove_func);
}
static void
clapper_enhancer_proxy_list_finalize (GObject *object)
{
ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
g_ptr_array_unref (self->proxies);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_enhancer_proxy_list_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperEnhancerProxyList *self = CLAPPER_ENHANCER_PROXY_LIST_CAST (object);
switch (prop_id) {
case PROP_N_PROXIES:
g_value_set_uint (value, clapper_enhancer_proxy_list_get_n_proxies (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_enhancer_proxy_list_class_init (ClapperEnhancerProxyListClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerproxylist", 0,
"Clapper Enhancer Proxy List");
gobject_class->get_property = clapper_enhancer_proxy_list_get_property;
gobject_class->finalize = clapper_enhancer_proxy_list_finalize;
/**
* ClapperEnhancerProxyList:n-proxies:
*
* Number of proxies in the list.
*
* Since: 0.10
*/
param_specs[PROP_N_PROXIES] = g_param_spec_uint ("n-proxies",
NULL, NULL, 0, G_MAXUINT, 0,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
}

View File

@@ -0,0 +1,53 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <clapper/clapper-visibility.h>
#include <clapper/clapper-enhancer-proxy.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_ENHANCER_PROXY_LIST (clapper_enhancer_proxy_list_get_type())
#define CLAPPER_ENHANCER_PROXY_LIST_CAST(obj) ((ClapperEnhancerProxyList *)(obj))
CLAPPER_API
G_DECLARE_FINAL_TYPE (ClapperEnhancerProxyList, clapper_enhancer_proxy_list, CLAPPER, ENHANCER_PROXY_LIST, GstObject)
CLAPPER_API
ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy (ClapperEnhancerProxyList *list, guint index);
CLAPPER_API
ClapperEnhancerProxy * clapper_enhancer_proxy_list_peek_proxy (ClapperEnhancerProxyList *list, guint index);
CLAPPER_API
ClapperEnhancerProxy * clapper_enhancer_proxy_list_get_proxy_by_module (ClapperEnhancerProxyList *list, const gchar *module_name);
CLAPPER_API
guint clapper_enhancer_proxy_list_get_n_proxies (ClapperEnhancerProxyList *list);
G_END_DECLS

View File

@@ -0,0 +1,54 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 <glib-object.h>
#include <gst/gst.h>
#include "clapper-enhancer-proxy.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
ClapperEnhancerProxy * clapper_enhancer_proxy_new_global_take (GObject *peas_info); // Using parent type for building without libpeas
G_GNUC_INTERNAL
ClapperEnhancerProxy * clapper_enhancer_proxy_copy (ClapperEnhancerProxy *src_proxy, const gchar *copy_name);
G_GNUC_INTERNAL
gboolean clapper_enhancer_proxy_fill_from_cache (ClapperEnhancerProxy *proxy);
G_GNUC_INTERNAL
gboolean clapper_enhancer_proxy_fill_from_instance (ClapperEnhancerProxy *proxy, GObject *enhancer);
G_GNUC_INTERNAL
void clapper_enhancer_proxy_export_to_cache (ClapperEnhancerProxy *proxy);
G_GNUC_INTERNAL
GObject * clapper_enhancer_proxy_get_peas_info (ClapperEnhancerProxy *proxy);
G_GNUC_INTERNAL
GstStructure * clapper_enhancer_proxy_make_current_config (ClapperEnhancerProxy *proxy);
G_GNUC_INTERNAL
void clapper_enhancer_proxy_apply_config_to_enhancer (ClapperEnhancerProxy *proxy, const GstStructure *config, GObject *enhancer);
G_END_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
/* Clapper Playback Library
* Copyright (C) 2025 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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
#if !defined(__CLAPPER_INSIDE__) && !defined(CLAPPER_COMPILATION)
#error "Only <clapper/clapper.h> can be included directly."
#endif
#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <gst/gst.h>
#include <clapper/clapper-visibility.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_ENHANCER_PROXY (clapper_enhancer_proxy_get_type())
#define CLAPPER_ENHANCER_PROXY_CAST(obj) ((ClapperEnhancerProxy *)(obj))
CLAPPER_API
G_DECLARE_FINAL_TYPE (ClapperEnhancerProxy, clapper_enhancer_proxy, CLAPPER, ENHANCER_PROXY, GstObject)
CLAPPER_API
const gchar * clapper_enhancer_proxy_get_friendly_name (ClapperEnhancerProxy *proxy);
CLAPPER_API
const gchar * clapper_enhancer_proxy_get_module_name (ClapperEnhancerProxy *proxy);
CLAPPER_API
const gchar * clapper_enhancer_proxy_get_module_dir (ClapperEnhancerProxy *proxy);
CLAPPER_API
const gchar * clapper_enhancer_proxy_get_description (ClapperEnhancerProxy *proxy);
CLAPPER_API
const gchar * clapper_enhancer_proxy_get_version (ClapperEnhancerProxy *proxy);
CLAPPER_API
const gchar * clapper_enhancer_proxy_get_extra_data (ClapperEnhancerProxy *proxy, const gchar *key);
CLAPPER_API
gboolean clapper_enhancer_proxy_extra_data_lists_value (ClapperEnhancerProxy *proxy, const gchar *key, const gchar *value);
CLAPPER_API
GType * clapper_enhancer_proxy_get_target_interfaces (ClapperEnhancerProxy *proxy, guint *n_interfaces);
CLAPPER_API
gboolean clapper_enhancer_proxy_target_has_interface (ClapperEnhancerProxy *proxy, GType iface_type);
CLAPPER_API
GParamSpec ** clapper_enhancer_proxy_get_target_properties (ClapperEnhancerProxy *proxy, guint *n_properties);
CLAPPER_API
GSettings * clapper_enhancer_proxy_get_settings (ClapperEnhancerProxy *proxy);
CLAPPER_API
void clapper_enhancer_proxy_set_locally (ClapperEnhancerProxy *proxy, const gchar *first_property_name, ...) G_GNUC_NULL_TERMINATED;
CLAPPER_API
void clapper_enhancer_proxy_set_locally_with_table (ClapperEnhancerProxy *proxy, GHashTable *table);
G_END_DECLS

View File

@@ -22,21 +22,15 @@
#include <glib.h>
#include <glib-object.h>
#include "clapper-enhancer-proxy-list.h"
#include "clapper-enhancer-proxy.h"
G_BEGIN_DECLS
G_GNUC_INTERNAL
void clapper_enhancers_loader_initialize (void);
void clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies);
G_GNUC_INTERNAL
gboolean clapper_enhancers_loader_has_enhancers (GType iface_type);
G_GNUC_INTERNAL
gchar ** clapper_enhancers_loader_get_schemes (GType iface_type);
G_GNUC_INTERNAL
gboolean clapper_enhancers_loader_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name);
G_GNUC_INTERNAL
GObject * clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri);
GObject * clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type);
G_END_DECLS

View File

@@ -28,11 +28,11 @@ static HMODULE _enhancers_dll_handle = NULL;
#endif
#include "clapper-enhancers-loader-private.h"
#include "clapper-enhancer-proxy-list-private.h"
#include "clapper-enhancer-proxy-private.h"
#define ENHANCER_INTERFACES "X-Interfaces"
#define ENHANCER_SCHEMES "X-Schemes"
#define ENHANCER_HOSTS "X-Hosts"
#define ENHANCER_IFACE_NAME_FROM_TYPE(type) (g_type_name (type) + 7) // strip "Clapper" prefix
// Supported interfaces
#include "clapper-extractable.h"
#define GST_CAT_DEFAULT clapper_enhancers_loader_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
@@ -58,10 +58,11 @@ _import_enhancers (const gchar *enhancers_path)
* Initializes #PeasEngine with directories that store enhancers.
*/
void
clapper_enhancers_loader_initialize (void)
clapper_enhancers_loader_initialize (ClapperEnhancerProxyList *proxies)
{
const gchar *enhancers_path;
gchar *custom_path = NULL;
guint i, n_items;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersloader", 0,
"Clapper Enhancer Loader");
@@ -74,9 +75,8 @@ clapper_enhancers_loader_initialize (void)
win_base_dir = g_win32_get_package_installation_directory_of_module (
_enhancers_dll_handle);
/* FIXME: Avoid hardcoded major version */
custom_path = g_build_filename (win_base_dir,
"lib", "clapper-0.0", "enhancers", NULL);
"lib", CLAPPER_API_NAME, "enhancers", NULL);
enhancers_path = custom_path; // assign temporarily
g_free (win_base_dir);
@@ -104,321 +104,85 @@ clapper_enhancers_loader_initialize (void)
_import_enhancers (enhancers_path);
}
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_INFO) {
GListModel *list = (GListModel *) _engine;
guint i, n_items = g_list_model_get_n_items (list);
n_items = g_list_model_get_n_items ((GListModel *) _engine);
for (i = 0; i < n_items; ++i) {
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item ((GListModel *) _engine, i);
ClapperEnhancerProxy *proxy;
gboolean filled;
for (i = 0; i < n_items; ++i) {
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
GST_INFO ("Found enhancer: %s (%s)", peas_plugin_info_get_name (info),
peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES));
g_object_unref (info);
/* Clapper supports only 1 proxy per plugin. Each plugin can
* ship 1 class, but it can implement more than 1 interface. */
proxy = clapper_enhancer_proxy_new_global_take ((GObject *) info);
/* Try to fill missing data from cache (fast).
* Otherwise make an instance and fill missing data from it (slow). */
if (!(filled = clapper_enhancer_proxy_fill_from_cache (proxy))) {
GObject *enhancer;
GType main_types[1] = { CLAPPER_TYPE_EXTRACTABLE };
guint j;
/* We cannot ask libpeas for "any" of our main interfaces, so try each one until found */
for (j = 0; j < G_N_ELEMENTS (main_types); ++j) {
if ((enhancer = clapper_enhancers_loader_create_enhancer (proxy, main_types[j]))) {
filled = clapper_enhancer_proxy_fill_from_instance (proxy, enhancer);
g_object_unref (enhancer);
clapper_enhancer_proxy_export_to_cache (proxy);
break;
}
}
}
GST_INFO ("Clapper enhancers initialized, found: %u", n_items);
if (G_LIKELY (filled)) {
GST_INFO ("Found enhancer: \"%s\" (%s)",
clapper_enhancer_proxy_get_friendly_name (proxy),
clapper_enhancer_proxy_get_module_name (proxy));
clapper_enhancer_proxy_list_take_proxy (proxies, proxy);
} else {
GST_WARNING ("Enhancer init failed: \"%s\" (%s)",
clapper_enhancer_proxy_get_friendly_name (proxy),
clapper_enhancer_proxy_get_module_name (proxy));
gst_object_unref (proxy);
}
}
clapper_enhancer_proxy_list_sort (proxies);
GST_INFO ("Clapper enhancers initialized, found: %u",
clapper_enhancer_proxy_list_get_n_proxies (proxies));
g_free (custom_path);
}
static inline gboolean
_is_name_listed (const gchar *name, const gchar *list_str)
{
gsize name_len = strlen (name);
guint i = 0;
while (list_str[i] != '\0') {
guint end = i;
while (list_str[end] != ';' && list_str[end] != '\0')
++end;
/* Compare letters count until separator and prefix of whole string */
if (end - i == name_len && g_str_has_prefix (list_str + i, name))
return TRUE;
i = end;
/* Move to the next letter after ';' */
if (list_str[i] != '\0')
++i;
}
return FALSE;
}
/*
* clapper_enhancers_loader_get_info:
* @iface_type: an interface #GType
* @scheme: an URI scheme
* @host: (nullable): an URI host
*
* Returns: (transfer full) (nullable): available #PeasPluginInfo or %NULL.
*/
static PeasPluginInfo *
clapper_enhancers_loader_get_info (GType iface_type, const gchar *scheme, const gchar *host)
{
GListModel *list = (GListModel *) _engine;
PeasPluginInfo *found_info = NULL;
guint i, n_plugins = g_list_model_get_n_items (list);
const gchar *iface_name;
gboolean is_https;
if (n_plugins == 0) {
GST_INFO ("No Clapper enhancers found");
return NULL;
}
iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type);
/* Strip common subdomains, so plugins do not
* have to list all combinations */
if (host) {
if (g_str_has_prefix (host, "www."))
host += 4;
else if (g_str_has_prefix (host, "m."))
host += 2;
}
GST_INFO ("Enhancer check, iface: %s, scheme: %s, host: %s",
iface_name, scheme, GST_STR_NULL (host));
is_https = (g_str_has_prefix (scheme, "http")
&& (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0')));
if (!host && is_https)
return NULL;
for (i = 0; i < n_plugins; ++i) {
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
const gchar *iface_names, *schemes, *hosts;
if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) {
GST_DEBUG ("Skipping enhancer without interfaces: %s", peas_plugin_info_get_name (info));
g_object_unref (info);
continue;
}
if (!_is_name_listed (iface_name, iface_names)) {
g_object_unref (info);
continue;
}
if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) {
GST_DEBUG ("Skipping enhancer without schemes: %s", peas_plugin_info_get_name (info));
g_object_unref (info);
continue;
}
if (!_is_name_listed (scheme, schemes)) {
g_object_unref (info);
continue;
}
if (is_https) {
if (!(hosts = peas_plugin_info_get_external_data (info, ENHANCER_HOSTS))) {
GST_DEBUG ("Skipping enhancer without hosts: %s", peas_plugin_info_get_name (info));
g_object_unref (info);
continue;
}
if (!_is_name_listed (host, hosts)) {
g_object_unref (info);
continue;
}
}
found_info = info;
break;
}
return found_info;
}
/*
* clapper_enhancers_loader_has_enhancers:
* @iface_type: an interface #GType
*
* Check if any enhancer implementing given interface type is available.
*
* Returns: whether any valid enhancer was found.
*/
gboolean
clapper_enhancers_loader_has_enhancers (GType iface_type)
{
GListModel *list = (GListModel *) _engine;
const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type);
guint i, n_plugins;
GST_DEBUG ("Checking for any enhancers of type: \"%s\"", iface_name);
n_plugins = g_list_model_get_n_items (list);
for (i = 0; i < n_plugins; ++i) {
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
const gchar *iface_names;
if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) {
g_object_unref (info);
continue;
}
if (!_is_name_listed (iface_name, iface_names)) {
g_object_unref (info);
continue;
}
/* Additional validation */
if (!peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES)
|| !peas_plugin_info_get_external_data (info, ENHANCER_HOSTS)) {
g_object_unref (info);
continue;
}
GST_DEBUG ("Found valid enhancers of type: \"%s\"", iface_name);
g_object_unref (info);
return TRUE;
}
GST_DEBUG ("No available enhancers of type: \"%s\"", iface_name);
return FALSE;
}
/*
* clapper_enhancers_loader_get_schemes:
* @iface_type: an interface #GType
*
* Get all supported schemes for a given interface type.
* The returned array consists of unique strings (no duplicates).
*
* Returns: (transfer full): all supported schemes by enhancers of type.
*/
gchar **
clapper_enhancers_loader_get_schemes (GType iface_type)
{
GListModel *list = (GListModel *) _engine;
GSList *found_schemes = NULL, *fs;
const gchar *iface_name = ENHANCER_IFACE_NAME_FROM_TYPE (iface_type);
gchar **schemes_strv;
guint i, n_plugins, n_schemes;
GST_DEBUG ("Checking supported URI schemes for \"%s\"", iface_name);
n_plugins = g_list_model_get_n_items (list);
for (i = 0; i < n_plugins; ++i) {
PeasPluginInfo *info = (PeasPluginInfo *) g_list_model_get_item (list, i);
const gchar *iface_names, *schemes;
gchar **tmp_strv;
gint j;
if (!(iface_names = peas_plugin_info_get_external_data (info, ENHANCER_INTERFACES))) {
g_object_unref (info);
continue;
}
if (!_is_name_listed (iface_name, iface_names)) {
g_object_unref (info);
continue;
}
if (!(schemes = peas_plugin_info_get_external_data (info, ENHANCER_SCHEMES))) {
g_object_unref (info);
continue;
}
tmp_strv = g_strsplit (schemes, ";", 0);
for (j = 0; tmp_strv[j]; ++j) {
const gchar *scheme = tmp_strv[j];
if (!found_schemes || !g_slist_find_custom (found_schemes,
scheme, (GCompareFunc) strcmp)) {
found_schemes = g_slist_append (found_schemes, g_strdup (scheme));
GST_INFO ("Found supported URI scheme: %s", scheme);
}
}
g_strfreev (tmp_strv);
g_object_unref (info);
}
n_schemes = g_slist_length (found_schemes);
schemes_strv = g_new0 (gchar *, n_schemes + 1);
fs = found_schemes;
for (i = 0; i < n_schemes; ++i) {
schemes_strv[i] = fs->data;
fs = fs->next;
}
GST_DEBUG ("Total found URI schemes: %u", n_schemes);
/* Since string pointers were taken,
* free list without content */
g_slist_free (found_schemes);
return schemes_strv;
}
/*
* clapper_enhancers_loader_check:
* clapper_enhancers_loader_create_enhancer:
* @iface_type: a requested #GType
* @scheme: an URI scheme
* @host: (nullable): an URI host
* @name: (out) (optional) (transfer none): return location for found enhancer name
* @info: a #PeasPluginInfo
*
* Checks if any enhancer can handle @uri without initializing loader
* or creating enhancer instance, thus this can be used freely from any thread.
*
* Returns: whether enhancer for given scheme and host is available.
*/
gboolean
clapper_enhancers_loader_check (GType iface_type,
const gchar *scheme, const gchar *host, const gchar **name)
{
PeasPluginInfo *info;
if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) {
if (name)
*name = peas_plugin_info_get_name (info);
g_object_unref (info);
return TRUE;
}
return FALSE;
}
/*
* clapper_enhancers_loader_create_enhancer_for_uri:
* @iface_type: a requested #GType
* @uri: a #GUri
*
* Creates a new enhancer object for given URI.
* Creates a new enhancer object using @info.
*
* Enhancer should only be created and used within single thread.
*
* Returns: (transfer full) (nullable): a new enhancer instance.
*/
GObject *
clapper_enhancers_loader_create_enhancer_for_uri (GType iface_type, GUri *uri)
clapper_enhancers_loader_create_enhancer (ClapperEnhancerProxy *proxy, GType iface_type)
{
GObject *enhancer = NULL;
PeasPluginInfo *info;
const gchar *scheme = g_uri_get_scheme (uri);
const gchar *host = g_uri_get_host (uri);
PeasPluginInfo *info = (PeasPluginInfo *) clapper_enhancer_proxy_get_peas_info (proxy);
if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) {
g_mutex_lock (&load_lock);
g_mutex_lock (&load_lock);
if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) {
GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_name (info));
} else if (!peas_engine_provides_extension (_engine, info, iface_type)) {
GST_ERROR ("No \"%s\" enhancer in plugin: %s", ENHANCER_IFACE_NAME_FROM_TYPE (iface_type),
peas_plugin_info_get_name (info));
} else {
enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL);
}
g_mutex_unlock (&load_lock);
g_object_unref (info);
if (!peas_plugin_info_is_loaded (info) && !peas_engine_load_plugin (_engine, info)) {
GST_ERROR ("Could not load enhancer: %s", peas_plugin_info_get_module_name (info));
} else if (!peas_engine_provides_extension (_engine, info, iface_type)) {
GST_LOG ("No \"%s\" enhancer in module: %s", g_type_name (iface_type),
peas_plugin_info_get_module_name (info));
} else {
enhancer = peas_engine_create_extension (_engine, info, iface_type, NULL);
}
g_mutex_unlock (&load_lock);
return enhancer;
}

View File

@@ -127,4 +127,28 @@ typedef enum
CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT,
} ClapperDiscovererDiscoveryMode;
/* NOTE: GStreamer uses param flags 8-16, so start with 17. */
/**
* ClapperEnhancerParamFlags:
* @CLAPPER_ENHANCER_PARAM_GLOBAL: Use this flag for enhancer properties that should have global access scope.
* Such are meant for application `USER` to configure.
* @CLAPPER_ENHANCER_PARAM_LOCAL: Use this flag for enhancer properties that should have local access scope.
* Such are meant for `APPLICATION` to configure.
* @CLAPPER_ENHANCER_PARAM_FILEPATH: Use this flag for enhancer properties that store string with a file path.
* Applications can use this as a hint to show file selection instead of a text entry.
* @CLAPPER_ENHANCER_PARAM_DIRPATH: Use this flag for enhancer properties that store string with a directory path.
* Applications can use this as a hint to show directory selection instead of a text entry.
*
* Additional [flags@GObject.ParamFlags] to be set in enhancer plugins implementations.
*
* Since: 0.10
*/
typedef enum
{
CLAPPER_ENHANCER_PARAM_GLOBAL = 1 << 17,
CLAPPER_ENHANCER_PARAM_LOCAL = 1 << 18,
CLAPPER_ENHANCER_PARAM_FILEPATH = 1 << 19,
CLAPPER_ENHANCER_PARAM_DIRPATH = 1 << 20,
} ClapperEnhancerParamFlags;
G_END_DECLS

View File

@@ -23,6 +23,7 @@
#include <gst/tag/tag.h>
#include "clapper-harvest.h"
#include "clapper-enhancer-proxy.h"
G_BEGIN_DECLS
@@ -32,4 +33,10 @@ ClapperHarvest * clapper_harvest_new (void);
G_GNUC_INTERNAL
gboolean clapper_harvest_unpack (ClapperHarvest *harvest, GstBuffer **buffer, gsize *buf_size, GstCaps **caps, GstTagList **tags, GstToc **toc, GstStructure **headers);
G_GNUC_INTERNAL
gboolean clapper_harvest_fill_from_cache (ClapperHarvest *harvest, ClapperEnhancerProxy *proxy, const GstStructure *config, GUri *uri);
G_GNUC_INTERNAL
void clapper_harvest_export_to_cache (ClapperHarvest *harvest, ClapperEnhancerProxy *proxy, const GstStructure *config, GUri *uri);
G_END_DECLS

View File

@@ -31,7 +31,11 @@
* https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2867
*/
#include "config.h"
#include "clapper-harvest-private.h"
#include "clapper-cache-private.h"
#include "clapper-utils.h"
#define GST_CAT_DEFAULT clapper_harvest_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
@@ -50,6 +54,8 @@ struct _ClapperHarvest
guint16 n_chapters;
guint16 n_tracks;
gint64 exp_epoch;
};
#define parent_class clapper_harvest_parent_class
@@ -119,6 +125,302 @@ clapper_harvest_unpack (ClapperHarvest *self,
return TRUE;
}
/* Custom implementation due to the lack of TOC serialization in GStreamer */
static void
_harvest_fill_toc_from_cache (ClapperHarvest *self, const gchar **data)
{
guint i, n_entries;
n_entries = clapper_cache_read_uint (data);
for (i = 0; i < n_entries; ++i) {
guint j, n_subentries;
n_subentries = clapper_cache_read_uint (data);
for (j = 0; j < n_subentries; ++j) {
GstTocEntryType type;
const gchar *title;
gdouble start, end;
type = (GstTocEntryType) clapper_cache_read_int (data);
title = clapper_cache_read_string (data);
start = clapper_cache_read_double (data);
end = clapper_cache_read_double (data);
clapper_harvest_toc_add (self, type, title, start, end);
}
}
}
static void
_harvest_store_toc_to_cache (ClapperHarvest *self, GByteArray *bytes)
{
GList *list = NULL, *el;
guint n_entries = 0;
if (self->toc) {
list = gst_toc_get_entries (self->toc);
n_entries = g_list_length (list);
}
clapper_cache_store_uint (bytes, n_entries);
for (el = list; el; el = g_list_next (el)) {
const GstTocEntry *entry = (const GstTocEntry *) el->data;
GList *subentries, *sub_el;
guint n_subentries;
subentries = gst_toc_entry_get_sub_entries (entry);
n_subentries = g_list_length (subentries);
clapper_cache_store_uint (bytes, n_subentries);
for (sub_el = subentries; sub_el; sub_el = g_list_next (sub_el)) {
const GstTocEntry *subentry = (const GstTocEntry *) sub_el->data;
GstTagList *tags;
gint64 start = 0, end = 0;
gdouble start_dbl, end_dbl;
const gchar *title = NULL;
clapper_cache_store_int (bytes, (gint) gst_toc_entry_get_entry_type (subentry));
if ((tags = gst_toc_entry_get_tags (subentry)))
gst_tag_list_peek_string_index (tags, GST_TAG_TITLE, 0, &title);
clapper_cache_store_string (bytes, title);
gst_toc_entry_get_start_stop_times (subentry, &start, &end);
start_dbl = ((gdouble) start) / GST_SECOND;
end_dbl = (end >= 0) ? ((gdouble) end) / GST_SECOND : -1;
clapper_cache_store_double (bytes, start_dbl);
clapper_cache_store_double (bytes, end_dbl);
}
}
}
static inline gchar *
_build_cache_filename (ClapperEnhancerProxy *proxy, GUri *uri)
{
gchar *uri_str = g_uri_to_string (uri);
gchar name[15];
g_snprintf (name, sizeof (name), "%u.bin", g_str_hash (uri_str));
g_free (uri_str);
return g_build_filename (g_get_user_cache_dir (), CLAPPER_API_NAME,
"enhancers", clapper_enhancer_proxy_get_module_name (proxy),
"harvests", name, NULL);
}
/* NOTE: On failure, this function must not modify harvest! */
gboolean
clapper_harvest_fill_from_cache (ClapperHarvest *self, ClapperEnhancerProxy *proxy,
const GstStructure *config, GUri *uri)
{
GMappedFile *mapped_file;
GstStructure *config_cached = NULL;
GError *error = NULL;
gchar *filename;
const gchar *data, *read_str;
const guint8 *buf_data;
guint8 *buf_copy;
gsize buf_size;
gint64 epoch_cached, epoch_now = 0;
gdouble exp_seconds;
gboolean changed, read_ok = FALSE;
filename = _build_cache_filename (proxy, uri);
GST_DEBUG_OBJECT (self, "Importing harvest from cache file: \"%s\"", filename);
mapped_file = clapper_cache_open (filename, &data, &error);
g_free (filename);
if (!mapped_file) {
/* No error if cache disabled or version mismatch */
if (error) {
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
GST_DEBUG_OBJECT (self, "No cached harvest found");
else
GST_ERROR_OBJECT (self, "Could not use cached harvest, reason: %s", error->message);
g_error_free (error);
}
return FALSE;
}
/* Plugin version check */
if (g_strcmp0 (clapper_cache_read_string (&data),
clapper_enhancer_proxy_get_version (proxy)) != 0)
goto finish; // no error printing here
if (G_LIKELY ((epoch_cached = clapper_cache_read_int64 (&data)) > 0)) {
GDateTime *date = g_date_time_new_now_utc ();
epoch_now = g_date_time_to_unix (date);
g_date_time_unref (date);
}
/* Check if expired */
if ((exp_seconds = (gdouble) (epoch_cached - epoch_now)) <= 0) {
GST_DEBUG_OBJECT (self, "Cached harvest expired"); // expiration is not an error
goto finish;
}
GST_DEBUG_OBJECT (self, "Cached harvest expiration in %" CLAPPER_TIME_FORMAT,
CLAPPER_TIME_ARGS (exp_seconds));
/* Read last used config to generate cache data */
if ((read_str = clapper_cache_read_string (&data)))
config_cached = gst_structure_from_string (read_str, NULL);
/* Compare used config when cache was generated to the current one */
changed = (config_cached && config)
? !gst_structure_is_equal (config_cached, config)
: (config_cached != config);
gst_clear_structure (&config_cached);
if (changed) {
GST_DEBUG_OBJECT (self, "Enhancer config differs from the last time");
goto finish;
}
/* Read media type */
read_str = clapper_cache_read_string (&data);
if (G_UNLIKELY (read_str == NULL)) {
GST_ERROR_OBJECT (self, "Could not read media type from cache file");
goto finish;
}
/* Read buffer data */
buf_data = clapper_cache_read_data (&data, &buf_size);
if (G_UNLIKELY (buf_data == NULL)) {
GST_ERROR_OBJECT (self, "Could not read buffer data from cache");
goto finish;
}
/* Fill harvest */
buf_copy = g_memdup2 (buf_data, buf_size);
if (!clapper_harvest_fill (self, read_str, buf_copy, buf_size))
goto finish;
/* Read tags */
read_str = clapper_cache_read_string (&data);
if (read_str && (self->tags = gst_tag_list_new_from_string (read_str))) {
GST_LOG_OBJECT (self, "Read %s", read_str);
gst_tag_list_set_scope (self->tags, GST_TAG_SCOPE_GLOBAL);
}
/* Read TOC */
_harvest_fill_toc_from_cache (self, &data);
/* Read headers */
read_str = clapper_cache_read_string (&data);
if (read_str && (self->headers = gst_structure_from_string (read_str, NULL)))
GST_LOG_OBJECT (self, "Read %s", read_str);
read_ok = TRUE;
finish:
g_mapped_file_unref (mapped_file);
if (!read_ok)
return FALSE;
GST_DEBUG_OBJECT (self, "Filled harvest from cache");
return TRUE;
}
void
clapper_harvest_export_to_cache (ClapperHarvest *self, ClapperEnhancerProxy *proxy,
const GstStructure *config, GUri *uri)
{
GByteArray *bytes;
const GstStructure *caps_structure;
gchar *filename, *temp_str = NULL;
gboolean data_ok = TRUE;
/* No caching if no expiration date set */
if (self->exp_epoch <= 0)
return;
/* Might happen if extractor extract function implementation
* returns %TRUE without filling harvest properly */
if (G_UNLIKELY (self->caps == NULL || self->buffer == NULL))
return; // no data to cache
bytes = clapper_cache_create ();
/* If cache disabled */
if (G_UNLIKELY (bytes == NULL))
return;
filename = _build_cache_filename (proxy, uri);
GST_DEBUG_OBJECT (self, "Exporting harvest to cache file: \"%s\"", filename);
/* Store enhancer version that generated harvest */
clapper_cache_store_string (bytes, clapper_enhancer_proxy_get_version (proxy));
/* Store expiration date */
clapper_cache_store_int64 (bytes, self->exp_epoch);
/* Store config used to generate harvest */
if (config)
temp_str = gst_structure_to_string (config);
clapper_cache_store_string (bytes, temp_str); // NULL when no config
g_clear_pointer (&temp_str, g_free);
/* Store media type */
caps_structure = gst_caps_get_structure (self->caps, 0);
if (G_LIKELY (caps_structure != NULL)) {
clapper_cache_store_string (bytes, gst_structure_get_name (caps_structure));
} else {
GST_ERROR_OBJECT (self, "Cannot cache empty caps");
data_ok = FALSE;
}
if (G_LIKELY (data_ok)) {
GstMemory *mem;
GstMapInfo map_info;
/* Store buffer data */
mem = gst_buffer_peek_memory (self->buffer, 0);
if (G_LIKELY (gst_memory_map (mem, &map_info, GST_MAP_READ))) {
clapper_cache_store_data (bytes, map_info.data, map_info.size);
gst_memory_unmap (mem, &map_info);
} else {
GST_ERROR_OBJECT (self, "Could not map harvest buffer for reading");
data_ok = FALSE;
}
}
if (G_LIKELY (data_ok)) {
GError *error = NULL;
/* Store tags */
if (self->tags)
temp_str = gst_tag_list_to_string (self->tags);
clapper_cache_store_string (bytes, temp_str);
g_clear_pointer (&temp_str, g_free);
/* Store TOC */
_harvest_store_toc_to_cache (self, bytes);
/* Store headers */
if (self->headers)
temp_str = gst_structure_to_string (self->headers);
clapper_cache_store_string (bytes, temp_str);
g_clear_pointer (&temp_str, g_free);
if (clapper_cache_write (filename, bytes, &error)) {
GST_DEBUG_OBJECT (self, "Successfully exported harvest to cache file");
} else if (error) {
GST_ERROR_OBJECT (self, "Could not cache harvest, reason: %s", error->message);
g_error_free (error);
}
}
g_free (filename);
g_byte_array_free (bytes, TRUE);
}
/**
* clapper_harvest_fill:
* @harvest: a #ClapperHarvest
@@ -155,7 +457,7 @@ clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer da
return FALSE;
}
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) {
if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_LOG) {
gboolean is_printable = (strcmp (media_type, "application/dash+xml") == 0)
|| (strcmp (media_type, "application/x-hls") == 0)
|| (strcmp (media_type, "text/uri-list") == 0);
@@ -166,7 +468,7 @@ clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer da
data_str = g_new0 (gchar, size + 1);
memcpy (data_str, data, size);
GST_DEBUG_OBJECT (self, "Filled with data:\n%s", data_str);
GST_LOG_OBJECT (self, "Filled with data:\n%s", data_str);
g_free (data_str);
}
@@ -328,7 +630,7 @@ clapper_harvest_toc_add (ClapperHarvest *self, GstTocEntryType type,
g_snprintf (edition, sizeof (edition), "0%i", type);
g_snprintf (id, sizeof (id), "%s.%" G_GUINT16_FORMAT, id_prefix, nth_entry);
GST_DEBUG_OBJECT (self, "Inserting TOC %s: \"%s\""
GST_LOG_OBJECT (self, "Inserting TOC %s: \"%s\""
" (%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")",
id, title, start_time, end_time);
@@ -380,7 +682,7 @@ clapper_harvest_headers_set (ClapperHarvest *self, const gchar *key, ...)
while (key != NULL) {
const gchar *val = va_arg (args, const gchar *);
GST_DEBUG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, val);
GST_LOG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, val);
gst_structure_set (self->headers, key, G_TYPE_STRING, val, NULL);
key = va_arg (args, const gchar *);
}
@@ -409,10 +711,70 @@ clapper_harvest_headers_set_value (ClapperHarvest *self, const gchar *key, const
_ensure_headers (self);
GST_DEBUG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, g_value_get_string (value));
GST_LOG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, g_value_get_string (value));
gst_structure_set_value (self->headers, key, value);
}
/**
* clapper_harvest_set_expiration_date_utc:
* @harvest: a #ClapperHarvest
* @date_utc: a #GDateTime in UTC time
*
* Set date in UTC time until harvested content is expected
* to stay alive.
*
* This is used for harvest caching, so next time user requests to
* play the same URI, recently harvested data can be reused without
* the need to run [vfunc@Clapper.Extractable.extract] again.
*
* Since: 0.10
*/
void
clapper_harvest_set_expiration_date_utc (ClapperHarvest *self, GDateTime *date_utc)
{
g_return_if_fail (CLAPPER_IS_HARVEST (self));
g_return_if_fail (date_utc != NULL);
self->exp_epoch = g_date_time_to_unix (date_utc);
GST_LOG_OBJECT (self, "Expiration epoch: %" G_GINT64_FORMAT, self->exp_epoch);
}
/**
* clapper_harvest_set_expiration_seconds:
* @harvest: a #ClapperHarvest
* @seconds: time in seconds until expiration
*
* Set amount of seconds for how long harvested content is
* expected to stay alive.
*
* Alternative function to [method@Clapper.Harvest.set_expiration_date_utc],
* but takes time as number in seconds from now.
*
* It is safe to pass zero or negative number to this function in
* case when calculating time manually and it already expired.
*
* Since: 0.10
*/
void
clapper_harvest_set_expiration_seconds (ClapperHarvest *self, gdouble seconds)
{
GDateTime *date, *date_epoch;
g_return_if_fail (CLAPPER_IS_HARVEST (self));
GST_LOG_OBJECT (self, "Set expiration in %" CLAPPER_TIME_FORMAT,
CLAPPER_TIME_ARGS (seconds));
date = g_date_time_new_now_utc ();
date_epoch = g_date_time_add_seconds (date, seconds);
g_date_time_unref (date);
self->exp_epoch = g_date_time_to_unix (date_epoch);
g_date_time_unref (date_epoch);
GST_LOG_OBJECT (self, "Expiration epoch: %" G_GINT64_FORMAT, self->exp_epoch);
}
static void
clapper_harvest_init (ClapperHarvest *self)
{

View File

@@ -61,4 +61,10 @@ void clapper_harvest_headers_set (ClapperHarvest *harvest, const gchar *key, ...
CLAPPER_API
void clapper_harvest_headers_set_value (ClapperHarvest *harvest, const gchar *key, const GValue *value);
CLAPPER_API
void clapper_harvest_set_expiration_date_utc (ClapperHarvest *harvest, GDateTime *date_utc);
CLAPPER_API
void clapper_harvest_set_expiration_seconds (ClapperHarvest *harvest, gdouble seconds);
G_END_DECLS

View File

@@ -29,12 +29,7 @@
#include "clapper-timeline-private.h"
#include "clapper-stream-private.h"
#include "clapper-stream-list-private.h"
#include "clapper-functionalities-availability.h"
#if CLAPPER_WITH_ENHANCERS_LOADER
#include "gst/clapper-enhancer-src-private.h"
#endif
#include "gst/clapper-extractable-src-private.h"
#define GST_CAT_DEFAULT clapper_playbin_bus_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
@@ -875,7 +870,6 @@ _handle_tag_msg (GstMessage *msg, ClapperPlayer *player)
{
GstObject *src = GST_MESSAGE_SRC (msg);
GstTagList *tags = NULL;
gboolean from_enhancer_src;
/* Tag messages should only be posted by sink elements */
if (G_UNLIKELY (!src))
@@ -886,14 +880,8 @@ _handle_tag_msg (GstMessage *msg, ClapperPlayer *player)
GST_LOG_OBJECT (player, "Got tags from element: %s: %" GST_PTR_FORMAT,
GST_OBJECT_NAME (src), tags);
#if CLAPPER_WITH_ENHANCERS_LOADER
from_enhancer_src = CLAPPER_IS_ENHANCER_SRC (src);
#else
from_enhancer_src = FALSE;
#endif
/* ClapperEnhancerSrc determines tags before stream start */
if (from_enhancer_src) {
/* ClapperExtractableSrc determines tags before stream start */
if (CLAPPER_IS_EXTRACTABLE_SRC (src)) {
if (player->pending_tags) {
gst_tag_list_unref (player->pending_tags);
}
@@ -910,7 +898,7 @@ _handle_toc_msg (GstMessage *msg, ClapperPlayer *player)
{
GstObject *src = GST_MESSAGE_SRC (msg);
GstToc *toc = NULL;
gboolean from_enhancer_src, updated = FALSE;
gboolean updated = FALSE;
/* TOC messages should only be posted by sink elements */
if (G_UNLIKELY (!src))
@@ -923,14 +911,8 @@ _handle_toc_msg (GstMessage *msg, ClapperPlayer *player)
" from element: %s, updated: %s",
toc, GST_OBJECT_NAME (src), (updated) ? "yes" : "no");
#if CLAPPER_WITH_ENHANCERS_LOADER
from_enhancer_src = CLAPPER_IS_ENHANCER_SRC (src);
#else
from_enhancer_src = FALSE;
#endif
/* ClapperEnhancerSrc determines TOC before stream start */
if (from_enhancer_src) {
/* ClapperExtractableSrc determines TOC before stream start */
if (CLAPPER_IS_EXTRACTABLE_SRC (src)) {
if (player->pending_toc) {
gst_toc_unref (player->pending_toc);
}

View File

@@ -47,6 +47,8 @@ struct _ClapperPlayer
ClapperFeaturesManager *features_manager;
gint have_features; // atomic integer
ClapperEnhancerProxyList *enhancer_proxies;
/* This is different from queue current item as it is used/changed only
* on player thread, so we can always update correct item without lock */
ClapperMediaItem *played_item;

View File

@@ -49,6 +49,7 @@
#include "clapper-video-stream-private.h"
#include "clapper-audio-stream-private.h"
#include "clapper-subtitle-stream-private.h"
#include "clapper-enhancer-proxy-list-private.h"
#include "clapper-enums-private.h"
#include "clapper-utils-private.h"
#include "../shared/clapper-shared-utils-private.h"
@@ -77,6 +78,7 @@ enum
PROP_VIDEO_STREAMS,
PROP_AUDIO_STREAMS,
PROP_SUBTITLE_STREAMS,
PROP_ENHANCER_PROXIES,
PROP_AUTOPLAY,
PROP_POSITION,
PROP_SPEED,
@@ -814,7 +816,11 @@ _element_setup_cb (GstElement *playbin, GstElement *element, ClapperPlayer *self
factory_name = g_intern_static_string (GST_OBJECT_NAME (factory));
GST_INFO_OBJECT (self, "Element setup: %s", factory_name);
if (factory_name == g_intern_static_string ("downloadbuffer")) {
if (factory_name == g_intern_static_string ("clapperextractablesrc")) {
g_object_set (element,
"enhancer-proxies", self->enhancer_proxies,
NULL);
} else if (factory_name == g_intern_static_string ("downloadbuffer")) {
gchar *download_template;
/* Only set props if we have download template */
@@ -1129,6 +1135,24 @@ clapper_player_get_subtitle_streams (ClapperPlayer *self)
return self->subtitle_streams;
}
/**
* clapper_player_get_enhancer_proxies:
* @player: a #ClapperPlayer
*
* Get a list of available enhancers in the form of [class@Clapper.EnhancerProxy] objects.
*
* Returns: (transfer none): a #ClapperEnhancerProxyList of enhancer proxies.
*
* Since: 0.10
*/
ClapperEnhancerProxyList *
clapper_player_get_enhancer_proxies (ClapperPlayer *self)
{
g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL);
return self->enhancer_proxies;
}
/**
* clapper_player_set_autoplay:
* @player: a #ClapperPlayer
@@ -2187,6 +2211,28 @@ clapper_player_add_feature (ClapperPlayer *self, ClapperFeature *feature)
clapper_features_manager_add_feature (self->features_manager, feature, GST_OBJECT (self));
}
/**
* clapper_player_make_pipeline_graph:
* @player: a #ClapperPlayer
* @details: a #GstDebugGraphDetails level
*
* Make current #GStreamer pipeline graph in `graphviz` dot format.
*
* Applications can use tools like `graphviz` to display returned
* data or just save it to a file as-is for the user to do it manually.
*
* Returns: (transfer full): current pipeline description in dot format.
*
* Since: 0.10
*/
gchar *
clapper_player_make_pipeline_graph (ClapperPlayer *self, GstDebugGraphDetails details)
{
g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL);
return gst_debug_bin_to_dot_data (GST_BIN (self->playbin), details);
}
static void
clapper_player_thread_start (ClapperThreadedObject *threaded_object)
{
@@ -2275,6 +2321,11 @@ clapper_player_init (ClapperPlayer *self)
self->subtitle_streams = clapper_stream_list_new ();
gst_object_set_parent (GST_OBJECT_CAST (self->subtitle_streams), GST_OBJECT_CAST (self));
self->enhancer_proxies = clapper_enhancer_proxy_list_new_named (NULL);
gst_object_set_parent (GST_OBJECT_CAST (self->enhancer_proxies), GST_OBJECT_CAST (self));
clapper_enhancer_proxy_list_fill_from_global_proxies (self->enhancer_proxies);
self->position_query = gst_query_new_position (GST_FORMAT_TIME);
self->current_state = GST_STATE_NULL;
@@ -2341,6 +2392,9 @@ clapper_player_finalize (GObject *object)
gst_object_unparent (GST_OBJECT_CAST (self->subtitle_streams));
gst_object_unref (self->subtitle_streams);
gst_object_unparent (GST_OBJECT_CAST (self->enhancer_proxies));
gst_object_unref (self->enhancer_proxies);
gst_query_unref (self->position_query);
gst_clear_object (&self->collection);
@@ -2372,6 +2426,9 @@ clapper_player_get_property (GObject *object, guint prop_id,
case PROP_SUBTITLE_STREAMS:
g_value_set_object (value, clapper_player_get_subtitle_streams (self));
break;
case PROP_ENHANCER_PROXIES:
g_value_set_object (value, clapper_player_get_enhancer_proxies (self));
break;
case PROP_AUTOPLAY:
g_value_set_boolean (value, clapper_player_get_autoplay (self));
break;
@@ -2571,6 +2628,20 @@ clapper_player_class_init (ClapperPlayerClass *klass)
NULL, NULL, CLAPPER_TYPE_STREAM_LIST,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperPlayer:enhancer-proxies:
*
* List of available enhancers in the form of [class@Clapper.EnhancerProxy] objects.
*
* Use these to inspect available enhancers on the system and configure
* their properties on a per player instance basis.
*
* Since: 0.10
*/
param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies",
NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST,
G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
/**
* ClapperPlayer:autoplay:
*

View File

@@ -31,6 +31,7 @@
#include <clapper/clapper-threaded-object.h>
#include <clapper/clapper-queue.h>
#include <clapper/clapper-stream-list.h>
#include <clapper/clapper-enhancer-proxy-list.h>
#include <clapper/clapper-feature.h>
#include <clapper/clapper-enums.h>
@@ -57,6 +58,9 @@ ClapperStreamList * clapper_player_get_audio_streams (ClapperPlayer *player);
CLAPPER_API
ClapperStreamList * clapper_player_get_subtitle_streams (ClapperPlayer *player);
CLAPPER_API
ClapperEnhancerProxyList * clapper_player_get_enhancer_proxies (ClapperPlayer *player);
CLAPPER_API
void clapper_player_set_autoplay (ClapperPlayer *player, gboolean enabled);
@@ -204,4 +208,7 @@ void clapper_player_seek_custom (ClapperPlayer *player, gdouble position, Clappe
CLAPPER_API
void clapper_player_add_feature (ClapperPlayer *player, ClapperFeature *feature);
CLAPPER_API
gchar * clapper_player_make_pipeline_graph (ClapperPlayer *player, GstDebugGraphDetails details);
G_END_DECLS

View File

@@ -19,9 +19,6 @@
#pragma once
#include <glib.h>
#include <glib-object.h>
#define __CLAPPER_INSIDE__
#include <clapper/clapper-visibility.h>
@@ -30,6 +27,9 @@
#include <clapper/clapper-version.h>
#include <clapper/clapper-audio-stream.h>
#include <clapper/clapper-basic-functions.h>
#include <clapper/clapper-enhancer-proxy.h>
#include <clapper/clapper-enhancer-proxy-list.h>
#include <clapper/clapper-feature.h>
#include <clapper/clapper-harvest.h>
#include <clapper/clapper-marker.h>
@@ -59,17 +59,4 @@
#include <clapper/features/server/clapper-server.h>
#endif
G_BEGIN_DECLS
CLAPPER_API
void clapper_init (int *argc, char **argv[]);
CLAPPER_API
gboolean clapper_init_check (int *argc, char **argv[]);
CLAPPER_API
gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name);
G_END_DECLS
#undef __CLAPPER_INSIDE__

View File

@@ -38,6 +38,6 @@ G_GNUC_INTERNAL
ClapperEnhancerDirector * clapper_enhancer_director_new (void);
G_GNUC_INTERNAL
ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GUri *uri, GCancellable *cancellable, GError **error);
ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *director, GList *filtered_proxies, GUri *uri, GCancellable *cancellable, GError **error);
G_END_DECLS

View File

@@ -17,14 +17,27 @@
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <gst/gst.h>
#include "clapper-enhancer-director-private.h"
#include "../clapper-enhancers-loader-private.h"
#include "../clapper-basic-functions.h"
#include "../clapper-cache-private.h"
#include "../clapper-enhancer-proxy-private.h"
#include "../clapper-extractable-private.h"
#include "../clapper-harvest-private.h"
#include "../clapper-utils.h"
#include "../../shared/clapper-shared-utils-private.h"
#include "../clapper-functionalities-availability.h"
#if CLAPPER_WITH_ENHANCERS_LOADER
#include "../clapper-enhancers-loader-private.h"
#endif
#define CLEANUP_INTERVAL 10800 // once every 3 hours
#define GST_CAT_DEFAULT clapper_enhancer_director_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
@@ -38,6 +51,8 @@ G_DEFINE_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER_TYPE_
typedef struct
{
ClapperEnhancerDirector *director;
GList *filtered_proxies;
GUri *uri;
GCancellable *cancellable;
GError **error;
@@ -46,42 +61,66 @@ typedef struct
static gpointer
clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data)
{
ClapperExtractable *extractable = NULL;
ClapperHarvest *harvest = clapper_harvest_new ();
gboolean success = FALSE, cached = FALSE;
ClapperEnhancerDirector *self = data->director;
GList *el;
ClapperHarvest *harvest = NULL;
gboolean success = FALSE;
GST_DEBUG_OBJECT (self, "Extraction start");
/* Cancelled during thread switching */
if (g_cancellable_is_cancelled (data->cancellable))
goto finish;
return NULL;
/* TODO: Cache lookup */
if (cached) {
// success = fill harvest from cache
goto finish;
}
GST_DEBUG_OBJECT (self, "Enhancer proxies for URI: %u",
g_list_length (data->filtered_proxies));
extractable = CLAPPER_EXTRACTABLE_CAST (clapper_enhancers_loader_create_enhancer_for_uri (
CLAPPER_TYPE_EXTRACTABLE, data->uri));
for (el = data->filtered_proxies; el; el = g_list_next (el)) {
ClapperEnhancerProxy *proxy = CLAPPER_ENHANCER_PROXY_CAST (el->data);
ClapperExtractable *extractable = NULL;
GstStructure *config;
/* Check just before extract */
if (g_cancellable_is_cancelled (data->cancellable))
goto finish;
harvest = clapper_harvest_new (); // fresh harvest for each iteration
config = clapper_enhancer_proxy_make_current_config (proxy);
success = clapper_extractable_extract (extractable, data->uri,
harvest, data->cancellable, data->error);
/* Cancelled during extract */
if (g_cancellable_is_cancelled (data->cancellable)) {
success = FALSE;
goto finish;
}
finish:
if (success) {
if (!cached) {
/* TODO: Store in cache */
if ((success = clapper_harvest_fill_from_cache (harvest, proxy, config, data->uri))
|| g_cancellable_is_cancelled (data->cancellable)) { // Check before extract
gst_clear_structure (&config);
break;
}
} else {
#if CLAPPER_WITH_ENHANCERS_LOADER
extractable = CLAPPER_EXTRACTABLE_CAST (
clapper_enhancers_loader_create_enhancer (proxy, CLAPPER_TYPE_EXTRACTABLE));
#endif
if (G_LIKELY (extractable != NULL)) {
clapper_enhancer_proxy_apply_config_to_enhancer (proxy, config, (GObject *) extractable);
success = clapper_extractable_extract (extractable, data->uri,
harvest, data->cancellable, data->error);
gst_object_unref (extractable);
/* We are done with extractable, but keep harvest and try to cache it */
if (success) {
if (!g_cancellable_is_cancelled (data->cancellable))
clapper_harvest_export_to_cache (harvest, proxy, config, data->uri);
gst_clear_structure (&config);
break;
}
}
/* Cleanup to try again with next enhancer */
g_clear_object (&harvest);
gst_clear_structure (&config);
}
/* Cancelled during extraction or exporting to cache */
if (g_cancellable_is_cancelled (data->cancellable))
success = FALSE;
if (!success) {
gst_clear_object (&harvest);
/* Ensure we have some error set on failure */
@@ -94,11 +133,176 @@ finish:
}
}
gst_clear_object (&extractable);
GST_DEBUG_OBJECT (self, "Extraction finish");
return harvest;
}
static inline void
_harvest_delete_if_expired (ClapperEnhancerDirector *self,
ClapperEnhancerProxy *proxy, GFile *file, const gint64 epoch_now)
{
GMappedFile *mapped_file;
const gchar *data;
gchar *filename;
GError *error = NULL;
gboolean delete = TRUE;
filename = g_file_get_path (file);
if ((mapped_file = clapper_cache_open (filename, &data, &error))) {
/* Do not delete if versions match and not expired */
if (g_strcmp0 (clapper_cache_read_string (&data),
clapper_enhancer_proxy_get_version (proxy)) == 0
&& clapper_cache_read_int64 (&data) > epoch_now) {
delete = FALSE;
}
g_mapped_file_unref (mapped_file);
} else if (error) {
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
GST_DEBUG_OBJECT (self, "No cached harvest file found");
else
GST_ERROR_OBJECT (self, "Could not read cached harvest file, reason: %s", error->message);
g_clear_error (&error);
}
if (delete) {
if (G_LIKELY (g_file_delete (file, NULL, &error))) {
GST_TRACE_OBJECT (self, "Deleted cached harvest: \"%s\"", filename);
} else {
GST_ERROR_OBJECT (self, "Could not delete harvest: \"%s\", reason: %s",
filename, GST_STR_NULL (error->message));
g_error_free (error);
}
}
g_free (filename);
}
static inline void
_cache_proxy_harvests_cleanup (ClapperEnhancerDirector *self,
ClapperEnhancerProxy *proxy, const gint64 epoch_now)
{
GFile *dir;
GFileEnumerator *dir_enum;
GError *error = NULL;
dir = g_file_new_build_filename (g_get_user_cache_dir (), CLAPPER_API_NAME,
"enhancers", clapper_enhancer_proxy_get_module_name (proxy),
"harvests", NULL);
if ((dir_enum = g_file_enumerate_children (dir,
G_FILE_ATTRIBUTE_STANDARD_NAME "," G_FILE_ATTRIBUTE_STANDARD_TYPE,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error))) {
while (TRUE) {
GFileInfo *info = NULL;
GFile *child = NULL;
if (!g_file_enumerator_iterate (dir_enum, &info,
&child, NULL, &error) || !info)
break;
if (G_LIKELY (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR
&& g_str_has_suffix (g_file_info_get_name (info), ".bin")))
_harvest_delete_if_expired (self, proxy, child, epoch_now);
}
g_object_unref (dir_enum);
}
if (error) {
if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_NOT_FOUND) {
gchar *path = g_file_get_path (dir);
GST_ERROR_OBJECT (self, "Could not cleanup in dir: \"%s\", reason: %s",
path, GST_STR_NULL (error->message));
g_free (path);
}
g_error_free (error);
}
g_object_unref (dir);
}
static gboolean
_cache_cleanup_func (ClapperEnhancerDirector *self)
{
GMappedFile *mapped_file;
GDateTime *date;
GError *error = NULL;
gchar *filename;
const gchar *data;
gint64 since_cleanup, epoch_now, epoch_last = 0;
date = g_date_time_new_now_utc ();
epoch_now = g_date_time_to_unix (date);
g_date_time_unref (date);
filename = g_build_filename (g_get_user_cache_dir (), CLAPPER_API_NAME,
"enhancers", "cleanup.bin", NULL);
if ((mapped_file = clapper_cache_open (filename, &data, &error))) {
epoch_last = clapper_cache_read_int64 (&data);
g_mapped_file_unref (mapped_file);
} else if (error) {
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
GST_DEBUG_OBJECT (self, "No cache cleanup file found");
else
GST_ERROR_OBJECT (self, "Could not read cache cleanup file, reason: %s", error->message);
g_clear_error (&error);
}
since_cleanup = epoch_now - epoch_last;
if (since_cleanup >= CLEANUP_INTERVAL) {
ClapperEnhancerProxyList *proxies;
guint i, n_proxies;
GByteArray *bytes;
GST_TRACE_OBJECT (self, "Time for cache cleanup, last was %"
CLAPPER_TIME_FORMAT " ago", CLAPPER_TIME_ARGS (since_cleanup));
/* Start with writing to cache cleanup time,
* so other directors can find it earlier */
if ((bytes = clapper_cache_create ())) {
clapper_cache_store_int64 (bytes, epoch_now);
if (clapper_cache_write (filename, bytes, &error)) {
GST_TRACE_OBJECT (self, "Written data to cache cleanup file, cleanup time: %"
G_GINT64_FORMAT, epoch_now);
} else if (error) {
GST_ERROR_OBJECT (self, "Could not write cache cleanup data, reason: %s", error->message);
g_clear_error (&error);
}
g_byte_array_free (bytes, TRUE);
}
/* Now do cleanup */
proxies = clapper_get_global_enhancer_proxies ();
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
for (i = 0; i < n_proxies; ++i) {
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
if (!clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE))
continue;
_cache_proxy_harvests_cleanup (self, proxy, epoch_now);
}
} else {
GST_TRACE_OBJECT (self, "No cache cleanup yet, last was %"
CLAPPER_TIME_FORMAT " ago", CLAPPER_TIME_ARGS (since_cleanup));
}
g_free (filename);
return G_SOURCE_REMOVE;
}
/*
* clapper_enhancer_director_new:
*
@@ -116,19 +320,32 @@ clapper_enhancer_director_new (void)
}
ClapperHarvest *
clapper_enhancer_director_extract (ClapperEnhancerDirector *self, GUri *uri,
clapper_enhancer_director_extract (ClapperEnhancerDirector *self,
GList *filtered_proxies, GUri *uri,
GCancellable *cancellable, GError **error)
{
ClapperEnhancerDirectorData *data = g_new (ClapperEnhancerDirectorData, 1);
GMainContext *context;
ClapperHarvest *harvest;
data->director = self;
data->filtered_proxies = filtered_proxies;
data->uri = uri;
data->cancellable = cancellable;
data->error = error;
return CLAPPER_HARVEST_CAST (clapper_shared_utils_context_invoke_sync_full (
clapper_threaded_object_get_context (CLAPPER_THREADED_OBJECT_CAST (self)),
context = clapper_threaded_object_get_context (CLAPPER_THREADED_OBJECT_CAST (self));
harvest = CLAPPER_HARVEST_CAST (clapper_shared_utils_context_invoke_sync_full (context,
(GThreadFunc) clapper_enhancer_director_extract_in_thread,
data, (GDestroyNotify) g_free));
/* Run cleanup async. Since context belongs to "self", do not ref it.
* This ensures clean shutdown with thread stop function called. */
if (!g_cancellable_is_cancelled (cancellable) && !clapper_cache_is_disabled ())
g_main_context_invoke (context, (GSourceFunc) _cache_cleanup_func, self);
return harvest;
}
static void

View File

@@ -1,495 +0,0 @@
/* Clapper Playback Library
* Copyright (C) 2024 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "config.h"
#include "clapper-enhancer-src-private.h"
#include "clapper-enhancer-director-private.h"
#include "../clapper-extractable-private.h"
#include "../clapper-harvest-private.h"
#include "../clapper-enhancers-loader-private.h"
#define GST_CAT_DEFAULT clapper_enhancer_src_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
struct _ClapperEnhancerSrc
{
GstPushSrc parent;
GCancellable *cancellable;
gsize buf_size;
ClapperEnhancerDirector *director;
gchar *uri;
GUri *guri;
};
enum
{
PROP_0,
PROP_URI,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstURIType
clapper_enhancer_src_uri_handler_get_type (GType type)
{
return GST_URI_SRC;
}
static gpointer
_get_schemes_once (gpointer user_data G_GNUC_UNUSED)
{
return clapper_enhancers_loader_get_schemes (CLAPPER_TYPE_EXTRACTABLE);
}
static const gchar *const *
clapper_enhancer_src_uri_handler_get_protocols (GType type)
{
static GOnce schemes_once = G_ONCE_INIT;
g_once (&schemes_once, _get_schemes_once, NULL);
return (const gchar *const *) schemes_once.retval;
}
static gchar *
clapper_enhancer_src_uri_handler_get_uri (GstURIHandler *handler)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (handler);
gchar *uri;
GST_OBJECT_LOCK (self);
uri = g_strdup (self->uri);
GST_OBJECT_UNLOCK (self);
return uri;
}
static gboolean
clapper_enhancer_src_uri_handler_set_uri (GstURIHandler *handler,
const gchar *uri, GError **error)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (handler);
GUri *guri;
const gchar *const *protocols;
gboolean supported = FALSE;
guint i;
GST_DEBUG_OBJECT (self, "Changing URI to: %s", uri);
if (!uri) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"URI property cannot be NULL");
return FALSE;
}
if (GST_STATE (GST_ELEMENT_CAST (self)) >= GST_STATE_PAUSED) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
"Cannot change URI property while element is running");
return FALSE;
}
protocols = gst_uri_handler_get_protocols (handler);
for (i = 0; protocols[i]; ++i) {
if ((supported = gst_uri_has_protocol (uri, protocols[i])))
break;
}
if (!supported) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
"URI protocol is not supported");
return FALSE;
}
if (!(guri = g_uri_parse (uri, G_URI_FLAGS_ENCODED, NULL))) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"URI is invalid");
return FALSE;
}
if (!clapper_enhancers_loader_check (CLAPPER_TYPE_EXTRACTABLE,
g_uri_get_scheme (guri), g_uri_get_host (guri), NULL)) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"None of the available enhancers can handle this URI");
g_uri_unref (guri);
return FALSE;
}
GST_OBJECT_LOCK (self);
g_set_str (&self->uri, uri);
g_clear_pointer (&self->guri, g_uri_unref);
self->guri = guri;
GST_INFO_OBJECT (self, "URI changed to: \"%s\"", self->uri);
GST_OBJECT_UNLOCK (self);
return TRUE;
}
static void
_uri_handler_iface_init (GstURIHandlerInterface *iface)
{
iface->get_type = clapper_enhancer_src_uri_handler_get_type;
iface->get_protocols = clapper_enhancer_src_uri_handler_get_protocols;
iface->get_uri = clapper_enhancer_src_uri_handler_get_uri;
iface->set_uri = clapper_enhancer_src_uri_handler_set_uri;
}
#define parent_class clapper_enhancer_src_parent_class
G_DEFINE_TYPE_WITH_CODE (ClapperEnhancerSrc, clapper_enhancer_src, GST_TYPE_PUSH_SRC,
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, _uri_handler_iface_init));
GST_ELEMENT_REGISTER_DEFINE (clapperenhancersrc, "clapperenhancersrc",
512, CLAPPER_TYPE_ENHANCER_SRC);
static gboolean
clapper_enhancer_src_start (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
gboolean can_start;
GST_DEBUG_OBJECT (self, "Start");
GST_OBJECT_LOCK (self);
can_start = (self->guri != NULL);
GST_OBJECT_UNLOCK (self);
if (G_UNLIKELY (!can_start)) {
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
("No media URI"), (NULL));
return FALSE;
}
return TRUE;
}
static gboolean
clapper_enhancer_src_stop (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
GST_DEBUG_OBJECT (self, "Stop");
self->buf_size = 0;
return TRUE;
}
static gboolean
clapper_enhancer_src_get_size (GstBaseSrc *base_src, guint64 *size)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
if (self->buf_size > 0) {
*size = self->buf_size;
return TRUE;
}
return FALSE;
}
static gboolean
clapper_enhancer_src_is_seekable (GstBaseSrc *base_src)
{
return FALSE;
}
static gboolean
clapper_enhancer_src_unlock (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
GST_LOG_OBJECT (self, "Cancel triggered");
g_cancellable_cancel (self->cancellable);
return TRUE;
}
static gboolean
clapper_enhancer_src_unlock_stop (GstBaseSrc *base_src)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (base_src);
GST_LOG_OBJECT (self, "Resetting cancellable");
g_object_unref (self->cancellable);
self->cancellable = g_cancellable_new ();
return TRUE;
}
/* Pushes tags, toc and request headers downstream (all transfer full) */
static void
_push_events (ClapperEnhancerSrc *self, GstTagList *tags, GstToc *toc,
GstStructure *headers, gboolean updated)
{
GstEvent *event;
if (tags) {
if (!gst_tag_list_is_empty (tags)) {
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, tags);
/* XXX: Normally, we should only be posting event to make it reach
* the app after stream start, but currently it is lost that way */
gst_element_post_message (GST_ELEMENT (self),
gst_message_new_tag (GST_OBJECT_CAST (self), tags));
} else {
gst_tag_list_unref (tags);
}
}
if (toc) {
if (g_list_length (gst_toc_get_entries (toc)) > 0) {
GST_DEBUG_OBJECT (self, "Pushing TOC"); // TOC is not printable
/* XXX: Normally, we should only be posting event to make it reach
* the app after stream start, but currently it is lost that way */
gst_element_post_message (GST_ELEMENT (self),
gst_message_new_toc (GST_OBJECT_CAST (self), toc, updated));
}
gst_toc_unref (toc);
}
if (headers) {
GstStructure *http_headers;
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, headers);
http_headers = gst_structure_new ("http-headers",
"request-headers", GST_TYPE_STRUCTURE, headers,
NULL);
gst_structure_free (headers);
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers);
gst_pad_push_event (GST_BASE_SRC_PAD (self), event);
}
GST_DEBUG_OBJECT (self, "Pushed all events");
}
static GstFlowReturn
clapper_enhancer_src_create (GstPushSrc *push_src, GstBuffer **outbuf)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (push_src);
GUri *guri;
GCancellable *cancellable;
ClapperHarvest *harvest;
GstCaps *caps = NULL;
GstTagList *tags = NULL;
GstToc *toc = NULL;
GstStructure *headers = NULL;
GError *error = NULL;
gboolean unpacked;
/* When non-zero, we already returned complete data */
if (self->buf_size > 0)
return GST_FLOW_EOS;
/* Ensure director is created. Since it spins up its own
* thread, create it here as we know that it will be used. */
if (!self->director)
self->director = clapper_enhancer_director_new ();
GST_OBJECT_LOCK (self);
guri = g_uri_ref (self->guri);
cancellable = g_object_ref (self->cancellable);
GST_OBJECT_UNLOCK (self);
harvest = clapper_enhancer_director_extract (self->director, guri, cancellable, &error);
g_uri_unref (guri);
g_object_unref (cancellable);
if (!harvest) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("%s", error->message), (NULL));
g_clear_error (&error);
return GST_FLOW_ERROR;
}
unpacked = clapper_harvest_unpack (harvest, outbuf, &self->buf_size,
&caps, &tags, &toc, &headers);
gst_object_unref (harvest);
if (!unpacked) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Extraction harvest is empty"), (NULL));
return GST_FLOW_ERROR;
}
if (gst_base_src_set_caps (GST_BASE_SRC (self), caps))
GST_INFO_OBJECT (self, "Using caps: %" GST_PTR_FORMAT, caps);
else
GST_ERROR_OBJECT (self, "Current caps could not be set");
gst_clear_caps (&caps);
/* Now push all events before buffer */
_push_events (self, tags, toc, headers, FALSE);
return GST_FLOW_OK;
}
static inline gboolean
_handle_uri_query (GstQuery *query)
{
/* Since our URI does not actually lead to manifest data, we answer
* with "nodata" equivalent, so upstream will not try to fetch it */
gst_query_set_uri (query, "data:,");
return TRUE;
}
static gboolean
clapper_enhancer_src_query (GstBaseSrc *base_src, GstQuery *query)
{
gboolean ret = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_URI:
ret = _handle_uri_query (query);
break;
default:
break;
}
if (!ret)
ret = GST_BASE_SRC_CLASS (parent_class)->query (base_src, query);
return ret;
}
static void
clapper_enhancer_src_init (ClapperEnhancerSrc *self)
{
self->cancellable = g_cancellable_new ();
}
static void
clapper_enhancer_src_dispose (GObject *object)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
GST_OBJECT_LOCK (self);
g_clear_object (&self->director);
GST_OBJECT_UNLOCK (self);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_enhancer_src_finalize (GObject *object)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
g_clear_object (&self->cancellable);
g_free (self->uri);
g_clear_pointer (&self->guri, g_uri_unref);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_enhancer_src_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
switch (prop_id) {
case PROP_URI:{
GError *error = NULL;
if (!gst_uri_handler_set_uri (GST_URI_HANDLER (self),
g_value_get_string (value), &error)) {
GST_ERROR_OBJECT (self, "%s", error->message);
g_error_free (error);
}
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_enhancer_src_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperEnhancerSrc *self = CLAPPER_ENHANCER_SRC_CAST (object);
switch (prop_id) {
case PROP_URI:
g_value_take_string (value, gst_uri_handler_get_uri (GST_URI_HANDLER (self)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_enhancer_src_class_init (ClapperEnhancerSrcClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersrc", 0,
"Clapper Enhancer Source");
gobject_class->set_property = clapper_enhancer_src_set_property;
gobject_class->get_property = clapper_enhancer_src_get_property;
gobject_class->dispose = clapper_enhancer_src_dispose;
gobject_class->finalize = clapper_enhancer_src_finalize;
gstbasesrc_class->start = clapper_enhancer_src_start;
gstbasesrc_class->stop = clapper_enhancer_src_stop;
gstbasesrc_class->get_size = clapper_enhancer_src_get_size;
gstbasesrc_class->is_seekable = clapper_enhancer_src_is_seekable;
gstbasesrc_class->unlock = clapper_enhancer_src_unlock;
gstbasesrc_class->unlock_stop = clapper_enhancer_src_unlock_stop;
gstbasesrc_class->query = clapper_enhancer_src_query;
gstpushsrc_class->create = clapper_enhancer_src_create;
param_specs[PROP_URI] = g_param_spec_string ("uri",
"URI", "URI", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
gst_element_class_set_static_metadata (gstelement_class, "Clapper Enhancer Source",
"Source", "A source element that uses Clapper Enhancers to produce data",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
}

View File

@@ -20,17 +20,18 @@
#pragma once
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <gst/base/gstpushsrc.h>
G_BEGIN_DECLS
#define CLAPPER_TYPE_ENHANCER_SRC (clapper_enhancer_src_get_type())
#define CLAPPER_ENHANCER_SRC_CAST(obj) ((ClapperEnhancerSrc *)(obj))
#define CLAPPER_TYPE_EXTRACTABLE_SRC (clapper_extractable_src_get_type())
#define CLAPPER_EXTRACTABLE_SRC_CAST(obj) ((ClapperExtractableSrc *)(obj))
G_GNUC_INTERNAL
G_DECLARE_FINAL_TYPE (ClapperEnhancerSrc, clapper_enhancer_src, CLAPPER, ENHANCER_SRC, GstPushSrc)
G_DECLARE_FINAL_TYPE (ClapperExtractableSrc, clapper_extractable_src, CLAPPER, EXTRACTABLE_SRC, GstPushSrc)
GST_ELEMENT_REGISTER_DECLARE (clapperenhancersrc)
GST_ELEMENT_REGISTER_DECLARE (clapperextractablesrc)
G_END_DECLS

View File

@@ -0,0 +1,700 @@
/* Clapper Playback Library
* Copyright (C) 2024 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 Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser 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 "config.h"
#include "clapper-extractable-src-private.h"
#include "clapper-enhancer-director-private.h"
#include "../clapper-basic-functions.h"
#include "../clapper-enhancer-proxy.h"
#include "../clapper-enhancer-proxy-list.h"
#include "../clapper-extractable.h"
#include "../clapper-harvest-private.h"
#define GST_CAT_DEFAULT clapper_extractable_src_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define CHECK_SCHEME_IS_HTTPS(scheme) (g_str_has_prefix (scheme, "http") \
&& (scheme[4] == '\0' || (scheme[4] == 's' && scheme[5] == '\0')))
struct _ClapperExtractableSrc
{
GstPushSrc parent;
GCancellable *cancellable;
gsize buf_size;
ClapperEnhancerDirector *director;
gchar *uri;
GUri *guri;
ClapperEnhancerProxyList *enhancer_proxies;
};
enum
{
PROP_0,
PROP_URI,
PROP_ENHANCER_PROXIES,
PROP_LAST
};
static GParamSpec *param_specs[PROP_LAST] = { NULL, };
static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS_ANY);
static GstURIType
clapper_extractable_src_uri_handler_get_type (GType type)
{
return GST_URI_SRC;
}
/*
* _make_schemes:
*
* Make supported schemes array for a given interface type.
* The returned array consists of unique strings (no duplicates).
*
* Returns: (transfer full): all supported schemes by enhancers of @iface_type.
*/
static gchar **
_make_schemes (gpointer user_data G_GNUC_UNUSED)
{
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
GSList *found_schemes = NULL, *fs;
gchar **schemes_strv;
guint i, n_schemes, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
GST_DEBUG ("Checking for supported URI schemes");
for (i = 0; i < n_proxies; ++i) {
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
const gchar *schemes;
if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE)
&& (schemes = clapper_enhancer_proxy_get_extra_data (proxy, "X-Schemes"))) {
gchar **tmp_strv;
gint j;
tmp_strv = g_strsplit (schemes, ";", 0);
for (j = 0; tmp_strv[j]; ++j) {
const gchar *scheme = tmp_strv[j];
if (!found_schemes || !g_slist_find_custom (found_schemes,
scheme, (GCompareFunc) strcmp)) {
found_schemes = g_slist_append (found_schemes, g_strdup (scheme));
GST_INFO ("Found supported URI scheme: \"%s\"", scheme);
}
}
g_strfreev (tmp_strv);
}
}
n_schemes = g_slist_length (found_schemes);
schemes_strv = g_new0 (gchar *, n_schemes + 1);
fs = found_schemes;
for (i = 0; i < n_schemes; ++i) {
schemes_strv[i] = fs->data;
fs = fs->next;
}
GST_DEBUG ("Total found URI schemes: %u", n_schemes);
/* Since string pointers were taken,
* free list without content */
g_slist_free (found_schemes);
return schemes_strv;
}
static inline const gchar *
_host_fixup (const gchar *host)
{
/* Strip common subdomains, so plugins do not
* have to list all combinations */
if (g_str_has_prefix (host, "www."))
host += 4;
else if (g_str_has_prefix (host, "m."))
host += 2;
return host;
}
/*
* _extractable_check_for_uri:
* @self: a #ClapperExtractableSrc
* @uri: a #GUri
*
* Check whether there is at least one extractable enhancer for @uri in global list.
* This is used to reject URI early, thus making playbin choose different
* source element. It uses global list, since at this stage element is not
* yet placed within pipeline, so it cannot get proxies from player.
*
* Returns: whether at least one extractable enhancer advertises support for given URI.
*/
static gboolean
_extractable_check_for_uri (ClapperExtractableSrc *self, GUri *uri)
{
ClapperEnhancerProxyList *proxies = clapper_get_global_enhancer_proxies ();
gboolean is_https;
guint i, n_proxies;
const gchar *scheme = g_uri_get_scheme (uri);
const gchar *host = g_uri_get_host (uri);
if (host)
host = _host_fixup (host);
GST_INFO_OBJECT (self, "Extractable check, scheme: \"%s\", host: \"%s\"",
scheme, GST_STR_NULL (host));
/* Whether "http(s)" scheme is used */
is_https = CHECK_SCHEME_IS_HTTPS (scheme);
if (!host && is_https)
return FALSE;
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
for (i = 0; i < n_proxies; ++i) {
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE)
&& clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme)
&& (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host)))
return TRUE;
}
return FALSE;
}
/*
* _filter_extractables_for_uri:
* @self: a #ClapperExtractableSrc
* @proxies: a #ClapperEnhancerProxyList
* @uri: a #GUri
*
* Finds all enhancer proxies of target implementing "Extractable"
* interface, which advertise support for given @uri.
*
* Returns: (transfer full): A sublist in the form of #GList with proxies.
*/
static GList *
_filter_extractables_for_uri (ClapperExtractableSrc *self,
ClapperEnhancerProxyList *proxies, GUri *uri)
{
GList *sublist = NULL;
guint i, n_proxies;
gboolean is_https;
const gchar *scheme = g_uri_get_scheme (uri);
const gchar *host = g_uri_get_host (uri);
if (host)
host = _host_fixup (host);
GST_INFO_OBJECT (self, "Extractable filter, scheme: \"%s\", host: \"%s\"",
scheme, GST_STR_NULL (host));
/* Whether "http(s)" scheme is used */
is_https = CHECK_SCHEME_IS_HTTPS (scheme);
if (!host && is_https)
return NULL;
n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
for (i = 0; i < n_proxies; ++i) {
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
if (clapper_enhancer_proxy_target_has_interface (proxy, CLAPPER_TYPE_EXTRACTABLE)
&& clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Schemes", scheme)
&& (!is_https || clapper_enhancer_proxy_extra_data_lists_value (proxy, "X-Hosts", host))) {
sublist = g_list_append (sublist, gst_object_ref (proxy));
break;
}
}
return sublist;
}
static const gchar *const *
clapper_extractable_src_uri_handler_get_protocols (GType type)
{
static GOnce schemes_once = G_ONCE_INIT;
g_once (&schemes_once, (GThreadFunc) _make_schemes, NULL);
return (const gchar *const *) schemes_once.retval;
}
static gchar *
clapper_extractable_src_uri_handler_get_uri (GstURIHandler *handler)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (handler);
gchar *uri;
GST_OBJECT_LOCK (self);
uri = g_strdup (self->uri);
GST_OBJECT_UNLOCK (self);
return uri;
}
static gboolean
clapper_extractable_src_uri_handler_set_uri (GstURIHandler *handler,
const gchar *uri, GError **error)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (handler);
GUri *guri;
const gchar *const *protocols;
gboolean supported = FALSE;
guint i;
GST_DEBUG_OBJECT (self, "Changing URI to: %s", uri);
if (!uri) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"URI property cannot be NULL");
return FALSE;
}
if (GST_STATE (GST_ELEMENT_CAST (self)) >= GST_STATE_PAUSED) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
"Cannot change URI property while element is running");
return FALSE;
}
protocols = gst_uri_handler_get_protocols (handler);
for (i = 0; protocols[i]; ++i) {
if ((supported = gst_uri_has_protocol (uri, protocols[i])))
break;
}
if (!supported) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_UNSUPPORTED_PROTOCOL,
"URI protocol is not supported");
return FALSE;
}
if (!(guri = g_uri_parse (uri, G_URI_FLAGS_ENCODED, NULL))) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"URI is invalid");
return FALSE;
}
if (!_extractable_check_for_uri (self, guri)) {
g_set_error (error, GST_URI_ERROR, GST_URI_ERROR_BAD_URI,
"None of the available enhancers can handle this URI");
g_uri_unref (guri);
return FALSE;
}
GST_OBJECT_LOCK (self);
g_set_str (&self->uri, uri);
g_clear_pointer (&self->guri, g_uri_unref);
self->guri = guri;
GST_INFO_OBJECT (self, "URI changed to: \"%s\"", self->uri);
GST_OBJECT_UNLOCK (self);
return TRUE;
}
static void
_uri_handler_iface_init (GstURIHandlerInterface *iface)
{
iface->get_type = clapper_extractable_src_uri_handler_get_type;
iface->get_protocols = clapper_extractable_src_uri_handler_get_protocols;
iface->get_uri = clapper_extractable_src_uri_handler_get_uri;
iface->set_uri = clapper_extractable_src_uri_handler_set_uri;
}
#define parent_class clapper_extractable_src_parent_class
G_DEFINE_TYPE_WITH_CODE (ClapperExtractableSrc, clapper_extractable_src, GST_TYPE_PUSH_SRC,
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, _uri_handler_iface_init));
GST_ELEMENT_REGISTER_DEFINE (clapperextractablesrc, "clapperextractablesrc",
512, CLAPPER_TYPE_EXTRACTABLE_SRC);
static gboolean
clapper_extractable_src_start (GstBaseSrc *base_src)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
gboolean can_start;
GST_DEBUG_OBJECT (self, "Start");
GST_OBJECT_LOCK (self);
can_start = (self->guri != NULL);
GST_OBJECT_UNLOCK (self);
if (G_UNLIKELY (!can_start)) {
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ,
("No media URI"), (NULL));
return FALSE;
}
return TRUE;
}
static gboolean
clapper_extractable_src_stop (GstBaseSrc *base_src)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
GST_DEBUG_OBJECT (self, "Stop");
self->buf_size = 0;
return TRUE;
}
static gboolean
clapper_extractable_src_get_size (GstBaseSrc *base_src, guint64 *size)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
if (self->buf_size > 0) {
*size = self->buf_size;
return TRUE;
}
return FALSE;
}
static gboolean
clapper_extractable_src_is_seekable (GstBaseSrc *base_src)
{
return FALSE;
}
static gboolean
clapper_extractable_src_unlock (GstBaseSrc *base_src)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
GST_LOG_OBJECT (self, "Cancel triggered");
g_cancellable_cancel (self->cancellable);
return TRUE;
}
static gboolean
clapper_extractable_src_unlock_stop (GstBaseSrc *base_src)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (base_src);
GST_LOG_OBJECT (self, "Resetting cancellable");
g_object_unref (self->cancellable);
self->cancellable = g_cancellable_new ();
return TRUE;
}
/* Pushes tags, toc and request headers downstream (all transfer full) */
static void
_push_events (ClapperExtractableSrc *self, GstTagList *tags, GstToc *toc,
GstStructure *headers, gboolean updated)
{
GstEvent *event;
if (tags) {
if (!gst_tag_list_is_empty (tags)) {
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, tags);
/* XXX: Normally, we should only be posting event to make it reach
* the app after stream start, but currently it is lost that way */
gst_element_post_message (GST_ELEMENT (self),
gst_message_new_tag (GST_OBJECT_CAST (self), tags));
} else {
gst_tag_list_unref (tags);
}
}
if (toc) {
if (g_list_length (gst_toc_get_entries (toc)) > 0) {
GST_DEBUG_OBJECT (self, "Pushing TOC"); // TOC is not printable
/* XXX: Normally, we should only be posting event to make it reach
* the app after stream start, but currently it is lost that way */
gst_element_post_message (GST_ELEMENT (self),
gst_message_new_toc (GST_OBJECT_CAST (self), toc, updated));
}
gst_toc_unref (toc);
}
if (headers) {
GstStructure *http_headers;
GST_DEBUG_OBJECT (self, "Pushing %" GST_PTR_FORMAT, headers);
http_headers = gst_structure_new ("http-headers",
"request-headers", GST_TYPE_STRUCTURE, headers,
NULL);
gst_structure_free (headers);
event = gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers);
gst_pad_push_event (GST_BASE_SRC_PAD (self), event);
}
GST_DEBUG_OBJECT (self, "Pushed all events");
}
static GstFlowReturn
clapper_extractable_src_create (GstPushSrc *push_src, GstBuffer **outbuf)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (push_src);
ClapperEnhancerProxyList *proxies;
GList *filtered_proxies;
GUri *guri;
GCancellable *cancellable;
ClapperHarvest *harvest;
GstCaps *caps = NULL;
GstTagList *tags = NULL;
GstToc *toc = NULL;
GstStructure *headers = NULL;
GError *error = NULL;
gboolean unpacked;
/* When non-zero, we already returned complete data */
if (self->buf_size > 0)
return GST_FLOW_EOS;
/* Ensure director is created. Since it spins up its own
* thread, create it here as we know that it will be used. */
if (!self->director)
self->director = clapper_enhancer_director_new ();
GST_OBJECT_LOCK (self);
if (G_LIKELY (self->enhancer_proxies != NULL)) {
GST_INFO_OBJECT (self, "Using enhancer proxies: %" GST_PTR_FORMAT, self->enhancer_proxies);
proxies = gst_object_ref (self->enhancer_proxies);
} else {
/* Compat for old ClapperDiscoverer feature that does not set this property */
GST_WARNING_OBJECT (self, "Falling back to using global enhancer proxy list!");
proxies = gst_object_ref (clapper_get_global_enhancer_proxies ());
}
guri = g_uri_ref (self->guri);
cancellable = g_object_ref (self->cancellable);
GST_OBJECT_UNLOCK (self);
filtered_proxies = _filter_extractables_for_uri (self, proxies, guri);
gst_object_unref (proxies);
harvest = clapper_enhancer_director_extract (self->director,
filtered_proxies, guri, cancellable, &error);
g_clear_list (&filtered_proxies, gst_object_unref);
g_uri_unref (guri);
g_object_unref (cancellable);
if (!harvest) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("%s", error->message), (NULL));
g_clear_error (&error);
return GST_FLOW_ERROR;
}
unpacked = clapper_harvest_unpack (harvest, outbuf, &self->buf_size,
&caps, &tags, &toc, &headers);
gst_object_unref (harvest);
if (!unpacked) {
GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND,
("Extraction harvest is empty"), (NULL));
return GST_FLOW_ERROR;
}
if (gst_base_src_set_caps (GST_BASE_SRC (self), caps))
GST_INFO_OBJECT (self, "Using caps: %" GST_PTR_FORMAT, caps);
else
GST_ERROR_OBJECT (self, "Current caps could not be set");
gst_clear_caps (&caps);
/* Now push all events before buffer */
_push_events (self, tags, toc, headers, FALSE);
return GST_FLOW_OK;
}
static inline gboolean
_handle_uri_query (GstQuery *query)
{
/* Since our URI does not actually lead to manifest data, we answer
* with "nodata" equivalent, so upstream will not try to fetch it */
gst_query_set_uri (query, "data:,");
return TRUE;
}
static gboolean
clapper_extractable_src_query (GstBaseSrc *base_src, GstQuery *query)
{
gboolean ret = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_URI:
ret = _handle_uri_query (query);
break;
default:
break;
}
if (!ret)
ret = GST_BASE_SRC_CLASS (parent_class)->query (base_src, query);
return ret;
}
static void
clapper_extractable_src_set_enhancer_proxies (ClapperExtractableSrc *self,
ClapperEnhancerProxyList *enhancer_proxies)
{
GST_OBJECT_LOCK (self);
gst_object_replace ((GstObject **) &self->enhancer_proxies,
GST_OBJECT_CAST (enhancer_proxies));
GST_OBJECT_UNLOCK (self);
}
static void
clapper_extractable_src_init (ClapperExtractableSrc *self)
{
self->cancellable = g_cancellable_new ();
}
static void
clapper_extractable_src_dispose (GObject *object)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
GST_OBJECT_LOCK (self);
g_clear_object (&self->director);
GST_OBJECT_UNLOCK (self);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
clapper_extractable_src_finalize (GObject *object)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
GST_TRACE_OBJECT (self, "Finalize");
g_clear_object (&self->cancellable);
g_free (self->uri);
g_clear_pointer (&self->guri, g_uri_unref);
gst_clear_object (&self->enhancer_proxies);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
clapper_extractable_src_set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
switch (prop_id) {
case PROP_URI:{
GError *error = NULL;
if (!gst_uri_handler_set_uri (GST_URI_HANDLER (self),
g_value_get_string (value), &error)) {
GST_ERROR_OBJECT (self, "%s", error->message);
g_error_free (error);
}
break;
}
case PROP_ENHANCER_PROXIES:
clapper_extractable_src_set_enhancer_proxies (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_extractable_src_get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
ClapperExtractableSrc *self = CLAPPER_EXTRACTABLE_SRC_CAST (object);
switch (prop_id) {
case PROP_URI:
g_value_take_string (value, gst_uri_handler_get_uri (GST_URI_HANDLER (self)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
clapper_extractable_src_class_init (ClapperExtractableSrcClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstElementClass *gstelement_class = (GstElementClass *) klass;
GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass;
GstPushSrcClass *gstpushsrc_class = (GstPushSrcClass *) klass;
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperextractablesrc", 0,
"Clapper Extractable Source");
gobject_class->set_property = clapper_extractable_src_set_property;
gobject_class->get_property = clapper_extractable_src_get_property;
gobject_class->dispose = clapper_extractable_src_dispose;
gobject_class->finalize = clapper_extractable_src_finalize;
gstbasesrc_class->start = clapper_extractable_src_start;
gstbasesrc_class->stop = clapper_extractable_src_stop;
gstbasesrc_class->get_size = clapper_extractable_src_get_size;
gstbasesrc_class->is_seekable = clapper_extractable_src_is_seekable;
gstbasesrc_class->unlock = clapper_extractable_src_unlock;
gstbasesrc_class->unlock_stop = clapper_extractable_src_unlock_stop;
gstbasesrc_class->query = clapper_extractable_src_query;
gstpushsrc_class->create = clapper_extractable_src_create;
param_specs[PROP_URI] = g_param_spec_string ("uri",
"URI", "URI", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
param_specs[PROP_ENHANCER_PROXIES] = g_param_spec_object ("enhancer-proxies",
NULL, NULL, CLAPPER_TYPE_ENHANCER_PROXY_LIST,
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (gobject_class, PROP_LAST, param_specs);
gst_element_class_add_static_pad_template (gstelement_class, &src_template);
gst_element_class_set_static_metadata (gstelement_class, "Clapper Extractable Source",
"Source", "A source element that uses Clapper extractable enhancers to produce data",
"Rafał Dzięgiel <rafostar.github@gmail.com>");
}

View File

@@ -21,31 +21,53 @@
#include <gst/gst.h>
#include "../clapper-basic-functions.h"
#include "../clapper-enhancer-proxy.h"
#include "../clapper-enhancer-proxy-list.h"
#include "../clapper-extractable.h"
#include "clapper-plugin-private.h"
#include "../clapper-functionalities-availability.h"
#if CLAPPER_WITH_ENHANCERS_LOADER
#include "clapper-enhancer-src-private.h"
#include "../clapper-extractable-private.h"
#include "../clapper-enhancers-loader-private.h"
#endif
#include "clapper-extractable-src-private.h"
#include "clapper-uri-list-demux-private.h"
/*
* clapper_gst_plugin_has_enhancers:
* @iface_type: an interface #GType
*
* Check if any enhancer implementing given interface type is available.
*
* Returns: whether any enhancer was found.
*/
static gboolean
clapper_gst_plugin_has_enhancers (ClapperEnhancerProxyList *proxies, GType iface_type)
{
guint i, n_proxies = clapper_enhancer_proxy_list_get_n_proxies (proxies);
for (i = 0; i < n_proxies; ++i) {
ClapperEnhancerProxy *proxy = clapper_enhancer_proxy_list_peek_proxy (proxies, i);
if (clapper_enhancer_proxy_target_has_interface (proxy, iface_type))
return TRUE;
}
return FALSE;
}
gboolean
clapper_gst_plugin_init (GstPlugin *plugin)
{
gboolean res = FALSE;
ClapperEnhancerProxyList *global_proxies;
#if CLAPPER_WITH_ENHANCERS_LOADER
gst_plugin_add_dependency_simple (plugin,
"CLAPPER_ENHANCERS_PATH", CLAPPER_ENHANCERS_PATH, NULL,
GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY);
global_proxies = clapper_get_global_enhancer_proxies ();
/* Avoid registering an URI handler without schemes */
if (clapper_enhancers_loader_has_enhancers (CLAPPER_TYPE_EXTRACTABLE))
res |= GST_ELEMENT_REGISTER (clapperenhancersrc, plugin);
#endif
if (clapper_gst_plugin_has_enhancers (global_proxies, CLAPPER_TYPE_EXTRACTABLE))
res |= GST_ELEMENT_REGISTER (clapperextractablesrc, plugin);
res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin);

View File

@@ -20,6 +20,7 @@
#pragma once
#include <glib.h>
#include <glib-object.h>
#include <gst/gst.h>
#include <gst/gstbin.h>

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