pax_global_header00006660000000000000000000000064147435311070014517gustar00rootroot0000000000000052 comment=a449b6b3f5e9c1c6852b26d8387d448ca7d0e756 clapper-0.8.0/000077500000000000000000000000001474353110700131525ustar00rootroot00000000000000clapper-0.8.0/.github/000077500000000000000000000000001474353110700145125ustar00rootroot00000000000000clapper-0.8.0/.github/workflows/000077500000000000000000000000001474353110700165475ustar00rootroot00000000000000clapper-0.8.0/.github/workflows/documentation.yml000066400000000000000000000037551474353110700221550ustar00rootroot00000000000000on: workflow_dispatch: push: branches: - master pull_request: branches: - master name: "Documentation" jobs: documentation: runs-on: ubuntu-latest container: image: registry.fedoraproject.org/fedora options: --privileged steps: - name: Prepare run: | dnf -y install git gcc meson glib2-devel \ gstreamer1-devel gstreamer1-plugins-base-devel \ gtk4-devel libsoup3-devel libmicrodns-devel \ gobject-introspection-devel graphviz gi-docgen - name: Checkout uses: actions/checkout@v4 with: persist-credentials: false fetch-depth: 0 - name: Build run: | meson setup builddir -Dauto_features=enabled \ -Denhancers-loader=disabled \ -Dclapper-app=disabled -Dvapi=disabled -Ddoc=true \ -Dglimporter=auto -Dgluploader=auto -Drawimporter=auto cd builddir meson compile - name: Commit files if: ${{ success() && github.ref == 'refs/heads/master' }} continue-on-error: true run: | git config --global --add safe.directory $GITHUB_WORKSPACE git checkout gh-pages rm -rf doc/clapper/* rm -rf doc/clapper-gtk/* mv builddir/doc/reference/clapper/clapper.toml doc/clapper/ mv builddir/doc/reference/clapper/clapper/* doc/clapper/ mv builddir/doc/reference/clapper-gtk/clapper-gtk.toml doc/clapper-gtk/ mv builddir/doc/reference/clapper-gtk/clapper-gtk/* doc/clapper-gtk/ git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" git config --local user.name "github-actions[bot]" git add doc/clapper/* doc/clapper-gtk/* git commit -m "doc: Update" || true - name: Push changes uses: ad-m/github-push-action@master if: ${{ success() && github.ref == 'refs/heads/master' }} continue-on-error: true with: github_token: ${{ secrets.GITHUB_TOKEN }} branch: gh-pages clapper-0.8.0/.github/workflows/flatpak-nightly.yml000066400000000000000000000031601474353110700223700ustar00rootroot00000000000000on: workflow_dispatch: schedule: - cron: "0 0 * * *" 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] fail-fast: false steps: - 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: bundle: com.github.rafostar.Clapper.flatpak manifest-path: pkgs/flatpak/com.github.rafostar.Clapper-nightly.json repository-name: gnome-nightly repository-url: https://nightly.gnome.org/gnome-nightly.flatpakrepo cache-key: flatpak-builder-${{ github.sha }}-testing-${{ github.run_number }} arch: ${{ matrix.arch }} upload-artifact: false - name: Upload uses: actions/upload-artifact@v4 with: name: clapper-flatpak-nightly-${{ matrix.arch }} path: com.github.rafostar.Clapper.flatpak if-no-files-found: error clapper-0.8.0/.github/workflows/flatpak.yml000066400000000000000000000023111474353110700207110ustar00rootroot00000000000000on: workflow_dispatch: push: branches: - master pull_request: branches: - master 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] fail-fast: false steps: - 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: bundle: com.github.rafostar.Clapper.flatpak manifest-path: pkgs/flatpak/com.github.rafostar.Clapper.json cache-key: flatpak-builder-${{ github.sha }} arch: ${{ matrix.arch }} upload-artifact: false - name: Upload uses: actions/upload-artifact@v4 with: name: clapper-flatpak-${{ matrix.arch }} path: com.github.rafostar.Clapper.flatpak if-no-files-found: error clapper-0.8.0/.github/workflows/windows.yml000066400000000000000000000076211474353110700207720ustar00rootroot00000000000000on: workflow_dispatch: push: branches: - master pull_request: branches: - master name: "Windows" jobs: windows: name: "Windows" runs-on: windows-latest strategy: matrix: arch: [x86_64] defaults: run: shell: msys2 {0} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup MSYS2 uses: msys2/setup-msys2@v2 with: msystem: MINGW64 update: true install: |- mingw-w64-${{ matrix.arch }}-meson mingw-w64-${{ matrix.arch }}-gcc mingw-w64-${{ matrix.arch }}-glib2 mingw-w64-${{ matrix.arch }}-gstreamer mingw-w64-${{ matrix.arch }}-gst-plugins-base mingw-w64-${{ matrix.arch }}-gst-plugins-good mingw-w64-${{ matrix.arch }}-gst-plugins-bad mingw-w64-${{ matrix.arch }}-gst-plugins-ugly mingw-w64-${{ matrix.arch }}-gst-libav mingw-w64-${{ matrix.arch }}-libpeas2 mingw-w64-${{ matrix.arch }}-libsoup3 mingw-w64-${{ matrix.arch }}-libmicrodns mingw-w64-${{ matrix.arch }}-gtk4 mingw-w64-${{ matrix.arch }}-libadwaita - name: Prepare run: | BUILD_PREFIX="$GITHUB_WORKSPACE/clapper-win-${{ matrix.arch }}" mkdir -p $BUILD_PREFIX/bin cp /mingw64/bin/gdbus.exe $BUILD_PREFIX/bin/ cp /mingw64/bin/gst-inspect-1.0.exe $BUILD_PREFIX/bin/ mkdir -p $BUILD_PREFIX/lib cp -r /mingw64/lib/gio $BUILD_PREFIX/lib/ cp -r /mingw64/lib/gstreamer-1.0 $BUILD_PREFIX/lib/ cp -r /mingw64/lib/gdk-pixbuf-2.0 $BUILD_PREFIX/lib/ mkdir -p $BUILD_PREFIX/share/glib-2.0/schemas cp -r /mingw64/share/glib-2.0/schemas/*.xml $BUILD_PREFIX/share/glib-2.0/schemas/ cp -r /mingw64/share/icons $BUILD_PREFIX/share/ mkdir -p $BUILD_PREFIX/share/xml/iso-codes cp /mingw64/share/xml/iso-codes/iso_639.xml $BUILD_PREFIX/share/xml/iso-codes/ cd "$BUILD_PREFIX/lib/gstreamer-1.0" rm -f \ libgstadpcmenc.dll libgstamfcodec.dll libgstdvbsubenc.dll libgstencoding.dll \ libgstfrei0r.dll libgstinter.dll libgstlame.dll libgstldac.dll libgstmpeg2enc.dll \ libgstmpegpsmux.dll libgstmpegtsmux.dll libgstmplex.dll libgstrealmedia.dll \ libgstsubenc.dll libgstsvtav1.dll libgstsvthevcenc.dll libgsttwolame.dll \ libgstvoamrwbenc.dll libgstwavenc.dll libgstx264.dll libgstx265.dll \ libgstxingmux.dll libgsty4menc.dll libgstzbar.dll - name: Build run: | meson setup builddir --prefix=$GITHUB_WORKSPACE/clapper-win-${{ matrix.arch }} cd builddir meson compile meson install - name: Package run: | BUILD_PREFIX="$GITHUB_WORKSPACE/clapper-win-${{ matrix.arch }}" ldd $BUILD_PREFIX/bin/clapper.exe | grep '\/mingw.*\.dll' -o | xargs -I{} cp -n "{}" $BUILD_PREFIX/bin find $BUILD_PREFIX/lib/clapper-0.0/ -name '*\.dll' -type f -exec ldd "{}" \; | grep '\/mingw.*\.dll' -o | xargs -I{} cp -n "{}" $BUILD_PREFIX/bin find $BUILD_PREFIX/lib/gstreamer-1.0/ -name '*\.dll' -type f -exec ldd "{}" \; | grep '\/mingw.*\.dll' -o | xargs -I{} cp -n "{}" $BUILD_PREFIX/bin find $BUILD_PREFIX/lib/gio/ -name '*\.dll' -type f -exec ldd "{}" \; | grep '\/mingw.*\.dll' -o | xargs -I{} cp -n "{}" $BUILD_PREFIX/bin - name: Cleanup run: | BUILD_PREFIX="$GITHUB_WORKSPACE/clapper-win-${{ matrix.arch }}" rm -rf $BUILD_PREFIX/include rm -rf $BUILD_PREFIX/lib/pkgconfig - name: Installer uses: Minionguyjpro/Inno-Setup-Action@v1.2.5 with: path: builddir/pkgs/windows-installer/clapper.iss options: /O+ - name: Upload uses: actions/upload-artifact@v4 with: name: clapper-win-${{ matrix.arch }} path: builddir/pkgs/windows-installer/InstallerOutput/Clapper*.exe if-no-files-found: error clapper-0.8.0/.gitignore000066400000000000000000000001051474353110700151360ustar00rootroot00000000000000# meson/ninja build/ install/ builddir/ # flatpak .flatpak-builder/ clapper-0.8.0/.gitmodules000066400000000000000000000002021474353110700153210ustar00rootroot00000000000000[submodule "pkgs/flatpak/flathub"] path = pkgs/flatpak/flathub url = https://github.com/flathub/com.github.rafostar.Clapper.git clapper-0.8.0/.obs/000077500000000000000000000000001474353110700140135ustar00rootroot00000000000000clapper-0.8.0/.obs/workflows.yml000066400000000000000000000002571474353110700165770ustar00rootroot00000000000000rebuild_master: steps: - trigger_services: project: home:Rafostar package: clapper filters: event: push branches: only: - master clapper-0.8.0/COPYING-GPL000066400000000000000000001045151474353110700146330ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . clapper-0.8.0/COPYING-LGPL000066400000000000000000000636361474353110700147570ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! clapper-0.8.0/FUNDING.yml000066400000000000000000000000621474353110700147650ustar00rootroot00000000000000liberapay: Clapper custom: ['paypal.me/Rafostar'] clapper-0.8.0/README.md000066400000000000000000000112621474353110700144330ustar00rootroot00000000000000# Clapper [![Flatpak](https://github.com/Rafostar/clapper/actions/workflows/flatpak.yml/badge.svg?event=push)](https://github.com/Rafostar/clapper/actions/workflows/flatpak.yml) [![Flatpak Nightly](https://github.com/Rafostar/clapper/actions/workflows/flatpak-nightly.yml/badge.svg?event=schedule)](https://github.com/Rafostar/clapper/actions/workflows/flatpak-nightly.yml) [![Crowdin](https://badges.crowdin.net/clapper/localized.svg)](https://crowdin.com/project/clapper) [![Matrix](https://img.shields.io/matrix/clapper-player:matrix.org?label=matrix)](https://matrix.to/#/#clapper-player:matrix.org) [![Donate](https://img.shields.io/liberapay/receives/Clapper.svg?logo=liberapay)](https://liberapay.com/Clapper) Clapper is a modern media player designed for simplicity and ease of use. Powered by [GStreamer](https://gstreamer.freedesktop.org/) and built for the GNOME desktop environment using [GTK4](https://www.gtk.org/) toolkit, it has a clean and stylish interface that lets you focus on enjoying your favorite videos. This application aim is to offer all the essentials features you'd expect from a video player in a simple form.

Clapper uses a playback queue where you can add multiple media files. Think of it like a playlist that you can build. You can easily reorder items or remove them from the queue with a simple drag and drop operation.

### Components Clapper's codebase consists of 2 libraries using which main application is built: * [Clapper](https://rafostar.github.io/clapper/doc/clapper/) - a playback library * [ClapperGtk](https://rafostar.github.io/clapper/doc/clapper-gtk/) - a GTK integration library Both libraries support *GObject Introspection* bindings. A simple application example can be found [here](https://github.com/Rafostar/clapper-vala-test). Above libraries are licensed under `LGPL-2.1-or-later`. You are free to use them in your own projects as long as you comply with license terms. Please note that until version 1.0 they should be considered as an unstable API (some things may change without prior notice). Clapper `Vala` bindings are part of this repo, while `Rust` bindings can be found [here](https://gitlab.gnome.org/JanGernert/clapper-rs). ## Installation from Flatpak The `Flatpak` package includes all required dependencies and codecs. Additionally it also has a few patches, thus some functionalities work better in `Flatpak` version (until my changes are accepted upstream). List of patches used in this version can be found [here](https://github.com/Rafostar/clapper/issues/35). Download on Flathub ## Packages in Linux Distributions [![Packaging status](https://repology.org/badge/vertical-allrepos/clapper.svg)](https://repology.org/project/clapper/versions) Pre-built RPM packages are also available in [my repo](https://software.opensuse.org//download.html?project=home%3ARafostar&package=clapper) ([see status](https://build.opensuse.org/package/show/home:Rafostar/clapper)).
Those are automatically built on each git commit, thus are considered unstable. ## Installation from Source Code ```sh meson setup builddir cd builddir meson compile sudo meson install ``` If you want to compile app as `Flatpak`, remember to clone this repo with `--recurse-submodules` option. ## Questions? Feel free to ask me any questions. Come and talk on Matrix: [#clapper-player:matrix.org](https://matrix.to/#/#clapper-player:matrix.org) ## Translations Preferred translation method is to use [Clapper Crowdin](https://crowdin.com/project/clapper) web page. Crowdin does not require any additional tools and translating can be done through web browser. You can login using GitHub account or create a new one. Only I can add new languages to this project, so if your language is not available, please contact me first. ## Special Thanks Many thanks to [sp1ritCS](https://github.com/sp1ritCS) for creating and maintaining package build files. Big thanks to [bridadan](https://github.com/bridadan) and [Uniformbuffer3](https://github.com/Uniformbuffer3) for helping with testing V4L2 and NVDEC hardware acceleration methods. Also words of appreciation for [JanGernert](https://gitlab.gnome.org/JanGernert) who made and is sharing Clapper Rust bindings. Thanks a lot to all the people who are supporting the development with their anonymous donations through [Liberapay](https://liberapay.com/Clapper/). I :heart: U. clapper-0.8.0/_config.yml000066400000000000000000000000321474353110700152740ustar00rootroot00000000000000theme: jekyll-theme-caymanclapper-0.8.0/crowdin.yml000066400000000000000000000003411474353110700153400ustar00rootroot00000000000000files: - source: /src/lib/clapper-gtk/po/clapper-gtk.pot translation: /src/lib/clapper-gtk/po/%osx_locale%.po - source: /src/bin/clapper-app/po/clapper-app.pot translation: /src/bin/clapper-app/po/%osx_locale%.po clapper-0.8.0/doc/000077500000000000000000000000001474353110700137175ustar00rootroot00000000000000clapper-0.8.0/doc/meson.build000066400000000000000000000005541474353110700160650ustar00rootroot00000000000000gi_docgen = find_program('gi-docgen', required: get_option('doc')) dot = find_program('dot', required: get_option('doc')) # Class hierarchy generation build_doc = (gi_docgen.found() and dot.found() and get_option('doc')) if build_doc if not build_gir error('Building documentation requires introspection to be compiled') endif subdir('reference') endif clapper-0.8.0/doc/reference/000077500000000000000000000000001474353110700156555ustar00rootroot00000000000000clapper-0.8.0/doc/reference/clapper-gtk/000077500000000000000000000000001474353110700200665ustar00rootroot00000000000000clapper-0.8.0/doc/reference/clapper-gtk/clapper-gtk.toml.in000066400000000000000000000033661474353110700236110ustar00rootroot00000000000000[library] version = "@CLAPPER_VERSION@" browse_url = "https://github.com/Rafostar/clapper/" repository_url = "https://github.com/Rafostar/clapper.git" website_url = "https://rafostar.github.io/clapper/" docs_url = "https://rafostar.github.io/clapper/doc/clapper-gtk/" authors = "Rafał Dzięgiel" logo_url = "clapper-logo.svg" license = "LGPL-2.1-or-later" description = "Clapper GTK integration library" devhelp = true search_index = true dependencies = ["Clapper@CLAPPER_VERSION_SUFFIX@", "Gtk-4.0"] [dependencies."Clapper@CLAPPER_VERSION_SUFFIX@"] name = "Clapper" description = "Clapper playback library" docs_url = "https://rafostar.github.io/clapper/doc/clapper/" [dependencies."Gtk-4.0"] name = "Gtk" description = "The GTK toolkit" docs_url = "https://docs.gtk.org/gtk4/" related = ["GLib-2.0", "GObject-2.0", "Gio-2.0", "Gst-1.0"] [related."GLib-2.0"] name = "GLib" description = "A general-purpose, portable utility library" docs_url = "https://docs.gtk.org/glib/" [related."GObject-2.0"] name = "GObject" description = "The base type system library" docs_url = "https://docs.gtk.org/gobject/" [related."Gio-2.0"] name = "Gio" description = "GObject Interfaces and Objects, Networking, IPC, and I/O" docs_url = "https://docs.gtk.org/gio/" [related."Gst-1.0"] name = "Gst" description = "GStreamer core library" docs_url = "https://gstreamer.freedesktop.org/documentation/gstreamer/gi-index.html" [theme] name = "basic" show_index_summary = true show_class_hierarchy = true [source-location] base_url = "https://github.com/Rafostar/clapper/tree/master/" [extra] # The same order will be used when generating the index content_files = [ ] content_images = [ "images/clapper-logo.svg", ] urlmap_file = "urlmap.js" clapper-0.8.0/doc/reference/clapper-gtk/images/000077500000000000000000000000001474353110700213335ustar00rootroot00000000000000clapper-0.8.0/doc/reference/clapper-gtk/images/clapper-logo.svg000066400000000000000000000075311474353110700244460ustar00rootroot00000000000000 clapper-0.8.0/doc/reference/clapper-gtk/meson.build000066400000000000000000000015701474353110700222330ustar00rootroot00000000000000clappergtk_toml = configure_file( input: 'clapper-gtk.toml.in', output: 'clapper-gtk.toml', configuration: doc_version_conf, install: true, install_dir: join_paths(datadir, 'doc', 'clapper-gtk'), ) custom_target('clapper-gtk-doc', input: [ clappergtk_toml, clappergtk_gir[0], ], output: 'clapper-gtk', command: [ gi_docgen, 'generate', gi_docgen_common_args, '--add-include-path=@0@'.format(join_paths(meson.project_build_root(), 'src', 'lib', 'clapper')), '--add-include-path=@0@'.format(join_paths(meson.project_build_root(), 'src', 'lib', 'clapper-gtk')), '--config=@INPUT0@', '--output-dir=@OUTPUT@', '--content-dir=@0@'.format(meson.current_build_dir()), '--content-dir=@0@'.format(meson.current_source_dir()), '@INPUT1@', ], build_by_default: true, install: true, install_dir: join_paths(datadir, 'doc'), ) clapper-0.8.0/doc/reference/clapper-gtk/urlmap.js000066400000000000000000000005231474353110700217240ustar00rootroot00000000000000baseURLs = [ ['GLib', 'https://docs.gtk.org/glib/'], ['GObject', 'https://docs.gtk.org/gobject/'], ['Gio', 'https://docs.gtk.org/gio/'], ['Gtk', 'https://docs.gtk.org/gtk4/'], ['Gst', 'https://gstreamer.freedesktop.org/documentation/gstreamer/gi-index.html?'], ['Clapper', 'https://rafostar.github.io/clapper/doc/clapper/'], ] clapper-0.8.0/doc/reference/clapper/000077500000000000000000000000001474353110700173035ustar00rootroot00000000000000clapper-0.8.0/doc/reference/clapper/clapper.toml.in000066400000000000000000000042211474353110700222320ustar00rootroot00000000000000[library] version = "@CLAPPER_VERSION@" browse_url = "https://github.com/Rafostar/clapper/" repository_url = "https://github.com/Rafostar/clapper.git" website_url = "https://rafostar.github.io/clapper/" docs_url = "https://rafostar.github.io/clapper/doc/clapper/" authors = "Rafał Dzięgiel" logo_url = "clapper-logo.svg" license = "LGPL-2.1-or-later" description = "Clapper playback library" devhelp = true search_index = true dependencies = ["GLib-2.0", "GObject-2.0", "Gio-2.0", "Gst-1.0", "GstBase-1.0", "GstAudio-1.0", "GstTag-1.0", "GstPbutils-1.0"] [dependencies."GLib-2.0"] name = "GLib" description = "A general-purpose, portable utility library" docs_url = "https://docs.gtk.org/glib/" [dependencies."GObject-2.0"] name = "GObject" description = "The base type system library" docs_url = "https://docs.gtk.org/gobject/" [dependencies."Gio-2.0"] name = "Gio" description = "GObject Interfaces and Objects, Networking, IPC, and I/O" docs_url = "https://docs.gtk.org/gio/" [dependencies."Gst-1.0"] name = "Gst" description = "GStreamer core library" docs_url = "https://gstreamer.freedesktop.org/documentation/gstreamer/gi-index.html" [dependencies."GstBase-1.0"] name = "GstBase" description = "GStreamer base and utility classes" docs_url = "https://gstreamer.freedesktop.org/documentation/base/" [dependencies."GstAudio-1.0"] name = "GstAudio" description = "GStreamer audio library" docs_url = "https://gstreamer.freedesktop.org/documentation/audio/" [dependencies."GstTag-1.0"] name = "GstTag" description = "GStreamer tag support library" docs_url = "https://gstreamer.freedesktop.org/documentation/tag/" [dependencies."GstPbutils-1.0"] name = "GstPbutils" description = "GStreamer base utils library" docs_url = "https://gstreamer.freedesktop.org/documentation/pbutils/" related = [] [theme] name = "basic" show_index_summary = true show_class_hierarchy = true [source-location] base_url = "https://github.com/Rafostar/clapper/tree/master/" [extra] # The same order will be used when generating the index content_files = [ ] content_images = [ "images/clapper-logo.svg", ] urlmap_file = "urlmap.js" clapper-0.8.0/doc/reference/clapper/images/000077500000000000000000000000001474353110700205505ustar00rootroot00000000000000clapper-0.8.0/doc/reference/clapper/images/clapper-logo.svg000066400000000000000000000075311474353110700236630ustar00rootroot00000000000000 clapper-0.8.0/doc/reference/clapper/meson.build000066400000000000000000000013611474353110700214460ustar00rootroot00000000000000clapper_toml = configure_file( input: 'clapper.toml.in', output: 'clapper.toml', configuration: doc_version_conf, install: true, install_dir: join_paths(datadir, 'doc', 'clapper'), ) custom_target('clapper-doc', input: [ clapper_toml, clapper_gir[0], ], output: 'clapper', command: [ gi_docgen, 'generate', gi_docgen_common_args, '--add-include-path=@0@'.format(join_paths(meson.project_build_root(), 'src', 'lib', 'clapper')), '--config=@INPUT0@', '--output-dir=@OUTPUT@', '--content-dir=@0@'.format(meson.current_build_dir()), '--content-dir=@0@'.format(meson.current_source_dir()), '@INPUT1@', ], build_by_default: true, install: true, install_dir: join_paths(datadir, 'doc'), ) clapper-0.8.0/doc/reference/clapper/urlmap.js000066400000000000000000000010221474353110700211340ustar00rootroot00000000000000baseURLs = [ ['GLib', 'https://docs.gtk.org/glib/'], ['GObject', 'https://docs.gtk.org/gobject/'], ['Gio', 'https://docs.gtk.org/gio/'], ['Gst', 'https://gstreamer.freedesktop.org/documentation/gstreamer/gi-index.html?'], ['GstBase', 'https://gstreamer.freedesktop.org/documentation/base/?'], ['GstAudio', 'https://gstreamer.freedesktop.org/documentation/audio/?'], ['GstTag', 'https://gstreamer.freedesktop.org/documentation/tag/?'], ['GstPbutils', 'https://gstreamer.freedesktop.org/documentation/pbutils/?'], ] clapper-0.8.0/doc/reference/meson.build000066400000000000000000000006361474353110700200240ustar00rootroot00000000000000doc_version_conf = configuration_data() doc_version_conf.set('CLAPPER_VERSION', meson.project_version()) doc_version_conf.set('CLAPPER_VERSION_SUFFIX', clapper_version_suffix) gi_docgen_common_args = [ '--quiet', '--no-namespace-dir', ] if get_option('werror') gi_docgen_common_args += ['--fatal-warnings'] endif if build_clapper subdir('clapper') endif if build_clappergtk subdir('clapper-gtk') endif clapper-0.8.0/examples/000077500000000000000000000000001474353110700147705ustar00rootroot00000000000000clapper-0.8.0/examples/clapper-gtk/000077500000000000000000000000001474353110700172015ustar00rootroot00000000000000clapper-0.8.0/examples/clapper-gtk/download_cache/000077500000000000000000000000001474353110700221335ustar00rootroot00000000000000clapper-0.8.0/examples/clapper-gtk/download_cache/python/000077500000000000000000000000001474353110700234545ustar00rootroot00000000000000clapper-0.8.0/examples/clapper-gtk/download_cache/python/example.py000077500000000000000000000042141474353110700254650ustar00rootroot00000000000000#!/usr/bin/env python3 import gi gi.require_version('Adw', '1') gi.require_version('Clapper', '0.0') gi.require_version('ClapperGtk', '0.0') gi.require_version('GLib', '2.0') gi.require_version('Gtk', '4.0') from gi.repository import Adw, Clapper, ClapperGtk, GLib, Gtk import shutil Clapper.init(None) download_dir = GLib.build_filenamev([GLib.get_user_cache_dir(), "example_download_dir", None]) print('Using cache directory: {0}'.format(download_dir)) def on_download_complete(player, item, location): # Media downloaded. Data from this file is still used for current playback (including seeking). print('Download complete: {0} => {1}'.format(item.props.uri, location)) def on_activate(app): win = Gtk.ApplicationWindow(application=app, default_width=640, default_height=396) video = ClapperGtk.Video() controls = ClapperGtk.SimpleControls(fullscreenable=False) # Enable local storage caching and monitor it video.props.player.set_download_dir(download_dir) video.props.player.set_download_enabled(True) video.props.player.connect('download-complete', on_download_complete) # Configure playback video.props.player.props.queue.set_progression_mode(Clapper.QueueProgressionMode.CAROUSEL) video.props.player.set_autoplay(True) # Assemble window video.add_fading_overlay(controls) win.set_child(video) win.present() # Create and add media for playback item_1 = Clapper.MediaItem(uri='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4') item_2 = Clapper.MediaItem(uri='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4') video.props.player.props.queue.add_item(item_1) video.props.player.props.queue.add_item(item_2) # Create a new application app = Adw.Application(application_id='com.example.ClapperDownloadCache') app.connect('activate', on_activate) # Run the application app.run(None) # Finally app should cleanup before exit. Possibly moving data to # another dir if it wants to use it on next run and deleting what's # left (so any unfinished downloads will also be removed). print('Cleanup') shutil.rmtree(download_dir) clapper-0.8.0/examples/clapper-gtk/simple/000077500000000000000000000000001474353110700204725ustar00rootroot00000000000000clapper-0.8.0/examples/clapper-gtk/simple/python/000077500000000000000000000000001474353110700220135ustar00rootroot00000000000000clapper-0.8.0/examples/clapper-gtk/simple/python/example.py000077500000000000000000000030261474353110700240240ustar00rootroot00000000000000#!/usr/bin/env python3 import gi gi.require_version('Adw', '1') gi.require_version('Clapper', '0.0') gi.require_version('ClapperGtk', '0.0') gi.require_version('Gtk', '4.0') from gi.repository import Adw, Clapper, ClapperGtk, Gtk Clapper.init(None) def on_toggle_fullscreen(video, win): # Since this example uses only video inside normal window, all we # need to toggle fullscreen is to invert its fullscreened property win.props.fullscreened ^= True def on_activate(app): win = Gtk.ApplicationWindow(application=app, default_width=640, default_height=396) video = ClapperGtk.Video() controls = ClapperGtk.SimpleControls() # This signal will be emitted when user requests fullscreen state change. # It is app job to fullscreen video only (which might require something # more than simply inverting fullscreen on the whole window). video.connect('toggle-fullscreen', on_toggle_fullscreen, win) # Create and add media for playback. First added media item to empty # playback queue will be automatically selected. item = Clapper.MediaItem(uri='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4') video.props.player.props.queue.add_item(item) # Assemble window video.add_fading_overlay(controls) win.set_child(video) win.present() # Start playback video.props.player.play() # Create a new application app = Adw.Application(application_id='com.example.ClapperSimple') app.connect('activate', on_activate) # Run the application app.run(None) clapper-0.8.0/meson.build000066400000000000000000000100071474353110700153120ustar00rootroot00000000000000project('clapper', 'c', version: '0.8.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: [ 'warning_level=1', 'buildtype=debugoptimized', ], ) glib_req = '>= 2.76.0' gst_req = '>= 1.24.0' gtk4_req = '>= 4.10.0' adw_req = '>= 1.4.0' clapper_version = meson.project_version().split('-')[0] version_array = clapper_version.split('.') clapper_version_suffix = '-' + version_array[0] + '.0' clapper_api_name = meson.project_name() + clapper_version_suffix devenv = environment() gnome = import('gnome') pkgconfig = import('pkgconfig') i18n = import('i18n') prefix = get_option('prefix') bindir = get_option('bindir') datadir = get_option('datadir') libdir = get_option('libdir') localedir = get_option('localedir') includedir = get_option('includedir') optimization = get_option('optimization') clapper_libdir = join_paths(prefix, libdir, clapper_api_name) build_optimized = optimization in ['2', '3', 's'] gst_dep = dependency('gstreamer-1.0', version: gst_req, required: false, ) gst_base_dep = dependency('gstreamer-base-1.0', version: gst_req, required: false, ) gst_video_dep = dependency('gstreamer-video-1.0', version: gst_req, required: false, ) gst_audio_dep = dependency('gstreamer-audio-1.0', version: gst_req, required: false, ) gst_pbutils_dep = dependency('gstreamer-pbutils-1.0', version: gst_req, required: false, ) gst_tag_dep = dependency('gstreamer-tag-1.0', version: gst_req, required: false, ) glib_dep = dependency('glib-2.0', version: glib_req, required: false, ) gobject_dep = dependency('gobject-2.0', version: glib_req, required: false, ) gio_dep = dependency('gio-2.0', version: glib_req, required: false, ) gmodule_dep = dependency('gmodule-2.0', version: glib_req, required: false, ) gtk4_dep = dependency('gtk4', version: gtk4_req, required: false, ) libadwaita_dep = dependency('libadwaita-1', version: adw_req, required: false, ) # Optional peas_dep = dependency('libpeas-2', required: false, ) cc = meson.get_compiler('c') libm = cc.find_library('m', required: false) warning_flags = [ '-Wmissing-declarations', '-Wredundant-decls', '-Wwrite-strings', '-Wformat', '-Wformat-security', '-Winit-self', '-Wmissing-include-dirs', '-Waddress', '-Wno-multichar', '-Wvla', '-Wpointer-arith', '-Wmissing-prototypes', '-Wdeclaration-after-statement', '-Wold-style-definition', '-Wsign-compare', ] foreach extra_arg : warning_flags if cc.has_argument (extra_arg) add_project_arguments([extra_arg], language: 'c') endif endforeach if build_optimized message('Disabling GLib cast checks') add_project_arguments('-DG_DISABLE_CAST_CHECKS', language: 'c') message('Disabling GLib asserts') add_project_arguments('-DG_DISABLE_ASSERT', language: 'c') message('Disabling GLib checks') add_project_arguments('-DG_DISABLE_CHECKS', language: 'c') endif subdir('src') subdir('doc') subdir('pkgs') meson.add_devenv(devenv) summary({ 'prefix': prefix, 'bindir': bindir, 'datadir': datadir, 'libdir': libdir, 'localedir': localedir, 'includedir': includedir, 'optimization': optimization, }, section: 'Directories') summary('clapper', build_clapper ? 'Yes' : 'No', section: 'Build') summary('clapper-gtk', build_clappergtk ? 'Yes' : 'No', section: 'Build') summary('clapper-app', build_clapperapp ? 'Yes' : 'No', section: 'Build') summary('gst-plugin', build_gst_plugin ? 'Yes' : 'No', section: 'Build') summary('introspection', build_gir ? 'Yes' : 'No', section: 'Build') summary('vapi', build_vapi ? 'Yes' : 'No', section: 'Build') summary('doc', build_doc ? 'Yes' : 'No', section: 'Build') if build_clapper foreach name : clapper_possible_functionalities summary(name, clapper_available_functionalities.contains(name) ? 'Yes' : 'No', section: 'Functionalities') endforeach foreach name : clapper_possible_features summary(name, clapper_available_features.contains(name) ? 'Yes' : 'No', section: 'Features') endforeach endif clapper-0.8.0/meson_options.txt000066400000000000000000000031161474353110700166100ustar00rootroot00000000000000# Build option('clapper', type: 'feature', value: 'auto', description: 'Build Clapper library' ) option('clapper-gtk', type: 'feature', value: 'auto', description: 'Build Clapper GTK integration library' ) option('clapper-app', type: 'feature', value: 'auto', description: 'Build Clapper application' ) option('gst-plugin', type: 'feature', value: 'auto', description: 'Build GStreamer plugin (includes GTK video sink element)' ) option('introspection', type: 'feature', value: 'auto', description: 'Build GObject Introspection data' ) option('vapi', type: 'feature', value: 'auto', description: 'Build Vala bindings' ) option('doc', type: 'boolean', value: false, description: 'Build documentation' ) # Functionalities option('enhancers-loader', type: 'feature', value: 'enabled', description: 'Ability to load libpeas based plugins that enhance capabilities' ) # Features option('discoverer', type: 'feature', value: 'auto', description: 'Build Clapper Discoverer feature' ) option('mpris', type: 'feature', value: 'auto', description: 'Build Clapper MPRIS feature' ) option('server', type: 'feature', value: 'auto', description: 'Build Clapper Server feature' ) # GStreamer plugin options option('glimporter', type: 'feature', value: 'auto', description: 'Build GL memory importer for clappersink' ) option('gluploader', type: 'feature', value: 'auto', description: 'Build GL uploader for clappersink' ) option('rawimporter', type: 'feature', value: 'auto', description: 'Build RAW system memory importer for clappersink' ) clapper-0.8.0/pkgs/000077500000000000000000000000001474353110700141165ustar00rootroot00000000000000clapper-0.8.0/pkgs/flatpak/000077500000000000000000000000001474353110700155405ustar00rootroot00000000000000clapper-0.8.0/pkgs/flatpak/.gitignore000066400000000000000000000001661474353110700175330ustar00rootroot00000000000000build/ builddir/ repo/ .flatpak-builder/ com.github.rafostar.Clapper.flatpak flathub/com.github.rafostar.Clapper.json clapper-0.8.0/pkgs/flatpak/com.github.rafostar.Clapper-nightly.json000066400000000000000000000051321474353110700253540ustar00rootroot00000000000000{ "app-id": "com.github.rafostar.Clapper", "runtime": "org.gnome.Platform", "runtime-version": "master", "sdk": "org.gnome.Sdk", "sdk-extensions": [ "org.freedesktop.Sdk.Extension.rust-stable", "org.freedesktop.Sdk.Extension.llvm18" ], "add-extensions": { "org.freedesktop.Platform.ffmpeg-full": { "version": "24.08", "directory": "lib/ffmpeg", "add-ld-path": ".", "no-autodownload": false, "autodelete": false } }, "command": "clapper", "separate-locales": false, "finish-args": [ "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--device=dri", "--socket=pulseaudio", "--share=network", "--device=all", "--filesystem=xdg-run/pipewire-0:ro", "--filesystem=xdg-videos", "--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" ], "build-options": { "append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm18/bin", "prepend-ld-library-path": "/usr/lib/sdk/llvm18/lib", "env": { "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "clang", "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold", "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER": "clang", "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold" } }, "modules": [ "flathub/shared-modules/gudev/gudev.json", "flathub/lib/liba52.json", "flathub/lib/libmpeg2.json", "flathub/lib/libdv.json", "flathub/lib/libdvdcss.json", "flathub/lib/libdvdread.json", "flathub/lib/libdvdnav.json", "flathub/lib/libass.json", "flathub/lib/libmicrodns.json", "testing/dav1d.json", "testing/gstreamer.json", "testing/gst-plugins-rs.json", "testing/yt-dlp.json", "testing/libpeas.json", { "name": "clapper", "buildsystem": "meson", "sources": [ { "type": "dir", "path": "../../." } ] }, "testing/clapper-enhancers.json" ], "cleanup-commands": [ "mkdir -p /app/lib/ffmpeg", "ln -s /lib/$FLATPAK_ARCH-linux-*/gstreamer-1.0/libgstpipewire.so /app/lib/gstreamer-1.0/" ] } clapper-0.8.0/pkgs/flatpak/com.github.rafostar.Clapper.json000066400000000000000000000035671474353110700237120ustar00rootroot00000000000000{ "app-id": "com.github.rafostar.Clapper", "runtime": "org.gnome.Platform", "runtime-version": "47", "sdk": "org.gnome.Sdk", "add-extensions": { "org.freedesktop.Platform.ffmpeg-full": { "version": "24.08", "directory": "lib/ffmpeg", "add-ld-path": ".", "no-autodownload": false, "autodelete": false } }, "command": "clapper", "separate-locales": false, "finish-args": [ "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--device=dri", "--socket=pulseaudio", "--share=network", "--device=all", "--filesystem=xdg-run/pipewire-0:ro", "--filesystem=xdg-videos", "--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" ], "modules": [ "flathub/shared-modules/gudev/gudev.json", "flathub/lib/liba52.json", "flathub/lib/libmpeg2.json", "flathub/lib/libdv.json", "flathub/lib/libdvdcss.json", "flathub/lib/libdvdread.json", "flathub/lib/libdvdnav.json", "flathub/lib/libass.json", "flathub/lib/uchardet.json", "flathub/lib/libmicrodns.json", "testing/gstreamer_stable.json", "testing/yt-dlp.json", "testing/libpeas.json", { "name": "clapper", "buildsystem": "meson", "sources": [ { "type": "dir", "path": "../../." } ] }, "testing/clapper-enhancers.json" ], "cleanup-commands": [ "mkdir -p /app/lib/ffmpeg", "ln -s /lib/$FLATPAK_ARCH-linux-*/gstreamer-1.0/libgstpipewire.so /app/lib/gstreamer-1.0/" ] } clapper-0.8.0/pkgs/flatpak/flathub/000077500000000000000000000000001474353110700171655ustar00rootroot00000000000000clapper-0.8.0/pkgs/flatpak/testing/000077500000000000000000000000001474353110700172155ustar00rootroot00000000000000clapper-0.8.0/pkgs/flatpak/testing/clapper-enhancers.json000066400000000000000000000004521474353110700235030ustar00rootroot00000000000000{ "name": "clapper-enhancers", "buildsystem": "meson", "config-opts": [ "-Dauto_features=enabled" ], "sources": [ { "type": "git", "url": "https://github.com/Rafostar/clapper-enhancers.git", "branch": "main" } ] } clapper-0.8.0/pkgs/flatpak/testing/dav1d.json000066400000000000000000000010071474353110700211050ustar00rootroot00000000000000{ "name": "dav1d", "buildsystem": "meson", "only-arches": [ "x86_64" ], "config-opts": [ "-Denable_tools=false", "-Denable_examples=false", "-Denable_tests=false", "-Denable_docs=false", "-Dlogging=false" ], "cleanup": [ "/include", "/lib/pkgconfig" ], "sources": [ { "type": "git", "url": "https://code.videolan.org/videolan/dav1d.git", "branch": "master" } ] } clapper-0.8.0/pkgs/flatpak/testing/gst-plugins-rs.json000066400000000000000000000011221474353110700230020ustar00rootroot00000000000000{ "name": "gst-plugins-rs", "buildsystem": "simple", "only-arches": [ "x86_64" ], "build-options": { "build-args": [ "--share=network" ], "env": { "CARGO_HOME": "/run/build/gst-plugins-rs/cargo" } }, "sources": [ { "type": "git", "url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git", "branch": "main" } ], "build-commands": [ "cargo install cargo-c", "cargo cinstall --prefix=/app -p gst-plugin-dav1d" ] } clapper-0.8.0/pkgs/flatpak/testing/gstreamer.json000066400000000000000000000030741474353110700221050ustar00rootroot00000000000000{ "name": "gstreamer", "buildsystem": "meson", "config-opts": [ "--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", "-Dgst-plugins-base:gl_api=opengl,gles2", "-Dgst-plugins-base:gl_platform=egl,glx", "-Dgst-plugins-good:gtk3=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:mpeg2dec=enabled" ], "sources": [ { "type": "git", "url": "https://gitlab.freedesktop.org/gstreamer/gstreamer.git", "branch": "main", "disable-submodules": true } ] } clapper-0.8.0/pkgs/flatpak/testing/gstreamer_stable.json000066400000000000000000000066401474353110700234410ustar00rootroot00000000000000{ "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" } ] } clapper-0.8.0/pkgs/flatpak/testing/libpeas.json000066400000000000000000000010641474353110700215300ustar00rootroot00000000000000{ "name": "libpeas", "buildsystem": "meson", "config-opts": [ "--wrap-mode=nodownload", "-Dgjs=false", "-Dlua51=false", "-Dintrospection=false" ], "sources": [ { "type": "archive", "url": "https://download.gnome.org/sources/libpeas/2.0/libpeas-2.0.5.tar.xz", "sha256": "376f2f73d731b54e13ddbab1d91b6382cf6a980524def44df62add15489de6dd", "x-checker-data": { "type": "gnome", "name": "libpeas" } } ] } clapper-0.8.0/pkgs/flatpak/testing/yt-dlp.json000066400000000000000000000012121474353110700213150ustar00rootroot00000000000000{ "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" } } ] } clapper-0.8.0/pkgs/meson.build000066400000000000000000000001541474353110700162600ustar00rootroot00000000000000is_windows = ['windows'].contains(host_machine.system()) if is_windows subdir('windows-installer') endif clapper-0.8.0/pkgs/windows-installer/000077500000000000000000000000001474353110700176035ustar00rootroot00000000000000clapper-0.8.0/pkgs/windows-installer/clapper.iss.in000066400000000000000000000017461474353110700223660ustar00rootroot00000000000000[Setup] AppName=Clapper AppVersion=@CLAPPER_APP_VERSION@ AppVerName=Clapper @CLAPPER_APP_VERSION@ VersionInfoVersion=@CLAPPER_APP_VERSION@.0 ArchitecturesAllowed=x64compatible ArchitecturesInstallIn64BitMode=x64compatible WizardStyle=modern DefaultDirName={autopf}\Clapper DefaultGroupName=Clapper DisableProgramGroupPage=yes ChangesAssociations=yes Compression=lzma2 SolidCompression=yes OutputDir=InstallerOutput OutputBaseFilename=Clapper_@CLAPPER_APP_VERSION@ SetupIconFile=@PROJECT_ROOT@\src\bin\clapper-app\windows\clapper.ico LicenseFile=@PROJECT_ROOT@\COPYING-GPL [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] Source: "@CLAPPER_APP_DEST@\*"; DestDir: "{app}"; Flags: recursesubdirs replacesameversion [Icons] Name: "{autoprograms}\Clapper"; Filename: "{app}\bin\clapper.exe" Name: "{autodesktop}\Clapper"; Filename: "{app}\bin\clapper.exe"; Tasks: desktopicon [Registry] @CLAPPER_APP_REG_ENTRIES@ clapper-0.8.0/pkgs/windows-installer/meson.build000066400000000000000000000034441474353110700217520ustar00rootroot00000000000000# Same as "clapper-app-utils.c" # FIXME: Have these in one place only win_extensions = [ 'avi', 'claps', 'm2ts', 'mkv', 'mov', 'mp4', 'webm', 'wmv' ] reg_entries = [] foreach ext : win_extensions ext_id = 'Clapper.' + ext exe_path = '{app}\\bin\\clapper.exe' subkey = 'Software\\Classes\\.' + ext + '\\OpenWithProgids' reg_entries += 'Root: HKA; Subkey: "@0@"; ValueType: string; ValueName: "@1@"; ValueData: ""; Flags: uninsdeletevalue'.format(subkey, ext_id) subkey = 'Software\\Classes\\' + ext_id reg_entries += 'Root: HKA; Subkey: "@0@"; ValueType: string; ValueName: ""; ValueData: "@1@"; Flags: uninsdeletekey'.format(subkey, ext) subkey = 'Software\\Classes\\' + ext_id + '\\DefaultIcon' reg_entries += 'Root: HKA; Subkey: "@0@"; ValueType: string; ValueName: ""; ValueData: "@1@,0"'.format(subkey, exe_path) subkey = 'Software\\Classes\\' + ext_id + '\\shell\\open\\command' reg_entries += 'Root: HKA; Subkey: "@0@"; ValueType: string; ValueName: ""; ValueData: """@1@"" ""%1"""'.format(subkey, exe_path) reg_entries += '' # Empty line for clarity endforeach # NOTE: In meson replacing "@@" vars after "\" sign does not work. # For this reason we do not pass app name here. iss_conf = configuration_data() iss_conf.set('CLAPPER_APP_VERSION', meson.project_version()) iss_conf.set('CLAPPER_APP_REG_ENTRIES', '\n'.join(reg_entries)) iss_conf.set('PROJECT_ROOT', meson.project_source_root()) # NOTE: We also need to package other dependencies, hence using whole prefix. # Workaround to reduce package size is to set prefix to some custom dir, install, # then determine the rest with "ldd" and copy remaining files manually there. iss_conf.set('CLAPPER_APP_DEST', prefix.replace('/', '\\')) configure_file( input: 'clapper.iss.in', output: 'clapper.iss', configuration: iss_conf, ) clapper-0.8.0/src/000077500000000000000000000000001474353110700137415ustar00rootroot00000000000000clapper-0.8.0/src/bin/000077500000000000000000000000001474353110700145115ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/000077500000000000000000000000001474353110700167155ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/clapper-app-about-window.c000066400000000000000000000045021474353110700237030ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "clapper-app-about-window.h" GtkWidget * clapper_app_about_window_new (GtkApplication *gtk_app) { AdwAboutWindow *about; GtkWindow *window; GString *string; gchar *gst_ver, *debug_info; about = ADW_ABOUT_WINDOW (adw_about_window_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); /* TRANSLATORS: Put your name(s) here for credits or leave untranslated */ adw_about_window_set_translator_credits (about, _("translator-credits")); string = g_string_new (NULL); g_string_append_printf (string, "GLib %u.%u.%u\n", glib_major_version, glib_minor_version, glib_micro_version); g_string_append_printf (string, "GTK %u.%u.%u\n", gtk_get_major_version (), gtk_get_minor_version (), gtk_get_micro_version ()); g_string_append_printf (string, "Adwaita %u.%u.%u\n", adw_get_major_version (), adw_get_minor_version (), adw_get_micro_version ()); gst_ver = gst_version_string (); g_string_append (string, gst_ver); g_free (gst_ver); debug_info = g_string_free_and_steal (string); adw_about_window_set_debug_info (about, debug_info); g_free (debug_info); return GTK_WIDGET (about); } clapper-0.8.0/src/bin/clapper-app/clapper-app-about-window.h000066400000000000000000000016461474353110700237160ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS G_GNUC_INTERNAL GtkWidget * clapper_app_about_window_new (GtkApplication *gtk_app); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-application.c000066400000000000000000000636661474353110700236070ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include "clapper-app-application.h" #include "clapper-app-window.h" #include "clapper-app-file-dialog.h" #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-utils.h" #define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) #define GST_CAT_DEFAULT clapper_app_application_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppApplication { GtkApplication parent; GSettings *settings; gboolean need_init_state; }; #define parent_class clapper_app_application_parent_class G_DEFINE_TYPE (ClapperAppApplication, clapper_app_application, GTK_TYPE_APPLICATION); struct ClapperPluginFeatureData { const gchar *name; GstRank rank; }; struct ClapperPluginData { const gchar *name; guint skip_version[3]; struct ClapperPluginFeatureData features[10]; }; typedef struct { const gchar *action; const gchar *accels[3]; } ClapperAppShortcut; static gboolean clapper_app_options_get (const gchar *key, const gchar *format, GVariantDict *options, GObject *src_object, GSettings *settings, gpointer output) { if (options && g_variant_dict_lookup (options, key, format, output)) { return TRUE; } else if (src_object) { g_object_get (src_object, key, output, NULL); return TRUE; } else if (settings) { g_settings_get (settings, key, format, output); return TRUE; } return FALSE; } static gboolean clapper_app_options_get_extra (const gchar *key, GVariantDict *options, const gchar *extra_value, GSettings *settings, gchar **output) { if (options && g_variant_dict_lookup (options, key, "s", output)) { return TRUE; } else if (extra_value) { *output = g_strdup (extra_value); return TRUE; } else if (settings) { *output = g_settings_get_string (settings, key); /* Ensure non-empty string */ if (*output) { if (strlen (*output) > 0) return TRUE; else g_clear_pointer (output, g_free); } } return FALSE; } /* * Apply options to @dest_window. Option providers will be used in args order. * If any arg is %NULL it will not be used. For example, passing %NULL as * @settings will avoid restoring values to @dest_window from GSettings. */ static void clapper_app_apply_options_to_window (ClapperAppWindow *dest_window, GVariantDict *options, ClapperAppWindow *src_window, GSettings *settings) { ClapperPlayer *dest_player; ClapperAppWindowExtraOptions *src_extra_opts = NULL, *dest_extra_opts = NULL; GObject *src_player_obj = NULL; GObject *src_queue_obj = NULL; gchar *option_str; gdouble option_dbl; gint option_int; gboolean option_bool; GST_DEBUG ("Applying options to window: %p", dest_window); dest_player = clapper_app_window_get_player (dest_window); dest_extra_opts = clapper_app_window_get_extra_options (dest_window); if (src_window) { src_player_obj = (GObject *) clapper_app_window_get_player (src_window); src_queue_obj = (GObject *) clapper_player_get_queue (CLAPPER_PLAYER_CAST (src_player_obj)); src_extra_opts = clapper_app_window_get_extra_options (src_window); } /* Apply player values, clamp them to be within allowed range */ if (clapper_app_options_get ("volume", "d", options, src_player_obj, settings, &option_dbl)) clapper_player_set_volume (dest_player, PERCENTAGE_ROUND (CLAMP (option_dbl, 0, 2.0))); if (clapper_app_options_get ("mute", "b", NULL, src_player_obj, settings, &option_bool)) clapper_player_set_mute (dest_player, option_bool); if (clapper_app_options_get ("speed", "d", options, src_player_obj, settings, &option_dbl)) clapper_player_set_speed (dest_player, PERCENTAGE_ROUND (CLAMP (option_dbl, 0.05, 2.0))); if (clapper_app_options_get ("adaptive-start-bitrate", "i", options, src_player_obj, settings, &option_int)) clapper_player_set_adaptive_start_bitrate (dest_player, option_int); if (clapper_app_options_get ("progression-mode", "i", options, src_queue_obj, settings, &option_int)) clapper_queue_set_progression_mode (clapper_player_get_queue (dest_player), CLAMP (option_int, 0, 4)); if (clapper_app_options_get ("subtitles-enabled", "b", NULL, src_player_obj, settings, &option_bool)) clapper_player_set_subtitles_enabled (dest_player, option_bool); if (clapper_app_options_get_extra ("video-filter", options, (src_extra_opts) ? src_extra_opts->video_filter : NULL, NULL, &option_str)) { clapper_player_set_video_filter (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->video_filter); dest_extra_opts->video_filter = option_str; } if (clapper_app_options_get_extra ("audio-filter", options, (src_extra_opts) ? src_extra_opts->audio_filter : NULL, NULL, &option_str)) { clapper_player_set_audio_filter (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->audio_filter); dest_extra_opts->audio_filter = option_str; } if (clapper_app_options_get_extra ("video-sink", options, (src_extra_opts) ? src_extra_opts->video_sink : NULL, NULL, &option_str)) { clapper_player_set_video_sink (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->video_sink); dest_extra_opts->video_sink = option_str; } if (clapper_app_options_get_extra ("audio-sink", options, (src_extra_opts) ? src_extra_opts->audio_sink : NULL, NULL, &option_str)) { clapper_player_set_audio_sink (dest_player, clapper_app_utils_make_element (option_str)); g_free (dest_extra_opts->audio_sink); dest_extra_opts->audio_sink = option_str; } /* Apply window options */ if ((options && g_variant_dict_contains (options, "fullscreen")) || (settings && g_settings_get_boolean (settings, "fullscreened"))) gtk_window_fullscreen (GTK_WINDOW (dest_window)); else if (settings && g_settings_get_boolean (settings, "maximized")) gtk_window_maximize (GTK_WINDOW (dest_window)); GST_DEBUG ("Options applied"); } static inline void _store_settings_from_window (ClapperAppApplication *self, ClapperAppWindow *app_window) { ClapperPlayer *player = clapper_app_window_get_player (app_window); ClapperQueue *queue = clapper_player_get_queue (player); GtkWindow *window = GTK_WINDOW (app_window); GST_DEBUG ("Storing current configuration to GSettings"); g_settings_set_double (self->settings, "volume", clapper_player_get_volume (player)); g_settings_set_boolean (self->settings, "mute", clapper_player_get_mute (player)); g_settings_set_double (self->settings, "speed", clapper_player_get_speed (player)); g_settings_set_int (self->settings, "adaptive-start-bitrate", CLAMP (clapper_player_get_adaptive_bandwidth (player) * 0.8, 0, G_MAXINT)); g_settings_set_boolean (self->settings, "subtitles-enabled", clapper_player_get_subtitles_enabled (player)); g_settings_set_int (self->settings, "progression-mode", clapper_queue_get_progression_mode (queue)); g_settings_set_boolean (self->settings, "maximized", gtk_window_is_maximized (window)); g_settings_set_boolean (self->settings, "fullscreened", gtk_window_is_fullscreen (window)); GST_DEBUG ("Configuration stored"); } static inline void _set_initial_plugin_feature_ranks (void) { GstRegistry *registry = gst_registry_get (); guint i; const struct ClapperPluginData plugins_data[] = { { .name = "va", .skip_version = { 1, 24, 0 }, .features = { { "vah264dec", GST_RANK_PRIMARY + 24 }, { "vah265dec", GST_RANK_PRIMARY + 24 }, { "vavp8dec", GST_RANK_PRIMARY + 24 }, { "vavp9dec", GST_RANK_PRIMARY + 24 }, { "vaav1dec", GST_RANK_PRIMARY + 24 }, { NULL, 0 } } }, { .name = "nvcodec", .skip_version = { 1, 24, 0 }, .features = { { "nvh264dec", GST_RANK_PRIMARY + 28 }, { "nvh265dec", GST_RANK_PRIMARY + 28 }, { "nvvp8dec", GST_RANK_PRIMARY + 28 }, { "nvvp9dec", GST_RANK_PRIMARY + 28 }, { "nvav1dec", GST_RANK_PRIMARY + 28 }, { NULL, 0 } } } }; for (i = 0; i < G_N_ELEMENTS (plugins_data); ++i) { GList *features; if (!(features = gst_registry_get_feature_list_by_plugin ( registry, plugins_data[i].name))) continue; if (g_list_length (features) > 0) { guint j; for (j = 0; G_N_ELEMENTS (plugins_data[i].features); ++j) { GstPluginFeature *feature; if (!plugins_data[i].features[j].name) break; if (!(feature = gst_registry_lookup_feature (registry, plugins_data[i].features[j].name))) continue; if (!gst_plugin_feature_check_version (feature, plugins_data[i].skip_version[0], plugins_data[i].skip_version[1], plugins_data[i].skip_version[2])) { gst_plugin_feature_set_rank (feature, plugins_data[i].features[j].rank); GST_DEBUG ("Initially set \"%s\" rank to: %i", plugins_data[i].features[j].name, plugins_data[i].features[j].rank); } gst_object_unref (feature); } } gst_plugin_feature_list_free (features); } } static void _iter_ranks_func (const gchar *feature_name, GstRank rank, gboolean from_env, gpointer user_data G_GNUC_UNUSED) { GstPluginFeature *feature; if ((feature = gst_registry_find_feature (gst_registry_get (), feature_name, GST_TYPE_ELEMENT_FACTORY))) { gst_plugin_feature_set_rank (feature, rank); GST_INFO ("Set \"%s\" rank to: %i", feature_name, rank); gst_object_unref (feature); } } static void plugin_feature_ranks_settings_changed_cb (GSettings *settings, gchar *key G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) { clapper_app_utils_iterate_plugin_feature_ranks (settings, (ClapperAppUtilsIterRanks) _iter_ranks_func, NULL); } static void _assemble_initial_state (GtkWindow *window) { GtkWidget *stack = gtk_window_get_child (window); GtkBuilder *builder = gtk_builder_new_from_resource ( CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-initial-state.ui"); GtkWidget *initial_state = GTK_WIDGET (gtk_builder_get_object (builder, "initial_state")); gtk_stack_add_named (GTK_STACK (stack), initial_state, "initial_state"); gtk_stack_set_visible_child (GTK_STACK (stack), initial_state); g_object_unref (builder); } static void add_files (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); clapper_app_file_dialog_open_files (gtk_app); } static void add_uri (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); clapper_app_uri_dialog_open_uri (gtk_app); } static void clear_queue (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWindow *window = gtk_application_get_active_window (gtk_app); ClapperPlayer *player; ClapperQueue *queue; while (window && !CLAPPER_APP_IS_WINDOW (window)) window = gtk_window_get_transient_for (window); player = clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)); queue = clapper_player_get_queue (player); clapper_queue_clear (queue); } static void new_window (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWidget *stack = gtk_window_get_child (gtk_application_get_active_window (gtk_app)); const gchar *child_name = gtk_stack_get_visible_child_name (GTK_STACK (stack)); /* Do not allow to open new windows during initial state, * there already is a free one to use */ if (g_strcmp0 (child_name, "initial_state") != 0) { ClapperAppWindow *src_window, *dest_window; src_window = CLAPPER_APP_WINDOW_CAST (gtk_application_get_active_window (gtk_app)); dest_window = CLAPPER_APP_WINDOW_CAST (clapper_app_window_new (gtk_app)); clapper_app_apply_options_to_window (dest_window, NULL, src_window, NULL); gtk_window_present (GTK_WINDOW (dest_window)); } } static void show_preferences (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWidget *preferences_window; preferences_window = clapper_app_preferences_window_new (gtk_app); gtk_window_present (GTK_WINDOW (preferences_window)); } static void show_info (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWidget *info_window; GtkWindow *window; ClapperPlayer *player; window = gtk_application_get_active_window (gtk_app); player = clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)); info_window = clapper_app_info_window_new (gtk_app, player); gtk_window_present (GTK_WINDOW (info_window)); } static void show_about (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkApplication *gtk_app = GTK_APPLICATION (user_data); GtkWidget *about_window; about_window = clapper_app_about_window_new (gtk_app); gtk_window_present (GTK_WINDOW (about_window)); } GApplication * clapper_app_application_new (void) { return g_object_new (CLAPPER_APP_TYPE_APPLICATION, "application-id", CLAPPER_APP_ID, "flags", G_APPLICATION_HANDLES_OPEN | G_APPLICATION_HANDLES_COMMAND_LINE, NULL); } static void clapper_app_application_window_removed (GtkApplication *gtk_app, GtkWindow *window) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (gtk_app); if (CLAPPER_APP_IS_WINDOW (window)) { GList *win, *windows = gtk_application_get_windows (gtk_app); gboolean has_player_windows = FALSE; for (win = windows; win != NULL; win = win->next) { GtkWindow *rem_window = GTK_WINDOW (win->data); if ((has_player_windows = (rem_window != window && CLAPPER_APP_IS_WINDOW (rem_window)))) break; } /* Last player window is closing, time to store settings */ if (!has_player_windows) _store_settings_from_window (self, CLAPPER_APP_WINDOW_CAST (window)); } GTK_APPLICATION_CLASS (parent_class)->window_removed (gtk_app, window); } static void clapper_app_application_activate (GApplication *app) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (app); GtkApplication *gtk_app = GTK_APPLICATION (app); GtkWindow *window; GST_INFO ("Activate"); G_APPLICATION_CLASS (parent_class)->activate (app); /* When activated through DBus command line does not run, * so create our first window here instead */ if (!(window = gtk_application_get_active_window (gtk_app))) { window = GTK_WINDOW (clapper_app_window_new (gtk_app)); clapper_app_apply_options_to_window (CLAPPER_APP_WINDOW_CAST (window), NULL, NULL, self->settings); } if (self->need_init_state) { _assemble_initial_state (window); self->need_init_state = FALSE; } gtk_window_present (window); } static gboolean clapper_app_application_local_command_line (GApplication *app, gchar ***arguments, gint *exit_status) { gchar **argv = *arguments; guint i; GST_INFO ("Handling local command line"); /* NOTE: argv is never NULL, so no need to check */ for (i = 0; argv[i]; ++i) { /* Handle "-" special case as URI */ if (strlen (argv[i]) == 1 && argv[i][0] == '-') { g_free (argv[i]); argv[i] = g_strdup ("fd://0"); } } return G_APPLICATION_CLASS (parent_class)->local_command_line (app, arguments, exit_status); } static gint clapper_app_application_command_line (GApplication *app, GApplicationCommandLine *cmd_line) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (app); ClapperAppWindow *src_window = NULL, *dest_window = NULL; GtkApplication *gtk_app = GTK_APPLICATION (app); GVariantDict *options; GFile **files = NULL; GSettings *settings; gint n_files = 0; GST_INFO ("Handling command line"); options = g_application_command_line_get_options_dict (cmd_line); dest_window = CLAPPER_APP_WINDOW_CAST (gtk_application_get_active_window (gtk_app)); /* Restore settings only once by making them %NULL when run again */ settings = (!dest_window) ? self->settings : NULL; if (!dest_window || g_variant_dict_contains (options, "new-window")) { src_window = dest_window; dest_window = CLAPPER_APP_WINDOW_CAST (clapper_app_window_new (gtk_app)); } clapper_app_apply_options_to_window (dest_window, options, src_window, settings); if (clapper_app_utils_files_from_command_line (cmd_line, &files, &n_files)) { g_application_open (app, files, n_files, (g_variant_dict_contains (options, "enqueue")) ? "add-only" : ""); clapper_app_utils_files_free (files); } else { g_application_activate (app); } return EXIT_SUCCESS; } static gboolean _is_claps_file (GFile *file) { gchar *basename = g_file_get_basename (file); gboolean is_claps; is_claps = (basename && g_str_has_suffix (basename, ".claps")); g_free (basename); return is_claps; } static void add_item_from_file (GFile *file, ClapperQueue *queue) { ClapperMediaItem *item = clapper_media_item_new_from_file (file); GST_DEBUG ("Adding media item with URI: %s", clapper_media_item_get_uri (item)); clapper_queue_add_item (queue, item); gst_object_unref (item); } static void add_items_from_claps_file (GFile *file, ClapperQueue *queue) { GDataInputStream *dstream = NULL; GFileInputStream *stream; GError *error = NULL; gchar *line; if (!(stream = g_file_read (file, NULL, &error))) goto finish; dstream = g_data_input_stream_new (G_INPUT_STREAM (stream)); while ((line = g_data_input_stream_read_line ( dstream, NULL, NULL, &error))) { g_strstrip (line); if (strlen (line) > 0) { GFile *tmp_file = gst_uri_is_valid (line) ? g_file_new_for_uri (line) : g_file_new_for_path (line); if (_is_claps_file (tmp_file)) add_items_from_claps_file (tmp_file, queue); else add_item_from_file (tmp_file, queue); g_object_unref (tmp_file); } g_free (line); } finish: if (error) { GST_ERROR ("Could not read \".claps\" file, reason: %s", error->message); g_error_free (error); } if (stream) { g_input_stream_close (G_INPUT_STREAM (stream), NULL, NULL); g_object_unref (stream); } g_clear_object (&dstream); } static void add_item_with_subtitles (GFile *media_file, GFile *subs_file, ClapperQueue *queue) { ClapperMediaItem *item = clapper_media_item_new_from_file (media_file); gchar *suburi = g_file_get_uri (subs_file); GST_DEBUG ("Adding media item with URI: %s, SUBURI: %s", clapper_media_item_get_uri (item), GST_STR_NULL (suburi)); clapper_media_item_set_suburi (item, suburi); clapper_queue_add_item (queue, item); gst_object_unref (item); g_free (suburi); } static void clapper_app_application_open (GApplication *app, GFile **files, gint n_files, const gchar *hint) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (app); GtkWindow *window; ClapperPlayer *player; ClapperQueue *queue; guint n_before; gboolean add_only, handled = FALSE; GST_INFO ("Open"); /* Since we startup with media, * no need to show initial state */ self->need_init_state = FALSE; g_application_activate (app); g_application_mark_busy (app); window = gtk_application_get_active_window (GTK_APPLICATION (app)); while (window && !CLAPPER_APP_IS_WINDOW (window)) window = gtk_window_get_transient_for (window); clapper_app_window_ensure_no_initial_state (CLAPPER_APP_WINDOW (window)); player = clapper_app_window_get_player (CLAPPER_APP_WINDOW (window)); queue = clapper_player_get_queue (player); n_before = clapper_queue_get_n_items (queue); /* Special path for opening video with subtitles at once */ if (n_files == 2) { gboolean first_subs, second_subs; first_subs = clapper_app_utils_is_subtitles_file (files[0]); second_subs = clapper_app_utils_is_subtitles_file (files[1]); if ((handled = first_subs != second_subs)) { guint media_index, subs_index; media_index = (second_subs) ? 0 : 1; subs_index = (media_index + 1) % 2; add_item_with_subtitles ( files[media_index], files[subs_index], queue); } } if (!handled) { gint i; for (i = 0; i < n_files; ++i) { if (_is_claps_file (files[i])) add_items_from_claps_file (files[i], queue); else add_item_from_file (files[i], queue); } } add_only = (g_strcmp0 (hint, "add-only") == 0); /* Select first thing from added item to play (behave like "open" should), * when queue was empty first item is automatically selected */ if (!add_only && n_before > 0) clapper_queue_select_index (queue, n_before); g_application_unmark_busy (app); } static void clapper_app_application_init (ClapperAppApplication *self) { self->need_init_state = TRUE; } static void clapper_app_application_constructed (GObject *object) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (object); GApplication *app = G_APPLICATION (self); guint i; const GOptionEntry app_options[] = { { "new-window", 'n', 0, G_OPTION_ARG_NONE, NULL, _("Create a new window"), NULL }, { "enqueue", 0, 0, G_OPTION_ARG_NONE, NULL, _("Add media to queue in primary application instance"), NULL }, { "volume", 0, 0, G_OPTION_ARG_DOUBLE, NULL, _("Audio volume to set (0 - 2.0 range)"), NULL }, { "speed", 0, 0, G_OPTION_ARG_DOUBLE, NULL, _("Playback speed to set (0.05 - 2.0 range)"), NULL }, { "adaptive-start-bitrate", 0, 0, G_OPTION_ARG_INT, NULL, _("Initial bitrate for adaptive streaming"), NULL }, { "progression-mode", 0, 0, G_OPTION_ARG_INT, NULL, _("Initial queue progression mode (0=none, 1=consecutive, 2=repeat-item, 3=carousel, 4=shuffle)"), NULL }, { "fullscreen", 'f', 0, G_OPTION_ARG_NONE, NULL, _("Set window to be fullscreen"), NULL }, { "video-filter", 0, 0, G_OPTION_ARG_STRING, NULL, _("Video filter to use (\"none\" to disable)"), NULL }, { "audio-filter", 0, 0, G_OPTION_ARG_STRING, NULL, _("Audio filter to use (\"none\" to disable)"), NULL }, { "video-sink", 0, 0, G_OPTION_ARG_STRING, NULL, _("Video sink to use"), NULL }, { "audio-sink", 0, 0, G_OPTION_ARG_STRING, NULL, _("Audio sink to use"), NULL }, { NULL } }; static const GActionEntry app_actions[] = { { "add-files", add_files, NULL, NULL, NULL }, { "add-uri", add_uri, NULL, NULL, NULL }, { "clear-queue", clear_queue, NULL, NULL, NULL }, { "new-window", new_window, NULL, NULL, NULL }, { "info", show_info, NULL, NULL, NULL }, { "preferences", show_preferences, NULL, NULL, NULL }, { "about", show_about, NULL, NULL, NULL }, }; static const ClapperAppShortcut app_shortcuts[] = { { "app.add-files", { "o", NULL, NULL }}, { "app.add-uri", { "u", NULL, NULL }}, { "app.new-window", { "n", NULL, NULL }}, { "app.info", { "i", NULL, NULL }}, { "app.preferences", { "comma", NULL, NULL }}, { "app.about", { "F1", NULL, NULL }}, { "win.toggle-fullscreen", { "F11", "f", NULL }}, { "win.unfullscreen", { "Escape", NULL, NULL }}, { "win.auto-resize", { "r", NULL, NULL }}, { "win.show-help-overlay", { "question", NULL, NULL }}, { "window.close", { "q", "q", NULL }}, }; /* Override initial ranks, they will be updated * from both stored settings and env below */ _set_initial_plugin_feature_ranks (); self->settings = g_settings_new (CLAPPER_APP_ID); g_signal_connect (self->settings, "changed::plugin-feature-ranks", G_CALLBACK (plugin_feature_ranks_settings_changed_cb), self); plugin_feature_ranks_settings_changed_cb (self->settings, NULL, NULL); g_action_map_add_action_entries (G_ACTION_MAP (app), app_actions, G_N_ELEMENTS (app_actions), app); for (i = 0; i < G_N_ELEMENTS (app_shortcuts); ++i) gtk_application_set_accels_for_action (GTK_APPLICATION (app), app_shortcuts[i].action, app_shortcuts[i].accels); g_application_set_option_context_parameter_string (app, "[URI1|FILE1] [URI2|FILE2] …"); g_application_add_main_option_entries (app, app_options); g_application_add_option_group (app, gst_init_get_option_group ()); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_app_application_finalize (GObject *object) { ClapperAppApplication *self = CLAPPER_APP_APPLICATION_CAST (object); GST_TRACE ("Finalize"); g_object_unref (self->settings); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_application_class_init (ClapperAppApplicationClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GApplicationClass *application_class = (GApplicationClass *) klass; GtkApplicationClass *gtk_application_class = (GtkApplicationClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappapplication", 0, "Clapper App Application"); gobject_class->constructed = clapper_app_application_constructed; gobject_class->finalize = clapper_app_application_finalize; gtk_application_class->window_removed = clapper_app_application_window_removed; application_class->activate = clapper_app_application_activate; application_class->local_command_line = clapper_app_application_local_command_line; application_class->command_line = clapper_app_application_command_line; application_class->open = clapper_app_application_open; } clapper-0.8.0/src/bin/clapper-app/clapper-app-application.h000066400000000000000000000022341474353110700235740ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_APPLICATION (clapper_app_application_get_type()) #define CLAPPER_APP_APPLICATION_CAST(obj) ((ClapperAppApplication *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppApplication, clapper_app_application, CLAPPER_APP, APPLICATION, GtkApplication) G_GNUC_INTERNAL GApplication * clapper_app_application_new (void); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-file-dialog.c000066400000000000000000000105351474353110700234430ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "clapper-app-file-dialog.h" #include "clapper-app-utils.h" static inline void _open_files_from_model (GtkApplication *gtk_app, GListModel *files_model) { GFile **files = NULL; gint n_files = 0; if (clapper_app_utils_files_from_list_model (files_model, &files, &n_files)) { g_application_open (G_APPLICATION (gtk_app), files, n_files, "add-only"); clapper_app_utils_files_free (files); } } static void _open_files_cb (GtkFileDialog *dialog, GAsyncResult *result, GtkApplication *gtk_app) { GError *error = NULL; GListModel *files_model = gtk_file_dialog_open_multiple_finish (dialog, result, &error); if (G_LIKELY (error == NULL)) { _open_files_from_model (gtk_app, files_model); } 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 (&files_model); } static void _open_subtitles_cb (GtkFileDialog *dialog, GAsyncResult *result, ClapperMediaItem *item) { GError *error = NULL; GFile *file = gtk_file_dialog_open_finish (dialog, result, &error); if (G_LIKELY (error == NULL)) { gchar *suburi = g_file_get_uri (file); clapper_media_item_set_suburi (item, suburi); g_free (suburi); } 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); gst_object_unref (item); // Borrowed reference } static void _dialog_add_mime_types (GtkFileDialog *dialog, const gchar *filter_name, const gchar *const *mime_types) { GListStore *filters = g_list_store_new (GTK_TYPE_FILE_FILTER); GtkFileFilter *filter = gtk_file_filter_new (); guint i; /* XXX: Windows does not support mime-types file * filters, so use file extensions instead */ for (i = 0; mime_types[i]; ++i) { #ifndef G_OS_WIN32 gtk_file_filter_add_mime_type (filter, mime_types[i]); #else gtk_file_filter_add_suffix (filter, mime_types[i]); #endif } gtk_file_filter_set_name (filter, filter_name); g_list_store_append (filters, filter); gtk_file_dialog_set_filters (dialog, G_LIST_MODEL (filters)); g_object_unref (filters); g_object_unref (filter); } void clapper_app_file_dialog_open_files (GtkApplication *gtk_app) { GtkWindow *window = gtk_application_get_active_window (gtk_app); GtkFileDialog *dialog = gtk_file_dialog_new (); _dialog_add_mime_types (dialog, "Media Files", #ifndef G_OS_WIN32 clapper_app_utils_get_mime_types ()); #else clapper_app_utils_get_extensions ()); #endif gtk_file_dialog_set_modal (dialog, TRUE); gtk_file_dialog_set_title (dialog, "Add Files"); gtk_file_dialog_open_multiple (dialog, window, NULL, (GAsyncReadyCallback) _open_files_cb, gtk_app); g_object_unref (dialog); } void clapper_app_file_dialog_open_subtitles (GtkApplication *gtk_app, ClapperMediaItem *item) { GtkWindow *window = gtk_application_get_active_window (gtk_app); GtkFileDialog *dialog = gtk_file_dialog_new (); _dialog_add_mime_types (dialog, "Subtitles", #ifndef G_OS_WIN32 clapper_app_utils_get_subtitles_mime_types ()); #else clapper_app_utils_get_subtitles_extensions ()); #endif gtk_file_dialog_set_modal (dialog, TRUE); gtk_file_dialog_set_title (dialog, "Open Subtitles"); gtk_file_dialog_open (dialog, window, NULL, (GAsyncReadyCallback) _open_subtitles_cb, gst_object_ref (item)); g_object_unref (dialog); } clapper-0.8.0/src/bin/clapper-app/clapper-app-file-dialog.h000066400000000000000000000020311474353110700234400ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS G_GNUC_INTERNAL 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_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-headerbar.c000066400000000000000000000134251474353110700232050ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "clapper-app-headerbar.h" #include "clapper-app-utils.h" #define GST_CAT_DEFAULT clapper_app_headerbar_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppHeaderbar { ClapperGtkContainer parent; GtkWidget *queue_revealer; GtkWidget *previous_item_revealer; GtkWidget *next_item_revealer; GtkWidget *win_buttons_revealer; GtkDropTarget *drop_target; gboolean adapt; }; #define parent_class clapper_app_headerbar_parent_class G_DEFINE_TYPE (ClapperAppHeaderbar, clapper_app_headerbar, CLAPPER_GTK_TYPE_CONTAINER); static void _determine_win_buttons_reveal (ClapperAppHeaderbar *self) { gboolean queue_reveal = gtk_revealer_get_reveal_child (GTK_REVEALER (self->queue_revealer)); gtk_revealer_set_reveal_child (GTK_REVEALER (self->win_buttons_revealer), (queue_reveal) ? !self->adapt : TRUE); } static void container_adapt_cb (ClapperGtkContainer *container, gboolean adapt, ClapperAppHeaderbar *self) { GST_DEBUG_OBJECT (self, "Width adapted: %s", (adapt) ? "yes" : "no"); self->adapt = adapt; gtk_revealer_set_reveal_child (GTK_REVEALER (self->previous_item_revealer), !adapt); gtk_revealer_set_reveal_child (GTK_REVEALER (self->next_item_revealer), !adapt); _determine_win_buttons_reveal (self); } static void queue_reveal_cb (GtkRevealer *revealer, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppHeaderbar *self) { _determine_win_buttons_reveal (self); } static void reveal_queue_button_clicked_cb (GtkButton *button, ClapperAppHeaderbar *self) { gboolean reveal; GST_INFO_OBJECT (self, "Reveal queue button clicked"); reveal = gtk_revealer_get_reveal_child (GTK_REVEALER (self->queue_revealer)); gtk_revealer_set_reveal_child (GTK_REVEALER (self->queue_revealer), !reveal); } static void drop_value_notify_cb (GtkDropTarget *drop_target, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppHeaderbar *self) { const GValue *value = gtk_drop_target_get_value (drop_target); if (value && !clapper_app_utils_value_for_item_is_valid (value)) gtk_drop_target_reject (drop_target); } static gboolean drop_cb (GtkDropTarget *drop_target, const GValue *value, gdouble x, gdouble y, ClapperAppHeaderbar *self) { GFile **files = NULL; gint n_files = 0; gboolean success = FALSE; if (clapper_app_utils_files_from_value (value, &files, &n_files)) { ClapperPlayer *player; if ((player = clapper_gtk_get_player_from_ancestor (GTK_WIDGET (self)))) { ClapperQueue *queue = clapper_player_get_queue (player); gint i; for (i = 0; i < n_files; ++i) { ClapperMediaItem *item = clapper_media_item_new_from_file (files[i]); clapper_queue_add_item (queue, item); if (i == 0) // Select first added item for playback clapper_queue_select_item (queue, item); gst_object_unref (item); } success = TRUE; } clapper_app_utils_files_free (files); } return success; } static void clapper_app_headerbar_init (ClapperAppHeaderbar *self) { gtk_widget_init_template (GTK_WIDGET (self)); gtk_drop_target_set_gtypes (self->drop_target, (GType[3]) { GDK_TYPE_FILE_LIST, G_TYPE_FILE, G_TYPE_STRING }, 3); } static void clapper_app_headerbar_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_APP_TYPE_HEADERBAR); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_app_headerbar_finalize (GObject *object) { ClapperAppHeaderbar *self = CLAPPER_APP_HEADERBAR_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_headerbar_class_init (ClapperAppHeaderbarClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappheaderbar", 0, "Clapper App Headerbar"); gobject_class->dispose = clapper_app_headerbar_dispose; gobject_class->finalize = clapper_app_headerbar_finalize; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-headerbar.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperAppHeaderbar, queue_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperAppHeaderbar, previous_item_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperAppHeaderbar, next_item_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperAppHeaderbar, win_buttons_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperAppHeaderbar, drop_target); gtk_widget_class_bind_template_callback (widget_class, container_adapt_cb); gtk_widget_class_bind_template_callback (widget_class, reveal_queue_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, queue_reveal_cb); gtk_widget_class_bind_template_callback (widget_class, drop_value_notify_cb); gtk_widget_class_bind_template_callback (widget_class, drop_cb); gtk_widget_class_set_css_name (widget_class, "clapper-app-headerbar"); } clapper-0.8.0/src/bin/clapper-app/clapper-app-headerbar.h000066400000000000000000000021371474353110700232100ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_HEADERBAR (clapper_app_headerbar_get_type()) #define CLAPPER_APP_HEADERBAR_CAST(obj) ((ClapperAppHeaderbar *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppHeaderbar, clapper_app_headerbar, CLAPPER_APP, HEADERBAR, ClapperGtkContainer) G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-info-window.c000066400000000000000000000173361474353110700235350ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "clapper-app-info-window.h" #include "clapper-app-property-row.h" #include "clapper-app-list-item-utils.h" #define GST_CAT_DEFAULT clapper_app_info_window_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppInfoWindow { AdwWindow parent; GtkWidget *vstreams_list; GtkWidget *astreams_list; GtkWidget *sstreams_list; ClapperPlayer *player; }; #define parent_class clapper_app_info_window_parent_class G_DEFINE_TYPE (ClapperAppInfoWindow, clapper_app_info_window, ADW_TYPE_WINDOW); enum { PROP_0, PROP_PLAYER, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static gchar * media_duration_closure (ClapperAppInfoWindow *self, gdouble duration) { return g_strdup_printf ("%" CLAPPER_TIME_MS_FORMAT, CLAPPER_TIME_MS_ARGS (duration)); } static gchar * playback_element_name_closure (ClapperAppInfoWindow *self, GstElement *element) { GstElementFactory *factory; if (!element || !(factory = gst_element_get_factory (element))) return NULL; return gst_object_get_name (GST_OBJECT_CAST (factory)); } static gchar * playback_decoder_closure (ClapperAppInfoWindow *self, GstElement *decoder) { GstElementFactory *factory; gchar *el_name, *text; gboolean is_hardware; if (!decoder || !(factory = gst_element_get_factory (decoder))) return NULL; el_name = gst_object_get_name (GST_OBJECT_CAST (factory)); is_hardware = gst_element_factory_list_is_type (factory, GST_ELEMENT_FACTORY_TYPE_HARDWARE); text = g_strdup_printf ("%s [%s]", el_name, (is_hardware) ? _("Hardware") : _("Software")); g_free (el_name); return text; } static gchar * playback_sink_closure (ClapperAppInfoWindow *self, GstElement *sink) { GstElementFactory *factory; GParamSpec *pspec; gchar *text, *el_name, *child_name = NULL; if (!sink || !(factory = gst_element_get_factory (sink))) return NULL; el_name = gst_object_get_name (GST_OBJECT_CAST (factory)); /* We want to show actual sink if it is configurable * through a property (e.g. sink of "glsinkbin") */ if (GST_IS_BIN (sink) && ((pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (sink), "video-sink")) || (pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (sink), "sink"))) && pspec->value_type == GST_TYPE_ELEMENT) { GstElement *child = NULL; GstElementFactory *child_factory; g_object_get (sink, pspec->name, &child, NULL); if (child) { if ((child_factory = gst_element_get_factory (child))) child_name = gst_object_get_name (GST_OBJECT_CAST (child_factory)); gst_object_unref (child); } } if (child_name) { text = g_strdup_printf ("%s + %s", el_name, child_name); g_free (el_name); g_free (child_name); } else { text = el_name; } return text; } static GtkSelectionModel * create_no_selection_closure (ClapperAppInfoWindow *self, ClapperStreamList *stream_list) { return GTK_SELECTION_MODEL (gtk_no_selection_new (gst_object_ref (stream_list))); } static gboolean has_streams_closure (ClapperAppInfoWindow *self, guint n_streams) { return (n_streams > 0); } static gboolean close_cb (GtkWidget *widget, GVariant *args G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) { gtk_window_close (GTK_WINDOW (widget)); return TRUE; } GtkWidget * clapper_app_info_window_new (GtkApplication *gtk_app, ClapperPlayer *player) { ClapperAppInfoWindow *window; window = g_object_new (CLAPPER_APP_TYPE_INFO_WINDOW, "application", gtk_app, "transient-for", gtk_application_get_active_window (gtk_app), "player", player, NULL); return GTK_WIDGET (window); } static void clapper_app_info_window_init (ClapperAppInfoWindow *self) { gtk_widget_init_template (GTK_WIDGET (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"); } static void clapper_app_info_window_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_APP_TYPE_INFO_WINDOW); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_app_info_window_finalize (GObject *object) { ClapperAppInfoWindow *self = CLAPPER_APP_INFO_WINDOW_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); gst_clear_object (&self->player); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_info_window_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperAppInfoWindow *self = CLAPPER_APP_INFO_WINDOW_CAST (object); switch (prop_id) { case PROP_PLAYER: g_value_set_object (value, self->player); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_info_window_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperAppInfoWindow *self = CLAPPER_APP_INFO_WINDOW_CAST (object); switch (prop_id) { case PROP_PLAYER: self->player = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_info_window_class_init (ClapperAppInfoWindowClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappinfowindow", 0, "Clapper App Info Window"); gobject_class->get_property = clapper_app_info_window_get_property; gobject_class->set_property = clapper_app_info_window_set_property; gobject_class->dispose = clapper_app_info_window_dispose; gobject_class->finalize = clapper_app_info_window_finalize; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-info-window.ui"); param_specs[PROP_PLAYER] = g_param_spec_object ("player", NULL, NULL, CLAPPER_TYPE_PLAYER, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_add_binding (widget_class, GDK_KEY_Escape, 0, close_cb, NULL); 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_callback (widget_class, media_duration_closure); gtk_widget_class_bind_template_callback (widget_class, playback_element_name_closure); gtk_widget_class_bind_template_callback (widget_class, playback_decoder_closure); gtk_widget_class_bind_template_callback (widget_class, playback_sink_closure); gtk_widget_class_bind_template_callback (widget_class, create_no_selection_closure); gtk_widget_class_bind_template_callback (widget_class, has_streams_closure); } clapper-0.8.0/src/bin/clapper-app/clapper-app-info-window.h000066400000000000000000000023561474353110700235360ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_INFO_WINDOW (clapper_app_info_window_get_type()) #define CLAPPER_APP_INFO_WINDOW_CAST(obj) ((ClapperAppInfoWindow *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppInfoWindow, clapper_app_info_window, CLAPPER_APP, INFO_WINDOW, AdwWindow) G_GNUC_INTERNAL GtkWidget * clapper_app_info_window_new (GtkApplication *gtk_app, ClapperPlayer *player); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-list-item-utils.c000066400000000000000000000044631474353110700243370ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include "clapper-app-list-item-utils.h" gchar * clapper_app_list_item_make_stream_group_title (GtkListItem *list_item, ClapperStream *stream) { ClapperStreamType stream_type = CLAPPER_STREAM_TYPE_UNKNOWN; guint position = gtk_list_item_get_position (list_item); gchar *title = NULL; if (stream) stream_type = clapper_stream_get_stream_type (stream); switch (stream_type) { case CLAPPER_STREAM_TYPE_VIDEO: title = g_strdup_printf ("%s #%u", _("Video"), position); break; case CLAPPER_STREAM_TYPE_AUDIO: title = g_strdup_printf ("%s #%u", _("Audio"), position); break; case CLAPPER_STREAM_TYPE_SUBTITLE: title = g_strdup_printf ("%s #%u", _("Subtitles"), position); break; default: break; } return title; } gchar * clapper_app_list_item_make_resolution (GtkListItem *list_item, gint width, gint height) { return g_strdup_printf ("%ix%i", width, height); } gchar * clapper_app_list_item_make_bitrate (GtkListItem *list_item, guint bitrate) { if (bitrate >= 1000000) return g_strdup_printf ("%.3lf Mbps", (gdouble) bitrate / 1000000); return g_strdup_printf ("%u kbps", bitrate / 1000); } gchar * clapper_app_list_item_convert_int (GtkListItem *list_item, gint value) { return g_strdup_printf ("%i", value); } gchar * clapper_app_list_item_convert_uint (GtkListItem *list_item, guint value) { return g_strdup_printf ("%u", value); } gchar * clapper_app_list_item_convert_double (GtkListItem *list_item, gdouble value) { return g_strdup_printf ("%.3lf", value); } clapper-0.8.0/src/bin/clapper-app/clapper-app-list-item-utils.h000066400000000000000000000030611474353110700243350ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include "clapper-app-internal-visibility.h" G_BEGIN_DECLS CLAPPER_APP_INTERNAL_API gchar * clapper_app_list_item_make_stream_group_title (GtkListItem *list_item, ClapperStream *stream); CLAPPER_APP_INTERNAL_API gchar * clapper_app_list_item_make_resolution (GtkListItem *list_item, gint width, gint height); CLAPPER_APP_INTERNAL_API gchar * clapper_app_list_item_make_bitrate (GtkListItem *list_item, guint value); CLAPPER_APP_INTERNAL_API gchar * clapper_app_list_item_convert_int (GtkListItem *list_item, gint value); CLAPPER_APP_INTERNAL_API gchar * clapper_app_list_item_convert_uint (GtkListItem *list_item, guint value); CLAPPER_APP_INTERNAL_API gchar * clapper_app_list_item_convert_double (GtkListItem *list_item, gdouble value); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-media-item-box.c000066400000000000000000000060141474353110700240650ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "clapper-app-media-item-box.h" struct _ClapperAppMediaItemBox { GtkBox parent; ClapperMediaItem *media_item; }; enum { PROP_0, PROP_MEDIA_ITEM, PROP_LAST }; #define parent_class clapper_app_media_item_box_parent_class G_DEFINE_TYPE (ClapperAppMediaItemBox, clapper_app_media_item_box, GTK_TYPE_BOX); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; ClapperMediaItem * clapper_app_media_item_box_get_media_item (ClapperAppMediaItemBox *self) { return self->media_item; } static void clapper_app_media_item_box_init (ClapperAppMediaItemBox *self) { } static void clapper_app_media_item_box_finalize (GObject *object) { ClapperAppMediaItemBox *self = CLAPPER_APP_MEDIA_ITEM_BOX_CAST (object); gst_clear_object (&self->media_item); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_media_item_box_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperAppMediaItemBox *self = CLAPPER_APP_MEDIA_ITEM_BOX_CAST (object); switch (prop_id) { case PROP_MEDIA_ITEM: g_value_set_object (value, clapper_app_media_item_box_get_media_item (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_media_item_box_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperAppMediaItemBox *self = CLAPPER_APP_MEDIA_ITEM_BOX_CAST (object); switch (prop_id) { case PROP_MEDIA_ITEM: gst_object_replace ((GstObject **) &self->media_item, GST_OBJECT_CAST (g_value_get_object (value))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_media_item_box_class_init (ClapperAppMediaItemBoxClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; gobject_class->get_property = clapper_app_media_item_box_get_property; gobject_class->set_property = clapper_app_media_item_box_set_property; gobject_class->finalize = clapper_app_media_item_box_finalize; param_specs[PROP_MEDIA_ITEM] = g_param_spec_object ("media-item", NULL, NULL, CLAPPER_TYPE_MEDIA_ITEM, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/bin/clapper-app/clapper-app-media-item-box.h000066400000000000000000000023531474353110700240740ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_MEDIA_ITEM_BOX (clapper_app_media_item_box_get_type()) #define CLAPPER_APP_MEDIA_ITEM_BOX_CAST(obj) ((ClapperAppMediaItemBox *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppMediaItemBox, clapper_app_media_item_box, CLAPPER_APP, MEDIA_ITEM_BOX, GtkBox) G_GNUC_INTERNAL ClapperMediaItem * clapper_app_media_item_box_get_media_item (ClapperAppMediaItemBox *box); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-preferences-window.c000066400000000000000000000501021474353110700250670ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "clapper-app-preferences-window.h" #include "clapper-app-application.h" #include "clapper-app-utils.h" #define GST_CAT_DEFAULT clapper_app_preferences_window_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppPreferencesWindow { AdwPreferencesWindow parent; AdwComboRow *seek_method_combo_row; AdwComboRow *seek_unit_combo_row; AdwSpinRow *seek_value_spin_row; AdwSwitchRow *server_switch_row; AdwSpinRow *audio_offset_spin_row; AdwSpinRow *subtitle_offset_spin_row; GtkFontDialogButton *font_dialog_button; AdwNavigationPage *plugins_subpage; AdwComboRow *plugins_combo_row; AdwComboRow *features_combo_row; AdwPreferencesGroup *overrides_group; GSettings *settings; GList *features; GtkStringList *plugins_list; GPtrArray *rank_rows; gulong ranks_setting_changed_id; gboolean ranking_has_plugins_model; }; #define parent_class clapper_app_preferences_window_parent_class G_DEFINE_TYPE (ClapperAppPreferencesWindow, clapper_app_preferences_window, ADW_TYPE_PREFERENCES_WINDOW); typedef struct { ClapperAppPreferencesWindow *prefs; GHashTable *parsed_overrides; gboolean updated; } ClapperAppPreferencesIterRanksData; enum { PROP_0, PROP_RANK_ROWS, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; /* Sort by plugin name and if the same, sort by element name */ static gint _compare_plugins_cb (gconstpointer ptr_a, gconstpointer ptr_b) { GstPluginFeature *feature_a = GST_PLUGIN_FEATURE_CAST (ptr_a); GstPluginFeature *feature_b = GST_PLUGIN_FEATURE_CAST (ptr_b); gint result; result = strcmp ( gst_plugin_feature_get_plugin_name (feature_a), gst_plugin_feature_get_plugin_name (feature_b)); if (result == 0) { result = strcmp ( gst_plugin_feature_get_name (feature_a), gst_plugin_feature_get_name (feature_b)); } return result; } static gint _compare_names_cb (gconstpointer ptr_a, gconstpointer ptr_b) { GstPluginFeature *feature = GST_PLUGIN_FEATURE_CAST (ptr_a); const gchar *plugin_name = (const gchar *) ptr_b; return strcmp (gst_plugin_feature_get_plugin_name (feature), plugin_name); } static gboolean _prefs_rows_compare_func (gconstpointer ptr_a, gconstpointer ptr_b) { AdwPreferencesRow *row = (AdwPreferencesRow *) ptr_a; const gchar *name = (const gchar *) ptr_b; return (strcmp (adw_preferences_row_get_title (row), name) == 0); } static gboolean _find_rank_overide_for_name (ClapperAppPreferencesWindow *self, const gchar *plugin_feature, guint *index) { return g_ptr_array_find_with_equal_func (self->rank_rows, plugin_feature, (GEqualFunc) _prefs_rows_compare_func, index); } static gboolean _plugin_feature_filter_cb (GstPluginFeature *feature, gpointer user_data G_GNUC_UNUSED) { return GST_IS_ELEMENT_FACTORY (feature); } static void remove_rank_override_button_clicked_cb (GtkButton *button, ClapperAppPreferencesWindow *self) { GtkWidget *spin_row; const gchar *feature_name; spin_row = gtk_widget_get_ancestor (GTK_WIDGET (button), ADW_TYPE_SPIN_ROW); feature_name = adw_preferences_row_get_title (ADW_PREFERENCES_ROW (spin_row)); GST_DEBUG ("Removing rank override for: %s", feature_name); g_ptr_array_remove (self->rank_rows, spin_row); adw_preferences_group_remove (self->overrides_group, GTK_WIDGET (spin_row)); gtk_widget_set_visible (GTK_WIDGET (self->overrides_group), self->rank_rows->len > 0); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_RANK_ROWS]); } static void _add_rank_override (ClapperAppPreferencesWindow *self, const gchar *feature_name, GstRank rank, gboolean from_env) { GtkWidget *spin_row, *remove_button; spin_row = adw_spin_row_new_with_range (0, G_MAXINT, 1); remove_button = gtk_button_new_from_icon_name ("user-trash-symbolic"); gtk_widget_set_halign (remove_button, GTK_ALIGN_CENTER); gtk_widget_set_valign (remove_button, GTK_ALIGN_CENTER); gtk_widget_add_css_class (remove_button, "circular"); adw_preferences_row_set_title (ADW_PREFERENCES_ROW (spin_row), feature_name); adw_action_row_add_prefix (ADW_ACTION_ROW (spin_row), remove_button); adw_spin_row_set_numeric (ADW_SPIN_ROW (spin_row), TRUE); adw_spin_row_set_value (ADW_SPIN_ROW (spin_row), rank); gtk_widget_set_sensitive (spin_row, !from_env); if (!from_env) { g_signal_connect (remove_button, "clicked", G_CALLBACK (remove_rank_override_button_clicked_cb), self); } adw_preferences_group_add (self->overrides_group, spin_row); g_ptr_array_add (self->rank_rows, spin_row); } static void _iter_ranks_func (const gchar *feature_name, GstRank rank, gboolean from_env, ClapperAppPreferencesIterRanksData *data) { ClapperAppPreferencesWindow *self = data->prefs; guint index = 0; if (_find_rank_overide_for_name (self, feature_name, &index)) { GtkWidget *spin_row = g_ptr_array_index (self->rank_rows, index); if (rank != adw_spin_row_get_value (ADW_SPIN_ROW (spin_row))) { adw_spin_row_set_value (ADW_SPIN_ROW (spin_row), rank); data->updated = TRUE; } if (from_env == gtk_widget_get_sensitive (spin_row)) { gtk_widget_set_sensitive (spin_row, !from_env); data->updated = TRUE; } } else { _add_rank_override (self, feature_name, rank, from_env); data->updated = TRUE; } g_hash_table_insert (data->parsed_overrides, g_strdup (feature_name), GINT_TO_POINTER (rank)); } static void _update_rank_overrides (ClapperAppPreferencesWindow *self) { ClapperAppPreferencesIterRanksData *data; gint i; data = g_new (ClapperAppPreferencesIterRanksData, 1); data->prefs = self; data->parsed_overrides = g_hash_table_new (g_str_hash, g_str_equal); data->updated = FALSE; GST_DEBUG ("Updating rank overrides"); clapper_app_utils_iterate_plugin_feature_ranks (self->settings, (ClapperAppUtilsIterRanks) _iter_ranks_func, data); for (i = self->rank_rows->len - 1; i >= 0; --i) { AdwPreferencesRow *prefs_row = ADW_PREFERENCES_ROW (g_ptr_array_index (self->rank_rows, i)); const gchar *feature_name = adw_preferences_row_get_title (prefs_row); if (!g_hash_table_contains (data->parsed_overrides, feature_name)) { g_ptr_array_remove_index (self->rank_rows, i); adw_preferences_group_remove (self->overrides_group, GTK_WIDGET (prefs_row)); data->updated = TRUE; } } if (data->updated) { gtk_widget_set_visible (GTK_WIDGET (self->overrides_group), self->rank_rows->len > 0); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_RANK_ROWS]); } g_hash_table_unref (data->parsed_overrides); g_free (data); } static void add_override_button_clicked_cb (GtkButton *button, ClapperAppPreferencesWindow *self) { GstPluginFeature *plugin_feature; GstRank rank; GtkStringObject *string_obj; const gchar *feature_name; string_obj = GTK_STRING_OBJECT (adw_combo_row_get_selected_item (self->features_combo_row)); /* Should never happen, as button is insensitive when no selection */ if (G_UNLIKELY (string_obj == NULL)) return; feature_name = gtk_string_object_get_string (string_obj); GST_DEBUG ("Adding rank override for: %s", feature_name); plugin_feature = gst_registry_lookup_feature (gst_registry_get (), feature_name); rank = gst_plugin_feature_get_rank (plugin_feature); _add_rank_override (self, feature_name, rank, FALSE); gtk_widget_set_visible (GTK_WIDGET (self->overrides_group), self->rank_rows->len > 0); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_RANK_ROWS]); gst_object_unref (plugin_feature); } static GtkStringList * _make_plugin_features_string_list (ClapperAppPreferencesWindow *self, const gchar *plugin_name) { GList *features, *feat; GtkStringList *features_list; GStrvBuilder *builder; gchar **features_names; GST_DEBUG ("Reading plugin features for plugin: %s", plugin_name); features = g_list_find_custom (self->features, plugin_name, (GCompareFunc) _compare_names_cb); builder = g_strv_builder_new (); for (feat = features; feat != NULL; feat = feat->next) { GstPluginFeature *feature = GST_PLUGIN_FEATURE_CAST (feat->data); const gchar *feature_name = gst_plugin_feature_get_name (feature); if (strcmp (gst_plugin_feature_get_plugin_name (feature), plugin_name) != 0) break; g_strv_builder_add (builder, feature_name); } features_names = g_strv_builder_end (builder); g_strv_builder_unref (builder); features_list = gtk_string_list_new ((const gchar *const *) features_names); g_strfreev (features_names); GST_DEBUG ("Found plugin features: %u", g_list_model_get_n_items (G_LIST_MODEL (features_list))); return features_list; } static GtkStringList * ranking_features_model_closure (ClapperAppPreferencesWindow *self, GtkStringObject *string_obj) { if (!string_obj || !self->ranking_has_plugins_model) return NULL; return _make_plugin_features_string_list (self, gtk_string_object_get_string (string_obj)); } static gboolean add_override_button_sensitive_closure (ClapperAppPreferencesWindow *self, GtkStringObject *string_obj, GPtrArray *rank_rows) { return (string_obj && !_find_rank_overide_for_name (self, gtk_string_object_get_string (string_obj), NULL)); } static void plugin_feature_ranks_settings_changed_cb (GSettings *settings, gchar *key G_GNUC_UNUSED, ClapperAppPreferencesWindow *self) { GST_DEBUG ("Plugin feature ranks stored setting changed"); _update_rank_overrides (self); } static void _ensure_plugins_and_features_lists (ClapperAppPreferencesWindow *self) { GList *feat; GStrvBuilder *builder; gchar **plugin_names; const gchar *last_plugin_name = NULL; /* Return if we were here already. Features can be NULL * when no plugins are found at specified directory */ if (self->features || self->plugins_list) return; GST_DEBUG ("Reading available plugin features..."); self->features = gst_registry_feature_filter (gst_registry_get (), (GstPluginFeatureFilter) _plugin_feature_filter_cb, FALSE, NULL); self->features = g_list_sort (self->features, (GCompareFunc) _compare_plugins_cb); builder = g_strv_builder_new (); for (feat = self->features; feat != NULL; feat = feat->next) { GstPluginFeature *feature = GST_PLUGIN_FEATURE_CAST (feat->data); const gchar *plugin_name = gst_plugin_feature_get_plugin_name (feature); if (g_strcmp0 (plugin_name, last_plugin_name) != 0) { g_strv_builder_add (builder, plugin_name); last_plugin_name = plugin_name; } } plugin_names = g_strv_builder_end (builder); g_strv_builder_unref (builder); GST_DEBUG ("Read all available plugin features"); self->plugins_list = gtk_string_list_new ((const gchar *const *) plugin_names); g_strfreev (plugin_names); } static void plugin_ranking_activated_cb (AdwActionRow *action_row, ClapperAppPreferencesWindow *self) { _ensure_plugins_and_features_lists (self); if (!self->ranking_has_plugins_model) { adw_combo_row_set_model (self->plugins_combo_row, G_LIST_MODEL (self->plugins_list)); adw_combo_row_set_selected (self->plugins_combo_row, GTK_INVALID_LIST_POSITION); GST_DEBUG ("Populated plugins combo row in ranking subpage"); /* This is needed here so we will not populate plugin features row after setting * model and unset it again after changing back to GTK_INVALID_LIST_POSITION */ self->ranking_has_plugins_model = TRUE; } if (self->ranks_setting_changed_id == 0) { self->ranks_setting_changed_id = g_signal_connect (self->settings, "changed::plugin-feature-ranks", G_CALLBACK (plugin_feature_ranks_settings_changed_cb), self); } _update_rank_overrides (self); adw_preferences_window_push_subpage (ADW_PREFERENCES_WINDOW (self), self->plugins_subpage); } static void plugin_ranking_unrealize_cb (GtkWidget *widget, ClapperAppPreferencesWindow *self) { GString *string; gchar *ranks_str; guint i; /* Since we are closing ranking subpage, disconnect this * signal as we do not need to update widgets immediately */ if (self->ranks_setting_changed_id != 0) { g_signal_handler_disconnect (self->settings, self->ranks_setting_changed_id); self->ranks_setting_changed_id = 0; } GST_DEBUG ("Saving current rank overrides"); string = g_string_new (NULL); for (i = 0; i < self->rank_rows->len; ++i) { GtkWidget *spin_row = g_ptr_array_index (self->rank_rows, i); GstRank rank; const gchar *feature_name; /* Insensitive are from env, we do not want to save these */ if (!gtk_widget_get_sensitive (spin_row)) continue; rank = adw_spin_row_get_value (ADW_SPIN_ROW (spin_row)); feature_name = adw_preferences_row_get_title (ADW_PREFERENCES_ROW (spin_row)); if (string->len == 0) g_string_append_printf (string, "%s:%i", feature_name, rank); else g_string_append_printf (string, ",%s:%i", feature_name, rank); } ranks_str = g_string_free_and_steal (string); g_settings_set_string (self->settings, "plugin-feature-ranks", ranks_str); g_free (ranks_str); } static gchar * seek_method_name_closure (AdwEnumListItem *list_item, gpointer *user_data G_GNUC_UNUSED) { switch (adw_enum_list_item_get_value (list_item)) { case CLAPPER_PLAYER_SEEK_METHOD_ACCURATE: return g_strdup (_("Accurate")); case CLAPPER_PLAYER_SEEK_METHOD_NORMAL: return g_strdup (_("Normal")); case CLAPPER_PLAYER_SEEK_METHOD_FAST: return g_strdup (_("Fast")); default: return NULL; } } static gboolean _get_font_mapping (GValue *value, GVariant *variant, gpointer user_data G_GNUC_UNUSED) { PangoFontDescription *desc; const gchar *desc_str = g_variant_get_string (variant, NULL); desc = pango_font_description_from_string (desc_str); g_value_set_boxed (value, desc); pango_font_description_free (desc); return TRUE; } static GVariant * _set_font_mapping (const GValue *value, const GVariantType *expected_type, gpointer user_data G_GNUC_UNUSED) { PangoFontDescription *desc; gchar *desc_str; desc = (PangoFontDescription *) g_value_get_boxed (value); desc_str = pango_font_description_to_string (desc); return g_variant_new_take_string (desc_str); } GtkWidget * clapper_app_preferences_window_new (GtkApplication *gtk_app) { ClapperAppPreferencesWindow *window; window = g_object_new (CLAPPER_APP_TYPE_PREFERENCES_WINDOW, "application", gtk_app, "transient-for", gtk_application_get_active_window (gtk_app), NULL); return GTK_WIDGET (window); } static void clapper_app_preferences_window_init (ClapperAppPreferencesWindow *self) { gtk_widget_init_template (GTK_WIDGET (self)); self->rank_rows = g_ptr_array_new (); self->settings = g_settings_new (CLAPPER_APP_ID); g_settings_bind (self->settings, "seek-method", self->seek_method_combo_row, "selected", G_SETTINGS_BIND_DEFAULT); g_settings_bind (self->settings, "seek-unit", self->seek_unit_combo_row, "selected", G_SETTINGS_BIND_DEFAULT); g_settings_bind (self->settings, "seek-value", self->seek_value_spin_row, "value", G_SETTINGS_BIND_DEFAULT); #if CLAPPER_HAVE_SERVER g_settings_bind (self->settings, "server-enabled", self->server_switch_row, "active", G_SETTINGS_BIND_DEFAULT); #else gtk_widget_set_sensitive (GTK_WIDGET (self->server_switch_row), FALSE); #endif g_settings_bind (self->settings, "audio-offset", self->audio_offset_spin_row, "value", G_SETTINGS_BIND_DEFAULT); g_settings_bind (self->settings, "subtitle-offset", self->subtitle_offset_spin_row, "value", G_SETTINGS_BIND_DEFAULT); g_settings_bind_with_mapping (self->settings, "subtitle-font-desc", self->font_dialog_button, "font-desc", G_SETTINGS_BIND_DEFAULT, (GSettingsBindGetMapping) _get_font_mapping, (GSettingsBindSetMapping) _set_font_mapping, NULL, NULL); } static void clapper_app_preferences_window_dispose (GObject *object) { ClapperAppPreferencesWindow *self = CLAPPER_APP_PREFERENCES_WINDOW_CAST (object); g_clear_pointer (&self->rank_rows, g_ptr_array_unref); gtk_widget_dispose_template (GTK_WIDGET (self), CLAPPER_APP_TYPE_PREFERENCES_WINDOW); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_app_preferences_window_finalize (GObject *object) { ClapperAppPreferencesWindow *self = CLAPPER_APP_PREFERENCES_WINDOW_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_object_unref (self->settings); g_clear_object (&self->plugins_list); if (self->features) gst_plugin_feature_list_free (self->features); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_preferences_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperAppPreferencesWindow *self = CLAPPER_APP_PREFERENCES_WINDOW_CAST (object); switch (prop_id) { case PROP_RANK_ROWS: g_value_set_boxed (value, self->rank_rows); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_preferences_window_class_init (ClapperAppPreferencesWindowClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperapppreferenceswindow", 0, "Clapper App Preferences Window"); gobject_class->get_property = clapper_app_preferences_get_property; gobject_class->dispose = clapper_app_preferences_window_dispose; gobject_class->finalize = clapper_app_preferences_window_finalize; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-preferences-window.ui"); param_specs[PROP_RANK_ROWS] = g_param_spec_boxed ("rank-rows", NULL, NULL, G_TYPE_PTR_ARRAY, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, seek_method_combo_row); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, seek_unit_combo_row); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, seek_value_spin_row); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, server_switch_row); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, audio_offset_spin_row); 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, 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); gtk_widget_class_bind_template_child (widget_class, ClapperAppPreferencesWindow, overrides_group); gtk_widget_class_bind_template_callback (widget_class, seek_method_name_closure); 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, 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); } clapper-0.8.0/src/bin/clapper-app/clapper-app-preferences-window.h000066400000000000000000000023751474353110700251050ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_PREFERENCES_WINDOW (clapper_app_preferences_window_get_type()) #define CLAPPER_APP_PREFERENCES_WINDOW_CAST(obj) ((ClapperAppPreferencesWindow *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppPreferencesWindow, clapper_app_preferences_window, CLAPPER_APP, PREFERENCES_WINDOW, AdwPreferencesWindow) G_GNUC_INTERNAL GtkWidget * clapper_app_preferences_window_new (GtkApplication *gtk_app); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-property-row.c000066400000000000000000000043421474353110700237570ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "clapper-app-property-row.h" struct _ClapperAppPropertyRow { AdwActionRow parent; }; #define parent_class clapper_app_property_row_parent_class G_DEFINE_TYPE (ClapperAppPropertyRow, clapper_app_property_row, ADW_TYPE_ACTION_ROW); static inline void _ensure_subtitle (AdwActionRow *action_row) { const gchar *subtitle = adw_action_row_get_subtitle (action_row); if (!subtitle || strlen (subtitle) == 0) adw_action_row_set_subtitle (action_row, "-"); } static void _subtitle_changed_cb (AdwActionRow *action_row, GParamSpec *pspec G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) { _ensure_subtitle (action_row); } static void clapper_app_property_row_realize (GtkWidget *widget) { _ensure_subtitle (ADW_ACTION_ROW (widget)); g_signal_connect (widget, "notify::subtitle", G_CALLBACK (_subtitle_changed_cb), NULL); GTK_WIDGET_CLASS (parent_class)->realize (widget); } static void clapper_app_property_row_unrealize (GtkWidget *widget) { g_signal_handlers_disconnect_by_func (widget, _subtitle_changed_cb, NULL); GTK_WIDGET_CLASS (parent_class)->unrealize (widget); } static void clapper_app_property_row_init (ClapperAppPropertyRow *self) { gtk_widget_add_css_class (GTK_WIDGET (self), "property"); } static void clapper_app_property_row_class_init (ClapperAppPropertyRowClass *klass) { GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; widget_class->realize = clapper_app_property_row_realize; widget_class->unrealize = clapper_app_property_row_unrealize; } clapper-0.8.0/src/bin/clapper-app/clapper-app-property-row.h000066400000000000000000000021331474353110700237600ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_PROPERTY_ROW (clapper_app_property_row_get_type()) #define CLAPPER_APP_PROPERTY_ROW_CAST(obj) ((ClapperAppPropertyRow *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppPropertyRow, clapper_app_property_row, CLAPPER_APP, PROPERTY_ROW, AdwActionRow) G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-list.c000066400000000000000000000357271474353110700233760ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include "clapper-app-queue-list.h" #include "clapper-app-queue-selection.h" #include "clapper-app-media-item-box.h" #include "clapper-app-utils.h" #define GST_CAT_DEFAULT clapper_app_queue_list_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppQueueList { GtkBox parent; GtkWidget *progression_drop_down; GtkWidget *list_view; GtkWidget *stack; GtkWidget *stack_default_page; GtkWidget *stack_trash_page; GtkDropTarget *trash_drop_target; GtkDropTarget *drop_target; GBinding *queue_progression_binding; GtkWidget *list_target; // store last target gboolean drop_after; // if should drop below list_target }; #define parent_class clapper_app_queue_list_parent_class G_DEFINE_TYPE (ClapperAppQueueList, clapper_app_queue_list, GTK_TYPE_BOX); typedef struct { ClapperMediaItem *item; GtkWidget *widget; GdkPaintable *paintable; gdouble x, y; } ClapperAppQueueListDragData; static GdkContentProvider * drag_item_prepare_cb (GtkDragSource *drag_source, gdouble x, gdouble y, ClapperAppQueueList *self) { GtkWidget *list_view, *pickup, *list_widget; GdkPaintable *paintable; ClapperMediaItem *item; ClapperAppQueueListDragData *drag_data; graphene_point_t p; /* Ensure no target yet */ self->list_target = NULL; list_view = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag_source)); pickup = gtk_widget_pick (list_view, x, y, GTK_PICK_DEFAULT); if (G_UNLIKELY (pickup == NULL) || !CLAPPER_APP_IS_MEDIA_ITEM_BOX (pickup)) return NULL; list_widget = gtk_widget_get_parent (pickup); item = clapper_app_media_item_box_get_media_item (CLAPPER_APP_MEDIA_ITEM_BOX_CAST (pickup)); if (G_UNLIKELY (item == NULL || list_widget == NULL)) return NULL; GST_DEBUG_OBJECT (self, "Preparing drag for: %" GST_PTR_FORMAT, item); if (!gtk_widget_compute_point (list_view, list_widget, &GRAPHENE_POINT_INIT (x, y), &p)) graphene_point_init (&p, x, y); paintable = gtk_widget_paintable_new (list_widget); drag_data = g_new0 (ClapperAppQueueListDragData, 1); drag_data->item = gst_object_ref (item); drag_data->widget = g_object_ref_sink (pickup); drag_data->paintable = gdk_paintable_get_current_image (paintable); drag_data->x = p.x; drag_data->y = p.y; g_object_set_data (G_OBJECT (self), "drag-data", drag_data); g_object_unref (paintable); return gdk_content_provider_new_typed (GTK_TYPE_WIDGET, pickup); } static void drag_item_drag_begin_cb (GtkDragSource *drag_source, GdkDrag *drag, ClapperAppQueueList *self) { ClapperAppQueueListDragData *drag_data; GtkWidget *list_view; drag_data = (ClapperAppQueueListDragData *) g_object_get_data (G_OBJECT (self), "drag-data"); gtk_drag_source_set_icon (drag_source, drag_data->paintable, drag_data->x, drag_data->y); gtk_widget_set_opacity (drag_data->widget, 0.3); list_view = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag_source)); gtk_widget_add_css_class (list_view, "dnd"); gtk_stack_set_visible_child (GTK_STACK (self->stack), self->stack_trash_page); } static void drag_item_drag_end_cb (GtkDragSource *drag_source, GdkDrag *drag, gboolean delete_data, ClapperAppQueueList *self) { ClapperAppQueueListDragData *drag_data; GtkWidget *list_view; drag_data = (ClapperAppQueueListDragData *) g_object_get_data (G_OBJECT (self), "drag-data"); g_object_set_data (G_OBJECT (self), "drag-data", NULL); list_view = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag_source)); gtk_widget_remove_css_class (list_view, "dnd"); gtk_widget_set_opacity (drag_data->widget, 1.0); gtk_stack_set_visible_child (GTK_STACK (self->stack), self->stack_default_page); gst_object_unref (drag_data->item); g_object_unref (drag_data->widget); g_object_unref (drag_data->paintable); g_free (drag_data); gtk_event_controller_reset (GTK_EVENT_CONTROLLER (drag_source)); } static void queue_drop_value_notify_cb (GtkDropTarget *drop_target, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppQueueList *self) { const GValue *value = gtk_drop_target_get_value (drop_target); if (value && !clapper_app_utils_value_for_item_is_valid (value)) gtk_drop_target_reject (drop_target); } static GdkDragAction queue_drop_motion_cb (GtkDropTarget *drop_target, gdouble x, gdouble y, ClapperAppQueueList *self) { GtkWidget *list_view, *pickup; GdkDrop *drop; list_view = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drop_target)); pickup = gtk_widget_pick (list_view, x, y, GTK_PICK_DEFAULT); if (pickup && CLAPPER_APP_IS_MEDIA_ITEM_BOX (pickup)) { ClapperAppQueueListDragData *drag_data; GtkWidget *list_widget; graphene_point_t point; gint height, margin_top = 0, margin_bottom = 0; drag_data = (ClapperAppQueueListDragData *) g_object_get_data (G_OBJECT (self), "drag-data"); list_widget = gtk_widget_get_parent (pickup); height = gtk_widget_get_height (list_widget); if ((!drag_data || pickup != drag_data->widget) && gtk_widget_compute_point (list_view, list_widget, &GRAPHENE_POINT_INIT (x, y), &point)) { GtkWidget *sibling = NULL; if (point.y < (gfloat) height / 2) { if (drag_data) sibling = gtk_widget_get_prev_sibling (list_widget); if (!sibling || gtk_widget_get_parent (drag_data->widget) != sibling) margin_top = height; } else { if (drag_data) sibling = gtk_widget_get_next_sibling (list_widget); if (!sibling || gtk_widget_get_parent (drag_data->widget) != sibling) margin_bottom = height; } } if (self->list_target && self->list_target != list_widget) { gtk_widget_set_margin_top (self->list_target, 0); gtk_widget_set_margin_bottom (self->list_target, 0); } gtk_widget_set_margin_top (list_widget, margin_top); gtk_widget_set_margin_bottom (list_widget, margin_bottom); self->list_target = list_widget; self->drop_after = (margin_bottom > margin_top); } if ((drop = gtk_drop_target_get_current_drop (drop_target))) { GdkContentFormats *formats = gdk_drop_get_formats (drop); /* If it is a widget we move it from one place to another */ if (gdk_content_formats_contain_gtype (formats, GTK_TYPE_WIDGET)) return GDK_ACTION_MOVE; } return GDK_ACTION_COPY; } static void queue_drop_leave_cb (GtkDropTarget *drop_target, ClapperAppQueueList *self) { if (self->list_target) { gtk_widget_set_margin_top (self->list_target, 0); gtk_widget_set_margin_bottom (self->list_target, 0); } } static gboolean queue_drop_cb (GtkDropTarget *drop_target, const GValue *value, gdouble x, gdouble y, ClapperAppQueueList *self) { ClapperQueue *queue; ClapperMediaItem *item; GtkWidget *pickup; guint drop_index = 0; gboolean success = FALSE; if (G_UNLIKELY (self->list_target == NULL)) return FALSE; pickup = gtk_widget_get_first_child (self->list_target); /* Reset margins on drop */ gtk_widget_set_margin_top (self->list_target, 0); gtk_widget_set_margin_bottom (self->list_target, 0); self->list_target = NULL; if (G_UNLIKELY (pickup == NULL) || !CLAPPER_APP_IS_MEDIA_ITEM_BOX (pickup)) return FALSE; item = clapper_app_media_item_box_get_media_item (CLAPPER_APP_MEDIA_ITEM_BOX_CAST (pickup)); queue = CLAPPER_QUEUE (gst_object_get_parent (GST_OBJECT (item))); if (G_UNLIKELY (queue == NULL)) return FALSE; if (!clapper_queue_find_item (queue, item, &drop_index)) { gst_object_unref (queue); return FALSE; } if (self->drop_after) drop_index++; /* Moving item with widget */ if (G_VALUE_HOLDS (value, GTK_TYPE_WIDGET)) { ClapperAppQueueListDragData *drag_data; drag_data = (ClapperAppQueueListDragData *) g_object_get_data (G_OBJECT (self), "drag-data"); /* Insert at different place */ if (item != drag_data->item) { guint index = 0; if (clapper_queue_find_item (queue, drag_data->item, &index)) { if (drop_index > index) drop_index--; clapper_queue_reposition_item (queue, drag_data->item, drop_index); success = TRUE; } } } else { GFile **files = NULL; gint n_files = 0; if (clapper_app_utils_files_from_value (value, &files, &n_files)) { gint i; for (i = 0; i < n_files; ++i) { ClapperMediaItem *new_item = clapper_media_item_new_from_file (files[i]); clapper_queue_insert_item (queue, new_item, drop_index + i); gst_object_unref (new_item); } clapper_app_utils_files_free (files); success = TRUE; } } gst_object_unref (queue); return success; } static gboolean trash_drop_cb (GtkDropTarget *drop_target, const GValue *value, gdouble x, gdouble y, ClapperAppQueueList *self) { ClapperAppQueueListDragData *drag_data; ClapperQueue *queue; drag_data = (ClapperAppQueueListDragData *) g_object_get_data (G_OBJECT (self), "drag-data"); if ((queue = CLAPPER_QUEUE (gst_object_get_parent (GST_OBJECT (drag_data->item))))) { clapper_queue_remove_item (queue, drag_data->item); gst_object_unref (queue); } return TRUE; } static void _item_selected_cb (ClapperAppQueueSelection *selection, guint index, ClapperAppQueueList *self) { GtkWidget *list_revealer; /* Auto hide queue list after selection */ list_revealer = gtk_widget_get_ancestor (self->list_view, GTK_TYPE_REVEALER); if (G_LIKELY (list_revealer != NULL)) gtk_revealer_set_reveal_child (GTK_REVEALER (list_revealer), FALSE); } static gboolean _queue_progression_mode_transform_to_func (GBinding *binding, const GValue *from_value, GValue *to_value, ClapperAppQueueList *self) { ClapperQueueProgressionMode mode = g_value_get_enum (from_value); g_value_set_uint (to_value, (guint) mode); return TRUE; } static gboolean _queue_progression_mode_transform_from_func (GBinding *binding, const GValue *from_value, GValue *to_value, ClapperAppQueueList *self) { guint mode = g_value_get_uint (from_value); if (mode == GTK_INVALID_LIST_POSITION) return FALSE; g_value_set_enum (to_value, (ClapperQueueProgressionMode) mode); return TRUE; } static void clapper_app_queue_list_realize (GtkWidget *widget) { ClapperAppQueueList *self = CLAPPER_APP_QUEUE_LIST_CAST (widget); ClapperPlayer *player; GTK_WIDGET_CLASS (parent_class)->realize (widget); GST_TRACE_OBJECT (self, "Realize"); if ((player = clapper_gtk_get_player_from_ancestor (widget))) { ClapperQueue *queue = clapper_player_get_queue (player); ClapperAppQueueSelection *selection = clapper_app_queue_selection_new (queue); g_signal_connect (selection, "item-selected", G_CALLBACK (_item_selected_cb), self); self->queue_progression_binding = g_object_bind_property_full (queue, "progression-mode", self->progression_drop_down, "selected", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL, (GBindingTransformFunc) _queue_progression_mode_transform_to_func, (GBindingTransformFunc) _queue_progression_mode_transform_from_func, self, NULL); gtk_list_view_set_model (GTK_LIST_VIEW (self->list_view), GTK_SELECTION_MODEL (selection)); g_object_unref (selection); } } static void clapper_app_queue_list_unrealize (GtkWidget *widget) { ClapperAppQueueList *self = CLAPPER_APP_QUEUE_LIST_CAST (widget); GST_TRACE_OBJECT (self, "Unrealize"); g_clear_pointer (&self->queue_progression_binding, g_binding_unbind); gtk_list_view_set_model (GTK_LIST_VIEW (self->list_view), NULL); GTK_WIDGET_CLASS (parent_class)->unrealize (widget); } static void clapper_app_queue_list_init (ClapperAppQueueList *self) { gtk_widget_init_template (GTK_WIDGET (self)); /* Does not work correctly with OSD */ gtk_widget_remove_css_class (self->list_view, "view"); gtk_drop_target_set_gtypes (self->trash_drop_target, (GType[1]) { GTK_TYPE_WIDGET }, 1); gtk_drop_target_set_gtypes (self->drop_target, (GType[4]) { GTK_TYPE_WIDGET, GDK_TYPE_FILE_LIST, G_TYPE_FILE, G_TYPE_STRING }, 4); } static void clapper_app_queue_list_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_APP_TYPE_QUEUE_LIST); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_app_queue_list_finalize (GObject *object) { ClapperAppQueueList *self = CLAPPER_APP_QUEUE_LIST_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_queue_list_class_init (ClapperAppQueueListClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappqueuelist", 0, "Clapper App Queue List"); gobject_class->dispose = clapper_app_queue_list_dispose; gobject_class->finalize = clapper_app_queue_list_finalize; widget_class->realize = clapper_app_queue_list_realize; widget_class->unrealize = clapper_app_queue_list_unrealize; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-queue-list.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperAppQueueList, progression_drop_down); gtk_widget_class_bind_template_child (widget_class, ClapperAppQueueList, list_view); gtk_widget_class_bind_template_child (widget_class, ClapperAppQueueList, stack); gtk_widget_class_bind_template_child (widget_class, ClapperAppQueueList, stack_default_page); gtk_widget_class_bind_template_child (widget_class, ClapperAppQueueList, stack_trash_page); gtk_widget_class_bind_template_child (widget_class, ClapperAppQueueList, trash_drop_target); gtk_widget_class_bind_template_child (widget_class, ClapperAppQueueList, drop_target); gtk_widget_class_bind_template_callback (widget_class, drag_item_prepare_cb); gtk_widget_class_bind_template_callback (widget_class, drag_item_drag_begin_cb); gtk_widget_class_bind_template_callback (widget_class, drag_item_drag_end_cb); gtk_widget_class_bind_template_callback (widget_class, queue_drop_value_notify_cb); gtk_widget_class_bind_template_callback (widget_class, queue_drop_motion_cb); gtk_widget_class_bind_template_callback (widget_class, queue_drop_leave_cb); gtk_widget_class_bind_template_callback (widget_class, queue_drop_cb); gtk_widget_class_bind_template_callback (widget_class, trash_drop_cb); gtk_widget_class_set_css_name (widget_class, "clapper-app-queue-list"); } clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-list.h000066400000000000000000000021541474353110700233670ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_QUEUE_LIST (clapper_app_queue_list_get_type()) #define CLAPPER_APP_QUEUE_LIST_CAST(obj) ((ClapperAppQueueList *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppQueueList, clapper_app_queue_list, CLAPPER_APP, QUEUE_LIST, GtkBox) G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-progression-item.c000066400000000000000000000061001474353110700257100ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "clapper-app-queue-progression-item.h" struct _ClapperAppQueueProgressionItem { GObject parent; gchar *icon_name; gchar *label; }; enum { PROP_0, PROP_ICON_NAME, PROP_LABEL, PROP_LAST }; #define parent_class clapper_app_queue_progression_item_parent_class G_DEFINE_TYPE (ClapperAppQueueProgressionItem, clapper_app_queue_progression_item, G_TYPE_OBJECT); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; ClapperAppQueueProgressionItem * clapper_app_queue_progression_item_new (const gchar *icon_name, const gchar *label) { ClapperAppQueueProgressionItem *item; item = g_object_new (CLAPPER_APP_TYPE_QUEUE_PROGRESSION_ITEM, NULL); item->icon_name = g_strdup (icon_name); item->label = g_strdup (label); return item; } static void clapper_app_queue_progression_item_init (ClapperAppQueueProgressionItem *self) { } static void clapper_app_queue_progression_item_finalize (GObject *object) { ClapperAppQueueProgressionItem *self = CLAPPER_APP_QUEUE_PROGRESSION_ITEM_CAST (object); g_free (self->icon_name); g_free (self->label); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_queue_progression_item_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperAppQueueProgressionItem *self = CLAPPER_APP_QUEUE_PROGRESSION_ITEM_CAST (object); switch (prop_id) { case PROP_ICON_NAME: g_value_set_string (value, self->icon_name); break; case PROP_LABEL: g_value_set_string (value, self->label); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_queue_progression_item_class_init (ClapperAppQueueProgressionItemClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; gobject_class->get_property = clapper_app_queue_progression_item_get_property; gobject_class->finalize = clapper_app_queue_progression_item_finalize; param_specs[PROP_ICON_NAME] = g_param_spec_string ("icon-name", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); param_specs[PROP_LABEL] = g_param_spec_string ("label", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-progression-item.h000066400000000000000000000024311474353110700257200ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_QUEUE_PROGRESSION_ITEM (clapper_app_queue_progression_item_get_type()) #define CLAPPER_APP_QUEUE_PROGRESSION_ITEM_CAST(obj) ((ClapperAppQueueProgressionItem *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppQueueProgressionItem, clapper_app_queue_progression_item, CLAPPER_APP, QUEUE_PROGRESSION_ITEM, GObject) ClapperAppQueueProgressionItem * clapper_app_queue_progression_item_new (const gchar *icon_name, const gchar *label); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-progression-model.c000066400000000000000000000066541474353110700260700ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "clapper-app-queue-progression-model.h" #include "clapper-app-queue-progression-item.h" #include "clapper-app-utils.h" #define N_PROGRESSION_MODES 5 struct _ClapperAppQueueProgressionModel { GObject parent; GListStore *store; }; static GType clapper_app_queue_progression_model_get_item_type (GListModel *model) { return CLAPPER_APP_TYPE_QUEUE_PROGRESSION_ITEM; } static guint clapper_app_queue_progression_model_get_n_items (GListModel *model) { ClapperAppQueueProgressionModel *self = CLAPPER_APP_QUEUE_PROGRESSION_MODEL_CAST (model); return g_list_model_get_n_items (G_LIST_MODEL (self->store)); } static gpointer clapper_app_queue_progression_model_get_item (GListModel *model, guint index) { ClapperAppQueueProgressionModel *self = CLAPPER_APP_QUEUE_PROGRESSION_MODEL_CAST (model); return g_list_model_get_item (G_LIST_MODEL (self->store), index); } static void _list_model_iface_init (GListModelInterface *iface) { iface->get_item_type = clapper_app_queue_progression_model_get_item_type; iface->get_n_items = clapper_app_queue_progression_model_get_n_items; iface->get_item = clapper_app_queue_progression_model_get_item; } #define parent_class clapper_app_queue_progression_model_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperAppQueueProgressionModel, clapper_app_queue_progression_model, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, _list_model_iface_init)); static void clapper_app_queue_progression_model_init (ClapperAppQueueProgressionModel *self) { self->store = g_list_store_new (CLAPPER_APP_TYPE_QUEUE_PROGRESSION_ITEM); } static void clapper_app_queue_progression_model_constructed (GObject *object) { ClapperAppQueueProgressionModel *self = CLAPPER_APP_QUEUE_PROGRESSION_MODEL_CAST (object); guint i; for (i = 0; i < N_PROGRESSION_MODES; ++i) { ClapperAppQueueProgressionItem *item; const gchar *icon = NULL, *label = NULL; clapper_app_utils_parse_progression (i, &icon, &label); item = clapper_app_queue_progression_item_new (icon, label); g_list_store_append (self->store, item); g_object_unref (item); } G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_app_queue_progression_model_finalize (GObject *object) { ClapperAppQueueProgressionModel *self = CLAPPER_APP_QUEUE_PROGRESSION_MODEL_CAST (object); g_object_unref (self->store); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_queue_progression_model_class_init (ClapperAppQueueProgressionModelClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; gobject_class->constructed = clapper_app_queue_progression_model_constructed; gobject_class->finalize = clapper_app_queue_progression_model_finalize; } clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-progression-model.h000066400000000000000000000022141474353110700260610ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_QUEUE_PROGRESSION_MODEL (clapper_app_queue_progression_model_get_type()) #define CLAPPER_APP_QUEUE_PROGRESSION_MODEL_CAST(obj) ((ClapperAppQueueProgressionModel *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppQueueProgressionModel, clapper_app_queue_progression_model, CLAPPER_APP, QUEUE_PROGRESSION_MODEL, GObject) G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-selection.c000066400000000000000000000261571474353110700244050ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "clapper-app-queue-selection.h" #define GST_CAT_DEFAULT clapper_app_queue_selection_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppQueueSelection { GObject parent; ClapperQueue *queue; ClapperMediaItem *current_item; guint current_position; }; enum { PROP_0, PROP_QUEUE, PROP_LAST }; enum { SIGNAL_ITEM_SELECTED, SIGNAL_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static guint signals[SIGNAL_LAST] = { 0, }; static GType clapper_app_queue_selection_get_item_type (GListModel *model) { return CLAPPER_TYPE_MEDIA_ITEM; } static guint clapper_app_queue_selection_get_n_items (GListModel *model) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model); return (self->queue) ? clapper_queue_get_n_items (self->queue) : 0; } static gpointer clapper_app_queue_selection_get_item (GListModel *model, guint index) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model); return (self->queue) ? clapper_queue_get_item (self->queue, index) : NULL; } static void _list_model_iface_init (GListModelInterface *iface) { iface->get_item_type = clapper_app_queue_selection_get_item_type; iface->get_n_items = clapper_app_queue_selection_get_n_items; iface->get_item = clapper_app_queue_selection_get_item; } static inline void _refresh_current_selection (ClapperAppQueueSelection *self) { guint position, old_position, index, n_changed; position = clapper_queue_get_current_index (self->queue); /* Clapper -> GTK expected value change. * Should be the same, but better be safe. */ if (position == CLAPPER_QUEUE_INVALID_POSITION) position = GTK_INVALID_LIST_POSITION; /* No change */ if (position == self->current_position) return; old_position = self->current_position; self->current_position = position; if (old_position == GTK_INVALID_LIST_POSITION) { index = position; n_changed = 1; } else if (position == GTK_INVALID_LIST_POSITION) { index = old_position; n_changed = 1; } else if (position < old_position) { index = position; n_changed = old_position - position + 1; } else { index = old_position; n_changed = position - old_position + 1; } GST_DEBUG ("Selection changed, index: %u, n_changed: %u", index, n_changed); gtk_selection_model_selection_changed (GTK_SELECTION_MODEL (self), index, n_changed); } static gboolean clapper_app_queue_selection_is_selected (GtkSelectionModel *model, guint position) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model); return (position == self->current_position); } static GtkBitset * clapper_app_queue_selection_get_selection_in_range (GtkSelectionModel *model, guint position, guint n_items) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model); GtkBitset *bitset = gtk_bitset_new_empty (); if (self->current_position != GTK_INVALID_LIST_POSITION && position <= self->current_position && position + n_items > self->current_position) gtk_bitset_add (bitset, self->current_position); return bitset; } static gboolean clapper_app_queue_selection_select_item (GtkSelectionModel *model, guint position, gboolean exclusive) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (model); gboolean res = TRUE; if (G_UNLIKELY (self->queue == NULL)) return FALSE; /* Disallow reselecting of the same item */ if (self->current_position != position) res = clapper_queue_select_index (self->queue, position); /* Need to always emit this signal when select item succeeds */ if (G_LIKELY (res)) g_signal_emit (self, signals[SIGNAL_ITEM_SELECTED], 0, position); return res; } static gboolean clapper_app_queue_selection_unselect_item (GtkSelectionModel *model, guint position) { return FALSE; } static void _selection_model_iface_init (GtkSelectionModelInterface *iface) { iface->is_selected = clapper_app_queue_selection_is_selected; iface->get_selection_in_range = clapper_app_queue_selection_get_selection_in_range; iface->select_item = clapper_app_queue_selection_select_item; iface->unselect_item = clapper_app_queue_selection_unselect_item; } #define parent_class clapper_app_queue_selection_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperAppQueueSelection, clapper_app_queue_selection, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, _list_model_iface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, _selection_model_iface_init)) static void _queue_model_items_changed_cb (GListModel *model, guint position, guint removed, guint added, ClapperAppQueueSelection *self) { /* Forward event from internal model */ g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); } static void _queue_current_index_changed_cb (ClapperQueue *queue, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppQueueSelection *self) { _refresh_current_selection (self); } /* * clapper_app_queue_selection_new: * @queue: (nullable): a #ClapperQueue * * Creates a new #ClapperAppQueueSelection instance. * * Returns: (transfer full): a new #ClapperAppQueueSelection. */ ClapperAppQueueSelection * clapper_app_queue_selection_new (ClapperQueue *queue) { return g_object_new (CLAPPER_APP_TYPE_QUEUE_SELECTION, "queue", queue, NULL); } /* * clapper_app_queue_selection_set_queue: * @selection: a #ClapperAppQueueSelection * @queue: a #ClapperQueue * * Set #ClapperQueue to be managed by this selection model. */ void clapper_app_queue_selection_set_queue (ClapperAppQueueSelection *self, ClapperQueue *queue) { guint n_before = 0, n_after = 0; g_return_if_fail (CLAPPER_APP_IS_QUEUE_SELECTION (self)); g_return_if_fail (CLAPPER_IS_QUEUE (queue)); if (self->queue) { g_signal_handlers_disconnect_by_func (G_LIST_MODEL (self->queue), _queue_model_items_changed_cb, self); g_signal_handlers_disconnect_by_func (self->queue, _queue_current_index_changed_cb, self); n_before = clapper_queue_get_n_items (self->queue); } gst_object_replace ((GstObject **) &self->queue, GST_OBJECT_CAST (queue)); g_signal_connect (G_LIST_MODEL (self->queue), "items-changed", G_CALLBACK (_queue_model_items_changed_cb), self); g_signal_connect (self->queue, "notify::current-index", G_CALLBACK (_queue_current_index_changed_cb), self); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_QUEUE]); n_after = clapper_queue_get_n_items (self->queue); /* Refresh selected item after queue change */ self->current_position = GTK_INVALID_LIST_POSITION; _queue_model_items_changed_cb (G_LIST_MODEL (self->queue), 0, n_before, n_after, self); _refresh_current_selection (self); } /* * clapper_app_queue_selection_get_queue: * @selection: a #ClapperAppQueueSelection * * Get #ClapperQueue managed by this selection model. * * Returns: (transfer none): #ClapperQueue being managed. */ ClapperQueue * clapper_app_queue_selection_get_queue (ClapperAppQueueSelection *self) { g_return_val_if_fail (CLAPPER_APP_IS_QUEUE_SELECTION (self), NULL); return self->queue; } static void clapper_app_queue_selection_init (ClapperAppQueueSelection *self) { self->current_position = GTK_INVALID_LIST_POSITION; } static void clapper_app_queue_selection_finalize (GObject *object) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (object); if (self->queue) { g_signal_handlers_disconnect_by_func (G_LIST_MODEL (self->queue), _queue_model_items_changed_cb, self); g_signal_handlers_disconnect_by_func (self->queue, _queue_current_index_changed_cb, self); g_object_unref (self->queue); } g_clear_object (&self->current_item); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_queue_selection_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (object); switch (prop_id) { case PROP_QUEUE: g_value_set_object (value, clapper_app_queue_selection_get_queue (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_queue_selection_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperAppQueueSelection *self = CLAPPER_APP_QUEUE_SELECTION_CAST (object); switch (prop_id) { case PROP_QUEUE: clapper_app_queue_selection_set_queue (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_queue_selection_class_init (ClapperAppQueueSelectionClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappqueueselection", 0, "Clapper App Queue Selection"); gobject_class->get_property = clapper_app_queue_selection_get_property; gobject_class->set_property = clapper_app_queue_selection_set_property; gobject_class->finalize = clapper_app_queue_selection_finalize; /* * ClapperAppQueueSelection:queue: * * The queue being managed. */ param_specs[PROP_QUEUE] = g_param_spec_object ("queue", NULL, NULL, CLAPPER_TYPE_QUEUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /* * ClapperAppQueueSelection::item-selected: * @selection: a #ClapperAppQueueSelection * @index: an index of selected item * * Signals when user selected item within the [iface@Gtk.SelectionModel]. * * Note that this signal is emitted only when item gets selected from * the GTK side (also when the same item is reselected). If item was * changed internally by e.g. progression of [class@Clapper.Queue], * this signal will not be emitted. * * #ClapperAppQueueSelection automatically takes care of having its * selection in sync with passed [class@Clapper.Queue], so you do not * have to listen for the changes. This signal is useful if you need * to differentiate what caused item selection, otherwise use either * [signal@Gtk.SelectionModel::selection-changed] signal or listen for * changes of [property@Clapper.Queue:current-item] property. */ signals[SIGNAL_ITEM_SELECTED] = g_signal_new ("item-selected", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_UINT); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/bin/clapper-app/clapper-app-queue-selection.h000066400000000000000000000026131474353110700244010ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_QUEUE_SELECTION (clapper_app_queue_selection_get_type()) #define CLAPPER_APP_QUEUE_SELECTION_CAST(obj) ((ClapperAppQueueSelection *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppQueueSelection, clapper_app_queue_selection, CLAPPER_APP, QUEUE_SELECTION, GObject) ClapperAppQueueSelection * clapper_app_queue_selection_new (ClapperQueue *queue); void clapper_app_queue_selection_set_queue (ClapperAppQueueSelection *selection, ClapperQueue *queue); ClapperQueue * clapper_app_queue_selection_get_queue (ClapperAppQueueSelection *selection); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-types.c000066400000000000000000000030011474353110700224210ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "clapper-app-types.h" #include "clapper-app-headerbar.h" #include "clapper-app-media-item-box.h" #include "clapper-app-property-row.h" #include "clapper-app-queue-list.h" #include "clapper-app-queue-progression-model.h" #include "clapper-app-window-state-buttons.h" /* * clapper_app_types_init: * * Ensure private types that appear in UI files in order for * GtkBuilder to be able to find them when building templates. */ inline void clapper_app_types_init (void) { g_type_ensure (CLAPPER_APP_TYPE_HEADERBAR); g_type_ensure (CLAPPER_APP_TYPE_MEDIA_ITEM_BOX); g_type_ensure (CLAPPER_APP_TYPE_PROPERTY_ROW); g_type_ensure (CLAPPER_APP_TYPE_QUEUE_LIST); g_type_ensure (CLAPPER_APP_TYPE_QUEUE_PROGRESSION_MODEL); g_type_ensure (CLAPPER_APP_TYPE_WINDOW_STATE_BUTTONS); } clapper-0.8.0/src/bin/clapper-app/clapper-app-types.h000066400000000000000000000015301474353110700224330ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_app_types_init (void); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-uri-dialog.c000066400000000000000000000076141474353110700233270ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "clapper-app-uri-dialog.h" #include "clapper-app-utils.h" static void _entry_text_changed_cb (GtkEntry *entry, GParamSpec *pspec G_GNUC_UNUSED, AdwMessageDialog *dialog) { GtkEntryBuffer *buffer = gtk_entry_get_buffer (entry); guint text_length = gtk_entry_buffer_get_length (buffer); gboolean enabled = FALSE; if (text_length > 0) { const gchar *text = gtk_entry_buffer_get_text (buffer); enabled = (text && gst_uri_is_valid (text)); } adw_message_dialog_set_response_enabled (dialog, "add", enabled); } static void _open_uri_cb (AdwMessageDialog *dialog, GAsyncResult *result, GtkApplication *gtk_app) { const gchar *response = adw_message_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); GFile **files = NULL; gint n_files = 0; if (clapper_app_utils_files_from_string (text, &files, &n_files)) { g_application_open (G_APPLICATION (gtk_app), files, n_files, "add-only"); clapper_app_utils_files_free (files); } } } static void _read_text_cb (GdkClipboard *clipboard, GAsyncResult *result, GtkWidget *extra_child) { 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); } } else { /* Common error when clipboard is empty or has unsupported content. * This we can safely ignore and not notify user. */ if (error->domain != G_IO_ERROR || error->code != G_IO_ERROR_NOT_SUPPORTED) { g_printerr ("Error: %s\n", (error->message) ? error->message : "Could not read clipboard"); } g_error_free (error); } g_free (text); } void 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; 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); extra_child = adw_message_dialog_get_extra_child (dialog); g_signal_connect (GTK_ENTRY (extra_child), "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); } /* NOTE: Dialog will automatically unref itself after response */ adw_message_dialog_choose (dialog, NULL, (GAsyncReadyCallback) _open_uri_cb, gtk_app); g_object_unref (builder); } clapper-0.8.0/src/bin/clapper-app/clapper-app-uri-dialog.h000066400000000000000000000016111474353110700233230ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_app_uri_dialog_open_uri (GtkApplication *gtk_app); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-utils.c000066400000000000000000000306711474353110700224320ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "clapper-app-utils.h" #include "clapper-app-media-item-box.h" #ifdef G_OS_WIN32 #include #ifdef HAVE_WIN_PROCESS_THREADS_API #include #endif #ifdef HAVE_WIN_TIME_API #include #endif #endif #define GST_CAT_DEFAULT clapper_app_utils_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); void clapper_app_utils_debug_init (void) { GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperapputils", 0, "Clapper App Utils"); } /* Windows specific functions */ #ifdef G_OS_WIN32 /* * clapper_app_utils_win_enforce_hi_res_clock: * * Enforce high resolution clock by explicitly disabling Windows * timer resolution power throttling. When disabled, system remembers * and honors any previous timer resolution request by the process. * * By default, Windows 11 may automatically ignore the timer * resolution requests in certain scenarios. */ void clapper_app_utils_win_enforce_hi_res_clock (void) { #ifdef HAVE_WIN_PROCESS_THREADS_API PROCESS_POWER_THROTTLING_STATE PowerThrottling; gboolean success; RtlZeroMemory (&PowerThrottling, sizeof (PowerThrottling)); PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION; PowerThrottling.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION; PowerThrottling.StateMask = 0; // Always honor timer resolution requests success = (gboolean) SetProcessInformation( GetCurrentProcess (), ProcessPowerThrottling, &PowerThrottling, sizeof (PowerThrottling)); /* Not an error. Older Windows does not have this functionality, but * also honor hi-res clock by default anyway, so do not print then. */ GST_INFO ("Windows hi-res clock support is %senforced", (success) ? "" : "NOT "); #endif } /* * clapper_app_utils_win_hi_res_clock_start: * * Start Windows high resolution clock which will improve * accuracy of various Windows timer APIs and precision * of #GstSystemClock during playback. * * On Windows 10 version 2004 (and older), this function affects * a global Windows setting. On any other (newer) version this * will only affect a single process. * * Returns: Timer resolution period value. */ guint clapper_app_utils_win_hi_res_clock_start (void) { guint resolution = 0; #ifdef HAVE_WIN_TIME_API TIMECAPS time_caps; MMRESULT res; if ((res = timeGetDevCaps (&time_caps, sizeof (TIMECAPS))) != TIMERR_NOERROR) { GST_WARNING ("Could not query timer resolution, code: %u", res); return 0; } resolution = MIN (MAX (time_caps.wPeriodMin, 1), time_caps.wPeriodMax); if ((res = timeBeginPeriod (resolution)) != TIMERR_NOERROR) { GST_WARNING ("Could not request timer resolution, code: %u", res); return 0; } GST_INFO ("Started Windows hi-res clock, precision: %ums", resolution); #endif return resolution; } /* * clapper_app_utils_win_hi_res_clock_stop: * @resolution: started resolution value (non-zero) * * Stop previously started Microsoft Windows high resolution clock. */ void clapper_app_utils_win_hi_res_clock_stop (guint resolution) { #ifdef HAVE_WIN_TIME_API MMRESULT res; if ((res = timeEndPeriod (resolution)) == TIMERR_NOERROR) GST_INFO ("Stopped Windows hi-res clock"); else GST_ERROR ("Could not stop hi-res clock, code: %u", res); #endif } /* Extensions are used only on Windows */ const gchar *const * clapper_app_utils_get_extensions (void) { static const gchar *const all_extensions[] = { "avi", "claps", "m2ts", "mkv", "mov", "mp4", "webm", "wmv", NULL }; return all_extensions; } const gchar *const * clapper_app_utils_get_subtitles_extensions (void) { static const gchar *const subs_extensions[] = { "srt", "vtt", NULL }; return subs_extensions; } #endif // G_OS_WIN32 const gchar *const * clapper_app_utils_get_mime_types (void) { static const gchar *const all_mime_types[] = { "video/*", "audio/*", "application/claps", "application/x-subrip", "text/x-ssa", NULL }; return all_mime_types; } const gchar *const * clapper_app_utils_get_subtitles_mime_types (void) { static const gchar *const subs_mime_types[] = { "application/x-subrip", "text/x-ssa", NULL }; return subs_mime_types; } void clapper_app_utils_parse_progression (ClapperQueueProgressionMode mode, const gchar **icon, const gchar **label) { const gchar *const icon_names[] = { "action-unavailable-symbolic", "media-playlist-consecutive-symbolic", "media-playlist-repeat-song-symbolic", "media-playlist-repeat-symbolic", "media-playlist-shuffle-symbolic", NULL }; const gchar *const labels[] = { _("No progression"), _("Consecutive"), _("Repeat item"), _("Carousel"), _("Shuffle"), NULL }; *icon = icon_names[mode]; *label = labels[mode]; } gboolean clapper_app_utils_is_subtitles_file (GFile *file) { GFileInfo *info; gboolean is_subs = FALSE; if ((info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL))) { const gchar *content_type = NULL; if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE)) { content_type = g_file_info_get_content_type (info); } else if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE)) { content_type = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE); } is_subs = (content_type && g_strv_contains ( clapper_app_utils_get_subtitles_mime_types (), content_type)); g_object_unref (info); } return is_subs; } gboolean clapper_app_utils_value_for_item_is_valid (const GValue *value) { if (G_VALUE_HOLDS (value, GTK_TYPE_WIDGET)) return CLAPPER_APP_IS_MEDIA_ITEM_BOX (g_value_get_object (value)); if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST) || G_VALUE_HOLDS (value, G_TYPE_FILE)) return TRUE; if (G_VALUE_HOLDS (value, G_TYPE_STRING)) return gst_uri_is_valid (g_value_get_string (value)); return FALSE; } gboolean clapper_app_utils_files_from_list_model (GListModel *files_model, GFile ***files, gint *n_files) { guint i, len = g_list_model_get_n_items (files_model); if (G_UNLIKELY (len == 0 || len > G_MAXINT)) return FALSE; *files = g_new (GFile *, len + 1); if (n_files) *n_files = (gint) len; for (i = 0; i < len; ++i) { (*files)[i] = g_list_model_get_item (files_model, i); } (*files)[i] = NULL; return TRUE; } gboolean clapper_app_utils_files_from_slist (GSList *file_list, GFile ***files, gint *n_files) { GSList *fl; guint len, i = 0; len = g_slist_length (file_list); if (G_UNLIKELY (len == 0 || len > G_MAXINT)) return FALSE; *files = g_new (GFile *, len + 1); if (n_files) *n_files = (gint) len; for (fl = file_list; fl != NULL; fl = fl->next) { (*files)[i] = (GFile *) g_object_ref (fl->data); i++; } (*files)[i] = NULL; return TRUE; } gboolean clapper_app_utils_files_from_string (const gchar *string, GFile ***files, gint *n_files) { GSList *slist = NULL; gchar **uris = g_strsplit (string, "\n", 0); guint i; gboolean success; for (i = 0; uris[i]; ++i) { const gchar *uri = uris[i]; if (!gst_uri_is_valid (uri)) continue; slist = g_slist_append (slist, g_file_new_for_uri (uri)); } g_strfreev (uris); if (!slist) return FALSE; success = clapper_app_utils_files_from_slist (slist, files, n_files); g_slist_free_full (slist, g_object_unref); return success; } gboolean clapper_app_utils_files_from_command_line (GApplicationCommandLine *cmd_line, GFile ***files, gint *n_files) { GSList *slist = NULL; gchar **argv; gint i, argc = 0; gboolean success; argv = g_application_command_line_get_arguments (cmd_line, &argc); for (i = 1; i < argc; ++i) slist = g_slist_append (slist, g_application_command_line_create_file_for_arg (cmd_line, argv[i])); g_strfreev (argv); if (!slist) return FALSE; success = clapper_app_utils_files_from_slist (slist, files, n_files); g_slist_free_full (slist, g_object_unref); return success; } static inline gboolean _files_from_file (GFile *file, GFile ***files, gint *n_files) { *files = g_new (GFile *, 2); (*files)[0] = g_object_ref (file); (*files)[1] = NULL; if (n_files) *n_files = 1; return TRUE; } gboolean clapper_app_utils_files_from_value (const GValue *value, GFile ***files, gint *n_files) { if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) { return clapper_app_utils_files_from_slist ( (GSList *) g_value_get_boxed (value), files, n_files); } else if (G_VALUE_HOLDS (value, G_TYPE_FILE)) { return _files_from_file ( (GFile *) g_value_get_object (value), files, n_files); } else if (G_VALUE_HOLDS (value, G_TYPE_STRING)) { return clapper_app_utils_files_from_string ( g_value_get_string (value), files, n_files); } return FALSE; } void clapper_app_utils_files_free (GFile **files) { gint i; for (i = 0; files[i]; ++i) g_object_unref (files[i]); g_free (files); } static inline gboolean _parse_feature_name (gchar *str, const gchar **feature_name) { if (!str) return FALSE; g_strstrip (str); if (str[0] == '\0') return FALSE; *feature_name = str; return TRUE; } static inline gboolean _parse_feature_rank (gchar *str, GstRank *rank) { if (!str) return FALSE; g_strstrip (str); if (str[0] == '\0') return FALSE; if (g_ascii_isdigit (str[0])) { gulong l; gchar *endptr; l = strtoul (str, &endptr, 10); if (endptr > str && endptr[0] == 0) { *rank = (GstRank) l; } else { return FALSE; } } else if (g_ascii_strcasecmp (str, "NONE") == 0) { *rank = GST_RANK_NONE; } else if (g_ascii_strcasecmp (str, "MARGINAL") == 0) { *rank = GST_RANK_MARGINAL; } else if (g_ascii_strcasecmp (str, "SECONDARY") == 0) { *rank = GST_RANK_SECONDARY; } else if (g_ascii_strcasecmp (str, "PRIMARY") == 0) { *rank = GST_RANK_PRIMARY; } else if (g_ascii_strcasecmp (str, "MAX") == 0) { *rank = (GstRank) G_MAXINT; } else { return FALSE; } return TRUE; } void clapper_app_utils_iterate_plugin_feature_ranks (GSettings *settings, ClapperAppUtilsIterRanks callback, gpointer user_data) { gchar **split, **walk, *stored_overrides; const gchar *env_overrides; gboolean from_env = FALSE; stored_overrides = g_settings_get_string (settings, "plugin-feature-ranks"); env_overrides = g_getenv ("GST_PLUGIN_FEATURE_RANK"); /* Iterate from GSettings, then from ENV */ parse_overrides: split = g_strsplit ((from_env) ? env_overrides : stored_overrides, ",", 0); for (walk = split; *walk; walk++) { gchar **values; if (!strchr (*walk, ':')) continue; values = g_strsplit (*walk, ":", 2); if (g_strv_length (values) == 2) { GstRank rank; const gchar *feature_name; if (_parse_feature_name (values[0], &feature_name) && _parse_feature_rank (values[1], &rank)) callback (feature_name, rank, from_env, user_data); } g_strfreev (values); } g_strfreev (split); if (!from_env && env_overrides) { from_env = TRUE; goto parse_overrides; } g_free (stored_overrides); } GstElement * clapper_app_utils_make_element (const gchar *string) { gchar *char_loc; if (strcmp (string, "none") == 0) return NULL; char_loc = strchr (string, ' '); if (char_loc) { GstElement *element; GError *error = NULL; element = gst_parse_bin_from_description (string, TRUE, &error); if (error) { GST_ERROR ("Bin parse error: \"%s\", reason: %s", string, error->message); g_error_free (error); } return element; } return gst_element_factory_make (string, NULL); } clapper-0.8.0/src/bin/clapper-app/clapper-app-utils.h000066400000000000000000000054241474353110700224350ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include G_BEGIN_DECLS typedef void (* ClapperAppUtilsIterRanks) (const gchar *feature_name, GstRank rank, gboolean from_env, gpointer user_data); void clapper_app_utils_debug_init (void); #ifdef G_OS_WIN32 G_GNUC_INTERNAL void clapper_app_utils_win_enforce_hi_res_clock (void); G_GNUC_INTERNAL guint clapper_app_utils_win_hi_res_clock_start (void); G_GNUC_INTERNAL void clapper_app_utils_win_hi_res_clock_stop (guint resolution); G_GNUC_INTERNAL const gchar *const * clapper_app_utils_get_extensions (void); G_GNUC_INTERNAL const gchar *const * clapper_app_utils_get_subtitles_extensions (void); #endif G_GNUC_INTERNAL const gchar *const * clapper_app_utils_get_mime_types (void); G_GNUC_INTERNAL const gchar *const * clapper_app_utils_get_subtitles_mime_types (void); G_GNUC_INTERNAL void clapper_app_utils_parse_progression (ClapperQueueProgressionMode mode, const gchar **icon, const gchar **label); G_GNUC_INTERNAL gboolean clapper_app_utils_is_subtitles_file (GFile *file); G_GNUC_INTERNAL gboolean clapper_app_utils_value_for_item_is_valid (const GValue *value); G_GNUC_INTERNAL gboolean clapper_app_utils_files_from_list_model (GListModel *files_model, GFile ***files, gint *n_files); G_GNUC_INTERNAL gboolean clapper_app_utils_files_from_slist (GSList *file_list, GFile ***files, gint *n_files); G_GNUC_INTERNAL gboolean clapper_app_utils_files_from_string (const gchar *string, GFile ***files, gint *n_files); G_GNUC_INTERNAL gboolean clapper_app_utils_files_from_command_line (GApplicationCommandLine *cmd_line, GFile ***files, gint *n_files); G_GNUC_INTERNAL gboolean clapper_app_utils_files_from_value (const GValue *value, GFile ***files, gint *n_files); G_GNUC_INTERNAL void clapper_app_utils_files_free (GFile **files); G_GNUC_INTERNAL void clapper_app_utils_iterate_plugin_feature_ranks (GSettings *settings, ClapperAppUtilsIterRanks callback, gpointer user_data); G_GNUC_INTERNAL GstElement * clapper_app_utils_make_element (const gchar *string); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-window-state-buttons.c000066400000000000000000000257051474353110700254150ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include "clapper-app-window-state-buttons.h" #define GST_CAT_DEFAULT clapper_app_window_state_buttons_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppWindowStateButtons { GtkBox parent; GtkWidget *minimize_button; GtkWidget *maximize_button; GtkWidget *close_button; /* Props */ GtkWidget *menu_button; GtkPositionType position; gboolean has_minimize; gboolean has_maximize; gboolean has_close; gboolean has_buttons; gboolean is_maximized; gboolean is_fullscreen; GtkSettings *settings; }; enum { PROP_0, PROP_POSITION, PROP_MENU_BUTTON, PROP_LAST }; #define parent_class clapper_app_window_state_buttons_parent_class G_DEFINE_TYPE (ClapperAppWindowStateButtons, clapper_app_window_state_buttons, GTK_TYPE_BOX) static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void minimize_button_clicked_cb (GtkButton *button, ClapperAppWindowStateButtons *self) { GST_INFO_OBJECT (self, "Minimize button clicked"); gtk_widget_activate_action (GTK_WIDGET (self), "window.minimize", NULL); } static void maximize_button_clicked_cb (GtkButton *button, ClapperAppWindowStateButtons *self) { GST_INFO_OBJECT (self, "Maximize button clicked"); gtk_widget_activate_action (GTK_WIDGET (self), "window.toggle-maximized", NULL); } static void close_button_clicked_cb (GtkButton *button, ClapperAppWindowStateButtons *self) { GST_INFO_OBJECT (self, "Close button clicked"); gtk_widget_activate_action (GTK_WIDGET (self), "window.close", NULL); } static void _refresh_buttons_visibility (ClapperAppWindowStateButtons *self) { gboolean show_minimize = (self->has_minimize && !self->is_fullscreen); gboolean show_maximize = (self->has_maximize && !self->is_fullscreen); gboolean has_buttons; gtk_widget_set_visible (self->minimize_button, show_minimize); gtk_widget_set_visible (self->maximize_button, show_maximize); gtk_widget_set_visible (self->close_button, self->has_close); has_buttons = (self->menu_button != NULL || show_minimize || show_maximize || self->has_close); if (self->has_buttons != has_buttons) { self->has_buttons = has_buttons; if (self->has_buttons) gtk_widget_add_css_class (GTK_WIDGET (self), "filled"); else gtk_widget_remove_css_class (GTK_WIDGET (self), "filled"); } } static void clapper_app_window_state_buttons_parse_layout (ClapperAppWindowStateButtons *self) { gchar *org_layout = NULL; gboolean has_minimize = FALSE; gboolean has_maximize = FALSE; gboolean has_close = FALSE; GST_DEBUG_OBJECT (self, "Buttons layout update"); g_object_get (self->settings, "gtk-decoration-layout", &org_layout, NULL); if (G_LIKELY (org_layout != NULL)) { GtkWidget *last_widget = self->menu_button; gboolean can_parse = (self->position == GTK_POS_LEFT); gboolean had_sign = can_parse; guint i; for (i = 0; org_layout[i] != '\0'; ++i) { const gchar *layout = org_layout + i; if (layout[0] == ',') { had_sign = TRUE; continue; } if (layout[0] == ':') { if (self->position == GTK_POS_LEFT) break; else can_parse = TRUE; had_sign = TRUE; continue; } if (had_sign && can_parse) { GtkWidget *widget = NULL; GST_TRACE_OBJECT (self, "Remaining layout: %s", layout); if (g_str_has_prefix (layout, "minimize")) { widget = self->minimize_button; has_minimize = TRUE; } else if (g_str_has_prefix (layout, "maximize")) { widget = self->maximize_button; has_maximize = TRUE; } else if (g_str_has_prefix (layout, "close")) { widget = self->close_button; has_close = TRUE; } if (widget) { gtk_box_reorder_child_after (GTK_BOX (self), widget, last_widget); last_widget = widget; } had_sign = FALSE; } } } self->has_minimize = has_minimize; self->has_maximize = has_maximize; self->has_close = has_close; GST_DEBUG_OBJECT (self, "Buttons layout parsed"); g_free (org_layout); } static void _decoration_layout_changed_cb (GtkSettings *settings, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindowStateButtons *self) { clapper_app_window_state_buttons_parse_layout (self); _refresh_buttons_visibility (self); } static void _surface_state_changed_cb (GdkSurface *surface, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindowStateButtons *self) { GdkToplevelState state = gdk_toplevel_get_state (GDK_TOPLEVEL (surface)); gboolean is_maximized, is_fullscreen; GST_LOG_OBJECT (self, "Surface state changed"); is_maximized = (state & GDK_TOPLEVEL_STATE_MAXIMIZED); is_fullscreen = (state & GDK_TOPLEVEL_STATE_FULLSCREEN); if (self->is_maximized != is_maximized) { self->is_maximized = is_maximized; gtk_button_set_icon_name (GTK_BUTTON (self->maximize_button), (self->is_maximized) ? "window-restore-symbolic" : "window-maximize-symbolic"); } if (self->is_fullscreen != is_fullscreen) { self->is_fullscreen = is_fullscreen; _refresh_buttons_visibility (self); } } static void clapper_app_window_state_buttons_init (ClapperAppWindowStateButtons *self) { self->position = GTK_POS_RIGHT; gtk_widget_init_template (GTK_WIDGET (self)); } static void clapper_app_window_state_buttons_constructed (GObject *object) { ClapperAppWindowStateButtons *self = CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST (object); if (self->position == GTK_POS_RIGHT) gtk_widget_add_css_class (GTK_WIDGET (self), "right"); else gtk_widget_add_css_class (GTK_WIDGET (self), "left"); G_OBJECT_CLASS (parent_class)->constructed (object); } static void _clear_stored_settings (ClapperAppWindowStateButtons *self) { if (self->settings) { g_signal_handlers_disconnect_by_func (self->settings, _decoration_layout_changed_cb, self); g_clear_object (&self->settings); } } static void clapper_app_window_state_buttons_realize (GtkWidget *widget) { ClapperAppWindowStateButtons *self = CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST (widget); GtkSettings *settings; GtkRoot *root; GdkSurface *surface; GST_TRACE_OBJECT (self, "Realize"); GTK_WIDGET_CLASS (parent_class)->realize (widget); settings = gtk_settings_get_for_display (gtk_widget_get_display (widget)); if (self->settings != settings) { _clear_stored_settings (self); self->settings = g_object_ref (settings); g_signal_connect (self->settings, "notify::gtk-decoration-layout", G_CALLBACK (_decoration_layout_changed_cb), self); _decoration_layout_changed_cb (self->settings, NULL, self); } root = gtk_widget_get_root (widget); surface = gtk_native_get_surface (GTK_NATIVE (root)); g_signal_connect (surface, "notify::state", G_CALLBACK (_surface_state_changed_cb), self); _surface_state_changed_cb (surface, NULL, self); } static void clapper_app_window_state_buttons_unrealize (GtkWidget *widget) { ClapperAppWindowStateButtons *self = CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST (widget); GtkRoot *root; GdkSurface *surface; GST_TRACE_OBJECT (self, "Unrealize"); _clear_stored_settings (self); root = gtk_widget_get_root (widget); surface = gtk_native_get_surface (GTK_NATIVE (root)); g_signal_handlers_disconnect_by_func (surface, _surface_state_changed_cb, self); GTK_WIDGET_CLASS (parent_class)->unrealize (widget); } static void clapper_app_window_state_buttons_dispose (GObject *object) { ClapperAppWindowStateButtons *self = CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST (object); _clear_stored_settings (self); gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_APP_TYPE_WINDOW_STATE_BUTTONS); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_app_window_state_buttons_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperAppWindowStateButtons *self = CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST (object); switch (prop_id) { case PROP_POSITION: self->position = g_value_get_enum (value); break; case PROP_MENU_BUTTON: if ((self->menu_button = GTK_WIDGET (g_value_get_object (value)))) { gtk_box_prepend (GTK_BOX (self), self->menu_button); _refresh_buttons_visibility (self); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_app_window_state_buttons_class_init (ClapperAppWindowStateButtonsClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappwindowstatebuttons", 0, "Clapper App Window State Buttons"); gobject_class->constructed = clapper_app_window_state_buttons_constructed; gobject_class->set_property = clapper_app_window_state_buttons_set_property; gobject_class->dispose = clapper_app_window_state_buttons_dispose; widget_class->realize = clapper_app_window_state_buttons_realize; widget_class->unrealize = clapper_app_window_state_buttons_unrealize; param_specs[PROP_POSITION] = g_param_spec_enum ("position", NULL, NULL, GTK_TYPE_POSITION_TYPE, GTK_POS_RIGHT, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); param_specs[PROP_MENU_BUTTON] = g_param_spec_object ("menu-button", NULL, NULL, GTK_TYPE_MENU_BUTTON, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-window-state-buttons.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperAppWindowStateButtons, minimize_button); gtk_widget_class_bind_template_child (widget_class, ClapperAppWindowStateButtons, maximize_button); gtk_widget_class_bind_template_child (widget_class, ClapperAppWindowStateButtons, close_button); gtk_widget_class_bind_template_callback (widget_class, minimize_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, maximize_button_clicked_cb); gtk_widget_class_bind_template_callback (widget_class, close_button_clicked_cb); gtk_widget_class_set_css_name (widget_class, "clapper-app-window-state-buttons"); } clapper-0.8.0/src/bin/clapper-app/clapper-app-window-state-buttons.h000066400000000000000000000022131474353110700254070ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_WINDOW_STATE_BUTTONS (clapper_app_window_state_buttons_get_type()) #define CLAPPER_APP_WINDOW_STATE_BUTTONS_CAST(obj) ((ClapperAppWindowStateButtons *)(obj)) G_DECLARE_FINAL_TYPE (ClapperAppWindowStateButtons, clapper_app_window_state_buttons, CLAPPER_APP, WINDOW_STATE_BUTTONS, GtkBox) G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app-window.c000066400000000000000000001230701474353110700225750ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include "clapper-app-window.h" #include "clapper-app-file-dialog.h" #include "clapper-app-utils.h" #define MIN_WINDOW_WIDTH 352 #define MIN_WINDOW_HEIGHT 198 #define DEFAULT_WINDOW_WIDTH 1024 #define DEFAULT_WINDOW_HEIGHT 576 #define N_PROGRESSION_MODES 5 #define CLAPPER_APP_SEEK_UNIT_SECOND 0 #define CLAPPER_APP_SEEK_UNIT_MINUTE 1 #define CLAPPER_APP_SEEK_UNIT_PERCENTAGE 2 #define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) #define AXIS_WINS_OVER(a,b) ((a > 0 && a - 0.3 > b) || (a < 0 && a + 0.3 < b)) #define MIN_STEP_DELAY 12000 #define GST_CAT_DEFAULT clapper_app_window_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppWindow { GtkApplicationWindow parent; GtkWidget *video; ClapperGtkBillboard *billboard; ClapperGtkSimpleControls *simple_controls; GtkDropTarget *drop_target; GtkCssProvider *provider; ClapperMediaItem *current_item; GSettings *settings; guint seek_timeout; guint resize_tick_id; gboolean key_held; gboolean scrolling; gboolean seeking; gboolean was_playing; gdouble pending_position; gdouble current_duration; gdouble last_volume; }; #define parent_class clapper_app_window_parent_class G_DEFINE_TYPE (ClapperAppWindow, clapper_app_window, GTK_TYPE_APPLICATION_WINDOW) typedef struct { gint dest_width, dest_height; gint64 last_tick; } ClapperAppWindowResizeData; #if CLAPPER_HAVE_MPRIS static guint16 instance_count = 0; #endif static inline GQuark clapper_app_window_extra_options_get_quark (void) { return g_quark_from_static_string ("clapper-app-window-extra-options-quark"); } static void clapper_app_window_extra_options_free (ClapperAppWindowExtraOptions *extra_opts) { GST_TRACE ("Freeing window extra options: %p", extra_opts); g_free (extra_opts->video_filter); g_free (extra_opts->audio_filter); g_free (extra_opts->video_sink); g_free (extra_opts->audio_sink); g_free (extra_opts); } static void _media_item_title_changed_cb (ClapperMediaItem *item, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindow *self) { gchar *title; if ((title = clapper_media_item_get_title (item))) { gtk_window_set_title (GTK_WINDOW (self), title); g_free (title); } else { gtk_window_set_title (GTK_WINDOW (self), CLAPPER_APP_NAME); } } static void _queue_current_item_changed_cb (ClapperQueue *queue, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindow *self) { ClapperMediaItem *current_item = clapper_queue_get_current_item (queue); /* Disconnect signal from old item */ if (self->current_item) { g_signal_handlers_disconnect_by_func (self->current_item, _media_item_title_changed_cb, self); } gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (current_item)); GST_DEBUG_OBJECT (self, "Current item changed to: %" GST_PTR_FORMAT, self->current_item); /* Reconnect signal to new item */ if (self->current_item) { g_signal_connect (self->current_item, "notify::title", G_CALLBACK (_media_item_title_changed_cb), self); _media_item_title_changed_cb (self->current_item, NULL, self); } else { gtk_window_set_title (GTK_WINDOW (self), CLAPPER_APP_NAME); } gst_clear_object (¤t_item); } static void _player_adaptive_bandwidth_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, gpointer *user_data G_GNUC_UNUSED) { /* Do not take whole bandwidth */ clapper_player_set_adaptive_start_bitrate (player, clapper_player_get_adaptive_bandwidth (player) * 0.8); } static gboolean _get_seek_method_mapping (GValue *value, GVariant *variant, gpointer user_data G_GNUC_UNUSED) { ClapperPlayerSeekMethod seek_method; seek_method = (ClapperPlayerSeekMethod) g_variant_get_int32 (variant); g_value_set_enum (value, seek_method); return TRUE; } static GtkWidget * _pick_pointer_widget (ClapperAppWindow *self) { GdkSurface *surface = gtk_native_get_surface (GTK_NATIVE (self)); GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (self)); GdkSeat *seat = gdk_display_get_default_seat (display); GtkWidget *widget = NULL; if (G_LIKELY (seat != NULL)) { GdkDevice *device = gdk_seat_get_pointer (seat); gdouble px = 0, py = 0, native_x = 0, native_y = 0; if (G_LIKELY (device != NULL)) gdk_surface_get_device_position (surface, device, &px, &py, NULL); gtk_native_get_surface_transform (GTK_NATIVE (self), &native_x, &native_y); widget = gtk_widget_pick (GTK_WIDGET (self), px - native_x, py - native_y, GTK_PICK_DEFAULT); } return widget; } static void _player_volume_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindow *self) { gdouble volume = PERCENTAGE_ROUND (clapper_player_get_volume (player)); /* Only notify when volume changes at least 1%. Remembering last volume * also prevents us from showing volume when it is restored on startup. */ if (volume != self->last_volume) { clapper_gtk_billboard_announce_volume (self->billboard); self->last_volume = volume; } } static void _player_speed_changed_cb (ClapperPlayer *player G_GNUC_UNUSED, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindow *self) { clapper_gtk_billboard_announce_speed (self->billboard); } static void video_toggle_fullscreen_cb (ClapperGtkVideo *video, ClapperAppWindow *self) { GtkWindow *window = GTK_WINDOW (self); g_object_set (window, "fullscreened", !gtk_window_is_fullscreen (window), NULL); } static void video_map_cb (GtkWidget *widget, ClapperAppWindow *self) { ClapperPlayer *player; gdouble speed; GST_TRACE_OBJECT (self, "Video map"); player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video)); g_signal_connect (player, "notify::volume", G_CALLBACK (_player_volume_changed_cb), self); g_signal_connect (player, "notify::speed", G_CALLBACK (_player_speed_changed_cb), self); speed = clapper_player_get_speed (player); /* If we are starting with non-1x speed, notify user about it */ if (!G_APPROX_VALUE (speed, 1.0, FLT_EPSILON)) clapper_gtk_billboard_announce_speed (self->billboard); } static void video_unmap_cb (GtkWidget *widget, ClapperAppWindow *self) { ClapperPlayer *player; GST_TRACE_OBJECT (self, "Video unmap"); player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video)); g_signal_handlers_disconnect_by_func (player, _player_volume_changed_cb, self); g_signal_handlers_disconnect_by_func (player, _player_speed_changed_cb, self); } static void _open_subtitles_cb (ClapperGtkExtraMenuButton *button G_GNUC_UNUSED, ClapperMediaItem *item, ClapperAppWindow *self) { GtkApplication *gtk_app = gtk_window_get_application (GTK_WINDOW (self)); clapper_app_file_dialog_open_subtitles (gtk_app, item); } static void click_pressed_cb (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, ClapperAppWindow *self) { GdkCursor *cursor; const gchar *cursor_name = NULL; if (gtk_gesture_single_get_current_button ( GTK_GESTURE_SINGLE (click)) != GDK_BUTTON_SECONDARY) return; GST_LOG_OBJECT (self, "Right click pressed"); if ((cursor = gtk_widget_get_cursor (self->video))) cursor_name = gdk_cursor_get_name (cursor); /* Restore cursor if faded on video */ if (g_strcmp0 (cursor_name, "none") == 0) { GdkCursor *new_cursor = gdk_cursor_new_from_name ("default", NULL); gtk_widget_set_cursor (self->video, new_cursor); g_object_unref (new_cursor); } } static gboolean _resize_tick (GtkWidget *widget, GdkFrameClock *frame_clock, ClapperAppWindowResizeData *resize_data) { gint64 now = gdk_frame_clock_get_frame_time (frame_clock); if (now - resize_data->last_tick >= MIN_STEP_DELAY) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (widget); gint win_width, win_height; GST_LOG_OBJECT (self, "Resize step, last: %" G_GINT64_FORMAT ", now: %" G_GINT64_FORMAT, resize_data->last_tick, now); gtk_window_get_default_size (GTK_WINDOW (self), &win_width, &win_height); if (win_width != resize_data->dest_width) { gint width_diff = ABS (win_width - resize_data->dest_width); gint step_size = (width_diff > 180) ? 120 : MAX (width_diff / 4, 1); win_width += (win_width > resize_data->dest_width) ? -step_size : step_size; } if (win_height != resize_data->dest_height) { gint height_diff = ABS (win_height - resize_data->dest_height); gint step_size = (height_diff > 180) ? 120 : MAX (height_diff / 4, 1); win_height += (win_height > resize_data->dest_height) ? -step_size : step_size; } gtk_window_set_default_size (GTK_WINDOW (self), win_width, win_height); if (win_width == resize_data->dest_width && win_height == resize_data->dest_height) { GST_DEBUG_OBJECT (self, "Window resize finish"); self->resize_tick_id = 0; return G_SOURCE_REMOVE; } resize_data->last_tick = now; } return G_SOURCE_CONTINUE; } static void _calculate_win_resize (gint win_w, gint win_h, gint vid_w, gint vid_h, gint *dest_w, gint *dest_h) { gdouble win_aspect = (gdouble) win_w / win_h; gdouble vid_aspect = (gdouble) vid_w / vid_h; if (win_aspect < vid_aspect) { while (!G_APPROX_VALUE (fmod (win_w, vid_aspect), 0, FLT_EPSILON)) win_w++; win_h = round ((gdouble) win_w / vid_aspect); if (win_h < MIN_WINDOW_HEIGHT) { _calculate_win_resize (G_MAXINT, MIN_WINDOW_HEIGHT, vid_w, vid_h, dest_w, dest_h); return; } } else { while (!G_APPROX_VALUE (fmod (win_h * vid_aspect, 1.0), 0, FLT_EPSILON)) win_h++; win_w = round ((gdouble) win_h * vid_aspect); if (win_w < MIN_WINDOW_WIDTH) { _calculate_win_resize (MIN_WINDOW_WIDTH, G_MAXINT, vid_w, vid_h, dest_w, dest_h); return; } } *dest_w = win_w; *dest_h = win_h; } static void _resize_window (ClapperAppWindow *self) { ClapperPlayer *player; ClapperStreamList *vstreams; ClapperVideoStream *vstream; GdkToplevelState toplevel_state, disallowed; if (self->resize_tick_id != 0) return; toplevel_state = gdk_toplevel_get_state (GDK_TOPLEVEL ( gtk_native_get_surface (GTK_NATIVE (self)))); disallowed = (GDK_TOPLEVEL_STATE_MINIMIZED | GDK_TOPLEVEL_STATE_MAXIMIZED | GDK_TOPLEVEL_STATE_FULLSCREEN | GDK_TOPLEVEL_STATE_TILED); if ((toplevel_state & disallowed) > 0) { GST_DEBUG_OBJECT (self, "Cannot resize window in disallowed state"); return; } player = clapper_app_window_get_player (self); vstreams = clapper_player_get_video_streams (player); vstream = CLAPPER_VIDEO_STREAM_CAST ( clapper_stream_list_get_current_stream (vstreams)); if (vstream) { gint video_width = clapper_video_stream_get_width (vstream); gint video_height = clapper_video_stream_get_height (vstream); if (G_LIKELY (video_width > 0 && video_height > 0)) { gint win_width, win_height, dest_width, dest_height; gtk_window_get_default_size (GTK_WINDOW (self), &win_width, &win_height); _calculate_win_resize (win_width, win_height, video_width, video_height, &dest_width, &dest_height); /* Only begin resize when not already at perfect size */ if (dest_width != win_width || dest_height != win_height) { ClapperAppWindowResizeData *resize_data; resize_data = g_new0 (ClapperAppWindowResizeData, 1); resize_data->dest_width = dest_width; resize_data->dest_height = dest_height; GST_DEBUG_OBJECT (self, "Window resize start, dest: %ix%i", resize_data->dest_width, resize_data->dest_height); self->resize_tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), (GtkTickCallback) _resize_tick, resize_data, g_free); } } gst_object_unref (vstream); } } static void _handle_middle_click (ClapperAppWindow *self, GtkGestureClick *click) { _resize_window (self); gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); } static void _handle_right_click (ClapperAppWindow *self, GtkGestureClick *click) { GdkSurface *surface; GdkEventSequence *sequence; GdkEvent *event; GST_LOG_OBJECT (self, "Right click released"); surface = gtk_native_get_surface (GTK_NATIVE (self)); sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (click)); event = gtk_gesture_get_last_event (GTK_GESTURE (click), sequence); if (G_UNLIKELY (event == NULL)) return; if (!gdk_toplevel_show_window_menu (GDK_TOPLEVEL (surface), event)) GST_FIXME_OBJECT (self, "Implement fallback context menu"); gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); } static void click_released_cb (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, ClapperAppWindow *self) { switch (gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (click))) { case GDK_BUTTON_MIDDLE: _handle_middle_click (self, click); break; case GDK_BUTTON_SECONDARY: _handle_right_click (self, click); break; default: break; } } static void drag_begin_cb (GtkGestureDrag *drag, gdouble start_x, gdouble start_y, ClapperAppWindow *self) { GtkWidget *widget, *pickup; widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (drag)); pickup = gtk_widget_pick (widget, start_x, start_y, GTK_PICK_DEFAULT); /* We do not want to cause drag on list view as it has * a GtkDragSource controller which acts on delay */ if (GTK_IS_LIST_VIEW (pickup) || gtk_widget_get_ancestor (pickup, GTK_TYPE_LIST_VIEW)) { gtk_gesture_set_state (GTK_GESTURE (drag), GTK_EVENT_SEQUENCE_DENIED); gtk_event_controller_reset (GTK_EVENT_CONTROLLER (drag)); GST_DEBUG_OBJECT (self, "Window drag denied"); } } static void drag_update_cb (GtkGestureDrag *drag, gdouble offset_x, gdouble offset_y, ClapperAppWindow *self) { GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (self)); gint drag_threshold = 8; // initially set to default g_object_get (settings, "gtk-dnd-drag-threshold", &drag_threshold, NULL); if (ABS (offset_x) > drag_threshold || ABS (offset_y) > drag_threshold) { GdkSurface *surface = gtk_native_get_surface (GTK_NATIVE (self)); gdouble start_x = 0, start_y = 0, native_x = 0, native_y = 0; gtk_gesture_set_state (GTK_GESTURE (drag), GTK_EVENT_SEQUENCE_CLAIMED); gtk_gesture_drag_get_start_point (drag, &start_x, &start_y); gtk_native_get_surface_transform (GTK_NATIVE (self), &native_x, &native_y); gdk_toplevel_begin_move (GDK_TOPLEVEL (surface), gtk_gesture_get_device (GTK_GESTURE (drag)), GDK_BUTTON_PRIMARY, start_x + native_x, start_y + native_y, gtk_event_controller_get_current_event_time (GTK_EVENT_CONTROLLER (drag))); gtk_event_controller_reset (GTK_EVENT_CONTROLLER (drag)); } } static inline void _alter_volume (ClapperAppWindow *self, gdouble dy) { ClapperPlayer *player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video)); gdouble volume = clapper_player_get_volume (player); /* We do not want for volume to change too suddenly */ if (dy > 2.0) dy = 2.0; else if (dy < -2.0) dy = -2.0; volume -= dy * 0.02; /* Prevent going out of range and make it easier to set exactly 100% */ if (volume > 2.0) volume = 2.0; else if (volume < 0.0) volume = 0.0; clapper_player_set_volume (player, PERCENTAGE_ROUND (volume)); } static inline void _alter_speed (ClapperAppWindow *self, gdouble dx) { ClapperPlayer *player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video)); gdouble speed = clapper_player_get_speed (player); speed -= dx * 0.02; /* Prevent going out of range and make it easier to set exactly 1.0x */ if (speed > 2.0) speed = 2.0; else if (speed < 0.05) speed = 0.05; clapper_player_set_speed (player, PERCENTAGE_ROUND (speed)); } static gboolean _begin_seek_operation (ClapperAppWindow *self) { ClapperPlayer *player; ClapperQueue *queue; ClapperMediaItem *current_item; if (self->seeking) return FALSE; player = clapper_gtk_video_get_player ( CLAPPER_GTK_VIDEO_CAST (self->video)); queue = clapper_player_get_queue (player); current_item = clapper_queue_get_current_item (queue); self->current_duration = (current_item != NULL) ? clapper_media_item_get_duration (current_item) : 0; gst_clear_object (¤t_item); /* Live content or not a video */ if (self->current_duration == 0) return FALSE; if ((self->was_playing = ( clapper_player_get_state (player) == CLAPPER_PLAYER_STATE_PLAYING))) clapper_player_pause (player); self->pending_position = clapper_player_get_position (player); self->seeking = TRUE; return TRUE; } static void _end_seek_operation (ClapperAppWindow *self) { if (self->seeking && self->current_duration > 0) { ClapperPlayer *player = clapper_gtk_video_get_player ( CLAPPER_GTK_VIDEO_CAST (self->video)); clapper_player_seek_custom (player, self->pending_position, g_settings_get_int (self->settings, "seek-method")); if (self->was_playing) clapper_player_play (player); } /* Reset */ self->was_playing = FALSE; self->pending_position = 0; self->current_duration = 0; self->seeking = FALSE; } static void _announce_current_seek_position (ClapperAppWindow *self, gboolean forward) { gchar *position_str = g_strdup_printf ( "%" CLAPPER_TIME_FORMAT " / %" CLAPPER_TIME_FORMAT, CLAPPER_TIME_ARGS (self->pending_position), CLAPPER_TIME_ARGS (self->current_duration)); clapper_gtk_billboard_post_message (self->billboard, (forward) ? "media-seek-forward-symbolic" : "media-seek-backward-symbolic", position_str); g_free (position_str); } static inline void _alter_position (ClapperAppWindow *self, gdouble dx) { gboolean forward; /* This can only work on devices that * can detect scrolling begin and end */ if (!self->scrolling || (!self->seeking && !_begin_seek_operation (self))) return; forward = (dx > 0); self->pending_position += dx; if (!forward) { if (self->pending_position < 0) self->pending_position = 0; } else { if (self->pending_position > self->current_duration) self->pending_position = self->current_duration; } _announce_current_seek_position (self, forward); } static void scroll_begin_cb (GtkEventControllerScroll *scroll, ClapperAppWindow *self) { GST_LOG_OBJECT (self, "Scroll begin"); /* Assume that if device can begin, it can also end */ self->scrolling = TRUE; } static gboolean scroll_cb (GtkEventControllerScroll *scroll, gdouble dx, gdouble dy, ClapperAppWindow *self) { GtkWidget *pickup; GdkDevice *device; gboolean handled; pickup = _pick_pointer_widget (self); /* We do not want to accidentally allow this controller to handle * scrolls when hovering over widgets that also handle scroll */ while (pickup && !CLAPPER_GTK_IS_VIDEO (pickup)) { if (GTK_IS_SCROLLED_WINDOW (pickup) || GTK_IS_RANGE (pickup)) return FALSE; pickup = gtk_widget_get_parent (pickup); } device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (scroll)); switch (gdk_device_get_source (device)) { case GDK_SOURCE_TOUCHPAD: case GDK_SOURCE_TOUCHSCREEN: dx *= 0.4; dy *= 0.4; break; default: break; } if ((handled = AXIS_WINS_OVER (dy, dx))) _alter_volume (self, dy); else if ((handled = AXIS_WINS_OVER (dx, dy))) _alter_position (self, dx); return handled; } static void scroll_end_cb (GtkEventControllerScroll *scroll, ClapperAppWindow *self) { GST_LOG_OBJECT (self, "Scroll end"); self->scrolling = FALSE; if (self->seeking) _end_seek_operation (self); } static void _handle_seek_key_press (ClapperAppWindow *self, gboolean forward) { gint unit; gdouble offset; if (!self->seeking && !_begin_seek_operation (self)) return; offset = (gdouble) g_settings_get_int (self->settings, "seek-value"); unit = g_settings_get_int (self->settings, "seek-unit"); switch (unit) { case CLAPPER_APP_SEEK_UNIT_SECOND: break; case CLAPPER_APP_SEEK_UNIT_MINUTE: offset *= 60; break; case CLAPPER_APP_SEEK_UNIT_PERCENTAGE: offset = (offset / 100.) * self->current_duration; break; default: g_assert_not_reached (); break; } forward ^= (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL); if (forward) self->pending_position += offset; else self->pending_position -= offset; if (!forward) { if (self->pending_position < 0) self->pending_position = 0; } else { if (self->pending_position > self->current_duration) self->pending_position = self->current_duration; } _announce_current_seek_position (self, forward); } static void _handle_chapter_key_press (ClapperAppWindow *self, gboolean forward) { ClapperPlayer *player = clapper_gtk_video_get_player ( CLAPPER_GTK_VIDEO_CAST (self->video)); ClapperQueue *queue = clapper_player_get_queue (player); ClapperMediaItem *current_item = clapper_queue_get_current_item (queue); ClapperTimeline *timeline; ClapperMarker *dest_marker = NULL; gdouble position; guint i; gboolean is_rtl; if (!current_item) return; timeline = clapper_media_item_get_timeline (current_item); i = clapper_timeline_get_n_markers (timeline); /* No markers to iterate */ if (i == 0) { gst_object_unref (current_item); return; } is_rtl = (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL); forward ^= is_rtl; position = clapper_player_get_position (player); /* When going backwards give small tolerance, so we can * still go to previous one even when directly at/after marker */ if (!forward) position -= 1.5; while (i--) { ClapperMarker *marker = clapper_timeline_get_marker (timeline, i); ClapperMarkerType marker_type = clapper_marker_get_marker_type (marker); gdouble start; gboolean found = FALSE; /* Ignore custom markers */ if (marker_type >= CLAPPER_MARKER_TYPE_CUSTOM_1) { gst_object_unref (marker); continue; } start = clapper_marker_get_start (marker); found = (start <= position); if (found) { if (!forward) dest_marker = marker; else gst_object_unref (marker); break; } if (forward) gst_object_replace ((GstObject **) &dest_marker, GST_OBJECT_CAST (marker)); gst_object_unref (marker); } if (dest_marker) { const gchar *title; gdouble start, duration; gchar *text; title = clapper_marker_get_title (dest_marker); start = clapper_marker_get_start (dest_marker); duration = clapper_media_item_get_duration (current_item); /* XXX: When RTL with mixed numbers and text, we have to * switch positions of start <-> duration ourselves */ text = g_strdup_printf ( "%s\n%" CLAPPER_TIME_FORMAT " / %" CLAPPER_TIME_FORMAT, title, CLAPPER_TIME_ARGS ((!is_rtl) ? start : duration), CLAPPER_TIME_ARGS ((!is_rtl) ? duration : start)); clapper_gtk_billboard_post_message (self->billboard, "user-bookmarks-symbolic", text); clapper_player_seek (player, start); g_free (text); gst_object_unref (dest_marker); } gst_object_unref (current_item); } static void _handle_item_key_press (ClapperAppWindow *self, gboolean forward) { ClapperPlayer *player = clapper_gtk_video_get_player ( CLAPPER_GTK_VIDEO_CAST (self->video)); ClapperQueue *queue = clapper_player_get_queue (player); guint prev_index, index; forward ^= (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL); prev_index = clapper_queue_get_current_index (queue); gtk_widget_activate_action (self->video, (forward) ? "video.next-item" : "video.previous-item", NULL); index = clapper_queue_get_current_index (queue); /* Notify only when changed */ if (prev_index != index) { clapper_gtk_billboard_post_message (self->billboard, "applications-multimedia-symbolic", gtk_window_get_title (GTK_WINDOW (self))); } } static void _handle_speed_key_press (ClapperAppWindow *self, gboolean forward) { forward ^= (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL); gtk_widget_activate_action (self->video, (forward) ? "video.speed-up" : "video.speed-down", NULL); } static inline void _handle_progression_key_press (ClapperAppWindow *self) { ClapperPlayer *player = clapper_gtk_video_get_player ( CLAPPER_GTK_VIDEO_CAST (self->video)); ClapperQueue *queue = clapper_player_get_queue (player); ClapperQueueProgressionMode mode; const gchar *icon = NULL, *label = NULL; mode = ((clapper_queue_get_progression_mode (queue) + 1) % N_PROGRESSION_MODES); clapper_app_utils_parse_progression (mode, &icon, &label); clapper_queue_set_progression_mode (queue, mode); clapper_gtk_billboard_post_message (self->billboard, icon, label); } static gboolean key_pressed_cb (GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, ClapperAppWindow *self) { switch (keyval) { case GDK_KEY_Up: if ((state & GDK_MODIFIER_MASK) == 0) gtk_widget_activate_action (self->video, "video.volume-up", NULL); break; case GDK_KEY_Down: if ((state & GDK_MODIFIER_MASK) == 0) gtk_widget_activate_action (self->video, "video.volume-down", NULL); break; case GDK_KEY_Left: if ((state & GDK_MODIFIER_MASK) == 0) { _handle_seek_key_press (self, FALSE); } else if (!self->key_held && (state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) { _handle_chapter_key_press (self, FALSE); } else if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) { _handle_item_key_press (self, FALSE); } break; case GDK_KEY_j: if ((state & GDK_MODIFIER_MASK) == 0) _handle_seek_key_press (self, FALSE); break; case GDK_KEY_Right: if ((state & GDK_MODIFIER_MASK) == 0) { _handle_seek_key_press (self, TRUE); } else if (!self->key_held && (state & GDK_SHIFT_MASK) == GDK_SHIFT_MASK) { _handle_chapter_key_press (self, TRUE); } else if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) { _handle_item_key_press (self, TRUE); } break; case GDK_KEY_l: if ((state & GDK_MODIFIER_MASK) == 0) _handle_seek_key_press (self, TRUE); break; case GDK_KEY_space: case GDK_KEY_k: if (!self->key_held && (state & GDK_MODIFIER_MASK) == 0) gtk_widget_activate_action (self->video, "video.toggle-play", NULL); break; case GDK_KEY_less: if (!self->key_held) // Needs seek (action is slow) _handle_speed_key_press (self, FALSE); break; case GDK_KEY_greater: if (!self->key_held) // Needs seek (action is slow) _handle_speed_key_press (self, TRUE); break; case GDK_KEY_m: if (!self->key_held && (state & GDK_MODIFIER_MASK) == 0) gtk_widget_activate_action (self->video, "video.toggle-mute", NULL); break; case GDK_KEY_p: if (!self->key_held && (state & GDK_MODIFIER_MASK) == 0) _handle_progression_key_press (self); break; default: return FALSE; } self->key_held = TRUE; return TRUE; } static void key_released_cb (GtkEventControllerKey *controller, guint keyval, guint keycode, GdkModifierType state, ClapperAppWindow *self) { switch (keyval) { case GDK_KEY_Left: case GDK_KEY_j: case GDK_KEY_Right: case GDK_KEY_l: _end_seek_operation (self); break; default: break; } self->key_held = FALSE; } static void _seek_delay_cb (ClapperAppWindow *self) { GST_LOG_OBJECT (self, "Delayed seek handler reached"); self->seek_timeout = 0; if (self->seeking) _end_seek_operation (self); } static void video_seek_request_cb (ClapperGtkVideo *video, gboolean forward, ClapperAppWindow *self) { g_clear_handle_id (&self->seek_timeout, g_source_remove); _handle_seek_key_press (self, forward); self->seek_timeout = g_timeout_add_once (500, (GSourceOnceFunc) _seek_delay_cb, self); } static void drop_value_notify_cb (GtkDropTarget *drop_target, GParamSpec *pspec G_GNUC_UNUSED, ClapperAppWindow *self) { GtkWidget *stack; const GValue *value = gtk_drop_target_get_value (drop_target); if (!value) { clapper_gtk_billboard_unpin_pinned_message (self->billboard); return; } if (!clapper_app_utils_value_for_item_is_valid (value)) { gtk_drop_target_reject (drop_target); return; } stack = gtk_window_get_child (GTK_WINDOW (self)); /* Do not pin message when still in initial state */ if (gtk_stack_get_visible_child (GTK_STACK (stack)) == self->video) { clapper_gtk_billboard_pin_message (self->billboard, "insert-object-symbolic", _("Drop on title bar to play now or anywhere else to enqueue.")); } } static gboolean drop_cb (GtkDropTarget *drop_target, const GValue *value, gdouble x, gdouble y, ClapperAppWindow *self) { GFile **files = NULL; gint n_files = 0; gboolean success = FALSE; if (clapper_app_utils_files_from_value (value, &files, &n_files)) { ClapperPlayer *player = clapper_app_window_get_player (self); ClapperQueue *queue = clapper_player_get_queue (player); gint i; clapper_app_window_ensure_no_initial_state (self); for (i = 0; i < n_files; ++i) { ClapperMediaItem *item = clapper_media_item_new_from_file (files[i]); clapper_queue_add_item (queue, item); gst_object_unref (item); } clapper_app_utils_files_free (files); success = TRUE; } return success; } static void toggle_fullscreen (GSimpleAction *action, GVariant *param, gpointer user_data) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (user_data); video_toggle_fullscreen_cb (CLAPPER_GTK_VIDEO_CAST (self->video), self); } static void unfullscreen (GSimpleAction *action, GVariant *param, gpointer user_data) { GtkWindow *window = GTK_WINDOW (user_data); if (gtk_window_is_fullscreen (window)) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (window); video_toggle_fullscreen_cb (CLAPPER_GTK_VIDEO_CAST (self->video), self); } } static void auto_resize (GSimpleAction *action, GVariant *param, gpointer user_data) { _resize_window (CLAPPER_APP_WINDOW_CAST (user_data)); } static void show_help_overlay (GSimpleAction *action, GVariant *param, gpointer user_data) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (user_data); GtkBuilder *builder; GtkWidget *help_overlay; builder = gtk_builder_new_from_resource ( CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-help-overlay.ui"); help_overlay = GTK_WIDGET (gtk_builder_get_object (builder, "help_overlay")); gtk_window_set_transient_for (GTK_WINDOW (help_overlay), GTK_WINDOW (self)); gtk_window_present (GTK_WINDOW (help_overlay)); g_object_unref (builder); } GtkWidget * clapper_app_window_new (GtkApplication *application) { return g_object_new (CLAPPER_APP_TYPE_WINDOW, "application", application, NULL); } GtkWidget * clapper_app_window_get_video (ClapperAppWindow *self) { return self->video; } ClapperPlayer * clapper_app_window_get_player (ClapperAppWindow *self) { return clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (self->video)); } ClapperAppWindowExtraOptions * clapper_app_window_get_extra_options (ClapperAppWindow *self) { return g_object_get_qdata ((GObject *) self, clapper_app_window_extra_options_get_quark ()); } void clapper_app_window_ensure_no_initial_state (ClapperAppWindow *self) { GtkWidget *stack = gtk_window_get_child (GTK_WINDOW (self)); const gchar *child_name = gtk_stack_get_visible_child_name (GTK_STACK (stack)); if (g_strcmp0 (child_name, "initial_state") == 0) gtk_stack_set_visible_child (GTK_STACK (stack), self->video); } static gboolean clapper_app_window_close_request (GtkWindow *window) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (window); /* FIXME: Have GSettings again to store these values GSettings *settings = g_settings_new (CLAPPER_APP_ID); gint width = DEFAULT_WINDOW_WIDTH, height = DEFAULT_WINDOW_HEIGHT; */ GST_DEBUG_OBJECT (self, "Close request"); /* gtk_window_get_default_size (window, &width, &height); g_settings_set (settings, "window-size", "(ii)", width, height); g_settings_set_boolean (settings, "maximized", gtk_window_is_maximized (window)); g_settings_set_boolean (settings, "fullscreen", gtk_window_is_fullscreen (window)); g_object_unref (settings); */ return GTK_WINDOW_CLASS (parent_class)->close_request (window); } static void clapper_app_window_realize (GtkWidget *widget) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (widget); GST_TRACE_OBJECT (self, "Realize"); GTK_WIDGET_CLASS (parent_class)->realize (widget); gtk_style_context_add_provider_for_display (gtk_widget_get_display (widget), (GtkStyleProvider *) self->provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); } static void clapper_app_window_unrealize (GtkWidget *widget) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (widget); GST_TRACE_OBJECT (self, "Unrealize"); gtk_style_context_remove_provider_for_display (gtk_widget_get_display (widget), (GtkStyleProvider *) self->provider); GTK_WIDGET_CLASS (parent_class)->unrealize (widget); } static void clapper_app_window_init (ClapperAppWindow *self) { ClapperAppWindowExtraOptions *extra_opts; GtkSettings *settings; GtkWidget *dummy_titlebar; gint distance = 0; gtk_widget_set_size_request (GTK_WIDGET (self), MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT); extra_opts = g_new0 (ClapperAppWindowExtraOptions, 1); GST_TRACE ("Created window extra options: %p", extra_opts); g_object_set_qdata_full ((GObject *) self, clapper_app_window_extra_options_get_quark (), extra_opts, (GDestroyNotify) clapper_app_window_extra_options_free); gtk_widget_init_template (GTK_WIDGET (self)); /* Make double tap easier to perform */ settings = gtk_widget_get_settings (self->video); g_object_get (settings, "gtk-double-click-distance", &distance, NULL); g_object_set (settings, "gtk-double-click-distance", MAX (distance, 32), NULL); dummy_titlebar = g_object_new (GTK_TYPE_BOX, "can_focus", FALSE, "focusable", FALSE, "visible", FALSE, NULL); gtk_window_set_titlebar (GTK_WINDOW (self), dummy_titlebar); gtk_window_set_title (GTK_WINDOW (self), CLAPPER_APP_NAME); /* Prevent GTK from redrawing background for each frame */ gtk_widget_remove_css_class (GTK_WIDGET (self), "background"); gtk_drop_target_set_gtypes (self->drop_target, (GType[3]) { GDK_TYPE_FILE_LIST, G_TYPE_FILE, G_TYPE_STRING }, 3); } static void clapper_app_window_constructed (GObject *object) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (object); ClapperPlayer *player = clapper_app_window_get_player (self); ClapperQueue *queue = clapper_player_get_queue (player); ClapperGtkExtraMenuButton *button; AdwStyleManager *manager; static const GActionEntry win_entries[] = { { "toggle-fullscreen", toggle_fullscreen, NULL, NULL, NULL }, { "unfullscreen", unfullscreen, NULL, NULL, NULL }, { "auto-resize", auto_resize, NULL, NULL, NULL }, { "show-help-overlay", show_help_overlay, NULL, NULL, NULL }, }; #if (CLAPPER_HAVE_MPRIS || CLAPPER_HAVE_SERVER || CLAPPER_HAVE_DISCOVERER) ClapperFeature *feature = NULL; #endif #if CLAPPER_HAVE_MPRIS gchar mpris_name[45]; g_snprintf (mpris_name, sizeof (mpris_name), "org.mpris.MediaPlayer2.Clapper.instance%" G_GUINT16_FORMAT, instance_count++); #endif self->settings = g_settings_new (CLAPPER_APP_ID); self->last_volume = PERCENTAGE_ROUND (g_settings_get_double (self->settings, "volume")); #if CLAPPER_HAVE_MPRIS feature = CLAPPER_FEATURE (clapper_mpris_new ( mpris_name, CLAPPER_APP_NAME, CLAPPER_APP_ID)); clapper_mpris_set_queue_controllable (CLAPPER_MPRIS (feature), TRUE); clapper_player_add_feature (player, feature); gst_object_unref (feature); #endif #if CLAPPER_HAVE_SERVER feature = CLAPPER_FEATURE (clapper_server_new ()); clapper_server_set_queue_controllable (CLAPPER_SERVER (feature), TRUE); g_settings_bind (self->settings, "server-enabled", feature, "enabled", G_SETTINGS_BIND_GET); clapper_player_add_feature (player, feature); gst_object_unref (feature); #endif #if CLAPPER_HAVE_DISCOVERER feature = CLAPPER_FEATURE (clapper_discoverer_new ()); clapper_player_add_feature (player, feature); gst_object_unref (feature); #endif /* FIXME: Allow setting sink/filter elements from prefs window * (this should include parsing bin descriptions) */ clapper_player_set_autoplay (player, TRUE); /* No need to also call these here, as they only change * after application window is contructed */ g_signal_connect (queue, "notify::current-item", G_CALLBACK (_queue_current_item_changed_cb), self); g_signal_connect (player, "notify::adaptive-bandwidth", G_CALLBACK (_player_adaptive_bandwidth_changed_cb), NULL); g_settings_bind (self->settings, "audio-offset", player, "audio-offset", G_SETTINGS_BIND_GET); g_settings_bind (self->settings, "subtitle-offset", player, "subtitle-offset", G_SETTINGS_BIND_GET); g_settings_bind (self->settings, "subtitle-font-desc", player, "subtitle-font-desc", G_SETTINGS_BIND_GET); button = clapper_gtk_simple_controls_get_extra_menu_button ( self->simple_controls); g_settings_bind_with_mapping (self->settings, "seek-method", self->simple_controls, "seek-method", G_SETTINGS_BIND_GET, (GSettingsBindGetMapping) _get_seek_method_mapping, NULL, NULL, NULL); g_signal_connect (button, "open-subtitles", G_CALLBACK (_open_subtitles_cb), self); clapper_gtk_extra_menu_button_set_can_open_subtitles (button, TRUE); manager = adw_style_manager_get_default (); adw_style_manager_set_color_scheme (manager, ADW_COLOR_SCHEME_FORCE_DARK); self->provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (self->provider, CLAPPER_APP_RESOURCE_PREFIX "/css/styles.css"); g_action_map_add_action_entries (G_ACTION_MAP (self), win_entries, G_N_ELEMENTS (win_entries), self); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_app_window_dispose (GObject *object) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (object); if (self->resize_tick_id != 0) { gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->resize_tick_id); self->resize_tick_id = 0; } g_clear_handle_id (&self->seek_timeout, g_source_remove); gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_APP_TYPE_WINDOW); gst_clear_object (&self->current_item); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_app_window_finalize (GObject *object) { ClapperAppWindow *self = CLAPPER_APP_WINDOW_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_object_unref (self->settings); g_object_unref (self->provider); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_window_class_init (ClapperAppWindowClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GtkWindowClass *window_class = (GtkWindowClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappwindow", 0, "Clapper App Window"); gobject_class->constructed = clapper_app_window_constructed; gobject_class->dispose = clapper_app_window_dispose; gobject_class->finalize = clapper_app_window_finalize; widget_class->realize = clapper_app_window_realize; widget_class->unrealize = clapper_app_window_unrealize; window_class->close_request = clapper_app_window_close_request; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_APP_RESOURCE_PREFIX "/ui/clapper-app-window.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperAppWindow, video); gtk_widget_class_bind_template_child (widget_class, ClapperAppWindow, billboard); gtk_widget_class_bind_template_child (widget_class, ClapperAppWindow, simple_controls); gtk_widget_class_bind_template_child (widget_class, ClapperAppWindow, drop_target); gtk_widget_class_bind_template_callback (widget_class, video_toggle_fullscreen_cb); gtk_widget_class_bind_template_callback (widget_class, video_seek_request_cb); gtk_widget_class_bind_template_callback (widget_class, video_map_cb); gtk_widget_class_bind_template_callback (widget_class, video_unmap_cb); gtk_widget_class_bind_template_callback (widget_class, scroll_begin_cb); gtk_widget_class_bind_template_callback (widget_class, scroll_cb); gtk_widget_class_bind_template_callback (widget_class, scroll_end_cb); gtk_widget_class_bind_template_callback (widget_class, key_pressed_cb); gtk_widget_class_bind_template_callback (widget_class, key_released_cb); gtk_widget_class_bind_template_callback (widget_class, click_pressed_cb); gtk_widget_class_bind_template_callback (widget_class, click_released_cb); gtk_widget_class_bind_template_callback (widget_class, drag_begin_cb); gtk_widget_class_bind_template_callback (widget_class, drag_update_cb); gtk_widget_class_bind_template_callback (widget_class, drop_value_notify_cb); gtk_widget_class_bind_template_callback (widget_class, drop_cb); } clapper-0.8.0/src/bin/clapper-app/clapper-app-window.h000066400000000000000000000032651474353110700226050ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include #include G_BEGIN_DECLS #define CLAPPER_APP_TYPE_WINDOW (clapper_app_window_get_type()) #define CLAPPER_APP_WINDOW_CAST(obj) ((ClapperAppWindow *)(obj)) typedef struct { gchar *video_filter; gchar *audio_filter; gchar *video_sink; gchar *audio_sink; } ClapperAppWindowExtraOptions; G_DECLARE_FINAL_TYPE (ClapperAppWindow, clapper_app_window, CLAPPER_APP, WINDOW, GtkApplicationWindow) G_GNUC_INTERNAL GtkWidget * clapper_app_window_new (GtkApplication *application); G_GNUC_INTERNAL GtkWidget * clapper_app_window_get_video (ClapperAppWindow *window); G_GNUC_INTERNAL ClapperPlayer * clapper_app_window_get_player (ClapperAppWindow *window); G_GNUC_INTERNAL ClapperAppWindowExtraOptions * clapper_app_window_get_extra_options (ClapperAppWindow *window); G_GNUC_INTERNAL void clapper_app_window_ensure_no_initial_state (ClapperAppWindow *window); G_END_DECLS clapper-0.8.0/src/bin/clapper-app/clapper-app.gresources.xml000066400000000000000000000026151474353110700240270ustar00rootroot00000000000000 data/metainfo/com.github.rafostar.Clapper.metainfo.xml ui/clapper-app-initial-state.ui ui/clapper-app-uri-dialog.ui ui/clapper-app-headerbar.ui ui/clapper-app-help-overlay.ui ui/clapper-app-info-window.ui ui/clapper-app-queue-list.ui ui/clapper-app-queue-list-item.ui ui/clapper-app-queue-progression-item.ui ui/clapper-app-video-stream-list-item.ui ui/clapper-app-audio-stream-list-item.ui ui/clapper-app-subtitle-stream-list-item.ui ui/clapper-app-preferences-window.ui ui/clapper-app-window.ui ui/clapper-app-window-state-buttons.ui css/styles.css clapper-0.8.0/src/bin/clapper-app/css/000077500000000000000000000000001474353110700175055ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/css/styles.css000066400000000000000000000051061474353110700215440ustar00rootroot00000000000000window .initialstate { padding-left: 6px; padding-right: 6px; padding-top: 8px; background-color: @dark_4; } window.info .maincontent { margin: 16px; margin-top: 0px; } window.info .subcontent { margin: 16px; margin-top: 0px; margin-bottom: 8px; } window.info .subcontent streamlist preferencesgroup { margin-bottom: 8px; } window.preferences .subcontent { margin: 16px; } window.preferences .pluginssubpage .subcontent popover { min-width: 264px; } window.preferences .pluginssubpage .subcontent button.pill { margin-top: 8px; margin-bottom: 8px; } clapper-gtk-seek-bar scale trough highlight { color: @accent_fg_color; background-color: @accent_bg_color; } clapper-gtk-video .overamp trough highlight { color: @error_fg_color; background-color: @error_bg_color; } clapper-app-headerbar .mainbox { margin: 6px; padding: 2px; } clapper-app-headerbar .titlelabel label { margin-left: 8px; margin-right: 8px; } clapper-app-headerbar clapper-app-window-state-buttons { margin-top: 8px; } clapper-app-headerbar clapper-app-window-state-buttons.filled.right:dir(ltr) { padding-right: 6px; } clapper-app-headerbar clapper-app-window-state-buttons.filled.right:dir(rtl) { padding-left: 6px; } clapper-app-headerbar clapper-app-window-state-buttons.filled.left:dir(ltr) { padding-left: 6px; } clapper-app-headerbar clapper-app-window-state-buttons.filled.left:dir(rtl) { padding-right: 6px; } clapper-app-queue-list .topseparator { margin-top: 2px; margin-bottom: 4px; } clapper-app-queue-list .buttonsbox { padding-left: 6px; padding-right: 6px; } clapper-app-queue-list .buttonsbox dropdown .toggle row { background-color: transparent; } clapper-app-queue-list .buttonsbox .additemsbutton box { padding-left: 6px; padding-right: 6px; } clapper-app-queue-list .buttonsbox .additemsbutton box image { margin-left: 3px; margin-right: 3px; } clapper-app-queue-list .buttonsbox .progressiondropdown popover label { min-width: 100px; } clapper-app-queue-list .removeitembox { margin-left: 6px; margin-right: 6px; border-radius: 9999px; background-color: alpha(@destructive_bg_color, 0.9); } clapper-app-queue-list .queue { padding: 3px; } clapper-app-queue-list .queue listview { padding: 2px; padding-bottom: 0px; border-radius: 0px 0px 19px 19px; } clapper-app-queue-list .queue listview row { padding-left: 10px; padding-right: 10px; min-height: 30px; border-radius: 9999px; } /* Disable background light on hover when doing D&D */ clapper-app-queue-list .queue listview.dnd row.activatable:not(:selected):hover { background: none; } clapper-0.8.0/src/bin/clapper-app/data/000077500000000000000000000000001474353110700176265ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/applications/000077500000000000000000000000001474353110700223145ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in000066400000000000000000000051661474353110700315700ustar00rootroot00000000000000[Desktop Entry] # Translators: Do NOT translate app name! Name=Clapper GenericName=Multimedia Player Comment=Play videos and music Categories=GTK;GNOME;AudioVideo;Player;Video;TV; MimeType=application/claps;application/mpeg4-iod;application/mpeg4-muxcodetable;application/mxf;application/ogg;application/ram;application/sdp;application/streamingmedia;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;application/x-extension-m4a;application/x-extension-mp4;application/x-flac;application/x-flash-video;application/x-matroska;application/x-ogg;application/x-streamingmedia;audio/3gpp;audio/3gpp2;audio/aac;audio/ac3;audio/amr;audio/amr-wb;audio/basic;audio/dv;audio/eac3;audio/flac;audio/m4a;audio/midi;audio/mp1;audio/mp2;audio/mp3;audio/mp4;audio/mpeg;audio/mpegurl;audio/mpg;audio/ogg;audio/opus;audio/scpls;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/vnd.dolby.mlp;audio/vnd.dts;audio/vnd.dts.hd;audio/vnd.rn-realaudio;audio/wav;audio/webm;audio/x-aac;audio/x-aiff;audio/x-ape;audio/x-flac;audio/x-gsm;audio/x-it;audio/x-m4a;audio/x-matroska;audio/x-mod;audio/x-mp1;audio/x-mp2;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-mpg;audio/x-ms-asf;audio/x-ms-wma;audio/x-musepack;audio/x-pn-aiff;audio/x-pn-au;audio/x-pn-realaudio;audio/x-pn-wav;audio/x-real-audio;audio/x-realaudio;audio/x-s3m;audio/x-scpls;audio/x-shorten;audio/x-speex;audio/x-tta;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-wav;audio/x-wavpack;audio/x-xm;video/3gp;video/3gpp;video/3gpp2;video/divx;video/dv;video/fli;video/flv;video/mp2t;video/mp4;video/mp4v-es;video/mpeg;video/mpeg-system;video/msvideo;video/ogg;video/quicktime;video/vnd.mpegurl;video/vnd.rn-realvideo;video/webm;video/x-avi;video/x-flc;video/x-fli;video/x-flv;video/x-m4v;video/x-matroska;video/x-mpeg;video/x-mpeg-system;video/x-mpeg2;video/x-ms-asf;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-msvideo;video/x-nsv;video/x-ogm+ogg;video/x-theora;video/x-theora+ogg;x-content/audio-cdda;x-content/audio-player;x-content/video-dvd;x-scheme-handler/mms;x-scheme-handler/mmsh;x-scheme-handler/rtmp;x-scheme-handler/rtp;x-scheme-handler/rtsp; Exec=clapper %U Icon=com.github.rafostar.Clapper DBusActivatable=true 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; # Translators: Do NOT translate or transliterate this text (these are enum types)! X-Purism-FormFactor=Workstation;Mobile; Actions=new-window; [Desktop Action new-window] Name=New Window Exec=clapper --new-window clapper-0.8.0/src/bin/clapper-app/data/dbus-1/000077500000000000000000000000001474353110700207215ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/dbus-1/meson.build000066400000000000000000000000231474353110700230560ustar00rootroot00000000000000subdir('services') clapper-0.8.0/src/bin/clapper-app/data/dbus-1/services/000077500000000000000000000000001474353110700225445ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/dbus-1/services/com.github.rafostar.Clapper.service.in000066400000000000000000000001111474353110700317700ustar00rootroot00000000000000[D-BUS Service] Name=@app_id@ Exec=@bindir@/@bin@ --gapplication-service clapper-0.8.0/src/bin/clapper-app/data/dbus-1/services/meson.build000066400000000000000000000006061474353110700247100ustar00rootroot00000000000000dbus_conf = configuration_data() dbus_conf.set('app_id', app_id) dbus_conf.set('bindir', join_paths(prefix, bindir)) dbus_conf.set('bin', meson.project_name()) configure_file( input: 'com.github.rafostar.Clapper.service.in', output: 'com.github.rafostar.Clapper.service', configuration: dbus_conf, install: true, install_dir: join_paths(prefix, datadir, 'dbus-1', 'services'), ) clapper-0.8.0/src/bin/clapper-app/data/glib-2.0/000077500000000000000000000000001474353110700210405ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/glib-2.0/schemas/000077500000000000000000000000001474353110700224635ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/glib-2.0/schemas/com.github.rafostar.Clapper.gschema.xml000066400000000000000000000052671474353110700320710ustar00rootroot00000000000000 1 Method used for seeking 0 Unit ID to use with seeking value 10 Time amount to seek with single press of arrow keys false Enable Server feature for remote playback control 0 Offset time for audio stream relative to video 0 Offset time for subtitle stream relative to video "Sans 12" Default subtitle stream font description '' Overrides for GStreamer plugin ranking 1.0 Stores last volume value to apply on startup false Stores last mute state to apply on startup 1.0 Stores last speed value to apply on startup 1600000 Stores initial adaptive streaming bitrate to apply on startup 1 Stores last queue progression mode used to apply on startup true Stores whether subtitles are enabled value to apply on startup false Stores whether window was last time maximized to restore on startup false Stores whether window was last time in fullscreen to restore on startup clapper-0.8.0/src/bin/clapper-app/data/glib-2.0/schemas/meson.build000066400000000000000000000003541474353110700246270ustar00rootroot00000000000000gnome.compile_schemas( build_by_default: true, depend_files: ['com.github.rafostar.Clapper.gschema.xml'] ) install_data('com.github.rafostar.Clapper.gschema.xml', install_dir: join_paths(prefix, datadir, 'glib-2.0', 'schemas') ) clapper-0.8.0/src/bin/clapper-app/data/icons/000077500000000000000000000000001474353110700207415ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/icons/hicolor/000077500000000000000000000000001474353110700224005ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/icons/hicolor/scalable/000077500000000000000000000000001474353110700241465ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/icons/hicolor/scalable/apps/000077500000000000000000000000001474353110700251115ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/icons/hicolor/scalable/apps/com.github.rafostar.Clapper.svg000066400000000000000000000075311474353110700331040ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/data/icons/hicolor/symbolic/000077500000000000000000000000001474353110700242215ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/icons/hicolor/symbolic/apps/000077500000000000000000000000001474353110700251645ustar00rootroot00000000000000com.github.rafostar.Clapper-symbolic.svg000066400000000000000000000114461474353110700347170ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/icons/hicolor/symbolic/apps clapper-0.8.0/src/bin/clapper-app/data/meson.build000066400000000000000000000031731474353110700217740ustar00rootroot00000000000000appstream_util = find_program('appstream-util', required: false) if appstream_util.found() test('Validate appstream file', appstream_util, args: [ 'validate-relax', '--nonet', join_paths(meson.current_source_dir(), 'metainfo', 'com.github.rafostar.Clapper.metainfo.xml'), ] ) endif glib_compile_schemas = find_program('glib-compile-schemas', required: false) if glib_compile_schemas.found() test('Validate gsettings schemas', glib_compile_schemas, args: [ '--strict', '--dry-run', join_paths(meson.current_source_dir(), 'glib-2.0', 'schemas'), ] ) endif install_subdir('icons', install_dir: join_paths(prefix, datadir) ) install_subdir('metainfo', install_dir: join_paths(prefix, datadir) ) is_linux = ['linux'].contains(host_machine.system()) is_windows = ['windows'].contains(host_machine.system()) if is_linux desktop_file = i18n.merge_file( input: 'applications/com.github.rafostar.Clapper.desktop.in', output: 'com.github.rafostar.Clapper.desktop', type: 'desktop', po_dir: '../po', install: true, install_dir: join_paths(prefix, datadir, 'applications'), ) desktop_utils = find_program('desktop-file-validate', required: false) if desktop_utils.found() test('Validate desktop file', desktop_utils, args: [desktop_file]) endif subdir('dbus-1') endif if not is_windows install_subdir('mime', install_dir: join_paths(prefix, datadir) ) endif subdir('glib-2.0/schemas') gnome.post_install( glib_compile_schemas: true, gtk_update_icon_cache: true, update_desktop_database: is_linux, update_mime_database: not is_windows, ) clapper-0.8.0/src/bin/clapper-app/data/metainfo/000077500000000000000000000000001474353110700214305ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/metainfo/com.github.rafostar.Clapper.metainfo.xml000066400000000000000000000311531474353110700312220ustar00rootroot00000000000000 com.github.rafostar.Clapper Clapper Lean back and enjoy videos CC0-1.0 GPL-3.0-or-later Rafał Dzięgiel Rafał Dzięgiel clapper-app com.github.rafostar.Clapper.desktop https://rafostar.github.io/clapper https://github.com/Rafostar/clapper/issues https://liberapay.com/Clapper https://github.com/Rafostar/clapper/wiki https://github.com/Rafostar/clapper

Clapper is a modern media player designed for simplicity and ease of use. Powered by GStreamer and built for the GNOME desktop environment using GTK4 toolkit, it has a clean and stylish interface that lets you focus on enjoying your favorite videos.

Clapper uses a playback queue where you can add multiple media files. Think of it like a playlist that you can build. You can easily reorder items or remove them from the queue with a simple drag and drop operation.

https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot_01.png Modern player with minimalist look https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot_02.png See actual title of what you are watching https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot_03.png Queue multiple media in any order you like https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot_04.png Play either local or network content https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot_05.png Enjoy adaptive UI that fits any screen size AudioVideo Video

Changes:

  • Now uses playbin3 by default
  • Added more command line options - including ability to set custom GStreamer filter and sink elements
  • Support for creating multiple application windows
  • Added menu option to clear playback queue
  • Auto resize window to match aspect ratio with middle click or keyboard shortcut
  • Added unfullscreen with Escape key
  • Brand new libpeas based plugin system
  • Remember and restore optimal bitrate (video quality) when starting adaptive streaming
  • Other various improvements and bug fixes

Bug fixes and translation updates

In this release Clapper code was rewritten from scratch. Now split into two libraries and an application built on top of them, with:

  • Brand new, simplified and consistent UI
  • Player now operates on playback queue model
  • Multiple queue progression modes to choose from (consecutive, repeat item, carousel, shuffle)
  • New media info window (shows media information and GStreamer elements used to play it)
  • More dialogs and windows use libadwaita
  • Multiple new status/info overlays showing what is going and other messages
  • Added video buffering animation
  • Clapper will now inform users about missing GStreamer plugins needed for playback
  • Video chapters now appear throughout the seek bar on hover and while dragging slider
  • Vastly expanded MPRIS implementation
  • Integrated media discovery (see titles of queued items instead of URIs)
  • Expanded WebSocket server functionality (for remote playback control)
  • Increased maximum volume amplification level to 200%
  • Added some new keyboard shortcuts
  • In addition to volume, now also speed and queue progression mode are restored on launch
  • Fixed keyboard shortcuts not being reversed (e.g. seek) in RTL languages
  • Toggle play moved to left mouse button, right one now opens context menu (which was previously hard to access)
  • Many bug fixes and QoL improvements

Fixes:

  • Fix time labels display on RTL languages
  • Improved GL/GLES context automatic selection

New translations:

  • Hebrew

A quick hotfix release. Fixes problems with new video sink on displays with non-100% scaling applied. See 0.5.0 version release notes for full recent changelog.

Changes:

  • Includes and uses new, improved GStreamer video sink
  • All networking ported to libsoup3
  • A lot of cleanup, including removal of unfinished web application and old YT code
  • App now supports D-Bus launching (DBusActivatable)
  • Other small fixes

New translations:

  • Arabic
  • Basque
  • French
  • Japanese
  • Swedish
  • Turkish

Fixes:

  • Compatibility with more recent libadwaita versions
  • Toggle mute with M button alone
  • Allow handling YouTube with external GStreamer plugins
  • Fix catching errors when reading clipboard
  • Fix missing translator-credits
  • Fix missing gio-unix-2.0 dep
  • Fix playback pausing when entering fullscreen with touchscreen
  • Fix GST_PLUGIN_FEATURE_RANK env usage
  • Fix video/audio decoder change detection
  • Merge global video tags instead replacing them
  • Few other misc bug fixes

New translations:

  • Chinese Simplified
  • Czech
  • Hungarian
  • Portuguese
  • Portuguese, Brazilian
  • Russian
  • Spanish

Changes:

  • Now uses libadwaita
  • New and adaptive preferences window
  • Improved open URI dialog
  • Few small tweaks to fullscreen UI design
  • Show current video and audio decoders in popovers (easy way to check if HW accel is used)
  • Enabled NVDEC hardware acceleration by default (requires Nvidia proprietary drivers)
  • Added option to use PipeWire for audio output (experimental)
  • Added option to use playbin3 element (experimental)
  • New PiP icon from icon development kit
  • Improved performance on devices running OpenGL ES
  • Translations support
  • Various bug fixes

New keyboard shortcuts:

  • Leave fullscreen with Escape key
  • Toggle mute with Ctrl+M

More touchscreen gestures:

  • Toggle playback with a long press
  • Switch playlist items via double tap on screen side

New translations:

  • Catalan
  • Dutch
  • German
  • Italian
  • Polish

Changes:

  • Added MPRIS support
  • Added repeat modes: single video, whole playlist and shuffle
  • Support opening folders with media files
  • Append playlist items by holding Ctrl while doing Drag and Drop
  • Improved handling of keyboard shortcuts
  • Added more keyboard shortcuts
  • Added window that shows available keyboard shortcuts
  • Show black screen by default after playback (make showing last frame optional instead)
  • Added ability to export playlist to file
  • Improve handling of changing displays with different resolutions
  • Added support for EGL under x11 with GTK 4.3.1 or later
  • Added missing symbolic app icon
  • Some misc bug fixes and code cleanups

Player:

  • Fix missing top left menu buttons on some system configurations
  • Fix potential video sink deadlock
  • Do not show mobile controls transition on launch
  • Show tooltip with full playlist item text on hover

YouTube:

  • Auto select best matching resolution for used monitor
  • Added some YouTube related preferences
  • Added support for live HLS videos
  • Added support for non-adaptive live HLS streaming

New features:

  • YouTube support - drag and drop videos from youtube or use open URI dialog to play them
  • Added convenient ways of opening external subtitles

Changes:

  • Few GUI layout improvements
  • Simplified video sink code
  • Fixed missing Ctrl+O common keybinding
  • Fixed error when playback finishes during controls reveal animation
  • Fixed startup window size on Xorg
  • Fixed top time not showing up on fullscreen startup
  • Fixed missing file extensions in online URIs
  • Fixed some error messages not being displayed

First stable release

GitHub version

#8484ee #1a1a74 keyboard pointing touch 360
clapper-0.8.0/src/bin/clapper-app/data/mime/000077500000000000000000000000001474353110700205555ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/mime/packages/000077500000000000000000000000001474353110700223335ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/data/mime/packages/com.github.rafostar.Clapper.xml000066400000000000000000000004511474353110700303210ustar00rootroot00000000000000 Clapper Playlist clapper-0.8.0/src/bin/clapper-app/main.c000066400000000000000000000040071474353110700200060ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include "clapper-app-application.h" #include "clapper-app-types.h" #include "clapper-app-utils.h" gint main (gint argc, gchar **argv) { const gchar *clapper_ldir; GApplication *application; gint status; #ifdef G_OS_WIN32 guint resolution = 0; #endif #ifndef G_OS_WIN32 g_setenv ("GSK_RENDERER", "gl", FALSE); #endif setlocale (LC_ALL, ""); if (!(clapper_ldir = g_getenv ("CLAPPER_APP_OVERRIDE_LOCALEDIR"))) clapper_ldir = LOCALEDIR; bindtextdomain (GETTEXT_PACKAGE, clapper_ldir); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); textdomain (GETTEXT_PACKAGE); clapper_init (&argc, &argv); gtk_init (); adw_init (); clapper_app_types_init (); clapper_app_utils_debug_init (); g_set_application_name ("Clapper"); #ifdef G_OS_WIN32 clapper_app_utils_win_enforce_hi_res_clock (); resolution = clapper_app_utils_win_hi_res_clock_start (); #endif application = clapper_app_application_new (); status = g_application_run (application, argc, argv); g_object_unref (application); #ifdef G_OS_WIN32 if (resolution > 0) clapper_app_utils_win_hi_res_clock_stop (resolution); #endif return status; } clapper-0.8.0/src/bin/clapper-app/meson.build000066400000000000000000000065571474353110700210740ustar00rootroot00000000000000clapperapp_option = get_option('clapper-app') app_id = 'com.github.rafostar.Clapper' app_resource_prefix = '/com/github/rafostar/Clapper/clapper-app' build_clapperapp = false if clapperapp_option.disabled() subdir_done() endif clapperapp_deps = [ clapper_dep, clappergtk_dep, gst_dep, gtk4_dep, libadwaita_dep, glib_dep, gobject_dep, ] foreach dep : clapperapp_deps if not dep.found() if clapperapp_option.enabled() error('clapper-app option was enabled, but required dependencies were not found') endif subdir_done() endif endforeach subdir('data') subdir('po') clapperapp_resources = gnome.compile_resources( 'clapper-app-resources', 'clapper-app.gresources.xml', c_name: 'clapper_app', ) # Include the generated headers clapperapp_conf_inc = [ include_directories('.'), include_directories('..'), ] config_h = configuration_data() config_h.set_quoted('GETTEXT_PACKAGE', meson.project_name() + '-app') config_h.set_quoted('LOCALEDIR', join_paths (prefix, localedir)) config_h.set_quoted('CLAPPER_APP_NAME', 'Clapper') config_h.set_quoted('CLAPPER_APP_ID', app_id) config_h.set_quoted('CLAPPER_APP_RESOURCE_PREFIX', app_resource_prefix) configure_file( output: 'config.h', configuration: config_h, ) visibility_conf = configuration_data() visibility_conf.set( 'CLAPPER_API', 'CLAPPER_APP_INTERNAL', ) configure_file( input: '../../lib/shared/clapper-api-visibility.h.in', output: 'clapper-app-internal-visibility.h', configuration: visibility_conf, ) clapperapp_sources = [ 'clapper-app-about-window.c', 'clapper-app-application.c', 'clapper-app-file-dialog.c', 'clapper-app-headerbar.c', 'clapper-app-info-window.c', 'clapper-app-list-item-utils.c', 'clapper-app-media-item-box.c', 'clapper-app-preferences-window.c', 'clapper-app-property-row.c', 'clapper-app-queue-list.c', 'clapper-app-queue-progression-item.c', 'clapper-app-queue-progression-model.c', 'clapper-app-queue-selection.c', 'clapper-app-types.c', 'clapper-app-uri-dialog.c', 'clapper-app-utils.c', 'clapper-app-window.c', 'clapper-app-window-state-buttons.c', 'main.c', clapperapp_resources, ] clapperapp_c_args = [ '-DG_LOG_DOMAIN="ClapperApp"', '-DCLAPPER_APP_INTERNAL_COMPILATION', '-DGST_USE_UNSTABLE_API', ] is_windows = ['windows'].contains(host_machine.system()) if is_windows clapperapp_c_args += ['-D_WIN32_WINNT=_WIN32_WINNT_WIN8'] kernel32_dep = cc.find_library('kernel32', required: false) if kernel32_dep.found() and cc.has_header('processthreadsapi.h') clapperapp_deps += kernel32_dep clapperapp_c_args += ['-DHAVE_WIN_PROCESS_THREADS_API'] endif winmm_dep = cc.find_library('winmm', required: false) if winmm_dep.found() and cc.has_header('timeapi.h') clapperapp_deps += winmm_dep clapperapp_c_args += ['-DHAVE_WIN_TIME_API'] endif subdir('windows') endif executable( meson.project_name(), clapperapp_sources, dependencies: clapperapp_deps, include_directories: clapperapp_conf_inc, c_args: clapperapp_c_args, install: true, install_dir: bindir, win_subsystem: 'windows', ) if is_windows executable( meson.project_name() + '-console', clapperapp_sources, dependencies: clapperapp_deps, include_directories: clapperapp_conf_inc, c_args: clapperapp_c_args, install: true, install_dir: bindir, win_subsystem: 'console', ) endif build_clapperapp = true clapper-0.8.0/src/bin/clapper-app/po/000077500000000000000000000000001474353110700173335ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/po/LINGUAS000066400000000000000000000001361474353110700203600ustar00rootroot00000000000000ar ast ca cs de es eu fa fi fr he hi hr hu it ja lt nl no oc pl pt pt_BR ro ru sk sv tr zh_CN clapper-0.8.0/src/bin/clapper-app/po/POTFILES000066400000000000000000000016571474353110700205140ustar00rootroot00000000000000src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui src/bin/clapper-app/ui/clapper-app-headerbar.ui src/bin/clapper-app/ui/clapper-app-help-overlay.ui src/bin/clapper-app/ui/clapper-app-info-window.ui src/bin/clapper-app/ui/clapper-app-initial-state.ui src/bin/clapper-app/ui/clapper-app-preferences-window.ui src/bin/clapper-app/ui/clapper-app-queue-list.ui 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-application.c src/bin/clapper-app/clapper-app-info-window.c src/bin/clapper-app/clapper-app-list-item-utils.c src/bin/clapper-app/clapper-app-preferences-window.c src/bin/clapper-app/clapper-app-utils.c src/bin/clapper-app/clapper-app-window.c clapper-0.8.0/src/bin/clapper-app/po/af.po000066400000000000000000000351771474353110700202760ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Afrikaans\n" "Language: af_ZA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: af\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/ar.po000066400000000000000000000370131474353110700203010ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Arabic\n" "Language: ar_SA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ar\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "قنوات" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "الإعدادات" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "حول Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "عام" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "إظهار الاختصارات" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "تبديل ملء الشاشة" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "نقرة مزدوجة" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "خروج" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "الوسائط" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "المحتوى التالي" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "المحتوى السابق" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "المشغل" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "بَدْءّ / إيقاف" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "التقدم للأمام" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "الرجوع للوراء" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "رفع مستوى الصوت" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "خفض مستوى الصوت" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "كتم الصوت" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "الفصل التالي" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "الفصل السابق" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "العنوان" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "الصوت" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "التَّرْجَمَةً" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "الوضع" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "القيمة" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "الوحدة" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "ثواني" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "دقائق" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "النسبة المئوية" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "الخادم" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "التحكم بالوسائط عن بعد" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "الخط الافتراضي" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "تعديلات" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "أعدادات الإضافات" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "تغيير الأعدادات الافتراضية للأضافات GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "أدخل أو الصق URI هنا" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "إلغاء" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Yousef Fawaz" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "دَقيق" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "عادي" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "سريع" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/ast.po000066400000000000000000000354611474353110700204730ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Asturian\n" "Language: ast_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ast\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferencies" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Tocante a Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Xenerales" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Amosar los atayos" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Colar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Títulu" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Sotítulos" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Segundu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minutu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Porcentaxe" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Sirvidor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Encaboxar" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Adolfo Jayme Barrientos , 2022" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/ca.po000066400000000000000000000414571474353110700202710ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Catalan\n" "Language: ca_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ca\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "Còdec" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Canals" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Format d'exemple" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Freqüència de mostreig" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Taxa de bits" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Idioma" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferències" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Dreceres del teclat" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Quant al Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "General" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Obre les preferències" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Mostra les dreceres" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Commuta la pantalla completa" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Prem dues vegades | Doble clic" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Surt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Mèdia" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Afegeix fitxers…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Afegeix un URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Cua" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Element següent" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Element anterior" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Canvia el mode de progressió" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Reproducció" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Commuta la reproducció" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Toc | Clic esquerre" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Avança" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Doble toc (costat dret) | Desplaçar-se a la dreta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Doble toc (costat esquerre) | Desplaçar-se a l'esquerra" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Retrocedeix" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Apuja el volum" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Roda del ratolí amunt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Baixa el volum" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Roda del ratolí avall" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Commuta el silenci" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Accelera" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Alenteix" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Capítol següent" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Capítol anterior" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Informació" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Títol" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Format del contenidor" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Duració" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Fluxos de dades" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Àudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Subtítols" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Reproducció de vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Descodificador" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtre" #: 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 "Receptor" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Reproducció d'àudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Fluxos de vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Fluxos d'àudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Fluxos de subtítols" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Comença per afegir multimèdia a la cua de reproducció" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Desplaçament" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Mètode" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "El mètode preferit pel desplaçament" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Valor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Valor usat per desplaçar-se endavant i enrere" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unitat" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "La unitat de mesura pel desplaçament endavant i enrere" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Segon" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minut" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Percentatge" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Característiques" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Servidor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Controla el reproductor remotament" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Decalatge" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Decalatge en segons per la sincronia entre el vídeo i l'àudio" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Decalatge en segons per la sincronia entre els subtítols i el vídeo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Tipus de lletra per defecte" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Família tipogràfica dels subtítols quan cap s'especifica" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Ajustaments" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Classificació dels connectors" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Altera els rangs predeterminats dels connectors del GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Classificació dels connectors" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Connectors disponibles" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Selecciona un connector i llur característica per sobreseure'n el rang. Quan múltiples elements tenen funcionalitats semblants, es prefereix el que té el valor més alt." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Connector" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Característica del connector" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Afegeix un sobreseïment" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Sobreseïments de rangs" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Afegeix" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Progressió de la cua" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Afegeix un URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Insereix un URI a afegir a la cua de reproducció" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Escriu o deixa anar una URI aquí" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Cancel·la" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Resolució" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Taxa de fotogrames" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Format del píxel" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Ícar Nin Solana , 2021" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Maquinari" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Programari" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Acurat" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Ràpid" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Sense progressió" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Consecutiu" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Repeteix l'ítem" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Carrusel" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Aleatori" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Deixa anar a sobre la barra de títol per reproduir-ho ara o a qualsevol altre lloc per posar-ho en cua." clapper-0.8.0/src/bin/clapper-app/po/clapper-app.pot000066400000000000000000000352411474353110700222700ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the clapper-app package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: clapper-app\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-11 15:22+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "" "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "" "Select a plugin and its feature to override rank for. When multiple elements " "have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/cs.po000066400000000000000000000413211474353110700203010ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Czech\n" "Language: cs_CZ\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: cs\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "Kodek" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanály" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Vzorkovací Formát" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Vzorkovací Frekvence" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Datový Tok" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Jazyk" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Předvolby" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Klávesové Zkratky" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "O aplikaci Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Obecné" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Otevřít předvolby" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Zobrazit klávesové zkratky" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Celá obrazovka" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Dvojitým poklepáním | Dvojitým kliknutím" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Ukončit" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Média" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Přidat Soubory…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Přidat URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Fronta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Další položka" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Předchozí položka" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Změnit způsob postupování" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Přehrávání" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Přepnout přehrávání" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Klepnutím | Levým tlačítkem" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Přetočit vpřed" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Dvojitě klepnout (pravá strana) | Posun vpravo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Dvojitě klepnout (levá strana) | Posun vlevo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Přetočit zpět" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Zesílit" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Posun nahoru" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Zeslabit" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Posun dolů" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Vypnout/zapnout zvuk" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Zrychlit" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Zpomalit" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Další kapitola" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Předchozí kapitola" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Informace" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Název" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Formát Kontejneru" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Doba Trvání" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Kanály" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Video" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Zvuk" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Titulky" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Přehrávání Videa" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Dekodér" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtr" #: 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" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Přehrávání Audia" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Video Kanály" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Audio Kanály" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Titulkové Kanály" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Začněte přidáním média do přehrávací fronty" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Přetáčení" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Způsob" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Upřednostňovaný způsob použitý pro přetáčení" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Hodnota" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Hodnota použitá pro přetáčení dopředu/dozadu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Jednotka" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Jednotka hodnoty pro přetáčení dopředu/dozadu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekundy" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuty" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Procenta" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Funkce" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Server" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Vzdáleně ovládat přehrávač" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Posun" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Synchronizační posun v sekundách mezi audio a video kanály" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Synchronizační posun v sekundách mezi titulkovými a video kanály" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Výchozí styl písma" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Písmo používané pro titulky, když jej médium výslovně neupřesní" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Vylepšení" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Pořadí pluginů" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Změna výchozího pořadí zásuvných modulů GStreameru" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Pořadí Pluginů" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Dostupné pluginy" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Vyberte plugin a jeho funkci, pro kterou chcete přepsat pořadí. Pokud má více prvků podobnou schopnost, je preferována ta s nejvyšší hodnotou." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Funkce pluginy" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Přidat přepsání" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Pořadí přepsání" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Přidat" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Postup fronty" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Přidat URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Vložte URI, která bude přidána do fronty přehrávání" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Zde zadejte nebo přetáhněte URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Zrušit" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Rozlišení" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Snímky za sekundu" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Formát Pixelu" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Vojtěch Perník " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Hardware" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Software" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Přesný" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normální" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rychlý" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Žádný postup" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Po sobě" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Opakovat položku" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Opakovat frontu" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Náhodně" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Přetáhněte na titulní lištu pro okamžité přehrání nebo kdekoliv jinde pro zařazení." clapper-0.8.0/src/bin/clapper-app/po/da.po000066400000000000000000000351741474353110700202710ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Danish\n" "Language: da_DK\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: da\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/de.po000066400000000000000000000431551474353110700202730ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: German\n" "Language: de_DE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: de\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Multimedia Wiedergabe" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Neues Fenster" #: 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 msgid "Codec" msgstr "Codec" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Audio-Kanäle" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Abtastformat" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Abtastrate" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Bitrate" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Sprache" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Einstellungen" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Tastaturkürzel" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Über Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Allgemein" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Neues Fenster" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Einstellungen öffnen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Tastenkombinationen zeigen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "In Vollbildmodus wechseln" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Doppelt tippen bzw. klicken" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "Vollbildmodus deaktivieren" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "Automatische Größenanpassung des Fensters" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Mittlere Maustaste" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Clapper beenden" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Medien" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Dateien hinzufügen…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "URI hinzufügen…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Warteschlange" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Nächster Titel" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Vorheriger Titel" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Wiedergabemodus wechseln" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Wiedergabe" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Wiedergabe umschalten" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Tippen | Linke Maustaste" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Vorwärts springen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Doppelt auf die rechte Seite tippen | Nach rechts scrollen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Doppelt auf die linke Seite tippen | Nach links scrollen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Rückwärts springen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Lautstärke erhöhen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Hochscrollen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Lautstärke verringern" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Runterscrollen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Stummschaltung umschalten" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Geschwindigkeit erhöhen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Geschwindigkeit verringern" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Nächstes Kapitel" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Vorheriges Kapitel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Informationen" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Titel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Containerformat" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Länge" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Spuren" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Video" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Untertitel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Videowiedergabe" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Dekodierer" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filter" #: 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 "Ausgabeziel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Audiowiedergabe" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Videospuren" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Audiospuren" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Untertitelspuren" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Beginnen sie damit, Medien zur Wiedergabewarteschlange hinzuzufügen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Springen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Verhalten" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Das bevorzugte Sprungverhalten" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Wert" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Um diesen Wert wird vor-/rückwärts gesprungen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Einheit" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Die Einheit für den Sprungwert" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekunden" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuten" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Prozentsatz" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Erweiterungen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Server" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Clapper fernsteuern" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Versetzung" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Versatz zwischen der Audio- und Videospur (in Sekunden)" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" 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" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Schriftart, welche für Untertitel verwendet wird, falls das Medium selbst keine vorgibt" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Optimierungen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Plugin-Reihenfolge" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Standard GStreamer Plugin Reihenfolge verändern" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Pluginreihenfolge" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Verfügbare Plugins" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Wähle ein Plugin und seine Funktion für welches sie die Position der Reihenfolge überschreiben möchten. Falls mehrere Plugins ähnliche Fähigkeiten besitzen, wird dasjenige mit dem höchsten Rang bevorzugt." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Pluginfunktion" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Hinzufügen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Funktionsrangliste" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Hinzufügen" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Warteschlangenwiedergabe" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Warteschlange leeren" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "URI hinzufügen" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Geben sie eine URI an, welche zur Wiedergabewarteschlange hinzugefügt werden soll" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "URI hier einfügen" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Abbrechen" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Auflösung" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Bildwiederholrate" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Pixelformat" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Florian \"sp1rit\" " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Erstelle ein neues Fenster" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "Füge ein Medium zur Warteschlange der primären Anwendungsinstanz hinzu" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "Wiedergabelautstärke ändern (zwischen 0 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "Wiedergabelautstärke ändern (zwischen 0.05 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" msgstr "Initiale Bitrate für adaptives Streaming" #: 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 "Initialer Warteschlangenfortschrittsmodus (0=keiner, 1=fortlaufend, 2=element wiederholen, 3=karussell, 4=zufällig)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "In den Vollbildmodus wechseln" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "Zu verwendender Videofilter (\"none\" für keinen)" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "Zu verwendender Audiofilter (\"none\" für keinen)" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "Zu verwendender Videoabfuss" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "Zu verwendender Audioabfuss" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Hardware" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Software" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Genau" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Schnell" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Kein Fortschritt" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Fortlaufend" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Wiederholen" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Kreislauf" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Zufallswiedergabe" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Zur direkten Wiedergabe legen Sie die Datei auf der Titelleiste ab, andernfalls wird die Datei der Warteschlange hinzugefügt." clapper-0.8.0/src/bin/clapper-app/po/el.po000066400000000000000000000351731474353110700203040ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Greek\n" "Language: el_GR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: el\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/es.po000066400000000000000000000432471474353110700203140ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Spanish\n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: es-ES\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Reproductor Multimedia" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Nueva Ventana" #: 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 msgid "Codec" msgstr "Códec" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Canales" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Formato de muestreo" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Frecuencia de muestreo" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Tasa de bits" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Idioma" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferencias" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Atajos de teclado" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Acerca de Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "General" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Nueva ventana" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Abrir preferencias" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Mostrar atajos" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Alternar pantalla completa" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Doble toque | Doble clic " #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "Salir del modo pantalla completa" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "Redimensionar ventana automáticamente" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Clic central" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Salir" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Medios" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Agregar archivos…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Agregar enlace URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Cola" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Elemento siguiente" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Elemento anterior" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Cambiar modo de progresión" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Reproducción" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Alternar reproducción" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Un toque | Clic izquierdo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Avanzar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Dos toques (lado derecho) | Derecha" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Dos toques (lado izquierdo) | Izquierda" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Retroceder" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Subir volumen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Arriba" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Bajar volumen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Abajo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Alternar silencio" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Acelerar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Ralentizar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Capítulo siguiente" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Capítulo anterior" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Información" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Título" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Formato de contenedor" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Duración" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Transmisiones" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Subtítulos" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Reproducción de video" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Decodificador" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtro" #: 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 "Salida" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Reproducción de audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Transmisiones de vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Transmisiones de audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Transmisiones de subtítulos" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Comienza por agregar medios a la cola de reproducción" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Navegación" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Método" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Método preferido para desplazarse por la línea de tiempo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Valor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Valor usado para navegar hacia adelante/atrás" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unidad" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Unidad del valor usado al avanzar/retroceder en la navegación" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Segundo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuto" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Porcentaje" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Características" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Servidor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Controlar el reproductor de forma remota" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Compensación" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Desfase de sincronización en segundos entre las secuencias de audio y video" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Desfase de sincronización en segundos entre las secuencias de subtítulos y video" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Fuente predeterminada" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Fuente de texto utilizada para los subtítulos cuando el medio no especifica una explícitamente" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Retoques" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Prioridades de complementos" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Altera las prioridades predeterminadas de los complementos de GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Prioridades de complementos" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Complementos disponibles" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Selecciona un complemento y su característica para sobrescribir su prioridad. Cuando varios elementos tienen capacidades similares, se prefiere el que tenga el valor más alto." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Complemento" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Característica del complemento" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Agregar excepción" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Excepciones de prioridades" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Agregar" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Progresión de la cola" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Vaciar Cola" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Agregar enlace URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Inserta un enlace URI para agregarlo a la cola de reproducción" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Introduce o arrastra aquí el enlace URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Cancelar" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Resolución" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Fotogramas por segundo" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Formato de píxel" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Mihai Pantazi\n" "carlosgonz" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Crear una nueva ventana" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "Agregar medios a la cola en la instancia principal de la aplicación" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "Volumen de audio a configurar (rango 0 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "Velocidad de reproducción a establecer (rango 0.05 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" msgstr "Tasa de bits inicial para la transmisión adaptativa" #: 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 "Modo inicial de progresión de la cola (0=ninguno, 1=consecutivo, 2=repetir-elemento, 3=carrusel, 4=aleatorio)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "Establecer la ventana a pantalla completa" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "Filtro de vídeo a utilizar (\"none\" para desactivar)" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "Filtro de audio a utilizar (\"none\" para desactivar)" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "Salida de video a usar" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "Salida de audio a usar" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Hardware" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Software" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Preciso" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rápido" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Sin progresión" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Consecutivo" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Repetir elemento" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Carrusel" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Aleatorio" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Suéltalo sobre la barra de título para reproducir ahora o en cualquier otro lugar para agregar a la cola." clapper-0.8.0/src/bin/clapper-app/po/eu.po000066400000000000000000000363371474353110700203200ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Basque\n" "Language: eu_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: eu\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanalak" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Hobespenak" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Clapperi buruz" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Orokorra" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Erakutsi lasterbideak" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Aktibatu pantaila osoa" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Ukitu bikoitza / Bikoitza klik" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Irten" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Media" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Hurrengo elementua" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Aurreko elementua" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Erreprodukzioa" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Erreprodukzioa aktibatu" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Aurrerapena bilatu" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Atzerantz bilatu" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Bolumena igo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Bolumena jeitsi" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Aktibatu isiltasuna" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Hurrengo kapitulua" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Aurreko kapitulua" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Titulua" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audioa" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Azpitituluak" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Bilaketa" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Balioa" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unitatea" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Segundu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minutu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Ehunekoa" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Zerbitzaria" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Urrutiko erreproduzitzailea kontrolatzea" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Hizki lehenetsia" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Ukituak" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Pluginen sailkapena" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Aldatu GStreamer-en pluginen lehenetsitako mailak" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Sartu edo utzi URIa erortzen hemen" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Ezeztatu" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Sergio Varela (@IngrownMink4)" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Zehatza" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normala" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Azkarra" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/fa.po000066400000000000000000000453671474353110700203000ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Persian\n" "Language: fa_IR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: fa\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "تخته‌نشان" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "پخش کنندهٔ چندرسانه‌ای" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;ویدیو;فیلم;کلیپ;سریتال;پخش;تلویزیون;آلبوم;آهنگ;گنوم;کلاکت;کلپر;" #: 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 "پنجرهٔ جدید" #: 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 msgid "Codec" msgstr "رمزینه" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "کانال‌ها" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "قالب نمونه" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "نرخ نمونه‌" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "نرخ بیت" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "زبان" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "ترجیحات" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "میان‌برهای صفحه‌کلید" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "دربارهٔ تخته‌نشان" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "عمومی" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "پنجرهٔ جدید" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "گشودن ترجیحات" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "نمایش میان‌برها" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "تغییر حالت تمام‌صفحه" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "دو بار ضربه | دو بار کلیک" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "ناتمام‌صفحه" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "تغییر اندازهٔ خودکار پنجره" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "کلیک وسط" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "خروج" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "رسانه" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "افزودن پرونده‌ها…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "افزودن نشانی…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "صف" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "مورد بعدی" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "مورد قبل" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "تغییر خالت پیشرفت" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "بازپخش" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "تغییر پخش" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "ضربه |‌ کلیک راست" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "پیمایش به جلو" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "دوبار ضربه (سمت راست) |‌ لغزش راست" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "دوبار ضربه (سمت چپ) |‌ لغزش چپ" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "پیمایش به عقب" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "افزایش صدا" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "لغزش به بالا" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "کاهش صدا" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "لغزش به پایین" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "تغییر وضعیت بیصدا" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "سرعت بخشیدن" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "کاهش سرعت" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "فصل بعدی" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "بخش قبلی" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "اطّلاعات" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "عنوان" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "قالب بارگنج" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "مدّت" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "جریان‌ها" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "ویدیو" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "صدا" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "زیرنویس‌ها" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "پخش ویدیو" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "رمزگشا" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "سینک" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "پخش صدا" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "جریان‌های ویدیویی" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "جریان‌های صوتی" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "جریان‌های زیرنویس" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "آغزا با افزودن رسانه به صف پخش" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "پوییدن" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "روش" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "روشی ترجیحی برای جویش" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "مقدار" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "یکای استفاده شده برای جویش به پیش و پ" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "واحد" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "یکای مقدار جویش به پیش و پس" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "ثانیه" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "دقیقه" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "درصد" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "ویژگی‌ها" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "کارساز" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "واپایش پخش‌کننده از راه دور" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "انحراف از مبدأ" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "همگام‌سازی انحراف از مبدأ به ثانیه بین جریان‌های ویدیو و صدا" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "همگام‌سازی انحراف از مبدأ به ثانیه بین جریان‌های ویدیو و زیرنویس" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "قلم پیش‌گزیده" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "قلم متن استفاده شده برای زیرنویس هنگامی که رسانه قلمی خاص را مشخّص نکرده" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "سیخونک‌ها" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "رتبه دهی افزایه" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "تغییر رتبه دهی پیش‌گزیده برای افزایه‌های جی‌استریمر" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "رتبه دهی افزایه" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "افزایه‌های موجود" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "گزینش افزایه و ویژگیش برای پایمالی رتبه دهیش. هنگامی که چندین عنصر قابلیت‌ّای مشابهی دارند، آنی که بیش‌ترین رتبه را دارد ترجیح داده می‌شود." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "افزایه" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "ویژگی افزایه" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "افزودن پایمالی" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "پایمالی‌های رتبه دهی" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "افزودن" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "پیشرفت صف" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "پاک‌سازی صف" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "افزودن نشانی" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "درج نشانی برای افزودن به صف پخش" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "ورود یا انداختن ناشنی" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "لغو" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "تفکیک‌پذیری" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "نرخ قاب" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "قالب پیکسلی" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "سجاد موسوی نژاد \n" "دانیال بهزادی " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "ایجاد پنجره‌ای جدید" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "افزودن رسانه به صف نمونهٔ اصلی برنامه" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "حجم صدای تنظیمی (بازهٔ 0 تا 2.0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "سرعت پخش تنظیمی (بازهٔ 0.05 تا 2.0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "حالت نخستین پیشرفت صف (0=هیچ، 1=متوالی، 2=تکرار مورد، 3=چرخ‌فلک، 4=بر زدن)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "تنظیم پنجره به تمام‌صفحه" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "پالایهٔ ویدیویی برای استفاده (none برای از کار انداختن)" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "پالایهٔ صوتی برای استفاده (none برای از کار انداختن)" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "دستگاه ویدیویی برای استفاده" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "دستگاه صوتی برای استفاده" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "سخت‌افزاری" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "نرم‌افزاری" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "دقیق" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "معمولی" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "تند" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "بدون پیشرفت" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "متوالی" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "تکرار مورد" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "چرخ فلک" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "بر زدن" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "انداختن روی نوار عنوان برای پخش اکنون یا هر جای دیگری برای صف کردن." clapper-0.8.0/src/bin/clapper-app/po/fi.po000066400000000000000000000413261474353110700202770ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Finnish\n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: fi\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Multimediasoitin" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Uusi ikkuna" #: 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 msgid "Codec" msgstr "Koodekki" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "kanavaa" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Näytemuoto" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Näytteenottotaajuus" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Bittinopeus" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Kieli" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Asetukset" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Pikanäppäimet" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Tietoja - Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Yleiset" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Uusi ikkuna" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Avaa asetukset" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Näytä pikanäppäimet" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Koko näyttö päällä/pois" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Kaksoisnapautus | Kaksoisnapsautus" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Hiiren keskinappi" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Lopeta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Media" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Lisää tiedostoja…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Lisää URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Jono" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Seuraava kohde" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Edellinen kohde" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Muuta etenemistilaa" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Toisto" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Aloita tai pysäytä toisto" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Napauta | Vasen napsautus" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Siirry eteenpäin" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Kaksoisnapautus (oikea puoli) | Vieritä oikealle" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Kaksoisnapautus (vasen puoli) | Vieritä vasemmalle" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Siirry taaksepäin" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Lisää äänenvoimakkuutta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Vieritä ylös" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Vähennä äänenvoimakkuutta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Vieritä alas" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Mykistä tai palauta ääni" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Nopeuta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Hidasta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Seuraava kappale" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Edellinen kappale" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Tiedot" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Nimi" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Säilön muoto" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Kesto" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Virrat" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Video" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Ääni" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Tekstitykset" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Videotoisto" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Dekooderi" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Suodatin" #: 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Äänitoisto" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Videovirrat" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Äänivirrat" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Tekstitysvirrat" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Aloita lisäämällä mediaa toistojonoon" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Siirtyminen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Menetelmä" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Kelaamiseen käytettävä ensisijainen menetelmä" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Arvo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Taakse- ja eteenpäin kelaamiseen käytettävä arvo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Yksikkö" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Taakse- ja eteenpäin kelaamiseen käytettävä yksikkö" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekunti" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuutti" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Prosentti" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Ominaisuudet" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Palvelin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Hallitse videosoitinta etänä" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Siirtymä" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Synkronoinnin siirtymä sekunteina äänen ja videon välillä" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Synkronoinnin siirtymä sekunteina teksityksen ja videon välillä" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Oletusfontti" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Tekstityksiin käytettävä fontti, kun media ei erikseen määritä tiettyä" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Lisäasetukset" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Liitännäisten sijoitus" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Muuta GStreamer-liitännäisten sijoitusten oletusarvoja" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Liitännäisen sijoitus" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Saatavilla olevat liitännäiset" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Liitännäinen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Liitännäisen ominaisuus" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Lisää ylitys" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Sijoitusylitykset" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Lisää" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Jonon eteneminen" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Tyhjennä jono" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Lisää URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Lisää URI toistettavien jonoon" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Syötä tai pudota URI tähän" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Peru" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Resoluutio" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Kuvataajuus" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Pikselimuoto" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Jiri Grönroos" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Luo uusi ikkuna" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "Aseta ikkuna koko näytön tilaan" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Laitteisto" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Ohjelmisto" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Tarkka" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normaali" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Nopea" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Ei etenemistä" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Perättäinen" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Toista kohde" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Karuselli" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Sekoita" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Pudota otsikkopalkkiin toistaaksesi nyt tai mihin tahansa asettaaksesi jonoon." clapper-0.8.0/src/bin/clapper-app/po/fr.po000066400000000000000000000433131474353110700203060ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: fr\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Lecteur multimédia" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Nouvelle fenêtre" #: 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 msgid "Codec" msgstr "Codec" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Canaux" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Format d’échantillonnage" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Fréquence d’échantillonnage" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Taux d’échantillonnage" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Langue" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Préférences" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Raccourcis clavier" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "À propos de Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Général" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Nouvelle fenêtre" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Ouvrir les préférences" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Afficher les raccourcis" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Basculer en plein écran" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Tapoter | cliquer deux fois" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "Quitter le plein écran" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "Redimensionner automatiquement la fenêtre" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Clic du milieu" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Quitter" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Média" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Ajouter des fichiers…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Ajouter un URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "File d’attente" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Prochain média" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Média précédent" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Changer le mode de progression" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Lecture" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Relancer/stopper la lecture" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Taper | Clic gauche" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Avancer dans la lecture" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Tapoter (côté droit) | scroller vers la droite" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Tapoter (côté gauche) | scroller vers la gauche" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Reculer dans la lecture" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Augmenter le volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Scroller vers le haut" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Baisser le volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Scroller vers le bas" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Basculer le mode silencieux" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Accélérer" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Ralentir" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Prochain chapitre" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Chapitre précédent" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Informations" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Titre" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Format de conteneur" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Durée" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Flux" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Vidéo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Sous-titres" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Lecture vidéo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Décodeur" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtre" #: 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" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Lecture audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Flux vidéo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Flux audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Flux de sous-titres" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Commencez par ajouter des médias à la file d’attente de lecture" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Avancement" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Méthode" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Méthode préférée pour l’avancement" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Longueur du saut" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Valeur utilisée pour l’avancement/recul" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unité des sauts" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Unité d’avancement/recul" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Seconde" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minute" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Pourcentage" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Fonctionnalités" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Serveur" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Controler le lecteur à distance" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Décalage" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Décalage de synchronisation entre les flux audio et vidéo, en secondes" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Décalage de synchronisation entre les flux des sous-titres et vidéo, en secondes" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Police par défaut" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Police de texte des sous-titres, utilisée quand le média n’en spécifie aucune" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Réglages" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Liste des plugins" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Changer les options par défaut de plugins GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Classement des greffons" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Greffons disponibles" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Sélectionnez un greffon et ses fonctionnalités à substituer. Lorsque plusieurs éléments ont des fonctionnalités similaires, celui qui a la valeur la plus élevée sera privilégié." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Greffon" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Fonctionnalité du greffon" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Ajouter une substitution" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Classement des substituts" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Ajouter" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Progression de la file d’attente" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Effacer la file d’attente" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Ajouter un URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Insérer un URI à ajouter à la file d’attente de lecture" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Entrer ou déposer un URI ici" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Annuler" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Résolution" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Fréquence d’images" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Format de pixel" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Robin Verdenal-Tallieux\n" "Irénée Thirion" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Créer une nouvelle fenêtre" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "Ajouter un média en file d’attente dans l’instance primaire de l’application" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "Volume audio à définir (de 0 à 2,0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "Vitesse de lecture à définir (de 0,05 à 2,0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" msgstr "Débit initial pour le streaming adaptatif" #: 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 "Mode initial de progression de la file d’attente (0=aucun, 1=en-ordre, 2=répéter-élément, 3=carousel, 4=aléatoire)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "Mettre la fenêtre en plein écran" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "Filtre vidéo à utiliser (\"none\" pour désactiver)" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "Filtre audio à utiliser (\"none\" pour désactiver)" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "Sortie vidéo à utiliser" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "Sortie audio à utiliser" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Matériel" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Logiciel" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Précis" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rapide" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Aucune progression" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Chronologique" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Répéter l’élement" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Carrousel" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Aléatoire" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Déposez sur la barre de titre pour lire maintenant, ou ailleurs pour ajouter à la file d’attente." clapper-0.8.0/src/bin/clapper-app/po/he.po000066400000000000000000000367021474353110700202770ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Hebrew\n" "Language: he_IL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: he\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "ערוצים" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "העדפות" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "על אודות Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "כללי" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "הצגת צירופי מקשים" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "שינוי מצב מסך מלא" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "נגיעה כפולה | לחיצה כפולה" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "יציאה" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "מדיה" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "פריט הבא" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "פריט קודם" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "השמעה" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "הפעלה/השהיה" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "דילוג קדימה" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "דילוג לאחור" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "הגברת עצמת השמע" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "הנמכת עצמת השמע" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "השתקה/ביטול השתקה" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "פרק הבא" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "פרק קודם" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "כותרת" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "שמע" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "כתוביות" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "דילוג" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "ערך" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "יחידה" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "שניה" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "דקה" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "אחוז" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "שרת" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "שליטה בנגן מרחוק" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "גופן ברירת מחדל" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "התאמות" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "דרגות תוספים" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "שינוי דירוג ברירת המחדל של תוספי GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "יש להזין או להשליך כתובת לכאן" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "ביטול" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "יוסף אור בוצ׳קו " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "מדויק" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "רגילה" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "מהיר" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/hi.po000066400000000000000000000457211474353110700203040ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Hindi\n" "Language: hi_IN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: hi\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "कोडेक" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "चैनल" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "सैंपल प्रारूप" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "सैंपल दर" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "बिटरेट" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "भाषा" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "प्राथमिकताएं" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "कीबोर्ड शॉर्टकट" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "क्लैपर के बारे में" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "सामान्य" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "प्राथमिकताएं खोलें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "शॉर्टकट दिखाएं" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "पूर्णस्क्रीन टॉगल करें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "डबल टैप | डबल क्लिक" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "बंद करें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "मीडिया" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "फाइलें जोड़ें…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "यूआरआई जोड़ें…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "कतार" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "अगला मद" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "पिछला मद" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "प्रगति मोड बदलें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "प्लेबैक" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "चजाना टॉगल करें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "टैप | बायां क्लिक" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "आगे तलाशें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "डबल टैप (दायीं ओर) | दाएं स्क्रॉल करें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "डबल टैप (बायीं ओर) | बाएं स्क्रॉल करें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "पीछे तलाशें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "आवाज बढ़ाएं" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "ऊपर स्क्रॉल" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "आवाज घटाएं" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "नीचे स्क्रॉल" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "मूक टॉगल करें" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "गति बढ़ाएं" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "गति घटाएं" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "अगला अध्याय" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "पिछला अध्याय" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "जानकारी" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "शीर्षक" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "कंटेनर प्रारूप" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "अवधि" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "स्ट्रीम" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "वीडियो" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "ऑडियो" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "उपशीर्षक" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "वीडियो प्लेबैक" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "डिकोडर" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "सिंक" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "ऑडियो प्लेबैक" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "वीडियो स्ट्रीम" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "ऑडियो स्ट्रीम" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "उपशीर्षक स्ट्रीम" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "प्लेबैक कतार में मीडिया जोड़कर प्रारंभ करें" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "तलाश" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "विधि" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "तलाशने की वरीय विधि" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "मान" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "आगे/पीछे तलाशने हेतु मान" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "इकाई" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "आगे/पीछे तलाशने हेतु मान की इकाई" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "सेकंड" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "मिनट" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "प्रतिशत" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "विशेषताएं" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "सर्वर" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "प्लेयर को रिमोट से नियंत्रित करें" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "ऑफसेट" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "ऑडियो और वीडियो स्ट्रीम के बीच सेकंड में समन्वयन ऑफसेट" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "उपशीर्षक और वीडियो स्ट्रीम के बीच सेकंड में समन्वयन ऑफसेट" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "तयशुदा फॉन्ट" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "उपशीर्षकों हेतु प्रयुक्त टेक्स्ट फॉन्ट जब मीडिया स्पष्ट रूप से निर्दिष्ट नहीं करता है" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "ट्विक्स" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "प्लगइन रैंकिंग" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "GStreamer प्लगइन्स की तयशुदा रैंक बदलें" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "प्लगइन रैंकिंग" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "उपलब्ध प्लगइन" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "रैंक को ओवरराइड करने के लिए प्लगइन और उसकी खासियत चुनें। जब कई तत्वों में समान क्षमताएं होती हैं, तो सबसे अधिक मान वाले को प्राथमिकता दी जाती है।" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "प्लगइन" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "प्लगइन सुविधा" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "ओवरराइड जोड़ें" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "रैंक ओवरराइड" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "जोड़ें" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "कतार प्रगति" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "यूआरआई जोड़ें" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "प्लेबैक कतार में जोड़ने के लिए यूआरआई डालें" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "यहां यूआरआई दर्ज करें या छोड़ें" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "रद्द करें" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "रिजोल्यूशन" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "फ्रेमरेट" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "पिक्सल प्रारूप" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Scrambled777 " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "हार्डवेयर" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "सॉफ्टवेयर" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "सटीक" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "साधारण" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "तेज" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "प्रगति नहीं" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "लगातार" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "मद दोहराएं" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "कैरोसेल" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "शफल" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "अभी चलाने के लिए शीर्षक पट्टी पर छोड़ें या कतार में लगाने के लिए कहीं और छोड़ें।" clapper-0.8.0/src/bin/clapper-app/po/hr.po000066400000000000000000000364621474353110700203170ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Croatian\n" "Language: hr_HR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: hr\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanali" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Postavke" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Clapper informacije" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Opće" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Prikaži prečace" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Uključi/isključi cjeloekranski prikaz" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Dodirni dvaput | Pritisni tipku miša dvaput" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Zatvori aplikaciju" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Mediji" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Sljedeće" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Prethodno" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Reprodukcija" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Uključi/Isključi reprodukciju" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Traži prema naprijed" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Traži prema natrag" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Glasnije" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Tiše" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Uključi/isključi zvuk" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Sljedeće poglavlje" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Prethodno poglavlje" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Naslov" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Titlovi" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Pretraživanje" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Vrijednost" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Jedinica" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekunda" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuta" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Postotak" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Poslužitelj" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Daljinsko upravljanje playerom" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Zadani font" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Podešavanja" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Ocjenjivanje dodataka" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Promijeni zadane ocjene GStreamer dodataka" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Ovdje upiši ili ispusti URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Odustani" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Milo Ivir " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Točno" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normalna" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Brzo" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/hu.po000066400000000000000000000416421474353110700203160ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Hungarian\n" "Language: hu_HU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: hu\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "Kodek" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Csatornák" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Minta formátum" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Mintavételezési ráta" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Bitráta" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Nyelv" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Beállítások" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Gyorsbillentyűk" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "A Clapper -ről" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Általános beállítások" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Beállítások megnyitása" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Gyorsbillentyűk megjelenítése" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Teljes képernyő be/ki" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Dupla koppintás | Dupla kattintás" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Kilépés" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Média" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Fájlok hozzáadása…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "URI hozzáadása…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Lejátszólista" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Következő listaelem" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Előző listaelem" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Haladási mód megváltoztatása" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Lejátszás" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Lejátszás indítása/megállítása" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Koppintás | Bal kattintás" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Előretekerés" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Dupla koppintás (a jobb oldalon) | Görgetés jobbra" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Dupla koppintás (a bal oldalon) | Görgetés balra" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Visszatekerés" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Hangerő növelése" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Görgetés felfelé" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Hangerő csökkentése" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Görgetés lefelé" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Némítás be-/kikapcsolása" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Gyorsítás" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Lassítás" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Következő jelenet" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Előző jelenet" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Információ" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Cím" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Tároló formátum" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Időtartam" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Adatfolyamok" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Kép" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Hang" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Feliratok" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Videólejátszás" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Dekóder" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Szűrő" #: 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 "Nyelő" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Hanglejátszás" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Videó adatfolyam" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Hang adatfolyam" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Felirat adatfolyam" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Induljon a média lejátszólistára vételével" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Tekerés" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Mód" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "A tekerés előnyben részesített módja" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Érték" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Az előre/hátra tekeréshez használt érték" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Mértékegység" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Az előre/hátra tekeréshez használt mértékegység" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Másodperc" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Perc" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Százalék" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Funkciók" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Szerver" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Lejátszó távirányítása" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Eltolás" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "A hang és kép adatfolyamok közötti szinkronizáció eltolása másodpercekben" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "A hang és kép adatfolyamok közötti szinkronizáció eltolása" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Alapértelmezett betűtípus" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "A feliratokoz használ betűtípus, ha a média nem határozza azt meg" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Finomhangolások" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Bővítményrangsor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Az alapértelmezett GStreamer bővítményrangsor megváltoztatása" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Bővítményrangsor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Elérhető bővítmények" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Válasszon ki egy bővítményt és annak funkcióját, a rangsor felülbírálásához. Ha több elemnek is hasonló képességei vannak, akkor a magasabb értékű részesül előnyben." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Bővítmény" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Bővítmény funkció" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Felülbírálás hozzáadása" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Rangsor felülbírálások" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Hozzáadás" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Lejátszólista haladása" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "URI hozzáadása" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Illesszen be egy URI-t a lejátszólistára vételéhez" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Adjon meg egy URI -t itt, vagy húzza és ejtse ide" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Mégsem" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Felbontás" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Képkockasebesség" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Képpont formátum" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Ferenc Géczi" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Hardver" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Szoftver" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Precíz" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normál" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Gyors" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Ne haladjon" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Folyamatos" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Listaelem ismétlése" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Körkörös" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Véletlenszerű" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Húzza és ejtse a címsorra az azonnali lejátszáshoz vagy bárhova máshova a lejátszólistára vételhez." clapper-0.8.0/src/bin/clapper-app/po/it.po000066400000000000000000000363351474353110700203210ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Italian\n" "Language: it_IT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: it\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Canali" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferenze" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Informazioni su Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Generale" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Mostra scorciatoie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Attiva/Disattiva schermo intero" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Doppio tocco | Doppio click" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Esci" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Media" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Elemento successivo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Elemento precedente" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Riproduzione" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Attiva/Disattiva riproduzione" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Scorri avanti" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Scorri indietro" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Aumenta volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Abbassa volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Attiva/Disattiva muto" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Capitolo successivo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Capitolo precedente" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Titolo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Sottotitoli" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Scorrimento" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Valore" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unità" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Secondo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuto" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Percentuale" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Server" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Controlla la riproduzione da remoto" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Carattere predefinito" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Aggiustamenti" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Ordine dei plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Altera l'ordine predefinito dei plugin di GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Inserisci o rilascia URI qui" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Cancella" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Uniformbuffer" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Accurata" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normale" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Veloce" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/ja.po000066400000000000000000000365201474353110700202730ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Japanese\n" "Language: ja_JP\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ja\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "チャンネル" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "設定" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Clapperについて" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "一般" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "ショートカットを表示する" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "フルスクリーン切り替え" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "ダブルタップ | ダブルクリック" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "終了" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "メディア" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "次の項目" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "前の項目" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "プレイバック" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "一時停止・再生" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "早送り" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "巻き戻し" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "音量を上げる" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "音量を下げる" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "ミュート切り替え" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "次のチャプターへ" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "前のチャプターへ\"" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "タイトル" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "オ−ディオ" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "字幕" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "シーキング" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "値" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "ユニット" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "秒" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "分" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "パーセント" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "サーバー" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "プレーヤーをリモートでコントロールする" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "既定のフォント" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "詳細設定" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "プライグインの優先順位" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "GStreamerプラグインの優先順位を変更します" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "ここにURIを入力またはドロップ" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "キャンセル" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "翻訳者" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "精密" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "標準" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "高速" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/ko.po000066400000000000000000000351651474353110700203160ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Korean\n" "Language: ko_KR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ko\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/lt.po000066400000000000000000000356651474353110700203310ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Lithuanian\n" "Language: lt_LT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && (n%100>19 || n%100<11) ? 0 : (n%10>=2 && n%10<=9) && (n%100>19 || n%100<11) ? 1 : n%1!=0 ? 2: 3);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: lt\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Nustatymai" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Apie Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Bendri" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Įjungti visame ekrane" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Baigti" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Medija" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Atkūrimas" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Pagarsinti" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Patildyti" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Pavadinimas" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Titrai" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Reikšmė" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Mat. vienetai" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Numatytasis šriftas" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Atsisakyti" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Mantas Kriaučiūnas " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Numatyta" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/meson.build000066400000000000000000000002051474353110700214720ustar00rootroot00000000000000i18n.gettext(meson.project_name() + '-app', preset: 'glib') devenv.set('CLAPPER_APP_OVERRIDE_LOCALEDIR', meson.current_build_dir()) clapper-0.8.0/src/bin/clapper-app/po/nl.po000066400000000000000000000362661474353110700203210ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Dutch\n" "Language: nl_NL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: nl\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanalen" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Voorkeuren" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Over Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Algemeen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Sneltoetsen tonen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Beeldvullende modus aan/uit" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Dubbeltikken/Dubbelklikken" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Afsluiten" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Media" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Volgend item" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Vorig item" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Afspelen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Afspelen/Pauzeren" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Vooruitspoelen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Terugspoelen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Volume omhoog" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Volume omlaag" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Geluid dempen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Volgend hoofdstuk" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Vorig hoofdstuk" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Titel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Ondertiteling" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Spoelen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Waarde" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Eenheid" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Seconde" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuut" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Percentage" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Server" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Speler op afstand bedienen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Standaardlettertype" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Aanpassingen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Plug-involgorde" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Pas de standaardvolgorde van GStreamer-plug-ins aan" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Voer een uri in of sleep een uri hierheen" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Annuleren" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Heimen Stoffels " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Nauwkeurig" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normaal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Snel" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/no.po000066400000000000000000000410211474353110700203050ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Norwegian\n" "Language: no_NO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: no\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "Codec" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanaler" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Samplingsformat" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Samplingsfrekvens" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Bitrate" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Språk" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Brukervalg" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Tastatursnarveier" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Om Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Generelt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Åpne brukervalg" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Vis tastatursnarveier" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Slå på/av fullskjermsmodus" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Dobbelttrykk | Dobbeltklikk" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Avslutt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Medium" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Legg til filer…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Legg til URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Kø" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Neste element" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Forrige element" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Endre progresjonsmodus" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Avspilling" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Spill av eller pause" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Trykk | Venstreklikk" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Søk fremover" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Dobbelttrykk (på høyre side) | Rull til høyre" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Dobbelttrykk (på venstre side) | Rull til venstre" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Søk bakover" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Øk volym" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Rull opp" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Senk volym" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Rull ned" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Demp/avdemp" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Øk farten" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Senk farten" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Neste kapittel" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Forrige kapittel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Informasjon" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Tittel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Konteinerformat" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Lengde" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Strømmer" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Video" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Lyd" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Undertekstning" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Videoavspilling" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Dekoder" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filter" #: 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 "Vask" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Lydavspilling" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Videostrømmer" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Lydstrømmer" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Undertekstingsstrømmer" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Start ved å legge til medier i avspillingskøen" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Søkning" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Metode" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "En foretrukket metode brukt for å søke" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Verdi" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Verdi brukt for å søke fremover/bakover" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Enhet" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "En enhet av en søkningsverdi" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekund" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minutt" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Prosentdel" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Funksjoner" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Server" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Fjernstyring av spilleren" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Forskyvning" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Synkroniseringsforskyvning i sekunder mellom lyd- og videostrømmene" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Synkroniseringsforskyvning i sekunder mellom untertekst- og videostrømmene" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Standard skrifttype" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Skrifttype som brukes for undertekster når mediet ikke angir en skrifttype" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Justeringer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Tilleggsrangering" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Endre den standarde rangeringen av GStreamer-tillegg" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Tilleggsrangering" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Tilgjengelige tillegg" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Velg et tillegg og funskjonen dets for å overstyre rangen. Når flere elementer har lignende egenskaper, skal det med den høyeste verdien brukes." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Tillegg" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Tilleggsfunksjon" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Legg till overstyring" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Rangoverstyringer" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Legg til" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Køprogresjon" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Legg til URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Tast inn en URI for å legge den til i avspillingskøen" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Tast inn eller slipp URI her" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Avbryt" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Oppløsning" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Bildefrekvens" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Pikselformat" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Sunniva Løvstad " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Maskinvare" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Programvare" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Nøyaktig" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rask" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Ingen progresjon" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Konsekutiv" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Gjenta element" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Gjenta kø" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Omstokking" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Slipp på tittellinjen for å spille av nå, eller hvor som helst annet for å legg til i køen." clapper-0.8.0/src/bin/clapper-app/po/oc.po000066400000000000000000000412531474353110700203010ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Occitan\n" "Language: oc_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: oc\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Frequéncia d'escandalhatge" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Taus d'escandalhatge" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Debit" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Lenga" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferéncias" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Acorchis de clavièr" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "A prepaus de Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Dobrir las preferéncias" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Afichar los acorchis" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Bascular en mòde ecran complèt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Doble tòc | Doble clic" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Quitar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Mèdia" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Apondre de fichièrs…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Apondre una URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Fila d'espèra" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Element seguent" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Element precedent" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Modificar lo mòde de progression" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Lectura" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Bascular la lectura" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Tòc | Clic esquèr" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Avançar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Doble tòc (costat drech) | Defilament drech" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Doble tòc (costat esquèr) | Defilament esquèr" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Recular" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Montar lo son" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Defilar cap amont" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Demesir lo son" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Defilar cap aval" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Activar / Desactivar lo mòde mut" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Accelerar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Alentir" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Capítol seguent" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Capítol precedent" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Títol" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Format del contenedor" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Durada" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Fluxes" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Lectura vidèo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Descodador" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtre" #: 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 "Receptor" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Lectura àudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Fluxes de vidèo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Fluxes d’àudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Fluxes de sostítol" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Començatz per apondre de mèdias a la fila d’espèra" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Recèrca" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Metòde" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Metòde preferit d’utilizar per se desplaçar" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Valor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Valor utilizada per avançar/recular" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unitat" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Una unitat per la valor d’avançament/reculada" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Segonda" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuta" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Percentatge" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Foncionalitats" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Servidor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Contrarotlar lo lectura a distància" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Decalatge" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Descalatge de la sincronizacion en segonda entre lo flux àudio e lo flux vidèo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Descalatge de la sincronizacion en segonda entre lo flux de sostítol e lo flux vidèo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Polissa per defaut" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Polissa utiliza pels sostítols quand lo mèdia n’especifica pas explicitament una." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Ajustaments" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Classament l’empeuton" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Alterar las foncionalitats per defaut dels empeutons GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Nòta de l’empeuton" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Empeutons disponibles" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Seleccionar un empeuton e sas foncionalitats de subrecargar. Quand mai d’un element an de foncionalitats similaras, aquela amb la valor mai nauta es preferida." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Empeuton" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Foncionalitat de l’emeuton" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Apondre una subrecarga" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Classament de las subrecargas" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Apondre" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Progression de la fila" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Apondre una URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Inserir una URI d’apondre a la fila d’espèra" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Picatz o depausatz una URI aicí" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Anullar" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Resolucion" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Cadéncia" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Format de pixèl" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Quentin PAGÈS" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Material" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Logicial" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Precís" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rapid" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Cap de progression" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Consecutiu" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Repetir l’element" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Vira-vira" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Aleatòri" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Depausar per la barra de títol per jogar ara o endacòm mai per metre en espèra." clapper-0.8.0/src/bin/clapper-app/po/pl.po000066400000000000000000000431101474353110700203050ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Polish\n" "Language: pl_PL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: pl\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Odtwarzacz multimedialny" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Nowe okno" #: 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 msgid "Codec" msgstr "Kodek" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanały" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Format próbkowania" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Częstotliwość próbkowania" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Szybkość transmisji bitów" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Język" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferencje" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Skróty klawiszowe" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "O programie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Główne" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Nowe okno" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Otwórz preferencje" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Pokaż skróty" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Przełącz tryb pełnoekranowy" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Podwójne dotknięcie | Podwójne kliknięcie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "Opuść pełny ekran" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "Dostosuj rozmiar okna" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Środkowy przycisk myszy" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Zamknij" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Media" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Dodaj pliki…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Dodaj URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Kolejka odtwarzania" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Następna pozycja" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Poprzednia pozycja" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Zmień tryb progresji" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Odtwarzanie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Przełącz odtwarzanie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Dotknij | Lewy przycisk myszy" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Przewiń do przodu" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Podwójne dotknięcie (po prawej) | Przewiń w prawo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Podwójne dotknięcie (po lewej stronie) | Przewiń w lewo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Przewiń do tyłu" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Zwiększ głośność" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Przewiń w górę" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Zmniejsz głośność" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Przewiń na dół" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Przełącz wyciszenie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Przyspiesz" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Zwolnij" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Następny rozdział" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Poprzedni rozdział" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Info" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Tytuł" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Format pliku kontenera" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Czas trwania" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Strumienie" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Wideo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Napisy" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Odtwarzanie wideo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Dekoder" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtr" #: 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 "Zlew" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Odtwarzanie dźwięku" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Strumienie wideo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Strumienie audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Strumienie napisów" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Zacznij przez dodanie mediów do kolejki odtwarzania" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Przewijanie" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Metoda" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Preferowana metoda przewijania" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Wartość" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Wartość używana do przewijania w przód/tył" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Jednostka" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Jednostka wartości przewijania do przodu/tyłu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekunda" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuta" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Procent" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Funkcje" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Serwer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Zdalne sterowanie odtwarzaczem" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Przesunięcie" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Przesunięcie synchronizacji w sekundach między strumieniami audio i wideo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Przesunięcie synchronizacji w sekundach między napisami i strumieniami wideo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Domyślna czcionka" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Czcionka tekstowa używana dla napisów, gdy media jej nie określają" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Dostrajanie" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Ranking pluginów" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Zmień domyślne rangi pluginów GStreamera" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Ranking pluginów" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Dostępne pluginy" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Wybierz plugin i jego funkcję do nadpisania rangi. Gdy wiele elementów oferuje podobne możliwości, preferowany będzie ten o najwyższej wartości." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Funkcja pluginu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Dodaj nadpisanie" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Nadpisane rangi" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Dodaj" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Progresja kolejki" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Wyczyść kolejkę" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Dodaj URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Wpisz URI, które chcesz dodać do kolejki odtwarzania" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Wprowadź lub upuść URI tutaj" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Anuluj" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Rozdzielczość" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Ilość klatek na sekundę" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Format pikseli" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Rafał Dzięgiel " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Utwórz nowe okno" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "Dodaj media do kolejki w głównej instancji aplikacji" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "Głośność dźwięku do ustawienia (zakres 0 - 2,0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "Prędkość odtwarzania do ustawienia (0,05 - zakres 2,0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" msgstr "Początkowy bitrate dla adaptacyjnego odtwarzania" #: 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 "Początkowy tryb progresji kolejki (0=brak, 1=po kolei, 2=powtarzaj rzecz, 3=karuzela, 4=losowo)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "Ustaw okno jako pełnoekranowe" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "Filtr wideo do użycia (\"none\" aby wyłączyć)" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "Filtr wideo do użycia (\"none\" aby wyłączyć)" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "Zlew wideo do użycia" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "Zlew audio do użycia" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Sprzętowy" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Programowy" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Dokładny" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normalny" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Szybki" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Brak progresji" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Po kolei" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Powtarzaj rzecz" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Karuzela" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Losowo" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Upuść na pasku tytułowym, aby odtworzyć teraz lub gdziekolwiek indziej by skolejkować." clapper-0.8.0/src/bin/clapper-app/po/pt.po000066400000000000000000000433071474353110700203250ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Portuguese\n" "Language: pt_PT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: pt-PT\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Reprodutor multimédia" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Nova Janela" #: 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 msgid "Codec" msgstr "Codificador" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Canais" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Formato de amostra" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Taxa de amostragem" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Taxa de Bits (Bitrate)" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Idioma" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferências" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Teclas de atalho" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Acerca do Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Geral" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Nova janela" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Abrir preferências" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Mostrar atalhos" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Ativar ecrã inteiro" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Toque duplo | Clique duplo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "Sair de ecrã inteiro" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "Redimensionamento automático da janela" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Clique com o botão do meio" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Sair" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Ficheiro" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Adicionar ficheiros…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Add URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Fila de espera" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Item seguinte" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Item anterior" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Alterar modo de progressão" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Reproduzir" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Alternar reprodução" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Tocar | Clique esquerdo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Procurar para a frente" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Toque duplo (lado direito) Deslize direito" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Toque duplo (lado esquerdo) Deslize esquerdo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Procurar para trás" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Aumentar o volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Deslizar para cima" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Diminuir o volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Deslizar para baixo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Ativar/Desativar Som" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Acelerar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Abrandar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Capítulo seguinte" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Capítulo anterior" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Informação" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Título" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Formato do contentor" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Duração" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Transmissões" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Áudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Legendas" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Reprodução de vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Descodificador" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtro" #: 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 "Saída" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Reprodução de áudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Transmissões de vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Transmissões de áudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Transmissões de legenda" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Comece por adicionar multimédia à fila de reprodução" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "A procurar" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Método" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Um método preferido usado para procurar" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Valor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Valor usado para procurar avançar/retroceder" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unidade" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Uma unidade de um valor de procura para avançar/retroceder" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Segundo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuto" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Percentagem" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Funcionalidades" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Servidor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Controlar reprodutor remotamente" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Desvio" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Desvio de sincronização em segundos entre as transmissões de áudio e vídeo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Desvio de sincronização em segundos entre as transmissões de legenda e vídeo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Tipo de letra predefinida" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Tipo de letra usada para legendas quando o conteúdo não especifica explicitamente uma" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Ajustes" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Classificação do plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Altera as classificações predefinidas dos plugins GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Classificação do plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Plugins disponíveis" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Selecionar um plugin e sua funcionalidade para substituir a classificação. Quando vários elementos têm capacidade semelhante, o com valor mais alto é preferido." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Funcionalidade do plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Adicionar substituição" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Substituições de classificação" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Adicionar" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Progresso da fila de espera" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Limpar fila de espera" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Adicionar URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Insira um URI a ser adicionado à fila de reprodução" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Introduzir ou largar o URI aqui" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Cancelar" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Resolução" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Taxa de fotogramas" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Formato de píxel" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Hugo Carvalho " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Cria uma nova janela" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "Adicionar ficheiros à fila de espera na instância da aplicação principal" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "Volume de áudio a definir (intervalo 0 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "Velocidade de reprodução a definir (0.05 - intervalo 2.0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" msgstr "Taxa de bits inicial para streaming adaptável" #: 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 "Modo de progressão inicial da fila de espera (0=nenhum, 1=consecutivo, 2=repetição de itens, 3=carrossel, 4=aleatório)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "Definir janela para ecrã inteiro" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "Filtro de vídeo a utilizar (\"nenhum\" para desativar)" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "Filtro de áudio a utilizar (\"nenhum\" para desativar)" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "A saída de vídeo a utilizar" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "A saída de áudio a utilizar" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Hardware" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Software" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Precisão" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Predefinido" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rápido" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Sem progressão" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Consecutiva" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Repetir item" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Carrossel" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Aleatório" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Largar na barra de título para reproduzir agora ou em qualquer outro lugar para adicionar à fila de espera." clapper-0.8.0/src/bin/clapper-app/po/pt_BR.po000066400000000000000000000432541474353110700207110ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Portuguese, Brazilian\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: pt-BR\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Reprodutor de multimídia" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Nova janela" #: 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 msgid "Codec" msgstr "Codec" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Canais" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Formato de amostra" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Taxa de amostragem" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Taxa de bits (Bitrate)" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Idioma" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Preferências" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Atalhos de teclado" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Sobre o Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Geral" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Nova janela" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Abrir preferências" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Mostrar atalhos" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Ativar/Desativar tela cheia" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Toque duplo | Clique duplo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "Sair da tela cheia" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "Redimensionar janela automaticamente" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Clique com o botão do meio" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Sair" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Mídia" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Adicionar arquivos…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Adicionar URI…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Fila" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Próximo item" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Item anterior" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Mudar modo de progressão" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Reprodução" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Alternar reproduzir/pausar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Toque | Clique esquerdo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Avançar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Toque duplo (lado direito) | Rolar para a direita" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Toque duplo (lado esquerdo) | Rolar para a esquerda" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Retroceder" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Aumentar o volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Rolar para cima" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Diminuir o volume" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Rolar para baixo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Ativar/Desativar som" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Acelerar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Desacelerar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Próximo capítulo" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Capítulo anterior" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Informações" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Título" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Formato de contêiner" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Duração" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Fluxos de dados" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Áudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Legendas" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Reprodução de vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Decodificador" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Filtro" #: 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" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Reprodução de áudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Fluxos de vídeo" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Fluxos de áudio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Fluxos de legendas" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Comece adicionando alguma mídia à fila de reprodução" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Avançar/Retroceder" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Método" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Um método preferencial usado para avançar/retroceder" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Valor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Valor usado para avançar/retroceder" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Unidade" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Uma unidade para o valor usado para avançar/retroceder" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Segundo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minuto" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Porcentagem" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Funcionalidades" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Servidor" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Controlar o reprodutor de vídeo remotamente" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Desvio" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Desvio de sincronização em segundos entre os fluxos de áudio e de vídeo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Desvio de sincronização em segundos entre os fluxos de vídeo e de legendas" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Fonte padrão" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Fonte de texto usada para as legendas quando a mídia não especificar alguma explicitamente" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Ajustes" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Classificação do plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Alterar as classificações padrão de plugins do GStreamer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Classificação dos plugins" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Plugins disponíveis" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Escolha um plugin e sua funcionalidade para alterar sua classificação. Quando múltiplos elementos possuem funcionalidades similares, aquele com o maior valor de classificação tem a preferência." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Funcionalidade do plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Adicionar alteração" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Alterações de classificação" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Adicionar" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Progressão da fila" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Limpar fila" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Adicionar URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Insira um URI para ser adicionado à fila de reprodução" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Digite ou solte o URI aqui" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Cancelar" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Resolução" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Taxa de quadros" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Formato de pixel" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "KevenDoriaLinuxBR\n" "Filipe Motta " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Criar nova janela" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "Adicionar mídia à fila na instância primária do aplicativo" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "Volume de áudio escolhido (escala de 0 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "Velocidade de reprodução escolhida (escala de 0.05 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" msgstr "Taxa de bits inicial para streaming adaptativo" #: 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 "Modo inicial de progressão da fila (0=nenhum, 1=consecutivo, 2=repetir-item, 3=carrossel, 4=aleatório)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "Definir janela como tela cheia" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "Filtro de vídeo a ser utilizado (\"none\" para desabilitar)" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "Filtro de áudio a ser utilizado (\"none\" para desabilitar)" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "Sink de vídeo a ser utilizada" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "Sink de áudio a ser utilizada" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Hardware" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Software" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Preciso" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rápido" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Sem progressão" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Consecutiva" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Repetir item" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Repetir tudo" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Aleatório" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Solte na barra de título para reproduzir agora ou em qualquer outro lugar para adicionar à fila." clapper-0.8.0/src/bin/clapper-app/po/ro.po000066400000000000000000000353771474353110700203320ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Romanian\n" "Language: ro_RO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ro\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Player Multimedia" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Canale" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Videoclip" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Audio" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Subtitrări" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/ru.po000066400000000000000000000451051474353110700203260ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Russian\n" "Language: ru_RU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ru\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "Кодек" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Каналы" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Формат семпла" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Частота дискретизации" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Битрейт" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Язык" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Параметры" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Сочетания клавиш" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "О приложении" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Общие" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Открыть настройки" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Комбинации клавиш" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Включить полноэкранный режим" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Двойное нажатие | Двойной клик" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Выйти из приложения" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Медиа" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Добавить файлы…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "Открыть по ссылке…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Дорожка" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Следующий элемент" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Предыдущий элемент" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "Изменить режим повтора" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Воспроизведение" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Управление воспроизведением" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Тап | Левая кнопка мыши" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Перемотка вперед" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "Двойной тап (правая сторона) | Скролл вправо" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "Двойной тап (левая сторона) | Скролл влево" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Перемотка назад" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Увеличить громкость" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Скролл вверх" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Уменьшить громкость" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Скролл вниз" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Отключить звук" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Ускорить" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Замедлить" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Следующая глава" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Предыдущая глава" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Информация" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Заголовок" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Формат контейнера" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Длительность" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Потоки" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Видео" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Аудио" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Субтитры" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Воспроизведение видео" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Декодер" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "Приемник" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Воспроизведение звука" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Видеопотоки" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Аудиопотоки" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Потоки субтитров" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Начните с добавления медиа в очередь воспроизведения" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Перемотка" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Метод" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Предпочтительный метод, используемый для поиска" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Значение" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "Значение, используемое для поиска вперед/назад" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Ед. изм" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "Единица поиска вперёд/назад" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Секунды" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Минуты" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Проценты" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Дополнительные настройки" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Сервер" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Удаленное управление плеером" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Задержка" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Задержка синхронизации в секундах между аудио- и видеопотоками" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Задержка синхронизации в секундах между потоком субтитров и видеопотоком" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Шрифт по умолчанию" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Шрифт, используемый для субтитров, если не указан какой именно" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Твики" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Рейтинг плагинов" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Изменить ранги плагинов GStreamer по умолчанию" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Ранжирование плагинов" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Доступные плагины" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Выберите плагин и его функцию, чтобы переопределить ранжирование. Когда несколько элементов имеют схожие возможности, предпочтение имеет один с наибольшим значением." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Плагин" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Настройка плагина" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Добавить переопределение" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Переопределение ранжирования" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Добавить" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Прогресс очереди" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Очистить очередь" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "Открыть по ссылке" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Вставьте URI, который будет добавлен в очередь воспроизведения" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Введите URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Отмена" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Разрешение" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Частота кадров" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Формат пикселей" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Игорь Дятлов" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Аппаратное" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Программное" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Точный" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Обычная" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Быстрый" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "Нет прогрессии" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Последовательно" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Повторить элемент" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Повтор очереди" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Случайно" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Перетащите на строку заголовка, чтобы воспроизвести сейчас или где-нибудь ещё, чтобы попасть в очередь." clapper-0.8.0/src/bin/clapper-app/po/sk.po000066400000000000000000000365051474353110700203210ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Slovak\n" "Language: sk_SK\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: sk\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanály" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Nastavenia" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "O programe Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Všeobecné" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Zobraziť klávesové skratky" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Na celú obrazovku" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Dvojité ťuknutie | Dvojité kliknutie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Ukončiť" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Médiá" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Ďalšia položka" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Predchádzajúca položka" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Prehrávanie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Vypnúť/zapnúť prehrávanie" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Pretočiť dopredu" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Pretočiť späť" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Zvýšiť hlasitosť" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Znížiť hlasitosť" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Vypnúť/zapnúť zvuk" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Ďalšia kapitola" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Predchádzajúca kapitola" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Názov" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Zvuk" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Titulky" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Pretáčanie" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Hodnota" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Jednotka" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekunda" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minúta" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Percento" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Server" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Ovládanie prehrávača na diaľku" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Predvolené písmo" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Vyladenia" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Radenie zásuvných modulov" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Zmeniť predvolené radenie zásuvných modulov GStreameru" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Sem zadajte alebo potiahnite URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Zrušiť" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "tj314 " #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Presné" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normálne" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Rýchle" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/sr.po000066400000000000000000000353221474353110700203240ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Serbian (Cyrillic)\n" "Language: sr_SP\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: sr\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/sv.po000066400000000000000000000362301474353110700203270ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Swedish\n" "Language: sv_SE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: sv-SE\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanaler" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Inställningar" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Om Clapper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Allmänt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Visa tangentbordsgenvägar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Växla helskärmsläge" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Dubbeltryck | Dubbelklicka" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Avsluta" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Media" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Nästa föremål" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Föregående föremål" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Uppspelning" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Spela/pausa" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "Spola framåt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Spola bakåt" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Höj volymen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Sänk volymen" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Växla ljudet på/av" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Nästa kapitel" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Föregående kapitel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Titel" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Ljud" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Undertexter" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Spolning" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Värde" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Enhet" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Sekund" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Minut" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Procent" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Server" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Fjärrstyra spelaren" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Standardteckensnitt" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "Tweaks" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Rangordning av plugin" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "Ändra standardrankningar för GStreamer-plugins" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Ange eller släpp URI här" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "Avbryt" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "SA ST (sastofficial)" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Riktig" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Snabbt" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/tr.po000066400000000000000000000427021474353110700203250ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Turkish\n" "Language: tr_TR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: tr\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "Çoklu Ortam Oynatıcı" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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;" #: 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 "Yeni Pencere" #: 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 msgid "Codec" msgstr "Kod çözücü" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "Kanallar" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "Örnek Biçimi" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "Örnek Hızı" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "Bit Hızı" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "Dil" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "Tercihler" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "Klavye Kısayolları" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "Clapper Hakkında" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "Genel" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "Yeni pencere" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "Tercihleri aç" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "Kısayolları göster" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "Tam ekranı aç/kapat" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "Çift dokun | Çift tıkla" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "Tam ekrandan çık" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "Kendiliğinden pencereyi yeniden boyutlandır" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "Orta tıklama" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "Çık" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "Ortam" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "Dosya Ekle…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "URI Ekle…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "Kuyruk" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "Sonraki öğe" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "Önceki öğe" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "İlerleme kipini değiştir" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "Oynatma" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "Oynatmayı başlat/durdur" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "Dokunma | Sol tık" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "İleri sar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "İki kez dokunma (sağ taraf) | Sağa kaydır" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "İki kez dokunma (sol taraf) | Sola kaydır" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "Geri sar" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "Sesi artır" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "Yukarı kaydır" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "Sesi kıs" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "Aşağı kaydır" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "Sesi kapat/aç" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "Hızlandır" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "Yavaşlat" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "Sonraki parça" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "Önceki parça" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "Bilgi" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "Başlık" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "Kapsayıcı Biçimi" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "Süre" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "Akışlar" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "Video" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "Ses" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "Alt Yazılar" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "Video Oynatma" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "Kod Çözücü" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" msgstr "Süz" #: 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 "Havuz" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "Ses Çalma" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "Video Akışları" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "Ses Akışları" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "Alt Yazı Akışları" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "Oynatma sırasına ortam ekleyerek başlayın" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "Sarma" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "Yöntem" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "Aramak için kullanılacak yöntem" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "Değer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "İleri/geri arama için kullanılan değer" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "Birim" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "İleri/geri arama değerinin birimi" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "Saniye" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "Dakika" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "Yüzde" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "Özellikler" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "Sunucu" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "Oynatıcıyı uzaktan denetle" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "Denge" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "Ses ve video akışları arasındaki eşzamanlama dengesi (saniye olarak)" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "Alt yazı ve video akışları arasındaki eşzamanlama dengesi (saniye olarak)" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "Öntanımlı yazı tipi" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "Ortam açıkça alt yazı belirtmediğinde alt yazılar için kullanılacak metin yazı tipi" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "İnce Ayarlar" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "Eklenti puanı" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "GStreamer eklentilerinin öntanımlı sıralarını değiştir" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "Eklenti Puanı" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "Kullanılabilir eklentiler" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "Puanlamayı geçersiz kılmak için bir eklenti ve onun özelliğini seçin. Birden çok ögenin benzer yeteneklere sahip olması durumunda değeri en yüksek olan tercih edilir." #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "Eklenti" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "Eklenti özellikleri" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "Üzerine yazma ekle" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "Puan üzerine yazmalar" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "Ekle" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "Kuyruk ilerlemesi" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "Kuyruğu Temizle" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "URI Ekle" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "Oynatma kuyruğuna eklemek için URI ekleyin" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "Bağlantıyı buraya gir veya sürükle" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "İptal Et" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "Çözünürlük" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "Kare Kızı" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "Piksel Biçimi" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "Clapper Çevirilerine Katkıda Bulunan Herkese Çok Teşekkür Ederiz" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "Yeni pencere oluştur" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "Birincil uygulama örneğinde kuyruğa ortam ekle" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "Ayarlanacak ses düzeyi (0 - 2.0 arası)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "Ayarlanacak oynatma hızı (0.05 - 2.0 arası)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" msgstr "Uyarlanabilir akış için başlangıç bit hızı" #: 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 "İlk kuyruk ilerleme kipi (0=none, 1=consecutive, 2=repeat-item, 3=carousel, 4=shuffle)" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "Pencereyi tam ekran olarak ayarla" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "Kullanılacak video süzgeci (devre dışı bırakmak için \"none\")" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "Kullanılacak ses süzgeci (devre dışı bırakmak için \"none\")" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "Kullanılacak video alıcısı" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "Kullanılacak ses alıcısı" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "Donanım" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "Yazılım" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "Hatasız" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "Normal" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "Hızlı" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "İlerleme yok" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "Ardaşık" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "Ögeyi Yinele" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "Karosel" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "Karıştır" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "Şimdi oynatmak için başlık çubuğuna ya da sıraya eklemek için başka bir yere bırakın." clapper-0.8.0/src/bin/clapper-app/po/uk.po000066400000000000000000000354441474353110700203240ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Ukrainian\n" "Language: uk_UA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: uk\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/vi.po000066400000000000000000000351711474353110700203200ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Vietnamese\n" "Language: vi_VN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: vi\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/po/zh_CN.po000066400000000000000000000405131474353110700206770ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: zh-CN\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "Clapper" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "多媒体播放器" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "编解码器" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "声道" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "采样格式" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "采样率" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "比特率" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "语言" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "首选项" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "键盘快捷键" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "关于 Claper" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "常规​​​​​" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "打开首选项" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "显示快捷键" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "切换全屏" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "双击 | 鼠标双击" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "取消全屏" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "自动调整窗口大小" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "中键点击" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "退出" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "媒体" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "添加文件…" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "添加URI..." #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "队列" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "下一集" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "上一集" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "切换播放模式" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "播放" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "切换播放" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "单击 | 左键点击" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "快进" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "双击(右侧) | 向右滚动" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "双击(左侧) | 向左滚动" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "快退" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "提高音量" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "向上滚动" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "降低音量" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "向下滚动" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "切换静音" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "加速" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "减速" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "下一章节" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "上一章节" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "信息" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "标题" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "封装格式" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "时长" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "串流" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "视频" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "音频" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "字幕" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "视频回放" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "解码器" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "音频回放" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "视频流" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "音频流" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "字幕流" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "添加媒体到播放队列即可开始" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "定位播放" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "快进/快退方式" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "快进/快退的偏好方式" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "数值" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "用于快进/快退的数值" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "单位" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "快进/快退数值的单位" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "秒钟" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "分钟" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "百分比" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "功能" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "服务器" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "远程控制播放器" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "偏移" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "音频流和视频流之间的同步偏移秒数" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "字幕流和视频流之间的同步偏移秒数" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "默认字体" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "媒体未特别指定时,用于字幕的字体" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "调整" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "插件等级" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "更改 GStreamer 插件的默认等级" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "插件排序" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "可用插件" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "选择一个插件及其用于覆盖等级的功能。当多个元素具有相似的功能时,具有最高值的插件功能会优先使用。" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "插件" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "插件功能" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "添加覆盖" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "覆盖排序" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "添加" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "播放列队" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "清空队列" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "添加" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "插入要添加到播放队列的URI" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "输入或拖放 URI 到这里" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "取消" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "分辨率" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "帧率" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "像素格式" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "刘韬\n" "Tommy He" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "新建一个窗口" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "设置音量 (范围 0 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "设置播放速度(范围 0.05 - 2.0)" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "设置窗口为全屏" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "硬件" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "软件" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "精确" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "标准" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "快速" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "单集" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "连续" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "单集循环" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "队列循环" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "随机" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "拖放到标题栏即刻播放或拖放任何其他位置加入队列。" clapper-0.8.0/src/bin/clapper-app/po/zh_TW.po000066400000000000000000000352051474353110700207330ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Chinese Traditional\n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: zh-TW\n" "X-Crowdin-File: /master/src/bin/clapper-app/po/clapper-app.pot\n" "X-Crowdin-File-ID: 48\n" #. Translators: Do NOT translate app name! #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:4 msgid "Clapper" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:5 msgid "Multimedia Player" msgstr "" #: src/bin/clapper-app/data/applications/com.github.rafostar.Clapper.desktop.in:6 msgid "Play videos and music" 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 "" #: 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 "" #: 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 msgid "Codec" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:24 msgid "Channels" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:36 msgid "Sample Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:46 msgid "Sample Rate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:58 #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:51 msgid "Bitrate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui:70 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:24 msgid "Language" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:189 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:80 msgid "Preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:193 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:84 msgid "Keyboard Shortcuts" msgstr "" #. TRANSLATORS: Please do not translate application name #: src/bin/clapper-app/ui/clapper-app-headerbar.ui:200 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:91 msgid "About Clapper" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:10 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:11 msgid "General" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:13 msgid "New window" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:19 msgid "Open preferences" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:25 msgid "Show shortcuts" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:31 msgid "Toggle fullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:32 msgid "Double tap | Double click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:38 msgid "Unfullscreen" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:44 msgid "Auto window resize" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:45 msgid "Middle click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:51 msgid "Quit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:59 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:32 msgid "Media" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:62 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:55 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:178 msgid "Add Files…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:68 #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:65 #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:182 msgid "Add URI…" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:76 msgid "Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:80 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:87 msgid "Next item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:94 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:101 msgid "Previous item" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:107 msgid "Change progression mode" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:115 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:76 msgid "Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:118 msgid "Toggle play" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:119 msgid "Tap | Left click" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:126 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:134 msgid "Seek forward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:127 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:151 msgid "Double tap (right side) | Scroll right" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:135 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:143 msgid "Double tap (left side) | Scroll left" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:142 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:150 msgid "Seek backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:157 msgid "Volume up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:158 msgid "Scroll up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:164 msgid "Volume down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:165 msgid "Scroll down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:171 msgid "Toggle mute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:177 msgid "Speed up" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:183 msgid "Speed down" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:190 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:197 msgid "Next chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:204 #: src/bin/clapper-app/ui/clapper-app-help-overlay.ui:211 msgid "Previous chapter" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:13 msgid "Info" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:35 #: src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui:14 msgid "Title" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:49 msgid "Container Format" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:63 msgid "Duration" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:81 msgid "Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:84 #: src/bin/clapper-app/clapper-app-list-item-utils.c:36 msgid "Video" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:107 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:80 #: src/bin/clapper-app/clapper-app-list-item-utils.c:39 msgid "Audio" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:130 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:100 #: src/bin/clapper-app/clapper-app-list-item-utils.c:42 msgid "Subtitles" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:155 msgid "Video Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:158 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:199 msgid "Decoder" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:170 #: src/bin/clapper-app/ui/clapper-app-info-window.ui:211 msgid "Filter" 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 "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:196 msgid "Audio Playback" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:247 msgid "Video Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:290 msgid "Audio Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-info-window.ui:333 msgid "Subtitle Streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-initial-state.ui:46 msgid "Start by adding media to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:15 msgid "Seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:18 msgid "Method" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:19 msgid "A preferred method used for seeking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:32 msgid "Value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:33 msgid "Value used for seeking forward/backward" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:46 msgid "Unit" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:47 msgid "An unit of a seek forward/backward value" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:51 msgid "Second" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:52 msgid "Minute" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:53 msgid "Percentage" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:63 msgid "Features" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:66 msgid "Server" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:67 msgid "Control player remotely" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:83 #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:103 msgid "Offset" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:84 msgid "Synchronisation offset in seconds between the audio and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:104 msgid "Synchronisation offset in seconds between the subtitle and video streams" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:118 msgid "Default font" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:119 msgid "Text font used for subtitles when media does not explicitly specify one" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:137 msgid "Tweaks" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:144 msgid "Plugin ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:145 msgid "Alter default ranks of GStreamer plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:164 msgid "Plugin Ranking" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:182 msgid "Available plugins" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:183 msgid "Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred." msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:186 msgid "Plugin" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:195 msgid "Plugin feature" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:213 msgid "Add override" msgstr "" #: src/bin/clapper-app/ui/clapper-app-preferences-window.ui:229 msgid "Rank overrides" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:60 #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:23 msgid "Add" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:87 msgid "Queue progression" msgstr "" #: src/bin/clapper-app/ui/clapper-app-queue-list.ui:188 msgid "Clear Queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:6 msgid "Add URI" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:7 msgid "Insert an URI to be added to playback queue" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:18 msgid "Enter or drop URI here" msgstr "" #: src/bin/clapper-app/ui/clapper-app-uri-dialog.ui:22 msgid "Cancel" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:24 msgid "Resolution" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:39 msgid "Framerate" msgstr "" #: src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui:63 msgid "Pixel Format" msgstr "" #. TRANSLATORS: Put your name(s) here for credits or leave untranslated #: src/bin/clapper-app/clapper-app-about-window.c:47 msgid "translator-credits" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:696 msgid "Create a new window" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:697 msgid "Add media to queue in primary application instance" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:698 msgid "Audio volume to set (0 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:699 msgid "Playback speed to set (0.05 - 2.0 range)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:700 msgid "Initial bitrate for adaptive streaming" 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 "" #: src/bin/clapper-app/clapper-app-application.c:702 msgid "Set window to be fullscreen" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:703 msgid "Video filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:704 msgid "Audio filter to use (\"none\" to disable)" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:705 msgid "Video sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-application.c:706 msgid "Audio sink to use" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Hardware" msgstr "" #: src/bin/clapper-app/clapper-app-info-window.c:86 msgid "Software" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:441 msgid "Accurate" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:443 msgid "Normal" msgstr "" #: src/bin/clapper-app/clapper-app-preferences-window.c:445 msgid "Fast" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:90 msgid "No progression" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:91 msgid "Consecutive" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:92 msgid "Repeat item" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:93 msgid "Carousel" msgstr "" #: src/bin/clapper-app/clapper-app-utils.c:94 msgid "Shuffle" msgstr "" #: src/bin/clapper-app/clapper-app-window.c:1036 msgid "Drop on title bar to play now or anywhere else to enqueue." msgstr "" clapper-0.8.0/src/bin/clapper-app/ui/000077500000000000000000000000001474353110700173325ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-audio-stream-list-item.ui000066400000000000000000000061361474353110700264000ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-headerbar.ui000066400000000000000000000222451474353110700240150ustar00rootroot00000000000000
New Window app.new-window
Preferences app.preferences Keyboard Shortcuts win.show-help-overlay
About Clapper app.about
clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-help-overlay.ui000066400000000000000000000235261474353110700245120ustar00rootroot00000000000000 true app General New window <Ctrl>n Open preferences <Ctrl>comma Show shortcuts <Ctrl>question Toggle fullscreen Double tap | Double click F11 f Unfullscreen Escape Auto window resize Middle click <Super>r Quit <Ctrl>q q Media Add Files… <Ctrl>o Add URI… <Ctrl>u Queue ltr Next item <Ctrl>Right rtl Next item <Ctrl>Left ltr Previous item <Ctrl>Left rtl Previous item <Ctrl>Right Change progression mode p Playback Toggle play Tap | Left click space k ltr Seek forward Double tap (right side) | Scroll right Right l rtl Seek forward Double tap (left side) | Scroll left Left j ltr Seek backward Double tap (left side) | Scroll left Left j rtl Seek backward Double tap (right side) | Scroll right Right l Volume up Scroll up Up Volume down Scroll down Down Toggle mute m Speed up greater Speed down less ltr Next chapter <Shift>Right rtl Next chapter <Shift>Left ltr Previous chapter <Shift>Left rtl Previous chapter <Shift>Right clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-info-window.ui000066400000000000000000000523131474353110700243370ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-initial-state.ui000066400000000000000000000070301474353110700246420ustar00rootroot00000000000000 vertical 6 horizontal fill start end center left end center open-menu-symbolic app_menu true true com.github.rafostar.Clapper Clapper Start by adding media to playback queue vertical center center 6 Add Files… app.add-files Add URI… app.add-uri
Preferences app.preferences Keyboard Shortcuts win.show-help-overlay
About Clapper app.about
clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-preferences-window.ui000066400000000000000000000265451474353110700257150ustar00rootroot00000000000000 Plugin Ranking vertical Available plugins Select a plugin and its feature to override rank for. When multiple elements have similiar capabilities, the one with highest value is preferred. Plugin true Plugin feature true plugins_combo_row center center Add override features_combo_row ClapperAppPreferencesWindow Rank overrides false clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-queue-list-item.ui000066400000000000000000000026571474353110700251360ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-queue-list.ui000066400000000000000000000177741474353110700242100ustar00rootroot00000000000000
Add Files… app.add-files Add URI… app.add-uri
Clear Queue app.clear-queue
clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-queue-progression-item.ui000066400000000000000000000027321474353110700265270ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-subtitle-stream-list-item.ui000066400000000000000000000023571474353110700271330ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-uri-dialog.ui000066400000000000000000000023051474353110700241270ustar00rootroot00000000000000 true 420 Add URI Insert an URI to be added to playback queue cancel add fill center true true true url Enter or drop URI here Cancel Add clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-video-stream-list-item.ui000066400000000000000000000055561474353110700264120ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-window-state-buttons.ui000066400000000000000000000027321474353110700262200ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/ui/clapper-app-window.ui000066400000000000000000000057341474353110700234130ustar00rootroot00000000000000 clapper-0.8.0/src/bin/clapper-app/windows/000077500000000000000000000000001474353110700204075ustar00rootroot00000000000000clapper-0.8.0/src/bin/clapper-app/windows/clapper.exe.manifest000066400000000000000000000024231474353110700243460ustar00rootroot00000000000000 true clapper-0.8.0/src/bin/clapper-app/windows/clapper.ico000066400000000000000000004346711474353110700225500ustar00rootroot00000000000000 v ( `` 1(@@ (Bټ00 %  $ hQ5PNG  IHDR\rfZIDATxixe7Sξ6AlA`Q d:y9f}=Dqsy .G@"$ ,"! Y:I'k=HB]ݵݿoJRuWO-s? E̚|0<sPQ=50ɜ9'LyEa5CUI!z7oާa!lpzt PFW,?t$睳8B-0:9~{9BQ+]"S%IP"ӦE1b1WCQ7!꽴g/$ yO|InEx<9,e ±_ "Sm!OFI819xa ьyޒ{QPMt8r& _9r)qů%IXPQڂ~R6Jt m)hG΍1W"_`oVC97J@ a֬}/x5 c@ՑGH0-:g_xjk R̙ $ Jwf\P&:tb:')nod\8*!I| 1?1ۑpW c| +> B. <)F";Nȱ- tD4CZay^osm>rKT% D]bΜ"~9|xuu| [)*kQLMms+Lɟ8ײٻwn \6nbdd2!3۷n7DHh; ZCCClSv<-M'F+UKol֊5U |ҋAmAC d9ƞ9}|̈+/\n2'~ Wl@U4^󨯿ns8|%%$&Vk (.v`-UV^(HNgZUgf5}N~;PZǣ ` wn >s}jmWdPׅ}ZZxV _APT^,oٯ"?.Ə=&F0+]O8{m[ܴ@; X: N;HȑJ655.)) `7s#.uӧ஻[!+7z N8j./M )t,?X _9L f'?")8A G[L#= c Lw˖%j>İȄ.Q׾ /nܞ?Hd$í?"6VOm Gw ]^+AZ&w\| . :8A GUl+)|>؍QhUtM"n-01Rh‘P"Ib}1~s4wXV&:mq:cg;;4be{tJص x(&DŽ#U[χW_ej@RteeuS8IM?ztDkyʄ#:feTd*8} KM&<55YY&E47[=*M8ji{뽟57ߴXLfNPV|8ј^_/-OH`wTɇ;{$ ]ye{R>r,^tMZ(/j6+yÆEd>Xlۦ*bP+6ev5]̹sgO==#GF 9٤9lކC4{^IO1~(gK,Sm:Ԓyj5->8#Ll9#Ô2'Nuk+Nv+]e$V"1W% ?Ψ6|eVfvR"rn*]}ozp\C/>9`ʸl3/Ni!DQ5/w[[q']%&+޽{R,rʙfAnޣЭg9!ۏzزn=ґ' O ]bb"v `„ >q<؉646jjԪ1sswWTU ҿ`ݺu3Ɗ '66?H0sLX,ҥj4C9v rE쩬O#4"6,uCiArq)̘1CdbˋM7wرá^<+a_2c䣅fAiPW֭l_o6lӧ#z +Ћ ^Ho7ׇ"##xJ O嫪4QQ [0gN\ U5ݜb'~_eh3\s m6ɑ'[o,>b ŀlq[ʈsj(]lE}wk{8i@A𗖖NiRH*cǎɤCOJq}IXЂj\/ӌIb%--n޸u Gmm-Ft)!b)X o$Jc/ % 0pȑ`:pn[JmOa%Z> ˅F t)V3gg5+]Q7C/iV l6ogm$_喬23-<@RRv,Yo|Y5\UCKT.?|sJD˰Ƿ2?ߌ;QPNj!jk_:ѣ4)]隈>:FjGll*:;#PS_jرX4S*7Xs>sfÆQ<"?C \ӆ#GR_}OpՑDZ<kj5vTlnn2M%!g6; ҍի0qb+c߼n ޓǎǔ)ړ2|~_zЉ.81 ܓѣѳ; ƍϭΎ>q…:L;LDV씤ZoUsoWRԌJ/!($$x1bD':΁CppO]w%!#?B:wm3'լ,Fc*Q\0~Ы7-?ù/i- 23FK1^^RQQ\\|r6#CǸqv8?|7ػw|, >ƌiѣi}DKtA0飏R7J(.2a'^lYҵjYr[F$ݭts9ȏcx)]!FoP߭7otDQ\t ݼys3'$L()JAHP\C6m O_0BŒF^^ڸq:Μl `9v;Y Խ{QQkkeeGG(9w /-r*Q BKK3BaSNJU`UPc|yjpxU;Hڽ1ȕ$y_. k6I=e+OJWnU%?c5f3 ~?>mtvJxF|~4%$(,ĕW䯭`͚F9A*;~X"VLȑJүg=X:Anl!_Ss甛[j"VfKי3nuʅ'l˥yՃ@ bz46ΎUHJRe E'tc::A˅K㮭bF5ʌGMŢޓȑ._N~|$IY@UV5WreO?ijjkk/3Y99QX*88Ёw@7A^uuu_f2GIGtz?J=!It_L5:{,88uʅuݭߓhsS7uj ~T={oPUx)]F̜Jɤޓ EEmJz!DBc)^ZuDUN\؃!1sΜ8,_ޥz;(빉H`7&ݝsPR]Dl.- :^/<N~9 _Ox>z ( o]KKK8ar8~#tPfk]P[V|\Cdd$V+bbbz]9"Ξرdtwk c&a.e6M|`A5+,,4d\Kʮ9)1uN$o/x^k*QdZ}Ln7kgzs>>$DD,>󄐐 (10 B  (10 B  (1N]ϩ/SuqqWܸa b`!F@Qb`!F@Qb`!FrJ@tL ܊)ho㥗hn)]ЀSca0XTzPhPP`ųõF˰EB(4'#Ä?1?t#OFMPZځ7lE/`Æl|s Ё$>f!B#&D3qI_-<Đo Y80zkH@S&<` V4\΍g+R/*]Q ` 8o0n 7( $.N}%'pUJCTŒĊ)HH#0ۂ zn  bc{n ~: \t9$(`^NCr2{ (DF_0y23 үZZ@%"+Q /n Ԏ*b[+jE@Bj3֮=`$# !wqK2=$lz{l܆ߧI;zE@w֭mI;zF@ɞI;554i(@v z'l nh0 CzQM!0CCs4i @::$lNv(t&퐡БꙴW4i Ф( I;$Xu 7Q[KvH(4ŏ[iFx<vlΝxpL@ee?` ?@+WA/x݉ڡb#rT ?JK ^ \Z:ioPc{bDN1};p)}tα@B~psVl6{^*.֬T! =Ƙɘ4 @"ng_SkWSj!B!B!B!|;IENDB`( GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGIIHHGGHHGGHHGGGG3HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH=HH9HH&HHDDGGQQDDHHGGHHGGAGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGgGGHHppCCIIHHHHGGkGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHNNKKCCGGUGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFF FFIIGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGJGG5qqGGGGDGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDDFFGGGGcGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG GGGGGGkGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFPP܂[[EEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU݅PPEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU湹ooIIEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU⡡^^FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUފRREEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU达uuKKEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU㨨bbFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUߐUUEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUyyLLEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU䭭ffGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUXXEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU~~NNEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU洴kkHHFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU᜜ZZFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU݆PPEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU湹ooIIEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU⢢__HHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUjjFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUߏSSFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU㤤__FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU缼rrJJEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU݈QQEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU⠠]]FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU游ooIIFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU܃OOEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUᛛZZEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU浵kkHHFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU~~NNEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUXXEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU山hhGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUzzMMEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUߓUUEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUU䫫eeGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFUUvvKKEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFTT浵ߐSSEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGJJUUGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGlGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHH HHGGGGkGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH HHAAAAm@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AABBAA9999o9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::<<;;99::o9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::<<<<99::o9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::<<<<99::o9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::<<<<??99o9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::<<==EEDDVVV..::o99888899::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9988888899999999999999999999999999999999999999999999999999998888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999::;;GGHHGGGGGG$pssiissvvww||||||||||||||||||||||||||||||||||||||||yyvvppXXBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@EE~~ПНННННННННННННННННО||DD@@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBFFGGGGvGG+KKEE@@(ćiiGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGnnߑKKFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG2GGaa~~~~NNFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEhhLLFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\\FFQQFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEeeMMFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGWWEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEbb᝝NNFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH宮TTFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEE``⡡OOFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH尰UUFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEE^^羾㥥PPFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH洴WWEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEE[[纺㩩RRFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH混YYEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEYY混䭭SSFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH绻[[EEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEWW崴尰UUFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH羾^^EEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV尰洴WWEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH``EEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFTT䭭混YYEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHccEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFRR㩩纺[[EEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHeeEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFQQ㥥羾]]EEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHhhEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFOO⠠``EEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHllEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFNN᜜bbEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGIIHHooFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFMMeeFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG;;JJrrGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFLLߕhhGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG(FF99|~~~~~~~~~~~~~~~~~~山{OOsGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrFFrRRs纺z~~~~~~~~~~~~~~~~~~~⣣zKKsGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGGrGG^GGIIIIꧧ9㥥MMGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFF\\JJGGGGGGGGGGGGGGGGGGGGGGGGGGFFGG 󩩩NHHIIAA'FF EEGGGGGGII@@RRFFGGGGsGG8<<wwGGGGGGFFEEWW宮d(游 WWFFFFEEQQ܁z>HHEEOO}}迿V⢢罽翿]]MMyy缼m/漼潽ᮮyy混Dffᡡ@@]"SSGGGG@@DD洴^^EEYGG%GGIIGGGGHHHH݆TTGGGGGGGGGGmGG3GGDDGGGGFF??EE݉UUEEFFGGGGGGGGGGGGGGGGGGGFFEEGGGGGGEEGG}ߎWWFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGG]HH'GG HHGGGGFFQQ33&ߒZZFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGsGG8GG>>GGGGGGEEFF!k̺~~}}ɾ\\FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGOGGGGGGGGGGEEFFǽ77>>z⠠aaGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG{GGBEE歭糳岲 )e⤤ddHHEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFGGqqg,洴輼⭭ޑO㩩hhIIEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFZZߓEdd翿翿7t䭭kkJJEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFXXߏ]#汱 混峳孭 %_尰nnKKEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFEEUU݊t8鹹鳳ppKᙙyOOEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFEESS݅P㬬㞞緷΍蹹88 GG$GGYGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFEEQQ܀i- 44JJGGDDEEJJMMGGGGGGGGFFGGDGG~GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFEEOO{{迿݆GGdFF*FF JJGGGGHH==UUIIYYGGGGGGJJGGGG1GGjGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEMMvv纺䬬kkKKGGGGGGGGwGG>DDFFEEGGGGFFHHFF!GGVGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEKKrr泳山nnJJEEGGGGGGGGGGGGGGGGGGRGGGGHHGGFFGGIILLGGGGGGDDGGGG?GG|GGGGGGGGGGGGGGGGGGGGGGGGGGEEJJmm寯涶rrLLEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGaHHHHEEFFIIFFGGGGLLGGGG/GGhGGGGGGGGGGGGGGGGEEIIhh㪪纺xxMMEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHGGUUEEDDGGGGFFFFGG GGRGGGGGGGGJJee㥥迿||OOEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGfGGUUIIOOFFGGGGRRGGFF=KK{ގ܁QQEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGzFFUUUU55IIGGMMF݆SSEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGNGGUU㩩܊/jދVVEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG䴴RߑYYFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHGG9u[[FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGXFFZZ %_ᛛ__GGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFF!GGJJvvFyttGGEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH죣FF GGQGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGaFFLLIIOOGGGGFFNNGGGG>FFGGGGEEHHGG7GGqGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG/GGHHUUBBGGGGGGFFFF GG$GGYGGGGGGGGGGGGGGGGGGGGGGGGGG GGHHJJGGGGGGFFGGGGFGGGGGGGGGGGGGGGGGGcHHEEIIYYGGGGGGGGGGGG8GGyGGGGGGGGHHGGFF???????????(` GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHGGGGGG+GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4GG4HH+HHBBGGFFIIGGGGGGGGrGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGvGGGGFFFFGGHH GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFIIEEGGGG[GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGaGGJJGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGKKYYGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV罽WWEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV~~MMEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV浵jjGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV᜜ZZEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV݅OOEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV绻ooHHEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV⢢]]FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVVދQQEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV迿ttJJEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV㩩aaFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVVߑTTEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVVzzLLGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV㤤OOFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVVxxJJGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVVߎRREEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV㧧``FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV翿ssIIEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVVފQQEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV⢢]]FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV绻ppHHFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV݅OOEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV⟟[[EEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV涶kkHHFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVV܁MMEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFVVXXEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFSSiiGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHH GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCC:::: 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::ƫ<<<<:::: 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::ƫ<<<<:::: 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::ƫ<<<<yy8888888888888888888888888888888888888888889999999999999999999999999999999999999988777777777777777777777777777777999999999999999999999999999999999999999999Ƣ99DDGGKK>>4<~~EPPХPPRRUUWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVRRMM??<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>hh=ˈߑXXEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGRGGUULLQQHHEEXX##7q\\FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"GGUU#W᝝__FFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHH弼@}⢢bbGGEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGaGGFF鰰 -d̛ppHHEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG(GGJJ22FF>>>>>>>>>>>>>>>>>>>==::999999999999999999999999==@@????????????????==88999999999999999999999999::ҵ>>jjGGGG΢PPCCCCCCCCCCCCCCCCCCCCBB]]ڰ۳aaAACCCCCCCCCCCCCCCCCCCCCCDDGGGGGGGGOrrEEGGGGGGGGGGGGGGGGGGGGHH܁ggEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGQGG[ϜOOFFGGGGGGGGGGGGGGGGGGGGFFzzjjEEGGGGGGGGGGGGGGGGGGGGGGGGGG[GGZ䨨PPFFGGGGGGGGGGGGGGGGGGGGFFwwmmEEGGGGGGGGGGGGGGGGGGGGGGGGZGGZ䫫QQFFGGGGGGGGGGGGGGGGGGGGEEssppEEGGGGGGGGGGGGGGGGGGGGGGZGGZ寯SSFFGGGGGGGGGGGGGGGGGGGGEEppssEEGGGGGGGGGGGGGGGGGGGGZGGZ峳UUEEGGGGGGGGGGGGGGGGGGGGEEmmwwFFGGGGGGGGGGGGGGGGGGZGGZ混WWFFGGGGGGGGGGGGGGGGGGGGEEjjzzFFGGGGGGGGGGGGGGGGZGGZ游WWFFGGGGGGGGGGGGGGGGGGGGEEhhzzGGGGGGGGGGGGGGGG3GGZjFHHHHHHHH~~EEECGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCGGCBBCߔEHHHHHHHHH游GLLCGGCGGCGGCGGCGGCGG)GGGGZ{{EEGGGGGGGGGGGGGGGGGGGGGGFF洴LLGGGGGGGGGGGGGGGGZw݅翿ZNNCCZZG᜜迿ZnnMM{{c+ 濿ZՃ缼z=NNGGFFCCHHZ‘[[JCCEEGGGGGGKKGGZ㤤ddHHGGGGGG[GG&GGGGGGGGFFEEKK2܇㨨ffGGEEGGGGGGGGGGGGGGsGG7GGHHGGGGGGUUGG-NNf~~Ӻ䬬iiHHEEGGGGGGGGGGGGGGGGGGGGGGGGGGMGGGG翿``#W山llIIEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFDDiid1 GGXB浵ppJJEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFEEQQ݄Hފ湹 .h֥~~KKEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFEEOO܀_)llIIFFGGGG弼33FFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEEMM{{䫫vII2EE GGGGGGFFEELL庺HHGGGGGG GG4GGnGGGGGGGGGGGGGGGGGGEELLvv绻ZZGGGGGGGGCGGCCGGGG@@GGFFGGGGGGGGGG#GGXGGGGGGGGFFKKqq涶\\FFFFGGGGGGGGGGGGGGGGFFEEGGGGGGHHGGGGCEE\\䬬ឞ__FFFFGGGGGGGGGGGGGGGGGG?GGJJEEGGGGGGmm=z⤤ccGGEEGGGGGGGGGGGGGGGGGGGGGG)GGGG溺 )`䩩ggGGEEGGGGGGGGGGGGGGGGGGGGGGGGGGGG㦦HɼooHHEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGAAAA <<*FF`GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG/GGFFggGGGGHHGGGGJGGGGGGGGGGGGGGGGGGGGGGGG GGGGOOHHGGGGFFGGGG6GGpGGGGGGGGGGGGGGvGGGGKKGGHHGGGGGGGGGG*GGgGGGGGGGG+GGGG??(0` $GGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGGGGGGG GGXGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG{GG-GGFFGGFFGGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG'GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGdGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFlliiGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDDᛛXXEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD܂MMEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD繹mmGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD⡡[[EEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD݈PPFFGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD迿ccFFGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD㥥YYFFGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD绻nnHHFFGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD݅NNEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDDឞZZEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGDD混kkGGFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGEE܁MMEEGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFF``YYEEFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGmGGDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDmDD;;::::ҩ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::o99DDmm3388ҧ8899999999999999888899999999999999999999888888888888778899999999999999999999n>>GGGGsvvzz||||||||||||||{{hhDDAAAAAAAAAAAAAABBuuҤңңңңңЙVV??AAAAAAAAAAAAAAAADDGGuGGGGBYYFFGGGGGGGGGGGGFF\\迿^^EEGGGGGGGGGGGGGGGGGGGGCGGJߢNNFFGGGGGGGGGGGGEEWW游``EEGGGGGGGGGGGGGGGGGGJGGI㧧OOFFGGGGGGGGGGGGEEUU洴ccEEGGGGGGGGGGGGGGGGIGGI䫫QQFFGGGGGGGGGGGGEESS山ffEEGGGGGGGGGGGGGGIGGI寯SSFFGGGGGGGGGGGGFFQQ䬬iiEEGGGGGGGGGGGGJGGI寯RRFFGGGGGGGGGGGGFFPP䭭ggFFGGGGGGGGGG2GGI;<<<<<>GG Vkk¨ffhhhhhhhhggSS>>??????>>HHyyńŃŃ}}LL==??????????EEfHH GG4OOFFGGGGGGIIތ混VVEEGGGGGGGGGGGG5GG9󝝝㤤NNFFGGGGGGGG݈绻YYEEGGGGGGGGGG9GG9󝝝㧧PPFFGGGGGGGG܄迿[[FFGGGGGGGG:GG9󝝝㥥MMGGGGGGGGFF݄羾XXFFGGGGGG,GG9󝝝@.111dd0EE/GG/GG/GG/FF/HH/111110EE/GG0GG&GGGG9󝝝XXY$݄GGGGGGGGGGPP}}EEGGGGGGGG9󝝝Ӌw:JJGGGGGGGG8᝝MMFFHGGGGGGGGHHhh浵ppLLGGGGGGGGGG_CC(混F混ssKKEEGGGGGGGGGGEEPP|?峳NNCCGGHHl88弼 3㨨jOODDGGGGGGGGGGEEMM{{UMM@@GGGGGG罽))GGGGQGGGGGGNNxx缼䬬ffGGGGGG^GG GGGGGGGGGGGGGGGGDD=ލ山llHHEEGGGGGGGG$GGHHGGGGGGII 0irrJJEEGGGGGGGGGGGG GG\\@@JGGGGGGGGGGGGGGxGGHHddGGGGGGGG>GGGGGGGG8GGGG(  GGGGGGGGGGGGFFFFGGGGGGGGGGGGGG/GGGGGGOGGGGGGGGSSKKEEGGGGGGGGGGGGtGGGGGGSGGGGGGEEߑ尰ddFFGGGGGGGGGGyGGGGGGSGGGGGGEEᛛߒLLGGGGGGGGxGGGGGGSGGGGGGDDᛛދLLGGGGGGGGxGGGGGGSGGGGGGEEߎ䨨^^EEGGGGGGGGGGxGGKKBBQCCCCCCDDNNGGBBBBBBDDEEEEEEwEEgdduVVWWVVDD<<==PPiiddFF==>>??NN-ᨤϔMMEE]]⪪QQEEGGGG.1驩ᛛJJEEbb㨨MMGGGG*1驩@)YY#FF$>>&ߔ'((dd'EE!GG/穩]@@$EE㧧ggEEGG P缼vvMMEEFF}}v;MMGGVcc4VVnCCFF\\᜜ggDDHGGffFFFF"尰Yᚚ[[HHGGGGGGGGFFHHᛛ⟟DDJEEGGGGGGclapper-0.8.0/src/bin/clapper-app/windows/clapper.rc.in000066400000000000000000000032471474353110700227760ustar00rootroot00000000000000/* Clapper Application * Copyright (C) 2024 Rafał Dzięgiel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #define VER_CLAPPERVERSION @VER_CLAPPERVERSION@ #define VER_NONE 0x0L MAIN_ICON ICON "clapper.ico" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_CLAPPERVERSION PRODUCTVERSION VER_CLAPPERVERSION FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS VER_NONE FILEOS VOS__WINDOWS32 FILETYPE VFT_APP FILESUBTYPE VER_NONE BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904E4" BEGIN VALUE "CompanyName", "Rafostar" VALUE "FileDescription", @CLAPPER_APP_NAME@ VALUE "FileVersion", @PACKAGE_VERSION@ VALUE "InternalName", @PACKAGE@ VALUE "LegalCopyright", "Copyright (C) Rafal Dziegiel" VALUE "OriginalFilename", @CLAPPER_EXE@ VALUE "ProductName", @CLAPPER_APP_NAME@ VALUE "ProductVersion", @PACKAGE_VERSION@ END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x409, 1252 END END #ifdef __MINGW32__ CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "clapper.exe.manifest" #endif clapper-0.8.0/src/bin/clapper-app/windows/meson.build000066400000000000000000000013611474353110700225520ustar00rootroot00000000000000rc_conf = configuration_data() rc_conf.set('VER_CLAPPERVERSION', clapper_version.replace('.', ',') + ',0') rc_conf.set_quoted('PACKAGE', meson.project_name()) rc_conf.set_quoted('PACKAGE_VERSION', meson.project_version()) rc_conf.set_quoted('PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') rc_conf.set_quoted('CLAPPER_APP_NAME', 'Clapper') rc_conf.set_quoted('CLAPPER_EXE', meson.project_name() + '.exe') clapperapp_rc = configure_file( input: 'clapper.rc.in', output: 'clapper.rc', configuration: rc_conf, ) windres = find_program('windres', required: true) clapperapp_sources += custom_target('clapper.o', input: clapperapp_rc, output: 'clapper.o', command: [windres, '-I', '@CURRENT_SOURCE_DIR@', '-o', '@OUTPUT@', '@INPUT@'] ) clapper-0.8.0/src/bin/meson.build000066400000000000000000000000261474353110700166510ustar00rootroot00000000000000subdir('clapper-app') clapper-0.8.0/src/lib/000077500000000000000000000000001474353110700145075ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper-gtk/000077500000000000000000000000001474353110700167205ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-billboard.c000066400000000000000000000337571474353110700232440ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkBillboard: * * A layer where various messages can be displayed. * * #ClapperGtkBillboard widget is meant to be overlaid on top of * [class@ClapperGtk.Video] as a normal (non-fading) overlay. * * It is used to display various messages/announcements and later * takes care of fading them on its own. * * If automatic volume/speed change notifications when their values do * change are desired, functions for announcing them can be run in callbacks * to corresponding property notify signals on the [class@Clapper.Player]. */ #include "config.h" #include #include #include "clapper-gtk-billboard.h" #include "clapper-gtk-utils-private.h" #define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) #define WORDS_PER_MSECOND 0.004 #define GST_CAT_DEFAULT clapper_gtk_billboard_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkBillboard { ClapperGtkContainer parent; GtkWidget *side_revealer; GtkWidget *progress_revealer; GtkWidget *progress_box; GtkWidget *top_progress; GtkWidget *bottom_progress; GtkWidget *progress_image; GtkWidget *progress_label; GtkWidget *message_revealer; GtkWidget *message_image; GtkWidget *message_label; gboolean mute; gboolean has_pinned; guint side_timeout; guint message_timeout; ClapperPlayer *player; }; #define parent_class clapper_gtk_billboard_parent_class G_DEFINE_TYPE (ClapperGtkBillboard, clapper_gtk_billboard, CLAPPER_GTK_TYPE_CONTAINER) /* We calculate estimated read time. This allows * translated text to be displayed as long as * necessary without app developer caring. */ static guint _estimate_read_time (const gchar *text) { guint i, n_words = 1; guint read_time; for (i = 0; text[i] != '\0'; ++i) { if (text[i] == ' ' || text[i] == '\n') n_words++; } read_time = MAX (1500, (n_words / WORDS_PER_MSECOND) + 500); GST_DEBUG ("Estimated message read time: %u", read_time); return read_time; } static void _unreveal_side_delay_cb (ClapperGtkBillboard *self) { GST_LOG_OBJECT (self, "Unreveal side handler reached"); self->side_timeout = 0; gtk_revealer_set_reveal_child (GTK_REVEALER (self->side_revealer), FALSE); } static void _unreveal_message_delay_cb (ClapperGtkBillboard *self) { GST_LOG_OBJECT (self, "Unreveal message handler reached"); self->message_timeout = 0; gtk_revealer_set_reveal_child (GTK_REVEALER (self->message_revealer), FALSE); } static void _reset_fade_side_timeout (ClapperGtkBillboard *self) { GST_TRACE_OBJECT (self, "Fade side timeout reset"); g_clear_handle_id (&self->side_timeout, g_source_remove); self->side_timeout = g_timeout_add_once (1500, (GSourceOnceFunc) _unreveal_side_delay_cb, self); } static void _reset_fade_message_timeout (ClapperGtkBillboard *self) { const gchar *text = gtk_label_get_text (GTK_LABEL (self->message_label)); GST_TRACE_OBJECT (self, "Fade side timeout reset"); g_clear_handle_id (&self->message_timeout, g_source_remove); self->message_timeout = g_timeout_add_once ( _estimate_read_time (text), (GSourceOnceFunc) _unreveal_message_delay_cb, self); } static void adapt_cb (ClapperGtkContainer *container, gboolean adapt, ClapperGtkBillboard *self) { GST_DEBUG_OBJECT (self, "Adapted: %s", (adapt) ? "yes" : "no"); gtk_revealer_set_reveal_child (GTK_REVEALER (self->progress_revealer), !adapt); } static void revealer_revealed_cb (GtkRevealer *revealer, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkBillboard *self) { if (!gtk_revealer_get_child_revealed (revealer)) { GtkWidget *other_revealer = (GTK_WIDGET (revealer) == self->side_revealer) ? self->message_revealer : self->side_revealer; gtk_widget_set_visible (GTK_WIDGET (revealer), FALSE); /* We only hide here when nothing is posted on the board, * visiblity is set to TRUE when post is made */ if (!gtk_revealer_get_child_revealed (GTK_REVEALER (other_revealer))) gtk_widget_set_visible (GTK_WIDGET (self), FALSE); } else { if ((GTK_WIDGET (revealer) == self->side_revealer)) _reset_fade_side_timeout (self); else if (!self->has_pinned) _reset_fade_message_timeout (self); } } static void reveal_side (ClapperGtkBillboard *self) { g_clear_handle_id (&self->side_timeout, g_source_remove); gtk_widget_set_visible (GTK_WIDGET (self), TRUE); gtk_widget_set_visible (self->side_revealer, TRUE); gtk_revealer_set_reveal_child (GTK_REVEALER (self->side_revealer), TRUE); if (gtk_revealer_get_child_revealed (GTK_REVEALER (self->side_revealer))) _reset_fade_side_timeout (self); } static void _post_message_internal (ClapperGtkBillboard *self, const gchar *icon_name, const gchar *message, gboolean pin) { if (self->has_pinned) return; self->has_pinned = pin; gtk_image_set_from_icon_name (GTK_IMAGE (self->message_image), icon_name); gtk_label_set_label (GTK_LABEL (self->message_label), message); g_clear_handle_id (&self->message_timeout, g_source_remove); gtk_widget_set_visible (GTK_WIDGET (self), TRUE); gtk_widget_set_visible (self->message_revealer, TRUE); gtk_revealer_set_reveal_child (GTK_REVEALER (self->message_revealer), TRUE); if (!self->has_pinned && gtk_revealer_get_child_revealed (GTK_REVEALER (self->message_revealer))) _reset_fade_message_timeout (self); } static void _player_mute_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkBillboard *self) { self->mute = clapper_player_get_mute (player); clapper_gtk_billboard_announce_volume (self); } /** * clapper_gtk_billboard_new: * * Creates a new #ClapperGtkBillboard instance. * * Returns: a new billboard #GtkWidget. */ GtkWidget * clapper_gtk_billboard_new (void) { return g_object_new (CLAPPER_GTK_TYPE_BILLBOARD, NULL); } /** * clapper_gtk_billboard_post_message: * @billboard: a #ClapperGtkBillboard * @icon_name: an icon name * @message: a message text * * Posts a temporary message on the @billboard. * * Duration how long a message will stay is automatically * calculated based on amount of text. */ void clapper_gtk_billboard_post_message (ClapperGtkBillboard *self, const gchar *icon_name, const gchar *message) { _post_message_internal (self, icon_name, message, FALSE); } /** * clapper_gtk_billboard_pin_message: * @billboard: a #ClapperGtkBillboard * @icon_name: an icon name * @message: a message text * * Pins a permanent message on the @billboard. * * The message will stay on the @billboard until a * [method@ClapperGtk.Billboard.unpin_pinned_message] is called. */ void clapper_gtk_billboard_pin_message (ClapperGtkBillboard *self, const gchar *icon_name, const gchar *message) { _post_message_internal (self, icon_name, message, TRUE); } /** * clapper_gtk_billboard_unpin_pinned_message: * @billboard: a #ClapperGtkBillboard * * Unpins previously pinned message on the @billboard. * * If no message was pinned this function will do nothing, * so it is safe to call when unsure. */ void clapper_gtk_billboard_unpin_pinned_message (ClapperGtkBillboard *self) { if (!self->has_pinned) return; _unreveal_message_delay_cb (self); self->has_pinned = FALSE; } /** * clapper_gtk_billboard_announce_volume: * @billboard: a #ClapperGtkBillboard * * Temporarily displays current volume level on the * side of @billboard. * * Use this if you want to present current volume level to the user. */ void clapper_gtk_billboard_announce_volume (ClapperGtkBillboard *self) { gdouble volume = PERCENTAGE_ROUND (clapper_player_get_volume (self->player)); gchar *percent_str; gboolean has_overamp; /* Revert popup_speed changes */ gtk_progress_bar_set_inverted (GTK_PROGRESS_BAR (self->bottom_progress), TRUE); has_overamp = gtk_widget_has_css_class (self->progress_box, "overamp"); percent_str = g_strdup_printf ("%.0lf%%", volume * 100); if (volume <= 1.0) { gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), 0.0); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), volume); if (has_overamp) gtk_widget_remove_css_class (self->progress_box, "overamp"); } else { gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), volume - 1.0); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), 1.0); if (!has_overamp) gtk_widget_add_css_class (self->progress_box, "overamp"); } gtk_image_set_from_icon_name (GTK_IMAGE (self->progress_image), clapper_gtk_get_icon_name_for_volume ((!self->mute) ? volume : 0)); gtk_label_set_label (GTK_LABEL (self->progress_label), percent_str); g_free (percent_str); reveal_side (self); } /** * clapper_gtk_billboard_announce_speed: * @billboard: a #ClapperGtkBillboard * * Temporarily displays current speed value on the * side of @billboard. * * Use this if you want to present current speed value to the user. */ void clapper_gtk_billboard_announce_speed (ClapperGtkBillboard *self) { gdouble speed = PERCENTAGE_ROUND (clapper_player_get_speed (self->player)); gchar *speed_str; /* Revert popup_volume changes */ if (gtk_widget_has_css_class (self->progress_box, "overamp")) gtk_widget_remove_css_class (self->progress_box, "overamp"); gtk_progress_bar_set_inverted (GTK_PROGRESS_BAR (self->bottom_progress), FALSE); speed_str = g_strdup_printf ("%.2lfx", speed); if (speed <= 1.0) { gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), 0.0); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), 1.0 - speed); } else { gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->top_progress), speed - 1.0); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->bottom_progress), 0.0); } gtk_image_set_from_icon_name (GTK_IMAGE (self->progress_image), clapper_gtk_get_icon_name_for_speed (speed)); gtk_label_set_label (GTK_LABEL (self->progress_label), speed_str); g_free (speed_str); reveal_side (self); } static void clapper_gtk_billboard_root (GtkWidget *widget) { ClapperGtkBillboard *self = CLAPPER_GTK_BILLBOARD_CAST (widget); GTK_WIDGET_CLASS (parent_class)->root (widget); if ((self->player = clapper_gtk_get_player_from_ancestor (widget))) { g_signal_connect (self->player, "notify::mute", G_CALLBACK (_player_mute_changed_cb), self); self->mute = clapper_player_get_mute (self->player); } } static void clapper_gtk_billboard_unroot (GtkWidget *widget) { ClapperGtkBillboard *self = CLAPPER_GTK_BILLBOARD_CAST (widget); if (self->player) { g_signal_handlers_disconnect_by_func (self->player, _player_mute_changed_cb, self); self->player = NULL; } /* Reset in case of rooted again not within video widget */ self->mute = FALSE; GTK_WIDGET_CLASS (parent_class)->unroot (widget); } static void clapper_gtk_billboard_init (ClapperGtkBillboard *self) { gtk_widget_init_template (GTK_WIDGET (self)); } static void clapper_gtk_billboard_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_BILLBOARD); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_billboard_finalize (GObject *object) { ClapperGtkBillboard *self = CLAPPER_GTK_BILLBOARD_CAST (object); g_clear_handle_id (&self->side_timeout, g_source_remove); g_clear_handle_id (&self->message_timeout, g_source_remove); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_gtk_billboard_class_init (ClapperGtkBillboardClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkbillboard", 0, "Clapper GTK Billboard"); gobject_class->dispose = clapper_gtk_billboard_dispose; gobject_class->finalize = clapper_gtk_billboard_finalize; /* Using root/unroot since initially invisible (unrealized) * and we want for signals to stay connected as long as parented */ widget_class->root = clapper_gtk_billboard_root; widget_class->unroot = clapper_gtk_billboard_unroot; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-billboard.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, side_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_box); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, top_progress); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, bottom_progress); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_image); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, progress_label); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, message_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, message_image); gtk_widget_class_bind_template_child (widget_class, ClapperGtkBillboard, message_label); gtk_widget_class_bind_template_callback (widget_class, adapt_cb); gtk_widget_class_bind_template_callback (widget_class, revealer_revealed_cb); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-billboard"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-billboard.h000066400000000000000000000040551474353110700232360ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_BILLBOARD (clapper_gtk_billboard_get_type()) #define CLAPPER_GTK_BILLBOARD_CAST(obj) ((ClapperGtkBillboard *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkBillboard, clapper_gtk_billboard, CLAPPER_GTK, BILLBOARD, ClapperGtkContainer) CLAPPER_GTK_API GtkWidget * clapper_gtk_billboard_new (void); CLAPPER_GTK_API void clapper_gtk_billboard_post_message (ClapperGtkBillboard *billboard, const gchar *icon_name, const gchar *message); CLAPPER_GTK_API void clapper_gtk_billboard_pin_message (ClapperGtkBillboard *billboard, const gchar *icon_name, const gchar *message); CLAPPER_GTK_API void clapper_gtk_billboard_unpin_pinned_message (ClapperGtkBillboard *billboard); CLAPPER_GTK_API void clapper_gtk_billboard_announce_volume (ClapperGtkBillboard *billboard); CLAPPER_GTK_API void clapper_gtk_billboard_announce_speed (ClapperGtkBillboard *billboard); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-buffering-animation-private.h000066400000000000000000000027561474353110700267060ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include #include "clapper-gtk-container.h" G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_BUFFERING_ANIMATION (clapper_gtk_buffering_animation_get_type()) #define CLAPPER_GTK_BUFFERING_ANIMATION_CAST(obj) ((ClapperGtkBufferingAnimation *)(obj)) G_DECLARE_FINAL_TYPE (ClapperGtkBufferingAnimation, clapper_gtk_buffering_animation, CLAPPER_GTK, BUFFERING_ANIMATION, ClapperGtkContainer) G_GNUC_INTERNAL void clapper_gtk_buffering_animation_start (ClapperGtkBufferingAnimation *animation); G_GNUC_INTERNAL void clapper_gtk_buffering_animation_stop (ClapperGtkBufferingAnimation *animation); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-buffering-animation.c000066400000000000000000000103621474353110700252210ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-gtk-buffering-animation-private.h" #include "clapper-gtk-buffering-paintable-private.h" #define MIN_STEP_DELAY 30000 #define GST_CAT_DEFAULT clapper_gtk_buffering_animation_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkBufferingAnimation { ClapperGtkContainer parent; ClapperGtkBufferingPaintable *buffering_paintable; guint tick_id; gint64 last_tick; }; #define parent_class clapper_gtk_buffering_animation_parent_class G_DEFINE_TYPE (ClapperGtkBufferingAnimation, clapper_gtk_buffering_animation, CLAPPER_GTK_TYPE_CONTAINER) static gboolean _animation_tick (GtkWidget *picture, GdkFrameClock *frame_clock, ClapperGtkBufferingAnimation *self) { gint64 now = gdk_frame_clock_get_frame_time (frame_clock); /* We do not want for this animation to move too fast */ if (now - self->last_tick >= MIN_STEP_DELAY) { GST_LOG_OBJECT (self, "Animation step, last: %" G_GINT64_FORMAT ", now: %" G_GINT64_FORMAT, self->last_tick, now); clapper_gtk_buffering_paintable_step (self->buffering_paintable); self->last_tick = now; } return G_SOURCE_CONTINUE; } void clapper_gtk_buffering_animation_start (ClapperGtkBufferingAnimation *self) { GtkWidget *picture; if (self->tick_id != 0) return; GST_DEBUG_OBJECT (self, "Animation start"); picture = clapper_gtk_container_get_child (CLAPPER_GTK_CONTAINER (self)); self->tick_id = gtk_widget_add_tick_callback (picture, (GtkTickCallback) _animation_tick, self, NULL); } void clapper_gtk_buffering_animation_stop (ClapperGtkBufferingAnimation *self) { GtkWidget *picture; if (self->tick_id == 0) return; GST_DEBUG_OBJECT (self, "Animation stop"); picture = clapper_gtk_container_get_child (CLAPPER_GTK_CONTAINER (self)); gtk_widget_remove_tick_callback (picture, self->tick_id); self->tick_id = 0; self->last_tick = 0; clapper_gtk_buffering_paintable_reset (self->buffering_paintable); } static void clapper_gtk_buffering_animation_init (ClapperGtkBufferingAnimation *self) { GtkWidget *picture = gtk_picture_new (); self->buffering_paintable = clapper_gtk_buffering_paintable_new (); gtk_picture_set_paintable (GTK_PICTURE (picture), GDK_PAINTABLE (self->buffering_paintable)); clapper_gtk_container_set_child (CLAPPER_GTK_CONTAINER (self), picture); } static void clapper_gtk_buffering_animation_unmap (GtkWidget *widget) { ClapperGtkBufferingAnimation *self = CLAPPER_GTK_BUFFERING_ANIMATION_CAST (widget); clapper_gtk_buffering_animation_stop (self); GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void clapper_gtk_buffering_animation_finalize (GObject *object) { ClapperGtkBufferingAnimation *self = CLAPPER_GTK_BUFFERING_ANIMATION_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_object_unref (self->buffering_paintable); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_gtk_buffering_animation_class_init (ClapperGtkBufferingAnimationClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkbufferinganimation", 0, "Clapper GTK Buffering Animation"); gobject_class->finalize = clapper_gtk_buffering_animation_finalize; widget_class->unmap = clapper_gtk_buffering_animation_unmap; gtk_widget_class_set_css_name (widget_class, "clapper-gtk-buffering-animation"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-buffering-paintable-private.h000066400000000000000000000030561474353110700266600ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_BUFFERING_PAINTABLE (clapper_gtk_buffering_paintable_get_type()) #define CLAPPER_GTK_BUFFERING_PAINTABLE_CAST(obj) ((ClapperGtkBufferingPaintable *)(obj)) G_DECLARE_FINAL_TYPE (ClapperGtkBufferingPaintable, clapper_gtk_buffering_paintable, CLAPPER_GTK, BUFFERING_PAINTABLE, GObject) G_GNUC_INTERNAL ClapperGtkBufferingPaintable * clapper_gtk_buffering_paintable_new (void); G_GNUC_INTERNAL void clapper_gtk_buffering_paintable_step (ClapperGtkBufferingPaintable *buffering_paintable); G_GNUC_INTERNAL void clapper_gtk_buffering_paintable_reset (ClapperGtkBufferingPaintable *buffering_paintable); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-buffering-paintable.c000066400000000000000000000132251474353110700252020ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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-gtk-buffering-paintable-private.h" #define CIRCLE_MAX_SIZE 48 #define CIRCLE_SPACING 10 #define CIRCLE_OUTLINE 2 #define INTRINSIC_SIZE 184 // 3 * CIRCLE_MAX_SIZE + 4 * CIRCLE_SPACING #define BLACK ((GdkRGBA) { 0, 0, 0, 1 }) #define WHITE ((GdkRGBA) { 1, 1, 1, 1 }) struct _ClapperGtkBufferingPaintable { GObject parent; gfloat sizes[3]; // current size of each circle gboolean reverses[3]; // grow/shrink direction gboolean initialized[3]; // big enough to start growing the next one }; static GdkPaintableFlags clapper_gtk_buffering_paintable_get_flags (GdkPaintable *paintable) { return GDK_PAINTABLE_STATIC_SIZE; } static gint clapper_gtk_buffering_paintable_get_intrinsic_size (GdkPaintable *paintable) { return INTRINSIC_SIZE; } static void _draw_scaled_circle (GdkSnapshot *snapshot, gfloat scale) { GskRoundedRect outline; gfloat half_size = ((gfloat) CIRCLE_MAX_SIZE / 2) * scale; gfloat inside_size = ((gfloat) (CIRCLE_MAX_SIZE - 2 * CIRCLE_OUTLINE) / 2) * scale; /* Draw white inner circle */ gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT (-inside_size, -inside_size, 2 * inside_size, 2 * inside_size), inside_size); gtk_snapshot_append_border (snapshot, &outline, (gfloat[4]) { inside_size, inside_size, inside_size, inside_size }, (GdkRGBA[4]) { WHITE, WHITE, WHITE, WHITE }); /* Draw black circle border */ gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT (-half_size, -half_size, 2 * half_size, 2 * half_size), half_size); gtk_snapshot_append_border (snapshot, &outline, (gfloat[4]) { CIRCLE_OUTLINE, CIRCLE_OUTLINE, CIRCLE_OUTLINE, CIRCLE_OUTLINE }, (GdkRGBA[4]) { BLACK, BLACK, BLACK, BLACK }); } static void clapper_gtk_buffering_paintable_snapshot (GdkPaintable *paintable, GdkSnapshot *snapshot, gdouble width, gdouble height) { ClapperGtkBufferingPaintable *self = CLAPPER_GTK_BUFFERING_PAINTABLE_CAST (paintable); guint i; gtk_snapshot_save (snapshot); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (0, height / 2)); gtk_snapshot_scale (snapshot, MIN (width, height) / INTRINSIC_SIZE, MIN (width, height) / INTRINSIC_SIZE); for (i = 0; i < 3; ++i) { gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (CIRCLE_SPACING + CIRCLE_MAX_SIZE / 2, 0)); _draw_scaled_circle (snapshot, self->sizes[i]); gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (CIRCLE_MAX_SIZE / 2, 0)); } gtk_snapshot_restore (snapshot); } static GdkPaintable * clapper_gtk_buffering_paintable_get_current_image (GdkPaintable *paintable) { ClapperGtkBufferingPaintable *self = CLAPPER_GTK_BUFFERING_PAINTABLE_CAST (paintable); ClapperGtkBufferingPaintable *copy = clapper_gtk_buffering_paintable_new (); guint i; /* Only current sizes are needed for static image */ for (i = 0; i < 3; ++i) copy->sizes[i] = self->sizes[i]; return GDK_PAINTABLE (copy); } static void _paintable_iface_init (GdkPaintableInterface *iface) { iface->get_flags = clapper_gtk_buffering_paintable_get_flags; iface->get_intrinsic_width = clapper_gtk_buffering_paintable_get_intrinsic_size; iface->get_intrinsic_height = clapper_gtk_buffering_paintable_get_intrinsic_size; iface->snapshot = clapper_gtk_buffering_paintable_snapshot; iface->get_current_image = clapper_gtk_buffering_paintable_get_current_image; } #define parent_class clapper_gtk_buffering_paintable_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperGtkBufferingPaintable, clapper_gtk_buffering_paintable, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, _paintable_iface_init)) ClapperGtkBufferingPaintable * clapper_gtk_buffering_paintable_new (void) { return g_object_new (CLAPPER_GTK_TYPE_BUFFERING_PAINTABLE, NULL); } void clapper_gtk_buffering_paintable_step (ClapperGtkBufferingPaintable *self) { guint i; for (i = 0; i < 3; ++i) { /* If previous circle is not big enough * do not start animating the next one */ if (i > 0 && !self->initialized[i - 1]) break; if (!self->initialized[i] && self->sizes[i] >= 0.3) self->initialized[i] = TRUE; self->sizes[i] += (self->reverses[i]) ? -0.04 : 0.04; if (self->sizes[i] > 1.0) { self->sizes[i] = 1.0; self->reverses[i] = TRUE; } else if (self->sizes[i] < 0.0) { self->sizes[i] = 0.0; self->reverses[i] = FALSE; } } gdk_paintable_invalidate_contents ((GdkPaintable *) self); } void clapper_gtk_buffering_paintable_reset (ClapperGtkBufferingPaintable *self) { guint i; for (i = 0; i < 3; ++i) { self->sizes[i] = 0; self->reverses[i] = FALSE; self->initialized[i] = FALSE; } gdk_paintable_invalidate_contents ((GdkPaintable *) self); } static void clapper_gtk_buffering_paintable_init (ClapperGtkBufferingPaintable *self) { } static void clapper_gtk_buffering_paintable_class_init (ClapperGtkBufferingPaintableClass *klass) { } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-container-private.h000066400000000000000000000017671474353110700247450ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 "clapper-gtk-container.h" G_BEGIN_DECLS void clapper_gtk_container_emit_adapt (ClapperGtkContainer *container, gboolean adapt); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-container.c000066400000000000000000000307641474353110700232670ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkContainer: * * A simple container widget that holds just one child. * * It is designed to work well with OSD overlay, adding some useful functionalities * to it, such as width and height that widget should target. This helps with * implementing simple adaptive widgets by observing its own width and signalling * when adaptive threshold is reached. * * You can use this when you need to create a widget that is adaptive or should have * a limited maximal width/height. * * If you need to have more then single widget as child, place a widget that * can hold multiple children such as [class@Gtk.Box] as a single conatiner child * and then your widgets into that child. */ #include "clapper-gtk-container.h" #include "clapper-gtk-container-private.h" #include "clapper-gtk-limited-layout-private.h" #define parent_class clapper_gtk_container_parent_class G_DEFINE_TYPE (ClapperGtkContainer, clapper_gtk_container, GTK_TYPE_WIDGET) enum { PROP_0, PROP_WIDTH_TARGET, PROP_HEIGHT_TARGET, PROP_ADAPTIVE_WIDTH, PROP_ADAPTIVE_HEIGHT, PROP_LAST }; enum { SIGNAL_ADAPT, SIGNAL_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static guint signals[SIGNAL_LAST] = { 0, }; static inline void _unparent_child (ClapperGtkContainer *self) { GtkWidget *child; if ((child = gtk_widget_get_first_child (GTK_WIDGET (self)))) gtk_widget_unparent (child); } /** * clapper_gtk_container_new: * * Creates a new #ClapperGtkContainer instance. * * Returns: a new container #GtkWidget. */ GtkWidget * clapper_gtk_container_new (void) { return g_object_new (CLAPPER_GTK_TYPE_CONTAINER, NULL); } /** * clapper_gtk_container_set_child: * @container: a #ClapperGtkContainer * @child: a #GtkWidget * * Set a child #GtkWidget of @container. */ void clapper_gtk_container_set_child (ClapperGtkContainer *self, GtkWidget *child) { g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); g_return_if_fail (GTK_IS_WIDGET (child)); _unparent_child (self); gtk_widget_set_parent (child, GTK_WIDGET (self)); } /** * clapper_gtk_container_get_child: * @container: a #ClapperGtkContainer * * Get a child #GtkWidget of @container. * * Returns: (transfer none) (nullable): #GtkWidget set as child. */ GtkWidget * clapper_gtk_container_get_child (ClapperGtkContainer *self) { g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), NULL); return gtk_widget_get_first_child (GTK_WIDGET (self)); } /** * clapper_gtk_container_set_width_target: * @container: a #ClapperGtkContainer * @width: width to target -1 to restore default behavior * * Set a width that @container should target. When set container * will not stretch beyond set @width while still expanding into * possible boundaries trying to reach its target. */ void clapper_gtk_container_set_width_target (ClapperGtkContainer *self, gint width) { GtkLayoutManager *layout; g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); clapper_gtk_limited_layout_set_max_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), width); } /** * clapper_gtk_container_get_width_target: * @container: a #ClapperGtkContainer * * Get a @container width target. * * Returns: width target set by user or -1 when none. */ gint clapper_gtk_container_get_width_target (ClapperGtkContainer *self) { GtkLayoutManager *layout; g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); return clapper_gtk_limited_layout_get_max_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); } /** * clapper_gtk_container_set_height_target: * @container: a #ClapperGtkContainer * @height: height to target or -1 to restore default behavior * * Same as clapper_gtk_container_set_width_target() but for widget height. */ void clapper_gtk_container_set_height_target (ClapperGtkContainer *self, gint height) { GtkLayoutManager *layout; g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); clapper_gtk_limited_layout_set_max_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), height); } /** * clapper_gtk_container_get_height_target: * @container: a #ClapperGtkContainer * * Get a @container height target. * * Returns: height target set by user or -1 when none. */ gint clapper_gtk_container_get_height_target (ClapperGtkContainer *self) { GtkLayoutManager *layout; g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); return clapper_gtk_limited_layout_get_max_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); } /** * clapper_gtk_container_set_adaptive_width: * @container: a #ClapperGtkContainer * @width: a threshold on which adapt signal should be triggered or -1 to disable. * * Set an adaptive width threshold. When widget is resized to value or lower, * an [signal@ClapperGtk.Container::adapt] signal will be emitted with %TRUE to * notify implementation about mobile adaptation request, otherwise %FALSE when * both threshold values are exceeded. */ void clapper_gtk_container_set_adaptive_width (ClapperGtkContainer *self, gint width) { GtkLayoutManager *layout; g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); clapper_gtk_limited_layout_set_adaptive_width ( CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), width); } /** * clapper_gtk_container_get_adaptive_width: * @container: a #ClapperGtkContainer * * Get a @container adaptive width threshold. * * Returns: adaptive width set by user or -1 when none. */ gint clapper_gtk_container_get_adaptive_width (ClapperGtkContainer *self) { GtkLayoutManager *layout; g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); return clapper_gtk_limited_layout_get_adaptive_width (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); } /** * clapper_gtk_container_set_adaptive_height: * @container: a #ClapperGtkContainer * @height: a threshold on which adapt signal should be triggered or -1 to disable. * * Set an adaptive height threshold. When widget is resized to value or lower, * an [signal@ClapperGtk.Container::adapt] signal will be emitted with %TRUE to * notify implementation about mobile adaptation request, otherwise %FALSE when * both threshold values are exceeded. */ void clapper_gtk_container_set_adaptive_height (ClapperGtkContainer *self, gint height) { GtkLayoutManager *layout; g_return_if_fail (CLAPPER_GTK_IS_CONTAINER (self)); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); clapper_gtk_limited_layout_set_adaptive_height ( CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout), height); } /** * clapper_gtk_container_get_adaptive_height: * @container: a #ClapperGtkContainer * * Get a @container adaptive height threshold. * * Returns: adaptive height set by user or -1 when none. */ gint clapper_gtk_container_get_adaptive_height (ClapperGtkContainer *self) { GtkLayoutManager *layout; g_return_val_if_fail (CLAPPER_GTK_IS_CONTAINER (self), -1); layout = gtk_widget_get_layout_manager (GTK_WIDGET (self)); return clapper_gtk_limited_layout_get_adaptive_height (CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout)); } void clapper_gtk_container_emit_adapt (ClapperGtkContainer *self, gboolean adapt) { if (g_signal_handler_find (self, G_SIGNAL_MATCH_ID, signals[SIGNAL_ADAPT], 0, NULL, NULL, NULL) != 0) { g_signal_emit (self, signals[SIGNAL_ADAPT], 0, adapt); } } static void clapper_gtk_container_init (ClapperGtkContainer *self) { } static void clapper_gtk_container_dispose (GObject *object) { ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object); _unparent_child (self); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_container_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object); switch (prop_id) { case PROP_WIDTH_TARGET: g_value_set_int (value, clapper_gtk_container_get_width_target (self)); break; case PROP_HEIGHT_TARGET: g_value_set_int (value, clapper_gtk_container_get_height_target (self)); break; case PROP_ADAPTIVE_WIDTH: g_value_set_int (value, clapper_gtk_container_get_adaptive_width (self)); break; case PROP_ADAPTIVE_HEIGHT: g_value_set_int (value, clapper_gtk_container_get_adaptive_height (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_container_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkContainer *self = CLAPPER_GTK_CONTAINER_CAST (object); switch (prop_id) { case PROP_WIDTH_TARGET: clapper_gtk_container_set_width_target (self, g_value_get_int (value)); break; case PROP_HEIGHT_TARGET: clapper_gtk_container_set_height_target (self, g_value_get_int (value)); break; case PROP_ADAPTIVE_WIDTH: clapper_gtk_container_set_adaptive_width (self, g_value_get_int (value)); break; case PROP_ADAPTIVE_HEIGHT: clapper_gtk_container_set_adaptive_height (self, g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_container_class_init (ClapperGtkContainerClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; gobject_class->get_property = clapper_gtk_container_get_property; gobject_class->set_property = clapper_gtk_container_set_property; gobject_class->dispose = clapper_gtk_container_dispose; /** * ClapperGtkContainer:width-target: * * Width that container should target. */ param_specs[PROP_WIDTH_TARGET] = g_param_spec_int ("width-target", NULL, NULL, -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkContainer:height-target: * * Height that container should target. */ param_specs[PROP_HEIGHT_TARGET] = g_param_spec_int ("height-target", NULL, NULL, -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkContainer:adaptive-width: * * Adaptive width threshold that triggers [signal@ClapperGtk.Container::adapt] signal. */ param_specs[PROP_ADAPTIVE_WIDTH] = g_param_spec_int ("adaptive-width", NULL, NULL, -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkContainer:adaptive-height: * * Adaptive height threshold that triggers [signal@ClapperGtk.Container::adapt] signal. */ param_specs[PROP_ADAPTIVE_HEIGHT] = g_param_spec_int ("adaptive-height", NULL, NULL, -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkContainer::adapt: * @container: a #ClapperGtkContainer * @adapt: %TRUE if narrowness reached adaptive threshold, %FALSE otherwise * * A helper signal for implementing mobile/narrow adaptive * behavior on descendants. */ signals[SIGNAL_ADAPT] = g_signal_new ("adapt", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_layout_manager_type (widget_class, CLAPPER_GTK_TYPE_LIMITED_LAYOUT); gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-container"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-container.h000066400000000000000000000050701474353110700232640ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_CONTAINER (clapper_gtk_container_get_type()) #define CLAPPER_GTK_CONTAINER_CAST(obj) ((ClapperGtkContainer *)(obj)) CLAPPER_GTK_API G_DECLARE_DERIVABLE_TYPE (ClapperGtkContainer, clapper_gtk_container, CLAPPER_GTK, CONTAINER, GtkWidget) struct _ClapperGtkContainerClass { GtkWidgetClass parent_class; /*< private >*/ gpointer padding[4]; }; CLAPPER_GTK_API GtkWidget * clapper_gtk_container_new (void); CLAPPER_GTK_API void clapper_gtk_container_set_child (ClapperGtkContainer *container, GtkWidget *child); CLAPPER_GTK_API GtkWidget * clapper_gtk_container_get_child (ClapperGtkContainer *container); CLAPPER_GTK_API void clapper_gtk_container_set_width_target (ClapperGtkContainer *container, gint width); CLAPPER_GTK_API gint clapper_gtk_container_get_width_target (ClapperGtkContainer *container); CLAPPER_GTK_API void clapper_gtk_container_set_height_target (ClapperGtkContainer *container, gint height); CLAPPER_GTK_API gint clapper_gtk_container_get_height_target (ClapperGtkContainer *container); CLAPPER_GTK_API void clapper_gtk_container_set_adaptive_width (ClapperGtkContainer *container, gint width); CLAPPER_GTK_API gint clapper_gtk_container_get_adaptive_width (ClapperGtkContainer *container); CLAPPER_GTK_API void clapper_gtk_container_set_adaptive_height (ClapperGtkContainer *container, gint height); CLAPPER_GTK_API gint clapper_gtk_container_get_adaptive_height (ClapperGtkContainer *container); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-enums.h000066400000000000000000000036071474353110700224350ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include G_BEGIN_DECLS /** * ClapperGtkVideoActionMask: * @CLAPPER_GTK_VIDEO_ACTION_NONE: No action * @CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS: Reveal fading overlays * @CLAPPER_GTK_VIDEO_ACTION_TOGGLE_PLAY: Toggle playback (triggered by single click/tap) * @CLAPPER_GTK_VIDEO_ACTION_TOGGLE_FULLSCREEN: Toggle fullscreen (triggered by double click/tap) * @CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST: Seek request (triggered by double tap on screen side) * @CLAPPER_GTK_VIDEO_ACTION_ANY: All of the above */ typedef enum { CLAPPER_GTK_VIDEO_ACTION_NONE = 0, CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS = 1 << 0, CLAPPER_GTK_VIDEO_ACTION_TOGGLE_PLAY = 1 << 1, CLAPPER_GTK_VIDEO_ACTION_TOGGLE_FULLSCREEN = 1 << 2, CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST = 1 << 3, CLAPPER_GTK_VIDEO_ACTION_ANY = 0x3FFFFFF } ClapperGtkVideoActionMask; G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c000066400000000000000000000645031474353110700247010ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkExtraMenuButton: * * A menu button with extra options. */ #include "config.h" #include #include #include #include "clapper-gtk-extra-menu-button.h" #include "clapper-gtk-stream-check-button-private.h" #include "clapper-gtk-utils-private.h" #define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) #define DEFAULT_CAN_OPEN_SUBTITLES FALSE #define GST_CAT_DEFAULT clapper_gtk_extra_menu_button_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkExtraMenuButton { GtkWidget parent; GtkWidget *menu_button; GtkWidget *volume_box; GtkWidget *volume_button; GtkWidget *volume_spin; GtkWidget *speed_box; GtkWidget *speed_button; GtkWidget *speed_spin; GtkWidget *top_separator; GtkWidget *video_list_view; GtkScrolledWindow *video_sw; GtkWidget *audio_list_view; GtkScrolledWindow *audio_sw; GtkWidget *subtitle_list_view; GtkScrolledWindow *subtitle_sw; ClapperPlayer *player; ClapperMediaItem *current_item; GSimpleActionGroup *action_group; gboolean mute; GBinding *volume_binding; GBinding *speed_binding; GBinding *video_binding; GBinding *audio_binding; GBinding *subtitle_binding; gboolean can_open_subtitles; }; #define parent_class clapper_gtk_extra_menu_button_parent_class G_DEFINE_TYPE (ClapperGtkExtraMenuButton, clapper_gtk_extra_menu_button, GTK_TYPE_WIDGET) enum { PROP_0, PROP_VOLUME_VISIBLE, PROP_SPEED_VISIBLE, PROP_CAN_OPEN_SUBTITLES, PROP_LAST }; enum { SIGNAL_OPEN_SUBTITLES, SIGNAL_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static guint signals[SIGNAL_LAST] = { 0, }; static void _set_action_enabled (ClapperGtkExtraMenuButton *self, const gchar *name, gboolean enabled) { GAction *action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group), name); gboolean was_enabled = g_action_get_enabled (action); if (was_enabled == enabled) return; g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled); } static gboolean _double_transform_func (GBinding *binding, const GValue *from_value, GValue *to_value, gpointer user_data G_GNUC_UNUSED) { gdouble val_dbl = g_value_get_double (from_value); g_value_set_double (to_value, PERCENTAGE_ROUND (val_dbl)); return TRUE; } static gint volume_spin_input_cb (GtkSpinButton *spin_button, gdouble *value, ClapperGtkExtraMenuButton *self) { const gchar *text = gtk_editable_get_text (GTK_EDITABLE (spin_button)); gdouble volume = g_strtod (text, NULL); if (volume < 0) volume = 0; else if (volume > 200) volume = 200; volume /= 100.0; if (volume > 0.99 && volume < 1.01) volume = 1.0; *value = volume; return TRUE; } static gboolean volume_spin_output_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self) { GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); gdouble volume = gtk_adjustment_get_value (adjustment); gchar *volume_str; volume_str = g_strdup_printf ("%.0lf%%", volume * 100); gtk_editable_set_text (GTK_EDITABLE (spin_button), volume_str); g_free (volume_str); return TRUE; } static void volume_spin_changed_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self) { GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); gdouble volume = gtk_adjustment_get_value (adjustment); gtk_button_set_icon_name (GTK_BUTTON (self->volume_button), clapper_gtk_get_icon_name_for_volume ((!self->mute) ? volume : 0)); } static gint speed_spin_input_cb (GtkSpinButton *spin_button, gdouble *value, ClapperGtkExtraMenuButton *self) { const gchar *text = gtk_editable_get_text (GTK_EDITABLE (spin_button)); gdouble speed = g_strtod (text, NULL); if (speed < 0.05) speed = 0.05; else if (speed > 2.0) speed = 2.0; if (speed > 0.99 && speed < 1.01) speed = 1.0; *value = speed; return TRUE; } static gboolean speed_spin_output_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self) { GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); gdouble speed = gtk_adjustment_get_value (adjustment); gchar *speed_str; speed_str = g_strdup_printf ("%.2lfx", speed); gtk_editable_set_text (GTK_EDITABLE (spin_button), speed_str); g_free (speed_str); return TRUE; } static void speed_spin_changed_cb (GtkSpinButton *spin_button, ClapperGtkExtraMenuButton *self) { GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button); gdouble speed = gtk_adjustment_get_value (adjustment); gtk_button_set_icon_name (GTK_BUTTON (self->speed_button), clapper_gtk_get_icon_name_for_speed (speed)); } static void visible_submenu_changed_cb (GtkPopoverMenu *popover_menu, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self) { gchar *name = NULL; gboolean in_video, in_audio, in_subtitles; g_object_get (popover_menu, "visible-submenu", &name, NULL); /* TODO: Check if we have to compare translated strings here */ GST_DEBUG ("Visible submenu changed to: \"%s\"", name); in_video = (g_strcmp0 (name, _("Video")) == 0); in_audio = (g_strcmp0 (name, _("Audio")) == 0); in_subtitles = (g_strcmp0 (name, _("Subtitles")) == 0); /* XXX: This works around the issue where popover does not adapt its * width when navigating submenus making spin buttons unnecesary centered */ gtk_scrolled_window_set_propagate_natural_width (self->video_sw, in_video); gtk_scrolled_window_set_propagate_natural_width (self->audio_sw, in_audio); gtk_scrolled_window_set_propagate_natural_width (self->subtitle_sw, in_subtitles); g_free (name); } static void _subtitles_enabled_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self) { GAction *action = g_action_map_lookup_action ( G_ACTION_MAP (self->action_group), "subtitle-stream-enabled"); GVariant *variant = g_action_get_state (action); gboolean was_enabled, enabled; was_enabled = g_variant_get_boolean (variant); enabled = clapper_player_get_subtitles_enabled (player); g_variant_unref (variant); if (was_enabled == enabled) return; variant = g_variant_ref_sink (g_variant_new_boolean (enabled)); g_simple_action_set_state (G_SIMPLE_ACTION (action), variant); g_variant_unref (variant); } static void _player_mute_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self) { self->mute = clapper_player_get_mute (player); volume_spin_changed_cb (GTK_SPIN_BUTTON (self->volume_spin), self); } static void _queue_current_item_changed_cb (ClapperQueue *queue, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkExtraMenuButton *self) { ClapperMediaItem *current_item = clapper_queue_get_current_item (queue); /* NOTE: This is also called after popover "map" signal */ if (gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (current_item))) { _set_action_enabled (self, "open-subtitle-stream", (self->can_open_subtitles && self->current_item != NULL)); } gst_clear_object (¤t_item); } static void change_subtitle_stream_enabled (GSimpleAction *action, GVariant *value, gpointer user_data) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (user_data); gboolean enable = g_variant_get_boolean (value); if (G_LIKELY (self->player != NULL)) clapper_player_set_subtitles_enabled (self->player, enable); g_simple_action_set_state (action, value); } static void open_subtitle_stream (GSimpleAction *action, GVariant *param, gpointer user_data) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (user_data); /* We should not be here otherwise */ if (G_LIKELY (self->can_open_subtitles && self->current_item != NULL)) g_signal_emit (self, signals[SIGNAL_OPEN_SUBTITLES], 0, self->current_item); } static void _determine_top_separator_visibility (ClapperGtkExtraMenuButton *self) { gboolean visible = gtk_widget_get_visible (self->volume_box) || gtk_widget_get_visible (self->speed_box); gtk_widget_set_visible (self->top_separator, visible); } /** * clapper_gtk_extra_menu_button_new: * * Creates a new #ClapperGtkExtraMenuButton instance. * * Returns: a new extra menu button #GtkWidget. */ GtkWidget * clapper_gtk_extra_menu_button_new (void) { return g_object_new (CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON, NULL); } /** * clapper_gtk_extra_menu_button_set_volume_visible: * @button: a #ClapperGtkExtraMenuButton * @visible: whether visible * * Set whether volume control inside popover should be visible. */ void clapper_gtk_extra_menu_button_set_volume_visible (ClapperGtkExtraMenuButton *self, gboolean visible) { g_return_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self)); if (gtk_widget_get_visible (self->volume_box) != visible) { gtk_widget_set_visible (self->volume_box, visible); _determine_top_separator_visibility (self); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_VOLUME_VISIBLE]); } } /** * clapper_gtk_extra_menu_button_get_volume_visible: * @button: a #ClapperGtkExtraMenuButton * * Get whether volume control inside popover is visible. * * Returns: TRUE if volume control is visible, %FALSE otherwise. */ gboolean clapper_gtk_extra_menu_button_get_volume_visible (ClapperGtkExtraMenuButton *self) { g_return_val_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self), FALSE); return gtk_widget_get_visible (self->volume_box); } /** * clapper_gtk_extra_menu_button_set_speed_visible: * @button: a #ClapperGtkExtraMenuButton * @visible: whether visible * * Set whether speed control inside popover should be visible. */ void clapper_gtk_extra_menu_button_set_speed_visible (ClapperGtkExtraMenuButton *self, gboolean visible) { g_return_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self)); if (gtk_widget_get_visible (self->speed_box) != visible) { gtk_widget_set_visible (self->speed_box, visible); _determine_top_separator_visibility (self); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_SPEED_VISIBLE]); } } /** * clapper_gtk_extra_menu_button_get_speed_visible: * @button: a #ClapperGtkExtraMenuButton * * Get whether speed control inside popover is visible. * * Returns: %TRUE if speed control is visible, %FALSE otherwise. */ gboolean clapper_gtk_extra_menu_button_get_speed_visible (ClapperGtkExtraMenuButton *self) { g_return_val_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self), FALSE); return gtk_widget_get_visible (self->speed_box); } /** * clapper_gtk_extra_menu_button_set_can_open_subtitles: * @button: a #ClapperGtkExtraMenuButton * @allowed: whether opening subtitles should be allowed * * Set whether an option to open external subtitle stream should be allowed. * * Note that this [class@Gtk.Widget] can only add subtitles to currently playing * [class@Clapper.MediaItem]. When no media is selected, option to open subtitles * will not be shown regardless how this option is set. */ void clapper_gtk_extra_menu_button_set_can_open_subtitles (ClapperGtkExtraMenuButton *self, gboolean allowed) { gboolean changed; g_return_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self)); if ((changed = self->can_open_subtitles != allowed)) { self->can_open_subtitles = allowed; _set_action_enabled (self, "open-subtitle-stream", (self->can_open_subtitles && self->current_item != NULL)); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CAN_OPEN_SUBTITLES]); } } /** * clapper_gtk_extra_menu_button_get_can_open_subtitles: * @button: a #ClapperGtkExtraMenuButton * * Get whether an option to open external subtitle stream inside popover is visible. * * Returns: %TRUE if open subtitles is visible, %FALSE otherwise. */ gboolean clapper_gtk_extra_menu_button_get_can_open_subtitles (ClapperGtkExtraMenuButton *self) { g_return_val_if_fail (CLAPPER_GTK_IS_EXTRA_MENU_BUTTON (self), FALSE); return self->can_open_subtitles; } static void clapper_gtk_extra_menu_button_init (ClapperGtkExtraMenuButton *self) { static GActionEntry action_entries[] = { { "subtitle-stream-enabled", NULL, NULL, "true", change_subtitle_stream_enabled }, { "open-subtitle-stream", open_subtitle_stream, NULL, NULL, NULL }, }; /* Ensure private types */ g_type_ensure (CLAPPER_GTK_TYPE_STREAM_CHECK_BUTTON); gtk_widget_init_template (GTK_WIDGET (self)); self->action_group = g_simple_action_group_new (); g_object_bind_property (self, "css-classes", self->menu_button, "css-classes", G_BINDING_DEFAULT); g_action_map_add_action_entries (G_ACTION_MAP (self->action_group), action_entries, G_N_ELEMENTS (action_entries), self); gtk_widget_insert_action_group (GTK_WIDGET (self), "clappergtk", G_ACTION_GROUP (self->action_group)); /* Set default values */ self->can_open_subtitles = DEFAULT_CAN_OPEN_SUBTITLES; _set_action_enabled (self, "open-subtitle-stream", self->can_open_subtitles); } static void clapper_gtk_extra_menu_button_compute_expand (GtkWidget *widget, gboolean *hexpand_p, gboolean *vexpand_p) { GtkWidget *child; gboolean hexpand = FALSE; gboolean vexpand = FALSE; if ((child = gtk_widget_get_first_child (widget))) { hexpand = gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL); vexpand = gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL); } *hexpand_p = hexpand; *vexpand_p = vexpand; } static void clapper_gtk_extra_menu_button_realize (GtkWidget *widget) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (widget); GST_TRACE_OBJECT (self, "Realize"); if ((self->player = clapper_gtk_get_player_from_ancestor (GTK_WIDGET (self)))) { ClapperStreamList *stream_list; GtkSingleSelection *selection; g_signal_connect (self->player, "notify::mute", G_CALLBACK (_player_mute_changed_cb), self); self->mute = clapper_player_get_mute (self->player); stream_list = clapper_player_get_video_streams (self->player); selection = gtk_single_selection_new (gst_object_ref (stream_list)); gtk_single_selection_set_autoselect (selection, FALSE); self->video_binding = g_object_bind_property (stream_list, "current-index", selection, "selected", G_BINDING_SYNC_CREATE); gtk_list_view_set_model (GTK_LIST_VIEW (self->video_list_view), GTK_SELECTION_MODEL (selection)); g_object_unref (selection); stream_list = clapper_player_get_audio_streams (self->player); selection = gtk_single_selection_new (gst_object_ref (stream_list)); gtk_single_selection_set_autoselect (selection, FALSE); self->audio_binding = g_object_bind_property (stream_list, "current-index", selection, "selected", G_BINDING_SYNC_CREATE); gtk_list_view_set_model (GTK_LIST_VIEW (self->audio_list_view), GTK_SELECTION_MODEL (selection)); g_object_unref (selection); stream_list = clapper_player_get_subtitle_streams (self->player); selection = gtk_single_selection_new (gst_object_ref (stream_list)); gtk_single_selection_set_autoselect (selection, FALSE); self->subtitle_binding = g_object_bind_property (stream_list, "current-index", selection, "selected", G_BINDING_SYNC_CREATE); gtk_list_view_set_model (GTK_LIST_VIEW (self->subtitle_list_view), GTK_SELECTION_MODEL (selection)); g_object_unref (selection); } GTK_WIDGET_CLASS (parent_class)->realize (widget); } static void clapper_gtk_extra_menu_button_unrealize (GtkWidget *widget) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (widget); GST_TRACE_OBJECT (self, "Unrealize"); g_clear_pointer (&self->video_binding, g_binding_unbind); g_clear_pointer (&self->audio_binding, g_binding_unbind); g_clear_pointer (&self->subtitle_binding, g_binding_unbind); gtk_list_view_set_model (GTK_LIST_VIEW (self->video_list_view), NULL); gtk_list_view_set_model (GTK_LIST_VIEW (self->audio_list_view), NULL); gtk_list_view_set_model (GTK_LIST_VIEW (self->subtitle_list_view), NULL); if (self->player) { g_signal_handlers_disconnect_by_func (self->player, _player_mute_changed_cb, self); self->player = NULL; } GTK_WIDGET_CLASS (parent_class)->unrealize (widget); } static void popover_map_cb (GtkWidget *widget, ClapperGtkExtraMenuButton *self) { ClapperQueue *queue; GST_TRACE_OBJECT (self, "Popover map"); gtk_widget_set_can_focus (widget, TRUE); if (G_UNLIKELY (self->player == NULL)) return; queue = clapper_player_get_queue (self->player); self->volume_binding = g_object_bind_property_full (self->player, "volume", self->volume_spin, "value", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE, (GBindingTransformFunc) _double_transform_func, (GBindingTransformFunc) _double_transform_func, NULL, NULL); self->speed_binding = g_object_bind_property_full (self->player, "speed", self->speed_spin, "value", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE, (GBindingTransformFunc) _double_transform_func, (GBindingTransformFunc) _double_transform_func, NULL, NULL); g_signal_connect (self->player, "notify::subtitles-enabled", G_CALLBACK (_subtitles_enabled_changed_cb), self); _subtitles_enabled_changed_cb (self->player, NULL, self); g_signal_connect (queue, "notify::current-item", G_CALLBACK (_queue_current_item_changed_cb), self); _queue_current_item_changed_cb (queue, NULL, self); } static void popover_unmap_cb (GtkWidget *widget, ClapperGtkExtraMenuButton *self) { ClapperQueue *queue; GST_TRACE_OBJECT (self, "Popover unmap"); /* Drop focus after popover is closed. Fixes issue * with keyboard shortcuts not working when closed * while within submenu */ gtk_widget_set_can_focus (widget, FALSE); if (G_UNLIKELY (self->player == NULL)) return; queue = clapper_player_get_queue (self->player); g_clear_pointer (&self->volume_binding, g_binding_unbind); g_clear_pointer (&self->speed_binding, g_binding_unbind); g_signal_handlers_disconnect_by_func (self->player, _subtitles_enabled_changed_cb, self); g_signal_handlers_disconnect_by_func (queue, _queue_current_item_changed_cb, self); } static void clapper_gtk_extra_menu_button_dispose (GObject *object) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object); gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON); g_clear_pointer (&self->menu_button, gtk_widget_unparent); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_extra_menu_button_finalize (GObject *object) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); gst_clear_object (&self->current_item); g_object_unref (self->action_group); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_gtk_extra_menu_button_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object); switch (prop_id) { case PROP_VOLUME_VISIBLE: g_value_set_boolean (value, clapper_gtk_extra_menu_button_get_volume_visible (self)); break; case PROP_SPEED_VISIBLE: g_value_set_boolean (value, clapper_gtk_extra_menu_button_get_speed_visible (self)); break; case PROP_CAN_OPEN_SUBTITLES: g_value_set_boolean (value, clapper_gtk_extra_menu_button_get_can_open_subtitles (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_extra_menu_button_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkExtraMenuButton *self = CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST (object); switch (prop_id) { case PROP_VOLUME_VISIBLE: clapper_gtk_extra_menu_button_set_volume_visible (self, g_value_get_boolean (value)); break; case PROP_SPEED_VISIBLE: clapper_gtk_extra_menu_button_set_speed_visible (self, g_value_get_boolean (value)); break; case PROP_CAN_OPEN_SUBTITLES: clapper_gtk_extra_menu_button_set_can_open_subtitles (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_extra_menu_button_class_init (ClapperGtkExtraMenuButtonClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkextramenubutton", 0, "Clapper GTK Extra Menu Button"); clapper_gtk_init_translations (); gobject_class->get_property = clapper_gtk_extra_menu_button_get_property; gobject_class->set_property = clapper_gtk_extra_menu_button_set_property; gobject_class->dispose = clapper_gtk_extra_menu_button_dispose; gobject_class->finalize = clapper_gtk_extra_menu_button_finalize; widget_class->compute_expand = clapper_gtk_extra_menu_button_compute_expand; widget_class->realize = clapper_gtk_extra_menu_button_realize; widget_class->unrealize = clapper_gtk_extra_menu_button_unrealize; /** * ClapperGtkExtraMenuButton:volume-visible: * * Visibility of volume control inside popover. */ param_specs[PROP_VOLUME_VISIBLE] = g_param_spec_boolean ("volume-visible", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkExtraMenuButton:speed-visible: * * Visibility of speed control inside popover. */ param_specs[PROP_SPEED_VISIBLE] = g_param_spec_boolean ("speed-visible", NULL, NULL, TRUE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkExtraMenuButton:can-open-subtitles: * * Visibility of open subtitles option inside popover. */ param_specs[PROP_CAN_OPEN_SUBTITLES] = g_param_spec_boolean ("can-open-subtitles", NULL, NULL, FALSE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkExtraMenuButton::open-subtitles: * @button: a #ClapperGtkExtraMenuButton * @item: a #ClapperMediaItem * * A signal that user wants to open subtitles file. * * Implementation should add a way for user to select subtitles to open * such as by e.g. using [class@Gtk.FileDialog] and then add them to the * @item using [method@Clapper.MediaItem.set_suburi] method. * * This signal will pass the [class@Clapper.MediaItem] that was current when * user clicked the open button and subtitles should be added to this @item. * This avoids situations where another item starts playing before user selects * subtitles file to be opened. When using asynchronous operations to open file, * implementation should [method@GObject.Object.ref] the item to ensure that it * stays valid until finish. * * Note that this signal will not be emitted if open button is not visible by * setting [method@ClapperGtk.ExtraMenuButton.set_can_open_subtitles] to %TRUE, * so you do not have to implement handler for it otherwise. */ signals[SIGNAL_OPEN_SUBTITLES] = g_signal_new ("open-subtitles", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, CLAPPER_TYPE_MEDIA_ITEM); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-extra-menu-button.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, menu_button); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, volume_box); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, volume_button); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, volume_spin); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, speed_box); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, speed_button); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, speed_spin); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, top_separator); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, video_list_view); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, video_sw); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, audio_list_view); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, audio_sw); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, subtitle_list_view); gtk_widget_class_bind_template_child (widget_class, ClapperGtkExtraMenuButton, subtitle_sw); gtk_widget_class_bind_template_callback (widget_class, volume_spin_input_cb); gtk_widget_class_bind_template_callback (widget_class, volume_spin_output_cb); gtk_widget_class_bind_template_callback (widget_class, volume_spin_changed_cb); gtk_widget_class_bind_template_callback (widget_class, speed_spin_input_cb); gtk_widget_class_bind_template_callback (widget_class, speed_spin_output_cb); gtk_widget_class_bind_template_callback (widget_class, speed_spin_changed_cb); gtk_widget_class_bind_template_callback (widget_class, popover_map_cb); gtk_widget_class_bind_template_callback (widget_class, popover_unmap_cb); gtk_widget_class_bind_template_callback (widget_class, visible_submenu_changed_cb); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-extra-menu-button"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-extra-menu-button.h000066400000000000000000000043211474353110700246760ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON (clapper_gtk_extra_menu_button_get_type()) #define CLAPPER_GTK_EXTRA_MENU_BUTTON_CAST(obj) ((ClapperGtkExtraMenuButton *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkExtraMenuButton, clapper_gtk_extra_menu_button, CLAPPER_GTK, EXTRA_MENU_BUTTON, GtkWidget) CLAPPER_GTK_API GtkWidget * clapper_gtk_extra_menu_button_new (void); CLAPPER_GTK_API void clapper_gtk_extra_menu_button_set_volume_visible (ClapperGtkExtraMenuButton *button, gboolean visible); CLAPPER_GTK_API gboolean clapper_gtk_extra_menu_button_get_volume_visible (ClapperGtkExtraMenuButton *button); CLAPPER_GTK_API void clapper_gtk_extra_menu_button_set_speed_visible (ClapperGtkExtraMenuButton *button, gboolean visible); CLAPPER_GTK_API gboolean clapper_gtk_extra_menu_button_get_speed_visible (ClapperGtkExtraMenuButton *button); CLAPPER_GTK_API void clapper_gtk_extra_menu_button_set_can_open_subtitles (ClapperGtkExtraMenuButton *button, gboolean allowed); CLAPPER_GTK_API gboolean clapper_gtk_extra_menu_button_get_can_open_subtitles (ClapperGtkExtraMenuButton *button); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-lead-container.c000066400000000000000000000164341474353110700241700ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkLeadContainer: * * A #ClapperGtkContainer that can take priority in user interactions with the #ClapperGtkVideo. * * #ClapperGtkLeadContainer is a special type of [class@ClapperGtk.Container] that can * lead in interaction events. When "leading", it is assumed that user interactions * over it which would normally trigger actions can be blocked/ignored when set in mask * of actions that this widget should block. * * This kind of container is useful when creating some statically visible overlays * covering top of [class@ClapperGtk.Video] that you want to take priority instead of * triggering default actions such as toggle play on click or revealing fading overlays. * * For more info how container widget works see [class@ClapperGtk.Container] documentation. */ #include "clapper-gtk-lead-container.h" #define DEFAULT_LEADING TRUE #define DEFAULT_BLOCKED_ACTIONS CLAPPER_GTK_VIDEO_ACTION_NONE typedef struct _ClapperGtkLeadContainerPrivate ClapperGtkLeadContainerPrivate; struct _ClapperGtkLeadContainerPrivate { gboolean leading; ClapperGtkVideoActionMask blocked_actions; }; #define parent_class clapper_gtk_lead_container_parent_class G_DEFINE_TYPE_WITH_PRIVATE (ClapperGtkLeadContainer, clapper_gtk_lead_container, CLAPPER_GTK_TYPE_CONTAINER) enum { PROP_0, PROP_LEADING, PROP_BLOCKED_ACTIONS, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; /** * clapper_gtk_lead_container_new: * * Creates a new #ClapperGtkLeadContainer instance. * * Returns: a new lead container #GtkWidget. */ GtkWidget * clapper_gtk_lead_container_new (void) { return g_object_new (CLAPPER_GTK_TYPE_LEAD_CONTAINER, NULL); } /** * clapper_gtk_lead_container_set_leading: * @lead_container: a #ClapperGtkLeadContainer * @leading: enable leadership * * Set if @lead_container leadership should be enabled. * * When enabled, interactions with @lead_container will not trigger * their default behavior, instead container and its contents will take priority. */ void clapper_gtk_lead_container_set_leading (ClapperGtkLeadContainer *self, gboolean leading) { ClapperGtkLeadContainerPrivate *priv; g_return_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self)); priv = clapper_gtk_lead_container_get_instance_private (self); priv->leading = leading; } /** * clapper_gtk_lead_container_get_leading: * @lead_container: a #ClapperGtkLeadContainer * * Get a whenever @lead_container has leadership set. * * Returns: %TRUE if container is leading, %FALSE otherwise. */ gboolean clapper_gtk_lead_container_get_leading (ClapperGtkLeadContainer *self) { ClapperGtkLeadContainerPrivate *priv; g_return_val_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self), FALSE); priv = clapper_gtk_lead_container_get_instance_private (self); return priv->leading; } /** * clapper_gtk_lead_container_set_blocked_actions: * @lead_container: a #ClapperGtkLeadContainer * @actions: a #ClapperGtkVideoActionMask of actions to block * * Set @actions that #ClapperGtkVideo should skip when #GdkEvent which * would normally trigger them happens inside @lead_container. */ void clapper_gtk_lead_container_set_blocked_actions (ClapperGtkLeadContainer *self, ClapperGtkVideoActionMask actions) { ClapperGtkLeadContainerPrivate *priv; g_return_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self)); priv = clapper_gtk_lead_container_get_instance_private (self); priv->blocked_actions = actions; } /** * clapper_gtk_lead_container_get_blocked_actions: * @lead_container: a #ClapperGtkLeadContainer * * Get @actions that were set for this @lead_container to block. * * Returns: a mask of actions that container blocks from being triggered on video. */ ClapperGtkVideoActionMask clapper_gtk_lead_container_get_blocked_actions (ClapperGtkLeadContainer *self) { ClapperGtkLeadContainerPrivate *priv; g_return_val_if_fail (CLAPPER_GTK_IS_LEAD_CONTAINER (self), 0); priv = clapper_gtk_lead_container_get_instance_private (self); return priv->blocked_actions; } static void clapper_gtk_lead_container_init (ClapperGtkLeadContainer *self) { ClapperGtkLeadContainerPrivate *priv = clapper_gtk_lead_container_get_instance_private (self); priv->leading = DEFAULT_LEADING; priv->blocked_actions = DEFAULT_BLOCKED_ACTIONS; } static void clapper_gtk_lead_container_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkLeadContainer *self = CLAPPER_GTK_LEAD_CONTAINER_CAST (object); switch (prop_id) { case PROP_LEADING: g_value_set_boolean (value, clapper_gtk_lead_container_get_leading (self)); break; case PROP_BLOCKED_ACTIONS: g_value_set_flags (value, clapper_gtk_lead_container_get_blocked_actions (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_lead_container_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkLeadContainer *self = CLAPPER_GTK_LEAD_CONTAINER_CAST (object); switch (prop_id) { case PROP_LEADING: clapper_gtk_lead_container_set_leading (self, g_value_get_boolean (value)); break; case PROP_BLOCKED_ACTIONS: clapper_gtk_lead_container_set_blocked_actions (self, g_value_get_flags (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_lead_container_class_init (ClapperGtkLeadContainerClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; gobject_class->get_property = clapper_gtk_lead_container_get_property; gobject_class->set_property = clapper_gtk_lead_container_set_property; /** * ClapperGtkLeadContainer:leading: * * Width that container should target. */ param_specs[PROP_LEADING] = g_param_spec_boolean ("leading", NULL, NULL, DEFAULT_LEADING, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkLeadContainer:blocked-actions: * * Mask of actions that container blocks from being triggered on video. */ param_specs[PROP_BLOCKED_ACTIONS] = g_param_spec_flags ("blocked-actions", NULL, NULL, CLAPPER_GTK_TYPE_VIDEO_ACTION_MASK, DEFAULT_BLOCKED_ACTIONS, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-lead-container"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-lead-container.h000066400000000000000000000043171474353110700241720ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_LEAD_CONTAINER (clapper_gtk_lead_container_get_type()) #define CLAPPER_GTK_LEAD_CONTAINER_CAST(obj) ((ClapperGtkLeadContainer *)(obj)) CLAPPER_GTK_API G_DECLARE_DERIVABLE_TYPE (ClapperGtkLeadContainer, clapper_gtk_lead_container, CLAPPER_GTK, LEAD_CONTAINER, ClapperGtkContainer) struct _ClapperGtkLeadContainerClass { ClapperGtkContainerClass parent_class; /*< private >*/ gpointer padding[4]; }; CLAPPER_GTK_API GtkWidget * clapper_gtk_lead_container_new (void); CLAPPER_GTK_API void clapper_gtk_lead_container_set_leading (ClapperGtkLeadContainer *lead_container, gboolean leading); CLAPPER_GTK_API gboolean clapper_gtk_lead_container_get_leading (ClapperGtkLeadContainer *lead_container); CLAPPER_GTK_API void clapper_gtk_lead_container_set_blocked_actions (ClapperGtkLeadContainer *lead_container, ClapperGtkVideoActionMask actions); CLAPPER_GTK_API ClapperGtkVideoActionMask clapper_gtk_lead_container_get_blocked_actions (ClapperGtkLeadContainer *lead_container); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-limited-layout-private.h000066400000000000000000000040711474353110700257140ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_LIMITED_LAYOUT (clapper_gtk_limited_layout_get_type()) #define CLAPPER_GTK_LIMITED_LAYOUT_CAST(obj) ((ClapperGtkLimitedLayout *)(obj)) G_DECLARE_FINAL_TYPE (ClapperGtkLimitedLayout, clapper_gtk_limited_layout, CLAPPER_GTK, LIMITED_LAYOUT, GtkLayoutManager) G_GNUC_INTERNAL void clapper_gtk_limited_layout_set_max_width (ClapperGtkLimitedLayout *layout, gint max_width); G_GNUC_INTERNAL gint clapper_gtk_limited_layout_get_max_width (ClapperGtkLimitedLayout *layout); G_GNUC_INTERNAL void clapper_gtk_limited_layout_set_max_height (ClapperGtkLimitedLayout *layout, gint max_height); G_GNUC_INTERNAL gint clapper_gtk_limited_layout_get_max_height (ClapperGtkLimitedLayout *layout); G_GNUC_INTERNAL void clapper_gtk_limited_layout_set_adaptive_width (ClapperGtkLimitedLayout *layout, gint width); G_GNUC_INTERNAL gint clapper_gtk_limited_layout_get_adaptive_width (ClapperGtkLimitedLayout *layout); G_GNUC_INTERNAL void clapper_gtk_limited_layout_set_adaptive_height (ClapperGtkLimitedLayout *layout, gint height); G_GNUC_INTERNAL gint clapper_gtk_limited_layout_get_adaptive_height (ClapperGtkLimitedLayout *layout); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-limited-layout.c000066400000000000000000000113741474353110700242430ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-gtk-limited-layout-private.h" #include "clapper-gtk-container-private.h" #define GST_CAT_DEFAULT clapper_gtk_limited_layout_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkLimitedLayout { GtkLayoutManager parent; gint max_width; gint max_height; gint adaptive_width; gint adaptive_height; gboolean adapt; }; #define parent_class clapper_gtk_limited_layout_parent_class G_DEFINE_TYPE (ClapperGtkLimitedLayout, clapper_gtk_limited_layout, GTK_TYPE_LAYOUT_MANAGER) void clapper_gtk_limited_layout_set_max_width (ClapperGtkLimitedLayout *self, gint max_width) { self->max_width = max_width; } gint clapper_gtk_limited_layout_get_max_width (ClapperGtkLimitedLayout *self) { return self->max_width; } void clapper_gtk_limited_layout_set_max_height (ClapperGtkLimitedLayout *self, gint max_height) { self->max_height = max_height; } gint clapper_gtk_limited_layout_get_max_height (ClapperGtkLimitedLayout *self) { return self->max_height; } void clapper_gtk_limited_layout_set_adaptive_width (ClapperGtkLimitedLayout *self, gint width) { self->adaptive_width = width; } gint clapper_gtk_limited_layout_get_adaptive_width (ClapperGtkLimitedLayout *self) { return self->adaptive_width; } void clapper_gtk_limited_layout_set_adaptive_height (ClapperGtkLimitedLayout *self, gint height) { self->adaptive_height = height; } gint clapper_gtk_limited_layout_get_adaptive_height (ClapperGtkLimitedLayout *self) { return self->adaptive_height; } static void clapper_gtk_limited_layout_measure (GtkLayoutManager *layout_manager, GtkWidget *widget, GtkOrientation orientation, gint for_size, gint *minimum, gint *natural, gint *minimum_baseline, gint *natural_baseline) { ClapperGtkLimitedLayout *self = CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout_manager); GtkWidget *child = gtk_widget_get_first_child (widget); if (child && gtk_widget_should_layout (child)) { gint child_min = 0, child_nat = 0; gint child_min_baseline = -1, child_nat_baseline = -1; gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, &child_min_baseline, &child_nat_baseline); if (orientation == GTK_ORIENTATION_HORIZONTAL) *natural = (self->max_width < 0) ? MAX (*natural, child_nat) : self->max_width; else if (orientation == GTK_ORIENTATION_VERTICAL) *natural = (self->max_height < 0) ? MAX (*natural, child_nat) : self->max_height; *minimum = MAX (*minimum, child_min); if (child_min_baseline > -1) *minimum_baseline = MAX (*minimum_baseline, child_min_baseline); if (child_nat_baseline > -1) *natural_baseline = MAX (*natural_baseline, child_nat_baseline); } } static void clapper_gtk_limited_layout_allocate (GtkLayoutManager *layout_manager, GtkWidget *widget, gint width, gint height, gint baseline) { ClapperGtkLimitedLayout *self = CLAPPER_GTK_LIMITED_LAYOUT_CAST (layout_manager); GtkWidget *child = gtk_widget_get_first_child (widget); gboolean adapt = (width <= self->adaptive_width || height <= self->adaptive_height); if (child && gtk_widget_should_layout (child)) gtk_widget_allocate (child, width, height, baseline, NULL); if (G_UNLIKELY (self->adapt != adapt)) { self->adapt = adapt; clapper_gtk_container_emit_adapt (CLAPPER_GTK_CONTAINER_CAST (widget), adapt); } } static void clapper_gtk_limited_layout_init (ClapperGtkLimitedLayout *self) { self->max_width = -1; self->max_height = -1; self->adaptive_width = -1; self->adaptive_height = -1; } static void clapper_gtk_limited_layout_class_init (ClapperGtkLimitedLayoutClass *klass) { GtkLayoutManagerClass *layout_manager_class = (GtkLayoutManagerClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtklimitedlayout", 0, "Clapper GTK Limited Layout"); layout_manager_class->measure = clapper_gtk_limited_layout_measure; layout_manager_class->allocate = clapper_gtk_limited_layout_allocate; } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-next-item-button.c000066400000000000000000000102171474353110700245170ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkNextItemButton: * * A #GtkButton for selecting next queue item. */ #include #include "clapper-gtk-next-item-button.h" #include "clapper-gtk-utils.h" #define GST_CAT_DEFAULT clapper_gtk_next_item_button_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkNextItemButton { GtkButton parent; GBinding *n_items_binding; GBinding *current_index_binding; }; #define parent_class clapper_gtk_next_item_button_parent_class G_DEFINE_TYPE (ClapperGtkNextItemButton, clapper_gtk_next_item_button, GTK_TYPE_BUTTON) static gboolean _transform_sensitive_func (GBinding *binding, const GValue *from_value, GValue *to_value, ClapperGtkNextItemButton *self) { ClapperQueue *queue = CLAPPER_QUEUE_CAST (g_binding_dup_source (binding)); guint current_index; gboolean can_skip; if (G_UNLIKELY (queue == NULL)) return FALSE; current_index = clapper_queue_get_current_index (queue); can_skip = (current_index != CLAPPER_QUEUE_INVALID_POSITION && current_index < clapper_queue_get_n_items (queue) - 1); gst_object_unref (queue); g_value_set_boolean (to_value, can_skip); GST_DEBUG_OBJECT (self, "Set sensitive: %s", (can_skip) ? "yes" : "no"); return TRUE; } /** * clapper_gtk_next_item_button_new: * * Creates a new #ClapperGtkNextItemButton to play next #ClapperMediaItem. * * Returns: a new next item button #GtkWidget. */ GtkWidget * clapper_gtk_next_item_button_new (void) { return g_object_new (CLAPPER_GTK_TYPE_NEXT_ITEM_BUTTON, NULL); } static void clapper_gtk_next_item_button_init (ClapperGtkNextItemButton *self) { gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); gtk_button_set_icon_name (GTK_BUTTON (self), "media-skip-forward-symbolic"); gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.next-item"); } static void clapper_gtk_next_item_button_map (GtkWidget *widget) { ClapperGtkNextItemButton *self = CLAPPER_GTK_NEXT_ITEM_BUTTON_CAST (widget); ClapperPlayer *player; if ((player = clapper_gtk_get_player_from_ancestor (widget))) { ClapperQueue *queue = clapper_player_get_queue (player); self->n_items_binding = g_object_bind_property_full (queue, "n-items", self, "sensitive", G_BINDING_DEFAULT, // Since we sync below, no need to do it twice (GBindingTransformFunc) _transform_sensitive_func, NULL, self, NULL); self->current_index_binding = g_object_bind_property_full (queue, "current-index", self, "sensitive", G_BINDING_SYNC_CREATE, (GBindingTransformFunc) _transform_sensitive_func, NULL, self, NULL); } GTK_WIDGET_CLASS (parent_class)->map (widget); } static void clapper_gtk_next_item_button_unmap (GtkWidget *widget) { ClapperGtkNextItemButton *self = CLAPPER_GTK_NEXT_ITEM_BUTTON_CAST (widget); g_clear_pointer (&self->n_items_binding, g_binding_unbind); g_clear_pointer (&self->current_index_binding, g_binding_unbind); GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void clapper_gtk_next_item_button_class_init (ClapperGtkNextItemButtonClass *klass) { GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtknextitembutton", 0, "Clapper GTK Next Item Button"); widget_class->map = clapper_gtk_next_item_button_map; widget_class->unmap = clapper_gtk_next_item_button_unmap; } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-next-item-button.h000066400000000000000000000027711474353110700245320ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_NEXT_ITEM_BUTTON (clapper_gtk_next_item_button_get_type()) #define CLAPPER_GTK_NEXT_ITEM_BUTTON_CAST(obj) ((ClapperGtkNextItemButton *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkNextItemButton, clapper_gtk_next_item_button, CLAPPER_GTK, NEXT_ITEM_BUTTON, GtkButton) CLAPPER_GTK_API GtkWidget * clapper_gtk_next_item_button_new (void); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-previous-item-button.c000066400000000000000000000103441474353110700254160ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkPreviousItemButton: * * A #GtkButton for selecting previous queue item. */ #include #include "clapper-gtk-previous-item-button.h" #include "clapper-gtk-utils.h" #define GST_CAT_DEFAULT clapper_gtk_previous_item_button_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkPreviousItemButton { GtkButton parent; GBinding *n_items_binding; GBinding *current_index_binding; }; #define parent_class clapper_gtk_previous_item_button_parent_class G_DEFINE_TYPE (ClapperGtkPreviousItemButton, clapper_gtk_previous_item_button, GTK_TYPE_BUTTON) static gboolean _transform_sensitive_func (GBinding *binding, const GValue *from_value, GValue *to_value, ClapperGtkPreviousItemButton *self) { ClapperQueue *queue = CLAPPER_QUEUE_CAST (g_binding_dup_source (binding)); guint current_index; gboolean can_skip; if (G_UNLIKELY (queue == NULL)) return FALSE; current_index = clapper_queue_get_current_index (queue); can_skip = (current_index != CLAPPER_QUEUE_INVALID_POSITION && current_index > 0); gst_object_unref (queue); g_value_set_boolean (to_value, can_skip); GST_DEBUG_OBJECT (self, "Set sensitive: %s", (can_skip) ? "yes" : "no"); return TRUE; } /** * clapper_gtk_previous_item_button_new: * * Creates a new #ClapperGtkPreviousItemButton to play previous #ClapperMediaItem. * * Returns: a new previous item button #GtkWidget. */ GtkWidget * clapper_gtk_previous_item_button_new (void) { return g_object_new (CLAPPER_GTK_TYPE_PREVIOUS_ITEM_BUTTON, NULL); } static void clapper_gtk_previous_item_button_init (ClapperGtkPreviousItemButton *self) { gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE); gtk_button_set_icon_name (GTK_BUTTON (self), "media-skip-backward-symbolic"); gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.previous-item"); } static void clapper_gtk_previous_item_button_map (GtkWidget *widget) { ClapperGtkPreviousItemButton *self = CLAPPER_GTK_PREVIOUS_ITEM_BUTTON_CAST (widget); ClapperPlayer *player; if ((player = clapper_gtk_get_player_from_ancestor (widget))) { ClapperQueue *queue = clapper_player_get_queue (player); self->n_items_binding = g_object_bind_property_full (queue, "n-items", self, "sensitive", G_BINDING_DEFAULT, // Since we sync below, no need to do it twice (GBindingTransformFunc) _transform_sensitive_func, NULL, self, NULL); self->current_index_binding = g_object_bind_property_full (queue, "current-index", self, "sensitive", G_BINDING_SYNC_CREATE, (GBindingTransformFunc) _transform_sensitive_func, NULL, self, NULL); } GTK_WIDGET_CLASS (parent_class)->map (widget); } static void clapper_gtk_previous_item_button_unmap (GtkWidget *widget) { ClapperGtkPreviousItemButton *self = CLAPPER_GTK_PREVIOUS_ITEM_BUTTON_CAST (widget); g_clear_pointer (&self->n_items_binding, g_binding_unbind); g_clear_pointer (&self->current_index_binding, g_binding_unbind); GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void clapper_gtk_previous_item_button_class_init (ClapperGtkPreviousItemButtonClass *klass) { GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkpreviousitembutton", 0, "Clapper GTK Previous Item Button"); widget_class->map = clapper_gtk_previous_item_button_map; widget_class->unmap = clapper_gtk_previous_item_button_unmap; } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-previous-item-button.h000066400000000000000000000030311474353110700254160ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_PREVIOUS_ITEM_BUTTON (clapper_gtk_previous_item_button_get_type()) #define CLAPPER_GTK_PREVIOUS_ITEM_BUTTON_CAST(obj) ((ClapperGtkPreviousItemButton *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkPreviousItemButton, clapper_gtk_previous_item_button, CLAPPER_GTK, PREVIOUS_ITEM_BUTTON, GtkButton) CLAPPER_GTK_API GtkWidget * clapper_gtk_previous_item_button_new (void); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-seek-bar.c000066400000000000000000000613551474353110700227760ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkSeekBar: * * A bar for seeking and displaying playback position. */ #include "config.h" #include #include "clapper-gtk-seek-bar.h" #include "clapper-gtk-container.h" #include "clapper-gtk-utils.h" #define DEFAULT_REVEAL_LABELS TRUE #define DEFAULT_SEEK_METHOD CLAPPER_PLAYER_SEEK_METHOD_NORMAL #define GST_CAT_DEFAULT clapper_gtk_seek_bar_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkSeekBar { GtkWidget parent; GtkWidget *position_revealer; GtkWidget *position_label; GtkWidget *scale; GtkPopover *popover; GtkLabel *popover_label; GtkWidget *duration_revealer; GtkWidget *duration_label; gboolean has_hours; gboolean has_markers; gboolean can_scrub; gboolean scrubbing; gboolean dragging; guint position_uint; gulong position_signal_id; gboolean reveal_labels; ClapperPlayerSeekMethod seek_method; ClapperPlayer *player; ClapperMediaItem *current_item; /* Cache */ gdouble curr_marker_start; gdouble next_marker_start; }; static void clapper_gtk_seek_bar_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const char *type) { if (GTK_IS_WIDGET (child)) { gtk_widget_insert_before (GTK_WIDGET (child), GTK_WIDGET (buildable), NULL); } else { GtkBuildableIface *parent_iface = g_type_interface_peek_parent (GTK_BUILDABLE_GET_IFACE (buildable)); parent_iface->add_child (buildable, builder, child, type); } } static void _buildable_iface_init (GtkBuildableIface *iface) { iface->add_child = clapper_gtk_seek_bar_add_child; } #define parent_class clapper_gtk_seek_bar_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperGtkSeekBar, clapper_gtk_seek_bar, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, _buildable_iface_init)) enum { PROP_0, PROP_REVEAL_LABELS, PROP_SEEK_METHOD, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static inline gboolean _prepare_popover (ClapperGtkSeekBar *self, gdouble x, gdouble pointing_val, gdouble upper) { /* Avoid iterating through markers if within last marker range * (currently set title label remains the same) */ gboolean found_title = (pointing_val >= self->curr_marker_start && pointing_val < self->next_marker_start); if (!found_title) { ClapperTimeline *timeline = clapper_media_item_get_timeline (self->current_item); guint i = clapper_timeline_get_n_markers (timeline); GST_DEBUG ("Searching for marker at: %lf", pointing_val); /* We start from the end of scale */ self->next_marker_start = upper; while (i--) { ClapperMarker *marker = clapper_timeline_get_marker (timeline, i); self->curr_marker_start = clapper_marker_get_start (marker); if (self->curr_marker_start <= pointing_val) { const gchar *title = clapper_marker_get_title (marker); GST_DEBUG ("Found marker, range: (%lf-%lf), title: \"%s\"", self->curr_marker_start, self->next_marker_start, GST_STR_NULL (title)); /* XXX: It does string comparison internally, so its more efficient * for us and we do not have to compare strings here too */ gtk_label_set_label (self->popover_label, title); found_title = (title != NULL); } gst_object_unref (marker); if (found_title) break; self->next_marker_start = self->curr_marker_start; } } gtk_popover_set_pointing_to (self->popover, &(const GdkRectangle){ x, 0, 1, 1 }); return found_title; } static inline gboolean _compute_scale_coords (ClapperGtkSeekBar *self, gdouble *min_pointing_val, gdouble *max_pointing_val) { graphene_rect_t slider_bounds; if (!gtk_widget_compute_bounds (GTK_WIDGET (self), self->scale, &slider_bounds)) return FALSE; /* XXX: Number "2" is the correction for range protruding rounded sides * compared to how marks above/below it are positioned */ *min_pointing_val = -slider_bounds.origin.x + 2; *max_pointing_val = slider_bounds.size.width + slider_bounds.origin.x - 2; return TRUE; } static void _player_position_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self) { GtkAdjustment *adjustment; gdouble position; if (self->dragging) return; position = clapper_player_get_position (player); if (ABS (self->position_uint - position) < 1) return; GST_LOG_OBJECT (self, "Position changed: %lf", position); self->position_uint = (guint) position; adjustment = gtk_range_get_adjustment (GTK_RANGE (self->scale)); gtk_adjustment_set_value (adjustment, position); } static void _player_state_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self) { switch (clapper_player_get_state (player)) { case CLAPPER_PLAYER_STATE_PAUSED: /* Force refresh, so scale always reaches end after playback */ self->position_uint = G_MAXUINT; _player_position_changed_cb (player, NULL, self); break; default: break; } } static void _player_seek_done_cb (ClapperPlayer *player, ClapperGtkSeekBar *self) { GST_DEBUG ("Seek done"); if (self->position_signal_id == 0) { self->position_signal_id = g_signal_connect (self->player, "notify::position", G_CALLBACK (_player_position_changed_cb), self); } _player_position_changed_cb (player, NULL, self); } static void scale_value_changed_cb (GtkRange *range, ClapperGtkSeekBar *self) { gdouble value = gtk_range_get_value (range); gchar *position_str = g_strdup_printf ("%" CLAPPER_TIME_FORMAT, CLAPPER_TIME_ARGS (value)); gtk_label_set_label (GTK_LABEL (self->position_label), (self->has_hours) ? position_str : position_str + 3); g_free (position_str); if (self->dragging && self->has_markers) { gdouble min_pointing_val, max_pointing_val; gdouble x, upper, scaling; if (!_compute_scale_coords (self, &min_pointing_val, &max_pointing_val)) { gtk_popover_popdown (self->popover); return; } upper = gtk_adjustment_get_upper ( gtk_range_get_adjustment (GTK_RANGE (self->scale))); scaling = (upper / (max_pointing_val - min_pointing_val)); x = min_pointing_val + (value / scaling); if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) x = max_pointing_val + min_pointing_val - x; if (_prepare_popover (self, x, value, upper)) gtk_popover_popup (self->popover); else gtk_popover_popdown (self->popover); } } static void scale_css_classes_changed_cb (GtkWidget *widget, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self) { const gboolean dragging = gtk_widget_has_css_class (widget, "dragging"); gdouble value; if (self->dragging == dragging) return; if ((self->dragging = dragging)) { GST_DEBUG_OBJECT (self, "Scale drag started"); return; } value = gtk_range_get_value (GTK_RANGE (widget)); GST_DEBUG_OBJECT (self, "Scale dropped at: %lf", value); if (G_UNLIKELY (self->player == NULL)) return; if (self->position_signal_id != 0) { g_signal_handler_disconnect (self->player, self->position_signal_id); self->position_signal_id = 0; } /* We should be ALWAYS doing normal seeks if dropped at marker position */ if (self->has_markers && G_APPROX_VALUE (self->curr_marker_start, value, FLT_EPSILON)) { GST_DEBUG ("Seeking to marker"); clapper_player_seek (self->player, value); } else { clapper_player_seek_custom (self->player, value, self->seek_method); } } static void scale_scroll_begin_cb (GtkEventControllerScroll *scroll, ClapperGtkSeekBar *self) { self->can_scrub = TRUE; } static gboolean scale_scroll_cb (GtkEventControllerScroll *scroll, gdouble dx, gdouble dy, ClapperGtkSeekBar *self) { if (self->can_scrub && !self->scrubbing) { GST_DEBUG_OBJECT (self, "Scrubbing start"); self->scrubbing = TRUE; gtk_widget_add_css_class (self->scale, "dragging"); return TRUE; } return FALSE; } static void scale_scroll_end_cb (GtkEventControllerScroll *scroll, ClapperGtkSeekBar *self) { if (self->scrubbing) { GST_DEBUG_OBJECT (self, "Scrubbing end"); gtk_widget_remove_css_class (self->scale, "dragging"); self->scrubbing = FALSE; } self->can_scrub = FALSE; } static void motion_cb (GtkEventControllerMotion *motion, gdouble x, gdouble y, ClapperGtkSeekBar *self) { gdouble min_pointing_val, max_pointing_val, pointing_val; gdouble upper, scaling; /* If no markers, popover should never popup, * so we do not try to pop it down here */ if (!self->has_markers) return; if (!_compute_scale_coords (self, &min_pointing_val, &max_pointing_val) || (x < min_pointing_val || x > max_pointing_val)) { gtk_popover_popdown (self->popover); return; } upper = gtk_adjustment_get_upper ( gtk_range_get_adjustment (GTK_RANGE (self->scale))); scaling = (upper / (max_pointing_val - min_pointing_val)); pointing_val = (x - min_pointing_val) * scaling; if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL) pointing_val = upper - pointing_val; GST_LOG ("Cursor pointing to: %lf", pointing_val); if (_prepare_popover (self, x, pointing_val, upper)) gtk_popover_popup (self->popover); else gtk_popover_popdown (self->popover); } static void motion_leave_cb (GtkEventControllerMotion *motion, ClapperGtkSeekBar *self) { gtk_popover_popdown (self->popover); } static void touch_released_cb (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, ClapperGtkSeekBar *self) { gtk_popover_popdown (self->popover); } static void _update_duration_label (ClapperGtkSeekBar *self, gdouble duration) { GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (self->scale)); gchar *duration_str = g_strdup_printf ("%" CLAPPER_TIME_FORMAT, CLAPPER_TIME_ARGS (duration)); gboolean has_hours = (duration >= 3600); GST_LOG_OBJECT (self, "Duration changed: %lf", duration); /* Refresh position label when changing text length */ if (has_hours != self->has_hours) { self->has_hours = has_hours; scale_value_changed_cb (GTK_RANGE (self->scale), self); } gtk_label_set_label (GTK_LABEL (self->duration_label), (self->has_hours) ? duration_str : duration_str + 3); g_free (duration_str); gtk_adjustment_set_upper (adjustment, duration); } static void _update_scale_marks (ClapperGtkSeekBar *self, ClapperTimeline *timeline) { GtkAdjustment *adjustment; guint i, n_markers = clapper_timeline_get_n_markers (timeline); GST_DEBUG_OBJECT (self, "Placing %u markers on scale", n_markers); gtk_scale_clear_marks (GTK_SCALE (self->scale)); self->curr_marker_start = -1; self->next_marker_start = -1; self->has_markers = FALSE; if (n_markers == 0) { gtk_popover_popdown (self->popover); return; } adjustment = gtk_range_get_adjustment (GTK_RANGE (self->scale)); /* Avoid placing marks when duration is zero. Otherwise we may * end up with a single mark at zero until another refresh. */ if (gtk_adjustment_get_upper (adjustment) <= 0) return; for (i = 0; i < n_markers; ++i) { ClapperMarker *marker = clapper_timeline_get_marker (timeline, i); gdouble start = clapper_marker_get_start (marker); gtk_scale_add_mark (GTK_SCALE (self->scale), start, GTK_POS_TOP, NULL); gtk_scale_add_mark (GTK_SCALE (self->scale), start, GTK_POS_BOTTOM, NULL); gst_object_unref (marker); } self->has_markers = TRUE; } static void _current_item_duration_changed_cb (ClapperMediaItem *current_item, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self) { /* GtkScale ignores markers placed post its adjustment upper range. * We need to place them again on scale AFTER duration changes. */ _update_duration_label (self, clapper_media_item_get_duration (current_item)); _update_scale_marks (self, clapper_media_item_get_timeline (current_item)); } static void _timeline_markers_changed_cb (GListModel *list_model, guint position, guint removed, guint added, ClapperGtkSeekBar *self) { _update_scale_marks (self, CLAPPER_TIMELINE (list_model)); } static void _queue_current_item_changed_cb (ClapperQueue *queue, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSeekBar *self) { ClapperMediaItem *current_item = clapper_queue_get_current_item (queue); /* Disconnect signals from old item */ if (self->current_item) { ClapperTimeline *timeline = clapper_media_item_get_timeline (self->current_item); g_signal_handlers_disconnect_by_func (self->current_item, _current_item_duration_changed_cb, self); g_signal_handlers_disconnect_by_func (timeline, _timeline_markers_changed_cb, self); } gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (current_item)); gst_clear_object (¤t_item); /* Reconnect signals to new item */ if (self->current_item) { ClapperTimeline *timeline = clapper_media_item_get_timeline (self->current_item); g_signal_connect (self->current_item, "notify::duration", G_CALLBACK (_current_item_duration_changed_cb), self); g_signal_connect (timeline, "items-changed", G_CALLBACK (_timeline_markers_changed_cb), self); _update_duration_label (self, clapper_media_item_get_duration (self->current_item)); _update_scale_marks (self, timeline); } else { gtk_scale_clear_marks (GTK_SCALE (self->scale)); _update_duration_label (self, 0); } } /** * clapper_gtk_seek_bar_new: * * Creates a new #ClapperGtkSeekBar instance. * * Returns: a new seek bar #GtkWidget. */ GtkWidget * clapper_gtk_seek_bar_new (void) { return g_object_new (CLAPPER_GTK_TYPE_SEEK_BAR, NULL); } /** * clapper_gtk_seek_bar_set_reveal_labels: * @seek_bar: a #ClapperGtkSeekBar * @reveal: whether to reveal labels * * Set whether the position and duration labels should be revealed. */ void clapper_gtk_seek_bar_set_reveal_labels (ClapperGtkSeekBar *self, gboolean reveal) { g_return_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self)); if (self->reveal_labels != reveal) { self->reveal_labels = reveal; gtk_revealer_set_reveal_child (GTK_REVEALER (self->position_revealer), reveal); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_REVEAL_LABELS]); } } /** * clapper_gtk_seek_bar_get_reveal_labels: * @seek_bar: a #ClapperGtkSeekBar * * Get whether the position and duration labels are going to be revealed. * * Returns: TRUE if the labels are going to be revealed, %FALSE otherwise. */ gboolean clapper_gtk_seek_bar_get_reveal_labels (ClapperGtkSeekBar *self) { g_return_val_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self), FALSE); return self->reveal_labels; } /** * clapper_gtk_seek_bar_set_seek_method: * @seek_bar: a #ClapperGtkSeekBar * @method: a #ClapperPlayerSeekMethod * * Set [enum@Clapper.PlayerSeekMethod] to use when seeking with seek bar. */ void clapper_gtk_seek_bar_set_seek_method (ClapperGtkSeekBar *self, ClapperPlayerSeekMethod method) { g_return_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self)); if (self->seek_method != method) { self->seek_method = method; GST_DEBUG_OBJECT (self, "Set seek method to: %i", self->seek_method); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_SEEK_METHOD]); } } /** * clapper_gtk_seek_bar_get_seek_method: * @seek_bar: a #ClapperGtkSeekBar * * Get [enum@Clapper.PlayerSeekMethod] used when seeking with seek bar. * * Returns: #ClapperPlayerSeekMethod used for seeking. */ ClapperPlayerSeekMethod clapper_gtk_seek_bar_get_seek_method (ClapperGtkSeekBar *self) { g_return_val_if_fail (CLAPPER_GTK_IS_SEEK_BAR (self), DEFAULT_SEEK_METHOD); return self->seek_method; } static void clapper_gtk_seek_bar_init (ClapperGtkSeekBar *self) { gtk_widget_init_template (GTK_WIDGET (self)); self->reveal_labels = DEFAULT_REVEAL_LABELS; self->seek_method = DEFAULT_SEEK_METHOD; self->curr_marker_start = -1; self->next_marker_start = -1; gtk_revealer_set_reveal_child (GTK_REVEALER (self->position_revealer), self->reveal_labels); /* Correction for calculated popover position when marks are drawn */ gtk_popover_set_offset (self->popover, 0, -2); } static void clapper_gtk_seek_bar_compute_expand (GtkWidget *widget, gboolean *hexpand_p, gboolean *vexpand_p) { GtkWidget *w; gboolean hexpand = FALSE; gboolean vexpand = FALSE; for (w = gtk_widget_get_first_child (widget); w != NULL; w = gtk_widget_get_next_sibling (w)) { hexpand = (hexpand || gtk_widget_compute_expand (w, GTK_ORIENTATION_HORIZONTAL)); vexpand = (vexpand || gtk_widget_compute_expand (w, GTK_ORIENTATION_VERTICAL)); } *hexpand_p = hexpand; *vexpand_p = vexpand; } static void clapper_gtk_seek_bar_size_allocate (GtkWidget *widget, gint width, gint height, gint baseline) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget); gtk_popover_present (self->popover); GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, width, height, baseline); } static void clapper_gtk_seek_bar_realize (GtkWidget *widget) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget); if ((self->player = clapper_gtk_get_player_from_ancestor (widget))) { ClapperQueue *queue = clapper_player_get_queue (self->player); g_signal_connect (queue, "notify::current-item", G_CALLBACK (_queue_current_item_changed_cb), self); _queue_current_item_changed_cb (queue, NULL, self); } GTK_WIDGET_CLASS (parent_class)->realize (widget); } static void clapper_gtk_seek_bar_unrealize (GtkWidget *widget) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget); if (self->player) { ClapperQueue *queue = clapper_player_get_queue (self->player); if (self->position_signal_id != 0) { g_signal_handler_disconnect (self->player, self->position_signal_id); self->position_signal_id = 0; } g_signal_handlers_disconnect_by_func (queue, _queue_current_item_changed_cb, self); self->player = NULL; } GTK_WIDGET_CLASS (parent_class)->unrealize (widget); } static void clapper_gtk_seek_bar_map (GtkWidget *widget) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget); if (self->player) { if (self->position_signal_id == 0) { self->position_signal_id = g_signal_connect (self->player, "notify::position", G_CALLBACK (_player_position_changed_cb), self); } g_signal_connect (self->player, "notify::state", G_CALLBACK (_player_state_changed_cb), self); g_signal_connect (self->player, "seek-done", G_CALLBACK (_player_seek_done_cb), self); _player_position_changed_cb (self->player, NULL, self); } GTK_WIDGET_CLASS (parent_class)->map (widget); } static void clapper_gtk_seek_bar_unmap (GtkWidget *widget) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (widget); if (self->player) { if (self->position_signal_id != 0) { g_signal_handler_disconnect (self->player, self->position_signal_id); self->position_signal_id = 0; } g_signal_handlers_disconnect_by_func (self->player, _player_state_changed_cb, self); g_signal_handlers_disconnect_by_func (self->player, _player_seek_done_cb, self); } GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void _popover_unparent (GtkPopover *popover) { gtk_widget_unparent (GTK_WIDGET (popover)); } static void clapper_gtk_seek_bar_dispose (GObject *object) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object); gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_SEEK_BAR); g_clear_pointer (&self->position_revealer, gtk_widget_unparent); g_clear_pointer (&self->scale, gtk_widget_unparent); g_clear_pointer (&self->popover, _popover_unparent); g_clear_pointer (&self->duration_revealer, gtk_widget_unparent); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_seek_bar_finalize (GObject *object) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); gst_clear_object (&self->current_item); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_gtk_seek_bar_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object); switch (prop_id) { case PROP_REVEAL_LABELS: g_value_set_boolean (value, clapper_gtk_seek_bar_get_reveal_labels (self)); break; case PROP_SEEK_METHOD: g_value_set_enum (value, clapper_gtk_seek_bar_get_seek_method (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_seek_bar_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkSeekBar *self = CLAPPER_GTK_SEEK_BAR_CAST (object); switch (prop_id) { case PROP_REVEAL_LABELS: clapper_gtk_seek_bar_set_reveal_labels (self, g_value_get_boolean (value)); break; case PROP_SEEK_METHOD: clapper_gtk_seek_bar_set_seek_method (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_seek_bar_class_init (ClapperGtkSeekBarClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkseekbar", 0, "Clapper GTK Seek Bar"); gobject_class->get_property = clapper_gtk_seek_bar_get_property; gobject_class->set_property = clapper_gtk_seek_bar_set_property; gobject_class->dispose = clapper_gtk_seek_bar_dispose; gobject_class->finalize = clapper_gtk_seek_bar_finalize; widget_class->compute_expand = clapper_gtk_seek_bar_compute_expand; widget_class->size_allocate = clapper_gtk_seek_bar_size_allocate; widget_class->realize = clapper_gtk_seek_bar_realize; widget_class->unrealize = clapper_gtk_seek_bar_unrealize; widget_class->map = clapper_gtk_seek_bar_map; widget_class->unmap = clapper_gtk_seek_bar_unmap; /** * ClapperGtkSeekBar:reveal-labels: * * Reveal state of the position and duration labels. */ param_specs[PROP_REVEAL_LABELS] = g_param_spec_boolean ("reveal-labels", NULL, NULL, DEFAULT_REVEAL_LABELS, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkSeekBar:seek-method: * * Method used for seeking. */ param_specs[PROP_SEEK_METHOD] = g_param_spec_enum ("seek-method", NULL, NULL, CLAPPER_TYPE_PLAYER_SEEK_METHOD, DEFAULT_SEEK_METHOD, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-seek-bar.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, position_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, position_label); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, scale); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, popover); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, popover_label); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, duration_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSeekBar, duration_label); gtk_widget_class_bind_template_callback (widget_class, scale_value_changed_cb); gtk_widget_class_bind_template_callback (widget_class, scale_css_classes_changed_cb); gtk_widget_class_bind_template_callback (widget_class, scale_scroll_begin_cb); gtk_widget_class_bind_template_callback (widget_class, scale_scroll_cb); gtk_widget_class_bind_template_callback (widget_class, scale_scroll_end_cb); gtk_widget_class_bind_template_callback (widget_class, motion_cb); gtk_widget_class_bind_template_callback (widget_class, motion_leave_cb); gtk_widget_class_bind_template_callback (widget_class, touch_released_cb); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT); gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-seek-bar"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-seek-bar.h000066400000000000000000000036141474353110700227750ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_SEEK_BAR (clapper_gtk_seek_bar_get_type()) #define CLAPPER_GTK_SEEK_BAR_CAST(obj) ((ClapperGtkSeekBar *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkSeekBar, clapper_gtk_seek_bar, CLAPPER_GTK, SEEK_BAR, GtkWidget) CLAPPER_GTK_API GtkWidget * clapper_gtk_seek_bar_new (void); CLAPPER_GTK_API void clapper_gtk_seek_bar_set_reveal_labels (ClapperGtkSeekBar *seek_bar, gboolean reveal); CLAPPER_GTK_API gboolean clapper_gtk_seek_bar_get_reveal_labels (ClapperGtkSeekBar *seek_bar); CLAPPER_GTK_API void clapper_gtk_seek_bar_set_seek_method (ClapperGtkSeekBar *seek_bar, ClapperPlayerSeekMethod method); CLAPPER_GTK_API ClapperPlayerSeekMethod clapper_gtk_seek_bar_get_seek_method (ClapperGtkSeekBar *seek_bar); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-simple-controls.c000066400000000000000000000266121474353110700244340ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkSimpleControls: * * A minimalistic playback controls panel widget. * * #ClapperGtkSimpleControls is a simple, ready to be used playback controls widget. * It is meant to be placed as an overlay (either fading or not) of [class@ClapperGtk.Video] * as-is, providing minimal yet universal playback controls for your app. * * If you need a further customized controls, please use individual widgets this * widget consists of to build your own controls implementation instead. */ #include "config.h" #include #include "clapper-gtk-simple-controls.h" #include "clapper-gtk-seek-bar.h" #define DEFAULT_FULLSCREENABLE TRUE #define DEFAULT_SEEK_METHOD CLAPPER_PLAYER_SEEK_METHOD_NORMAL #define IS_REVEALED(widget) (gtk_revealer_get_child_revealed ((GtkRevealer *) (widget))) #define IS_REVEAL(widget) (gtk_revealer_get_reveal_child ((GtkRevealer *) (widget))) #define SET_REVEAL(widget,reveal) (gtk_revealer_set_reveal_child ((GtkRevealer *) (widget), reveal)) #define GST_CAT_DEFAULT clapper_gtk_simple_controls_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkSimpleControls { ClapperGtkContainer parent; GtkWidget *seek_bar; GtkWidget *extra_menu_button; GtkWidget *fullscreen_top_revealer; GtkWidget *fullscreen_bottom_revealer; GtkWidget *controls_slide_revealer; gboolean fullscreenable; ClapperPlayerSeekMethod seek_method; gboolean adapt; }; #define parent_class clapper_gtk_simple_controls_parent_class G_DEFINE_TYPE (ClapperGtkSimpleControls, clapper_gtk_simple_controls, CLAPPER_GTK_TYPE_CONTAINER) enum { PROP_0, PROP_FULLSCREENABLE, PROP_SEEK_METHOD, PROP_EXTRA_MENU_BUTTON, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void initial_adapt_cb (ClapperGtkContainer *container, gboolean adapt, ClapperGtkSimpleControls *self) { GST_DEBUG_OBJECT (self, "Initially adapted: %s", (adapt) ? "yes" : "no"); clapper_gtk_seek_bar_set_reveal_labels (CLAPPER_GTK_SEEK_BAR (self->seek_bar), !adapt); } static void full_adapt_cb (ClapperGtkContainer *container, gboolean adapt, ClapperGtkSimpleControls *self) { self->adapt = adapt; GST_DEBUG_OBJECT (self, "Width adapted: %s", (self->adapt) ? "yes" : "no"); /* Take different action, depending on transition step we are currently at */ if (self->adapt) { if (IS_REVEAL (self->fullscreen_bottom_revealer)) SET_REVEAL (self->fullscreen_bottom_revealer, FALSE); else if (IS_REVEAL (self->controls_slide_revealer)) SET_REVEAL (self->controls_slide_revealer, FALSE); else SET_REVEAL (self->fullscreen_top_revealer, TRUE); } else { if (IS_REVEAL (self->fullscreen_top_revealer)) SET_REVEAL (self->fullscreen_top_revealer, FALSE); else if (!IS_REVEAL (self->controls_slide_revealer)) SET_REVEAL (self->controls_slide_revealer, TRUE); else SET_REVEAL (self->fullscreen_bottom_revealer, TRUE); } } static void controls_revealed_cb (GtkRevealer *revealer, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkSimpleControls *self) { gboolean revealed = IS_REVEALED (revealer); GST_DEBUG_OBJECT (self, "Slide revealed: %s", (revealed) ? "yes" : "no"); /* We should be hidden when adapted, otherwise go back */ if (G_UNLIKELY (revealed == self->adapt)) gtk_revealer_set_reveal_child (revealer, !revealed); } /** * clapper_gtk_simple_controls_new: * * Creates a new #ClapperGtkSimpleControls instance. * * Returns: a new simple controls #GtkWidget. */ GtkWidget * clapper_gtk_simple_controls_new (void) { return g_object_new (CLAPPER_GTK_TYPE_SIMPLE_CONTROLS, NULL); } /** * clapper_gtk_simple_controls_set_fullscreenable: * @controls: a #ClapperGtkSimpleControls * @fullscreenable: whether show button for toggling fullscreen state * * Set whether [class@ClapperGtk.ToggleFullscreenButton] button in the @controls * should be visible. * * You might want to consider setting this to %FALSE, if your application * does not implement [signal@ClapperGtk.Video::toggle-fullscreen] signal. */ void clapper_gtk_simple_controls_set_fullscreenable (ClapperGtkSimpleControls *self, gboolean fullscreenable) { g_return_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self)); if (self->fullscreenable != fullscreenable) { self->fullscreenable = fullscreenable; g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_FULLSCREENABLE]); } } /** * clapper_gtk_simple_controls_get_fullscreenable: * @controls: a #ClapperGtkSimpleControls * * Get whether [class@ClapperGtk.ToggleFullscreenButton] button in the @controls * is set to be visible. * * Returns: %TRUE when fullscreenable, %FALSE otherwise. */ gboolean clapper_gtk_simple_controls_get_fullscreenable (ClapperGtkSimpleControls *self) { g_return_val_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self), FALSE); return self->fullscreenable; } /** * clapper_gtk_simple_controls_set_seek_method: * @controls: a #ClapperGtkSimpleControls * @method: a #ClapperPlayerSeekMethod * * Set [enum@Clapper.PlayerSeekMethod] to use when seeking with progress bar. */ void clapper_gtk_simple_controls_set_seek_method (ClapperGtkSimpleControls *self, ClapperPlayerSeekMethod method) { g_return_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self)); clapper_gtk_seek_bar_set_seek_method (CLAPPER_GTK_SEEK_BAR (self->seek_bar), method); } /** * clapper_gtk_simple_controls_get_seek_method: * @controls: a #ClapperGtkSimpleControls * * Get [enum@Clapper.PlayerSeekMethod] used when seeking with progress bar. * * Returns: #ClapperPlayerSeekMethod used for seeking. */ ClapperPlayerSeekMethod clapper_gtk_simple_controls_get_seek_method (ClapperGtkSimpleControls *self) { g_return_val_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self), DEFAULT_SEEK_METHOD); return clapper_gtk_seek_bar_get_seek_method (CLAPPER_GTK_SEEK_BAR (self->seek_bar)); } /** * clapper_gtk_simple_controls_get_extra_menu_button: * @controls: a #ClapperGtkSimpleControls * * Get [class@ClapperGtk.ExtraMenuButton] that resides within @controls. * * Returns: (transfer none): #ClapperGtkExtraMenuButton within simple controls panel. */ ClapperGtkExtraMenuButton * clapper_gtk_simple_controls_get_extra_menu_button (ClapperGtkSimpleControls *self) { g_return_val_if_fail (CLAPPER_GTK_IS_SIMPLE_CONTROLS (self), NULL); return CLAPPER_GTK_EXTRA_MENU_BUTTON (self->extra_menu_button); } static void clapper_gtk_simple_controls_init (ClapperGtkSimpleControls *self) { self->fullscreenable = DEFAULT_FULLSCREENABLE; self->seek_method = DEFAULT_SEEK_METHOD; gtk_widget_init_template (GTK_WIDGET (self)); /* Set our defaults to children */ clapper_gtk_seek_bar_set_seek_method ( CLAPPER_GTK_SEEK_BAR_CAST (self->seek_bar), self->seek_method); } static void clapper_gtk_simple_controls_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_SIMPLE_CONTROLS); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_simple_controls_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkSimpleControls *self = CLAPPER_GTK_SIMPLE_CONTROLS_CAST (object); switch (prop_id) { case PROP_FULLSCREENABLE: g_value_set_boolean (value, clapper_gtk_simple_controls_get_fullscreenable (self)); break; case PROP_SEEK_METHOD: g_value_set_enum (value, clapper_gtk_simple_controls_get_seek_method (self)); break; case PROP_EXTRA_MENU_BUTTON: g_value_set_object (value, clapper_gtk_simple_controls_get_extra_menu_button (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_simple_controls_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkSimpleControls *self = CLAPPER_GTK_SIMPLE_CONTROLS_CAST (object); switch (prop_id) { case PROP_FULLSCREENABLE: clapper_gtk_simple_controls_set_fullscreenable (self, g_value_get_boolean (value)); break; case PROP_SEEK_METHOD: clapper_gtk_simple_controls_set_seek_method (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_simple_controls_class_init (ClapperGtkSimpleControlsClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtksimplecontrols", 0, "Clapper GTK Simple Controls"); gobject_class->get_property = clapper_gtk_simple_controls_get_property; gobject_class->set_property = clapper_gtk_simple_controls_set_property; gobject_class->dispose = clapper_gtk_simple_controls_dispose; /** * ClapperGtkSimpleControls:fullscreenable: * * Whether toggle fullscreen button should be visible. */ param_specs[PROP_FULLSCREENABLE] = g_param_spec_boolean ("fullscreenable", NULL, NULL, DEFAULT_FULLSCREENABLE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkSimpleControls:seek-method: * * Method used for seeking. */ param_specs[PROP_SEEK_METHOD] = g_param_spec_enum ("seek-method", NULL, NULL, CLAPPER_TYPE_PLAYER_SEEK_METHOD, DEFAULT_SEEK_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); /** * ClapperGtkSimpleControls:extra-menu-button: * * Access to extra menu button within controls. */ param_specs[PROP_EXTRA_MENU_BUTTON] = g_param_spec_object ("extra-menu-button", NULL, NULL, CLAPPER_GTK_TYPE_EXTRA_MENU_BUTTON, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-simple-controls.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, seek_bar); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, extra_menu_button); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, fullscreen_top_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, fullscreen_bottom_revealer); gtk_widget_class_bind_template_child (widget_class, ClapperGtkSimpleControls, controls_slide_revealer); gtk_widget_class_bind_template_callback (widget_class, initial_adapt_cb); gtk_widget_class_bind_template_callback (widget_class, full_adapt_cb); gtk_widget_class_bind_template_callback (widget_class, controls_revealed_cb); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-simple-controls"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-simple-controls.h000066400000000000000000000043361474353110700244400ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_SIMPLE_CONTROLS (clapper_gtk_simple_controls_get_type()) #define CLAPPER_GTK_SIMPLE_CONTROLS_CAST(obj) ((ClapperGtkSimpleControls *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkSimpleControls, clapper_gtk_simple_controls, CLAPPER_GTK, SIMPLE_CONTROLS, ClapperGtkContainer) CLAPPER_GTK_API GtkWidget * clapper_gtk_simple_controls_new (void); CLAPPER_GTK_API void clapper_gtk_simple_controls_set_fullscreenable (ClapperGtkSimpleControls *controls, gboolean fullscreenable); CLAPPER_GTK_API gboolean clapper_gtk_simple_controls_get_fullscreenable (ClapperGtkSimpleControls *controls); CLAPPER_GTK_API void clapper_gtk_simple_controls_set_seek_method (ClapperGtkSimpleControls *controls, ClapperPlayerSeekMethod method); CLAPPER_GTK_API ClapperPlayerSeekMethod clapper_gtk_simple_controls_get_seek_method (ClapperGtkSimpleControls *controls); CLAPPER_GTK_API ClapperGtkExtraMenuButton * clapper_gtk_simple_controls_get_extra_menu_button (ClapperGtkSimpleControls *controls); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-status-private.h000066400000000000000000000027421474353110700243000ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include #include "clapper-gtk-container.h" G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_STATUS (clapper_gtk_status_get_type()) #define CLAPPER_GTK_STATUS_CAST(obj) ((ClapperGtkStatus *)(obj)) G_DECLARE_FINAL_TYPE (ClapperGtkStatus, clapper_gtk_status, CLAPPER_GTK, STATUS, ClapperGtkContainer) G_GNUC_INTERNAL void clapper_gtk_status_set_error (ClapperGtkStatus *status, const GError *error); G_GNUC_INTERNAL void clapper_gtk_status_set_missing_plugin (ClapperGtkStatus *status, const gchar *name); G_GNUC_INTERNAL void clapper_gtk_status_clear (ClapperGtkStatus *status); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-status.c000066400000000000000000000110301474353110700226110ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-gtk-status-private.h" #include "clapper-gtk-utils-private.h" #define NORMAL_SPACING 16 #define ADAPT_SPACING 8 #define GST_CAT_DEFAULT clapper_gtk_status_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkStatus { ClapperGtkContainer parent; GtkWidget *status_box; GtkWidget *image; GtkWidget *title_label; GtkWidget *description_label; }; #define parent_class clapper_gtk_status_parent_class G_DEFINE_TYPE (ClapperGtkStatus, clapper_gtk_status, CLAPPER_GTK_TYPE_CONTAINER) static void adapt_cb (ClapperGtkContainer *container, gboolean adapt, ClapperGtkStatus *self) { GST_DEBUG_OBJECT (self, "Adapted: %s", (adapt) ? "yes" : "no"); gtk_box_set_spacing (GTK_BOX (self->status_box), (adapt) ? ADAPT_SPACING : NORMAL_SPACING); if (adapt) { gtk_widget_add_css_class (GTK_WIDGET (self), "adapted"); gtk_widget_add_css_class (GTK_WIDGET (self->title_label), "title-2"); } else { gtk_widget_remove_css_class (GTK_WIDGET (self), "adapted"); gtk_widget_remove_css_class (GTK_WIDGET (self->title_label), "title-2"); } } static void _set_status (ClapperGtkStatus *self, const gchar *icon_name, const gchar *title, const gchar *description) { gtk_image_set_from_icon_name (GTK_IMAGE (self->image), icon_name); gtk_label_set_label (GTK_LABEL (self->title_label), title); gtk_label_set_label (GTK_LABEL (self->description_label), description); gtk_widget_set_visible (GTK_WIDGET (self), TRUE); } void clapper_gtk_status_set_error (ClapperGtkStatus *self, const GError *error) { GST_DEBUG_OBJECT (self, "Status set to \"error\""); _set_status (self, "dialog-warning-symbolic", _("Unplayable Content"), error->message); } void clapper_gtk_status_set_missing_plugin (ClapperGtkStatus *self, const gchar *name) { gchar *description; GST_DEBUG_OBJECT (self, "Status set to \"missing-plugin\""); /* TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). */ description = g_strdup_printf (_("Your GStreamer installation is missing a plugin: %s"), name); _set_status (self, "dialog-information-symbolic", _("Missing Plugin"), description); g_free (description); } void clapper_gtk_status_clear (ClapperGtkStatus *self) { GST_DEBUG_OBJECT (self, "Status cleared"); gtk_widget_set_visible (GTK_WIDGET (self), FALSE); } static void clapper_gtk_status_init (ClapperGtkStatus *self) { gtk_widget_init_template (GTK_WIDGET (self)); gtk_box_set_spacing (GTK_BOX (self->status_box), NORMAL_SPACING); } static void clapper_gtk_status_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_STATUS); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_status_class_init (ClapperGtkStatusClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkstatus", 0, "Clapper GTK Status"); clapper_gtk_init_translations (); gobject_class->dispose = clapper_gtk_status_dispose; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-status.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, status_box); gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, image); gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, title_label); gtk_widget_class_bind_template_child (widget_class, ClapperGtkStatus, description_label); gtk_widget_class_bind_template_callback (widget_class, adapt_cb); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-status"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-stream-check-button-private.h000066400000000000000000000023661474353110700266360ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_STREAM_CHECK_BUTTON (clapper_gtk_stream_check_button_get_type()) #define CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST(obj) ((ClapperGtkStreamCheckButton *)(obj)) G_DECLARE_FINAL_TYPE (ClapperGtkStreamCheckButton, clapper_gtk_stream_check_button, CLAPPER_GTK, STREAM_CHECK_BUTTON, GtkCheckButton) G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-stream-check-button.c000066400000000000000000000211021474353110700251460ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-gtk-stream-check-button-private.h" #include "clapper-gtk-utils-private.h" #define MAX_SIGNALS 4 struct _ClapperGtkStreamCheckButton { GtkCheckButton parent; ClapperStream *stream; GtkWidget *fallback_check_button; gulong signal_ids[MAX_SIGNALS]; gboolean grouped; }; #define parent_class clapper_gtk_stream_check_button_parent_class G_DEFINE_TYPE (ClapperGtkStreamCheckButton, clapper_gtk_stream_check_button, GTK_TYPE_CHECK_BUTTON) enum { PROP_0, PROP_STREAM, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static inline gchar * _get_video_stream_label (ClapperVideoStream *vstream) { gchar *label, *codec = clapper_video_stream_get_codec (vstream); gint height = clapper_video_stream_get_height (vstream); gdouble fps = clapper_video_stream_get_fps (vstream); if (codec) { label = g_strdup_printf ("%ip@%.3f [%s]", height, fps, codec); g_free (codec); } else { label = g_strdup_printf ("%ip@%.3f", height, fps); } return label; } static inline gchar * _get_audio_stream_label (ClapperAudioStream *astream) { gchar *label, *title, *codec = clapper_audio_stream_get_codec (astream); gint channels = clapper_audio_stream_get_channels (astream); if (!(title = clapper_stream_get_title (CLAPPER_STREAM_CAST (astream)))) title = clapper_audio_stream_get_lang_name (astream); if (codec) { label = g_strdup_printf ("%s [%s, %i %s]", (title) ? title : _("Undetermined"), codec, channels, _("Channels")); g_free (title); g_free (codec); } else { label = title; } return label; } static inline gchar * _get_subtitle_stream_label (ClapperSubtitleStream *sstream) { gchar *title; if (!(title = clapper_stream_get_title (CLAPPER_STREAM_CAST (sstream)))) title = clapper_subtitle_stream_get_lang_name (sstream); return title; } static void _refresh_label_cb (ClapperStream *stream, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkStreamCheckButton *self) { gchar *label = NULL; if (stream) { switch (clapper_stream_get_stream_type (stream)) { case CLAPPER_STREAM_TYPE_VIDEO: label = _get_video_stream_label (CLAPPER_VIDEO_STREAM (stream)); break; case CLAPPER_STREAM_TYPE_AUDIO: label = _get_audio_stream_label (CLAPPER_AUDIO_STREAM (stream)); break; case CLAPPER_STREAM_TYPE_SUBTITLE: label = _get_subtitle_stream_label (CLAPPER_SUBTITLE_STREAM (stream)); break; default: break; } } gtk_check_button_set_label (GTK_CHECK_BUTTON (self), (label != NULL) ? label : _("Undetermined")); g_free (label); } static inline void _disconnect_current_signals (ClapperGtkStreamCheckButton *self) { guint i; for (i = 0; i < MAX_SIGNALS; ++i) { /* No more signals connected */ if (self->signal_ids[i] == 0) break; g_signal_handler_disconnect (self->stream, self->signal_ids[i]); self->signal_ids[i] = 0; } } static inline void clapper_gtk_stream_check_button_set_stream (ClapperGtkStreamCheckButton *self, ClapperStream *stream) { guint i = 0; _disconnect_current_signals (self); gst_object_replace ((GstObject **) &self->stream, GST_OBJECT_CAST (stream)); if (!self->stream) return; switch (clapper_stream_get_stream_type (stream)) { case CLAPPER_STREAM_TYPE_VIDEO: self->signal_ids[i++] = g_signal_connect (self->stream, "notify::codec", G_CALLBACK (_refresh_label_cb), self); self->signal_ids[i++] = g_signal_connect (self->stream, "notify::height", G_CALLBACK (_refresh_label_cb), self); self->signal_ids[i++] = g_signal_connect (self->stream, "notify::fps", G_CALLBACK (_refresh_label_cb), self); break; case CLAPPER_STREAM_TYPE_AUDIO: self->signal_ids[i++] = g_signal_connect (self->stream, "notify::codec", G_CALLBACK (_refresh_label_cb), self); self->signal_ids[i++] = g_signal_connect (self->stream, "notify::channels", G_CALLBACK (_refresh_label_cb), self); G_GNUC_FALLTHROUGH; case CLAPPER_STREAM_TYPE_SUBTITLE: self->signal_ids[i++] = g_signal_connect (self->stream, "notify::title", G_CALLBACK (_refresh_label_cb), self); self->signal_ids[i++] = g_signal_connect (self->stream, "notify::lang-name", G_CALLBACK (_refresh_label_cb), self); break; default: break; } _refresh_label_cb (self->stream, NULL, self); } static void clapper_gtk_stream_check_button_realize (GtkWidget *widget) { ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (widget); GtkWidget *other_widget; GST_TRACE_OBJECT (self, "Realize"); /* Set same group as previous check button in the same list view */ if (!self->grouped) { if ((other_widget = gtk_widget_get_parent (widget)) && (other_widget = gtk_widget_get_prev_sibling (other_widget)) && (other_widget = gtk_widget_get_first_child (other_widget)) && CLAPPER_GTK_IS_STREAM_CHECK_BUTTON (other_widget)) { gtk_check_button_set_group (GTK_CHECK_BUTTON (self), GTK_CHECK_BUTTON (other_widget)); } else { if (!self->fallback_check_button) self->fallback_check_button = g_object_ref_sink (gtk_check_button_new ()); gtk_check_button_set_group (GTK_CHECK_BUTTON (self), GTK_CHECK_BUTTON (self->fallback_check_button)); } self->grouped = TRUE; } GTK_WIDGET_CLASS (parent_class)->realize (widget); } static void clapper_gtk_stream_check_button_toggled (GtkCheckButton *check_button) { ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (check_button); if (gtk_check_button_get_active (check_button) && self->stream) { ClapperStreamList *stream_list = CLAPPER_STREAM_LIST (gst_object_get_parent (GST_OBJECT (self->stream))); if (G_LIKELY (stream_list != NULL)) { GST_INFO_OBJECT (self, "Toggled: %" GST_PTR_FORMAT, self->stream); clapper_stream_list_select_stream (stream_list, self->stream); gst_object_unref (stream_list); } } } static void clapper_gtk_stream_check_button_init (ClapperGtkStreamCheckButton *self) { } static void clapper_gtk_stream_check_button_finalize (GObject *object) { ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); _disconnect_current_signals (self); gst_clear_object (&self->stream); g_clear_object (&self->fallback_check_button); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_gtk_stream_check_button_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkStreamCheckButton *self = CLAPPER_GTK_STREAM_CHECK_BUTTON_CAST (object); switch (prop_id) { case PROP_STREAM: clapper_gtk_stream_check_button_set_stream (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_stream_check_button_class_init (ClapperGtkStreamCheckButtonClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GtkCheckButtonClass *check_button_class = (GtkCheckButtonClass *) klass; clapper_gtk_init_translations (); gobject_class->set_property = clapper_gtk_stream_check_button_set_property; gobject_class->finalize = clapper_gtk_stream_check_button_finalize; widget_class->realize = clapper_gtk_stream_check_button_realize; check_button_class->toggled = clapper_gtk_stream_check_button_toggled; param_specs[PROP_STREAM] = g_param_spec_object ("stream", NULL, NULL, CLAPPER_TYPE_STREAM, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-title-header.c000066400000000000000000000156701474353110700236530ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkTitleHeader: * * A header panel widget that displays current media title. * * #ClapperGtkTitleHeader is a simple, ready to be used header widget that * displays current media title. It can be placed as-is as a [class@ClapperGtk.Video] * overlay (either fading or not). * * If you need a further customized header, you can use [class@ClapperGtk.TitleLabel] * which is used by this widget to build your own implementation instead. */ #include "config.h" #include #include "clapper-gtk-title-header.h" #include "clapper-gtk-title-label.h" #define DEFAULT_FALLBACK_TO_URI FALSE #define GST_CAT_DEFAULT clapper_gtk_title_header_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkTitleHeader { ClapperGtkLeadContainer parent; ClapperGtkTitleLabel *label; }; #define parent_class clapper_gtk_title_header_parent_class G_DEFINE_TYPE (ClapperGtkTitleHeader, clapper_gtk_title_header, CLAPPER_GTK_TYPE_LEAD_CONTAINER) enum { PROP_0, PROP_CURRENT_TITLE, PROP_FALLBACK_TO_URI, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void _label_current_title_changed_cb (ClapperGtkTitleLabel *label G_GNUC_UNUSED, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleHeader *self) { /* Forward current title changed notify from internal label */ g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_TITLE]); } /** * clapper_gtk_title_header_new: * * Creates a new #ClapperGtkTitleHeader instance. * * Returns: a new title header #GtkWidget. */ GtkWidget * clapper_gtk_title_header_new (void) { return g_object_new (CLAPPER_GTK_TYPE_TITLE_HEADER, NULL); } /** * clapper_gtk_title_header_get_current_title: * @header: a #ClapperGtkTitleHeader * * Get currently displayed title by @header. * * Returns: (transfer none): text of title label. */ const gchar * clapper_gtk_title_header_get_current_title (ClapperGtkTitleHeader *self) { g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_HEADER (self), NULL); return clapper_gtk_title_label_get_current_title (self->label); } /** * clapper_gtk_title_header_set_fallback_to_uri: * @header: a #ClapperGtkTitleHeader * @enabled: whether enabled * * Set whether a [property@Clapper.MediaItem:uri] property should * be displayed as a header text when no other title could be determined. */ void clapper_gtk_title_header_set_fallback_to_uri (ClapperGtkTitleHeader *self, gboolean enabled) { g_return_if_fail (CLAPPER_GTK_IS_TITLE_HEADER (self)); clapper_gtk_title_label_set_fallback_to_uri (self->label, enabled); } /** * clapper_gtk_title_header_get_fallback_to_uri: * @header: a #ClapperGtkTitleHeader * * Get whether a [property@Clapper.MediaItem:uri] property is going * be displayed as a header text when no other title could be determined. * * Returns: %TRUE when item URI will be used as fallback, %FALSE otherwise. */ gboolean clapper_gtk_title_header_get_fallback_to_uri (ClapperGtkTitleHeader *self) { g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_HEADER (self), FALSE); return clapper_gtk_title_label_get_fallback_to_uri (self->label); } static void clapper_gtk_title_header_init (ClapperGtkTitleHeader *self) { gtk_widget_init_template (GTK_WIDGET (self)); clapper_gtk_title_label_set_fallback_to_uri (self->label, DEFAULT_FALLBACK_TO_URI); g_object_bind_property (self->label, "fallback-to-uri", self, "fallback-to-uri", G_BINDING_DEFAULT); g_signal_connect (self->label, "notify::current-title", G_CALLBACK (_label_current_title_changed_cb), self); } static void clapper_gtk_title_header_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_TITLE_HEADER); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_title_header_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkTitleHeader *self = CLAPPER_GTK_TITLE_HEADER_CAST (object); switch (prop_id) { case PROP_CURRENT_TITLE: g_value_set_string (value, clapper_gtk_title_header_get_current_title (self)); break; case PROP_FALLBACK_TO_URI: g_value_set_boolean (value, clapper_gtk_title_header_get_fallback_to_uri (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_title_header_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkTitleHeader *self = CLAPPER_GTK_TITLE_HEADER_CAST (object); switch (prop_id) { case PROP_FALLBACK_TO_URI: clapper_gtk_title_header_set_fallback_to_uri (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_title_header_class_init (ClapperGtkTitleHeaderClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktitleheader", 0, "Clapper GTK Title Header"); gobject_class->get_property = clapper_gtk_title_header_get_property; gobject_class->set_property = clapper_gtk_title_header_set_property; gobject_class->dispose = clapper_gtk_title_header_dispose; /** * ClapperGtkTitleHeader:current-title: * * Currently displayed title. */ param_specs[PROP_CURRENT_TITLE] = g_param_spec_string ("current-title", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkTitleHeader:fallback-to-uri: * * When title cannot be determined, show URI instead. */ param_specs[PROP_FALLBACK_TO_URI] = g_param_spec_boolean ("fallback-to-uri", NULL, NULL, DEFAULT_FALLBACK_TO_URI, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-title-header.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkTitleHeader, label); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-title-header"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-title-header.h000066400000000000000000000035461474353110700236570ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_TITLE_HEADER (clapper_gtk_title_header_get_type()) #define CLAPPER_GTK_TITLE_HEADER_CAST(obj) ((ClapperGtkTitleHeader *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkTitleHeader, clapper_gtk_title_header, CLAPPER_GTK, TITLE_HEADER, ClapperGtkLeadContainer) CLAPPER_GTK_API GtkWidget * clapper_gtk_title_header_new (void); CLAPPER_GTK_API const gchar * clapper_gtk_title_header_get_current_title (ClapperGtkTitleHeader *header); CLAPPER_GTK_API void clapper_gtk_title_header_set_fallback_to_uri (ClapperGtkTitleHeader *header, gboolean enabled); CLAPPER_GTK_API gboolean clapper_gtk_title_header_get_fallback_to_uri (ClapperGtkTitleHeader *header); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-title-label.c000066400000000000000000000342371474353110700235020ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkTitleLabel: * * A label showing an up to date title of media item. * * By default #ClapperGtkTitleLabel will automatically show title * of [property@Clapper.Queue:current-item] when placed within * [class@ClapperGtk.Video] widget hierarchy. * * Setting [property@ClapperGtk.TitleLabel:media-item] property will * make it show title of that particular [class@Clapper.MediaItem] * instead. Providing an item to read title from also allows using * this [class@Gtk.Widget] outside of [class@ClapperGtk.Video]. */ #include "config.h" #include #include #include "clapper-gtk-title-label.h" #include "clapper-gtk-utils-private.h" #define DEFAULT_FALLBACK_TO_URI FALSE #define GST_CAT_DEFAULT clapper_gtk_title_label_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkTitleLabel { GtkWidget parent; GtkLabel *label; ClapperMediaItem *current_item; ClapperMediaItem *custom_item; gboolean fallback_to_uri; ClapperPlayer *player; }; #define parent_class clapper_gtk_title_label_parent_class G_DEFINE_TYPE (ClapperGtkTitleLabel, clapper_gtk_title_label, GTK_TYPE_WIDGET) enum { PROP_0, PROP_MEDIA_ITEM, PROP_CURRENT_TITLE, PROP_FALLBACK_TO_URI, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void _label_changed_cb (GtkLabel *label G_GNUC_UNUSED, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleLabel *self) { g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_TITLE]); } static void _refresh_title (ClapperGtkTitleLabel *self) { ClapperMediaItem *item; gchar *title; item = (self->custom_item) ? self->custom_item : self->current_item; if (!item) { gtk_label_set_label (self->label, _("No media")); return; } title = clapper_media_item_get_title (item); if (title) { gtk_label_set_label (self->label, title); g_free (title); } else if (self->fallback_to_uri) { gtk_label_set_label (self->label, clapper_media_item_get_uri (item)); } else { gtk_label_set_label (self->label, _("Unknown title")); } } static void _media_item_title_changed_cb (ClapperMediaItem *item G_GNUC_UNUSED, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleLabel *self) { _refresh_title (self); } static void _set_current_item (ClapperGtkTitleLabel *self, ClapperMediaItem *current_item) { /* Disconnect signal from old item */ if (self->current_item) { g_signal_handlers_disconnect_by_func (self->current_item, _media_item_title_changed_cb, self); } gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (current_item)); GST_DEBUG ("Current item changed to: %" GST_PTR_FORMAT, self->current_item); /* Reconnect signal to new item */ if (self->current_item) { g_signal_connect (self->current_item, "notify::title", G_CALLBACK (_media_item_title_changed_cb), self); } } static void _queue_current_item_changed_cb (ClapperQueue *queue, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkTitleLabel *self) { ClapperMediaItem *current_item = clapper_queue_get_current_item (queue); _set_current_item (self, current_item); _refresh_title (self); gst_clear_object (¤t_item); } static void _bind_current_item (ClapperGtkTitleLabel *self) { ClapperQueue *queue = clapper_player_get_queue (self->player); ClapperMediaItem *current_item; GST_DEBUG ("Binding current item"); g_signal_connect (queue, "notify::current-item", G_CALLBACK (_queue_current_item_changed_cb), self); current_item = clapper_queue_get_current_item (queue); _set_current_item (self, current_item); gst_clear_object (¤t_item); } static void _unbind_current_item (ClapperGtkTitleLabel *self) { ClapperQueue *queue = clapper_player_get_queue (self->player); GST_DEBUG ("Unbinding current item"); g_signal_handlers_disconnect_by_func (queue, _queue_current_item_changed_cb, self); _set_current_item (self, NULL); } /** * clapper_gtk_title_label_new: * * Creates a new #ClapperGtkTitleLabel instance. * * Returns: a new title label #GtkWidget. */ GtkWidget * clapper_gtk_title_label_new (void) { return g_object_new (CLAPPER_GTK_TYPE_TITLE_LABEL, NULL); } /** * clapper_gtk_title_label_set_media_item: * @label: a #ClapperGtkTitleLabel * @item: (nullable): a #ClapperMediaItem * * Set a media item to display title of as label. When set to %NULL, * @label will use default behavior (showing title of current queue item). */ void clapper_gtk_title_label_set_media_item (ClapperGtkTitleLabel *self, ClapperMediaItem *item) { g_return_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self)); g_return_if_fail (item == NULL || CLAPPER_IS_MEDIA_ITEM (item)); if (self->custom_item == item) return; if (self->player) { _unbind_current_item (self); self->player = NULL; } if (self->custom_item) { g_signal_handlers_disconnect_by_func (self->custom_item, _media_item_title_changed_cb, self); } gst_object_replace ((GstObject **) &self->custom_item, GST_OBJECT_CAST (item)); GST_DEBUG ("Set media item: %" GST_PTR_FORMAT, self->custom_item); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_MEDIA_ITEM]); if (self->custom_item) { g_signal_connect (self->custom_item, "notify::title", G_CALLBACK (_media_item_title_changed_cb), self); } else if ((self->player = clapper_gtk_get_player_from_ancestor (GTK_WIDGET (self)))) { _bind_current_item (self); } _refresh_title (self); } /** * clapper_gtk_title_label_get_media_item: * @label: a #ClapperGtkTitleLabel * * Get currently set media item to display title of. * * Returns: (transfer none) (nullable): currently set media item. */ ClapperMediaItem * clapper_gtk_title_label_get_media_item (ClapperGtkTitleLabel *self) { g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self), NULL); return self->custom_item; } /** * clapper_gtk_title_label_get_current_title: * @label: a #ClapperGtkTitleLabel * * Get currently displayed title by @label. * * Returns: (transfer none): text of title label. */ const gchar * clapper_gtk_title_label_get_current_title (ClapperGtkTitleLabel *self) { g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self), NULL); return gtk_label_get_label (self->label); } /** * clapper_gtk_title_label_set_fallback_to_uri: * @label: a #ClapperGtkTitleLabel * @enabled: whether enabled * * Set whether a [property@Clapper.MediaItem:uri] property should * be displayed as a label text when no other title could be determined. */ void clapper_gtk_title_label_set_fallback_to_uri (ClapperGtkTitleLabel *self, gboolean enabled) { g_return_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self)); if (self->fallback_to_uri != enabled) { self->fallback_to_uri = enabled; g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_FALLBACK_TO_URI]); _refresh_title (self); } } /** * clapper_gtk_title_label_get_fallback_to_uri: * @label: a #ClapperGtkTitleLabel * * Get whether a [property@Clapper.MediaItem:uri] property is going * be displayed as a label text when no other title could be determined. * * Returns: %TRUE when item URI will be used as fallback, %FALSE otherwise. */ gboolean clapper_gtk_title_label_get_fallback_to_uri (ClapperGtkTitleLabel *self) { g_return_val_if_fail (CLAPPER_GTK_IS_TITLE_LABEL (self), FALSE); return self->fallback_to_uri; } static void clapper_gtk_title_label_init (ClapperGtkTitleLabel *self) { self->label = GTK_LABEL (gtk_label_new (NULL)); gtk_label_set_single_line_mode (self->label, TRUE); gtk_label_set_ellipsize (self->label, PANGO_ELLIPSIZE_END); gtk_widget_set_can_target (GTK_WIDGET (self->label), FALSE); gtk_widget_set_parent (GTK_WIDGET (self->label), GTK_WIDGET (self)); self->fallback_to_uri = DEFAULT_FALLBACK_TO_URI; /* Apply CSS styles to internal label */ g_object_bind_property (self, "css-classes", self->label, "css-classes", G_BINDING_DEFAULT); } static void clapper_gtk_title_label_constructed (GObject *object) { ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object); /* Ensure label if no custom item set yet */ if (!self->custom_item) _refresh_title (self); /* This avoids us from comparing label changes as GTK will do this * for us and emit this signal only when label text actually changes. */ g_signal_connect (self->label, "notify::label", G_CALLBACK (_label_changed_cb), self); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_gtk_title_label_compute_expand (GtkWidget *widget, gboolean *hexpand_p, gboolean *vexpand_p) { ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (widget); *hexpand_p = gtk_widget_compute_expand ((GtkWidget *) self->label, GTK_ORIENTATION_HORIZONTAL); *vexpand_p = gtk_widget_compute_expand ((GtkWidget *) self->label, GTK_ORIENTATION_VERTICAL); } static void clapper_gtk_title_label_root (GtkWidget *widget) { ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (widget); GTK_WIDGET_CLASS (parent_class)->root (widget); if (!self->custom_item && (self->player = clapper_gtk_get_player_from_ancestor (widget))) { GST_LOG ("Label placed without media item set"); _bind_current_item (self); _refresh_title (self); } } static void clapper_gtk_title_label_unroot (GtkWidget *widget) { ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (widget); if (self->player) { _unbind_current_item (self); self->player = NULL; } GTK_WIDGET_CLASS (parent_class)->unroot (widget); } static void _label_unparent (GtkLabel *label) { gtk_widget_unparent (GTK_WIDGET (label)); } static void clapper_gtk_title_label_dispose (GObject *object) { ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object); if (self->custom_item) { g_signal_handlers_disconnect_by_func (self->custom_item, _media_item_title_changed_cb, self); } if (self->label) { g_signal_handlers_disconnect_by_func (self->label, _label_changed_cb, self); } gst_clear_object (&self->current_item); gst_clear_object (&self->custom_item); g_clear_pointer (&self->label, _label_unparent); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_title_label_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object); switch (prop_id) { case PROP_MEDIA_ITEM: g_value_set_object (value, clapper_gtk_title_label_get_media_item (self)); break; case PROP_CURRENT_TITLE: g_value_set_string (value, clapper_gtk_title_label_get_current_title (self)); break; case PROP_FALLBACK_TO_URI: g_value_set_boolean (value, clapper_gtk_title_label_get_fallback_to_uri (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_title_label_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkTitleLabel *self = CLAPPER_GTK_TITLE_LABEL_CAST (object); switch (prop_id) { case PROP_MEDIA_ITEM: clapper_gtk_title_label_set_media_item (self, g_value_get_object (value)); break; case PROP_FALLBACK_TO_URI: clapper_gtk_title_label_set_fallback_to_uri (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_title_label_class_init (ClapperGtkTitleLabelClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktitlelabel", 0, "Clapper GTK Title Label"); clapper_gtk_init_translations (); gobject_class->constructed = clapper_gtk_title_label_constructed; gobject_class->get_property = clapper_gtk_title_label_get_property; gobject_class->set_property = clapper_gtk_title_label_set_property; gobject_class->dispose = clapper_gtk_title_label_dispose; widget_class->compute_expand = clapper_gtk_title_label_compute_expand; /* Using root/unroot so label "current-title" is immediately * updated and can be accessed before label was made visible */ widget_class->root = clapper_gtk_title_label_root; widget_class->unroot = clapper_gtk_title_label_unroot; /** * ClapperGtkTitleLabel:media-item: * * Currently set media item to display title of. */ param_specs[PROP_MEDIA_ITEM] = g_param_spec_object ("media-item", NULL, NULL, CLAPPER_TYPE_MEDIA_ITEM, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkTitleLabel:current-title: * * Currently displayed title. */ param_specs[PROP_CURRENT_TITLE] = g_param_spec_string ("current-title", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkTitleLabel:fallback-to-uri: * * When title cannot be determined, show URI instead. */ param_specs[PROP_FALLBACK_TO_URI] = g_param_spec_boolean ("fallback-to-uri", NULL, NULL, DEFAULT_FALLBACK_TO_URI, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-title-label"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-title-label.h000066400000000000000000000040161474353110700234770ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_TITLE_LABEL (clapper_gtk_title_label_get_type()) #define CLAPPER_GTK_TITLE_LABEL_CAST(obj) ((ClapperGtkTitleLabel *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkTitleLabel, clapper_gtk_title_label, CLAPPER_GTK, TITLE_LABEL, GtkWidget) CLAPPER_GTK_API GtkWidget * clapper_gtk_title_label_new (void); CLAPPER_GTK_API void clapper_gtk_title_label_set_media_item (ClapperGtkTitleLabel *label, ClapperMediaItem *item); CLAPPER_GTK_API ClapperMediaItem * clapper_gtk_title_label_get_media_item (ClapperGtkTitleLabel *label); CLAPPER_GTK_API const gchar * clapper_gtk_title_label_get_current_title (ClapperGtkTitleLabel *label); CLAPPER_GTK_API void clapper_gtk_title_label_set_fallback_to_uri (ClapperGtkTitleLabel *label, gboolean enabled); CLAPPER_GTK_API gboolean clapper_gtk_title_label_get_fallback_to_uri (ClapperGtkTitleLabel *label); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.c000066400000000000000000000112501474353110700262240ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkToggleFullscreenButton: * * A #GtkButton for toggling fullscreen state. */ #include #include "clapper-gtk-toggle-fullscreen-button.h" #include "clapper-gtk-video.h" #define ENTER_FULLSCREEN_ICON_NAME "view-fullscreen-symbolic" #define LEAVE_FULLSCREEN_ICON_NAME "view-restore-symbolic" #define GST_CAT_DEFAULT clapper_gtk_toggle_fullscreen_button_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkToggleFullscreenButton { GtkButton parent; gboolean is_fullscreen; }; #define parent_class clapper_gtk_toggle_fullscreen_button_parent_class G_DEFINE_TYPE (ClapperGtkToggleFullscreenButton, clapper_gtk_toggle_fullscreen_button, GTK_TYPE_BUTTON) static void _toplevel_state_changed_cb (GdkToplevel *toplevel, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkToggleFullscreenButton *self) { GdkToplevelState state = gdk_toplevel_get_state (toplevel); gboolean is_fullscreen = (state & GDK_TOPLEVEL_STATE_FULLSCREEN); if (self->is_fullscreen == is_fullscreen) return; self->is_fullscreen = is_fullscreen; GST_DEBUG_OBJECT (self, "Toplevel state changed, fullscreen: %s", (self->is_fullscreen) ? "yes" : "no"); gtk_button_set_icon_name (GTK_BUTTON (self), (!self->is_fullscreen) ? ENTER_FULLSCREEN_ICON_NAME : LEAVE_FULLSCREEN_ICON_NAME); } /** * clapper_gtk_toggle_fullscreen_button_new: * * Creates a new #ClapperGtkToggleFullscreenButton instance. * * Returns: a new toggle fullscreen button #GtkWidget. */ GtkWidget * clapper_gtk_toggle_fullscreen_button_new (void) { return g_object_new (CLAPPER_GTK_TYPE_TOGGLE_FULLSCREEN_BUTTON, NULL); } static void clapper_gtk_toggle_fullscreen_button_init (ClapperGtkToggleFullscreenButton *self) { gtk_button_set_icon_name (GTK_BUTTON (self), ENTER_FULLSCREEN_ICON_NAME); } static void clapper_gtk_toggle_fullscreen_button_map (GtkWidget *widget) { ClapperGtkToggleFullscreenButton *self = CLAPPER_GTK_TOGGLE_FULLSCREEN_BUTTON_CAST (widget); GtkRoot *root; GdkSurface *surface; GST_TRACE_OBJECT (self, "Map"); root = gtk_widget_get_root (widget); surface = gtk_native_get_surface (GTK_NATIVE (root)); if (G_LIKELY (GDK_IS_TOPLEVEL (surface))) { GdkToplevel *toplevel = GDK_TOPLEVEL (surface); g_signal_connect (toplevel, "notify::state", G_CALLBACK (_toplevel_state_changed_cb), self); _toplevel_state_changed_cb (toplevel, NULL, self); } GTK_WIDGET_CLASS (parent_class)->map (widget); } static void clapper_gtk_toggle_fullscreen_button_unmap (GtkWidget *widget) { ClapperGtkToggleFullscreenButton *self = CLAPPER_GTK_TOGGLE_FULLSCREEN_BUTTON_CAST (widget); GtkRoot *root; GdkSurface *surface; GST_TRACE_OBJECT (self, "Unmap"); root = gtk_widget_get_root (widget); surface = gtk_native_get_surface (GTK_NATIVE (root)); if (G_LIKELY (GDK_IS_TOPLEVEL (surface))) { GdkToplevel *toplevel = GDK_TOPLEVEL (surface); g_signal_handlers_disconnect_by_func (toplevel, _toplevel_state_changed_cb, self); } GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void clapper_gtk_toggle_fullscreen_button_clicked (GtkButton* button) { GtkWidget *video; GST_DEBUG_OBJECT (button, "Clicked"); if ((video = gtk_widget_get_ancestor (GTK_WIDGET (button), CLAPPER_GTK_TYPE_VIDEO))) g_signal_emit_by_name (video, "toggle-fullscreen"); } static void clapper_gtk_toggle_fullscreen_button_class_init (ClapperGtkToggleFullscreenButtonClass *klass) { GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GtkButtonClass *button_class = (GtkButtonClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktogglefullscreenbutton", 0, "Clapper GTK Toggle Fullscreen Button"); widget_class->map = clapper_gtk_toggle_fullscreen_button_map; widget_class->unmap = clapper_gtk_toggle_fullscreen_button_unmap; button_class->clicked = clapper_gtk_toggle_fullscreen_button_clicked; } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-toggle-fullscreen-button.h000066400000000000000000000030711474353110700262330ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_TOGGLE_FULLSCREEN_BUTTON (clapper_gtk_toggle_fullscreen_button_get_type()) #define CLAPPER_GTK_TOGGLE_FULLSCREEN_BUTTON_CAST(obj) ((ClapperGtkToggleFullscreenButton *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkToggleFullscreenButton, clapper_gtk_toggle_fullscreen_button, CLAPPER_GTK, TOGGLE_FULLSCREEN_BUTTON, GtkButton) CLAPPER_GTK_API GtkWidget * clapper_gtk_toggle_fullscreen_button_new (void); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.c000066400000000000000000000073561474353110700250430ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkTogglePlayButton: * * A #GtkButton for toggling play/pause of playback. */ #include #include "clapper-gtk-toggle-play-button.h" #include "clapper-gtk-utils.h" #define PLAY_ICON_NAME "media-playback-start-symbolic" #define PAUSE_ICON_NAME "media-playback-pause-symbolic" #define GST_CAT_DEFAULT clapper_gtk_toggle_play_button_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkTogglePlayButton { GtkButton parent; GBinding *state_binding; }; #define parent_class clapper_gtk_toggle_play_button_parent_class G_DEFINE_TYPE (ClapperGtkTogglePlayButton, clapper_gtk_toggle_play_button, GTK_TYPE_BUTTON) static gboolean _transform_state_func (GBinding *binding, const GValue *from_value, GValue *to_value, ClapperGtkTogglePlayButton *self) { ClapperPlayerState state = g_value_get_enum (from_value); GST_DEBUG_OBJECT (self, "Reflecting player state change, now: %i", state); switch (state) { case CLAPPER_PLAYER_STATE_STOPPED: case CLAPPER_PLAYER_STATE_PAUSED: g_value_set_string (to_value, PLAY_ICON_NAME); break; case CLAPPER_PLAYER_STATE_PLAYING: g_value_set_string (to_value, PAUSE_ICON_NAME); break; default: return FALSE; // no change } return TRUE; } /** * clapper_gtk_toggle_play_button_new: * * Creates a new #ClapperGtkTogglePlayButton instance. * * Returns: a new toggle play button #GtkWidget. */ GtkWidget * clapper_gtk_toggle_play_button_new (void) { return g_object_new (CLAPPER_GTK_TYPE_TOGGLE_PLAY_BUTTON, NULL); } static void clapper_gtk_toggle_play_button_init (ClapperGtkTogglePlayButton *self) { gtk_button_set_icon_name (GTK_BUTTON (self), PLAY_ICON_NAME); gtk_actionable_set_action_name (GTK_ACTIONABLE (self), "video.toggle-play"); } static void clapper_gtk_toggle_play_button_map (GtkWidget *widget) { ClapperGtkTogglePlayButton *self = CLAPPER_GTK_TOGGLE_PLAY_BUTTON_CAST (widget); ClapperPlayer *player; if ((player = clapper_gtk_get_player_from_ancestor (widget))) { self->state_binding = g_object_bind_property_full (player, "state", self, "icon-name", G_BINDING_SYNC_CREATE, (GBindingTransformFunc) _transform_state_func, NULL, self, NULL); } GTK_WIDGET_CLASS (parent_class)->map (widget); } static void clapper_gtk_toggle_play_button_unmap (GtkWidget *widget) { ClapperGtkTogglePlayButton *self = CLAPPER_GTK_TOGGLE_PLAY_BUTTON_CAST (widget); g_clear_pointer (&self->state_binding, g_binding_unbind); GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void clapper_gtk_toggle_play_button_class_init (ClapperGtkTogglePlayButtonClass *klass) { GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtktoggleplaybutton", 0, "Clapper GTK Toggle Play Button"); widget_class->map = clapper_gtk_toggle_play_button_map; widget_class->unmap = clapper_gtk_toggle_play_button_unmap; } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-toggle-play-button.h000066400000000000000000000030111474353110700250300ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_TOGGLE_PLAY_BUTTON (clapper_gtk_toggle_play_button_get_type()) #define CLAPPER_GTK_TOGGLE_PLAY_BUTTON_CAST(obj) ((ClapperGtkTogglePlayButton *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkTogglePlayButton, clapper_gtk_toggle_play_button, CLAPPER_GTK, TOGGLE_PLAY_BUTTON, GtkButton) CLAPPER_GTK_API GtkWidget * clapper_gtk_toggle_play_button_new (void); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-utils-private.h000066400000000000000000000021761474353110700241160ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 "clapper-gtk-utils.h" G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_gtk_init_translations (void); G_GNUC_INTERNAL const gchar * clapper_gtk_get_icon_name_for_volume (gfloat volume); G_GNUC_INTERNAL const gchar * clapper_gtk_get_icon_name_for_speed (gfloat speed); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-utils.c000066400000000000000000000062531474353110700224410ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-gtk-utils-private.h" #include "clapper-gtk-video.h" static gboolean initialized = FALSE; /** * clapper_gtk_get_player_from_ancestor: * @widget: a #GtkWidget * * Get [class@Clapper.Player] used by [class@ClapperGtk.Video] ancestor of @widget. * * This utility is a convenience wrapper for calling [method@Gtk.Widget.get_ancestor] * of type `CLAPPER_GTK_TYPE_VIDEO` and [method@ClapperGtk.Video.get_player] with * additional %NULL checking and type casting. * * This is meant to be used mainly for custom widget development as an easy access to the * underlying parent [class@Clapper.Player] object. If you want to get the player from * [class@ClapperGtk.Video] widget itself, use [method@ClapperGtk.Video.get_player] instead. * * Rememeber that this function will return %NULL when widget does not have * a [class@ClapperGtk.Video] ancestor in widget hierarchy (widget is not yet placed). * * Returns: (transfer none) (nullable): a #ClapperPlayer from ancestor of a @widget. */ ClapperPlayer * clapper_gtk_get_player_from_ancestor (GtkWidget *widget) { GtkWidget *parent; ClapperPlayer *player = NULL; g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); if ((parent = gtk_widget_get_ancestor (widget, CLAPPER_GTK_TYPE_VIDEO))) player = clapper_gtk_video_get_player (CLAPPER_GTK_VIDEO_CAST (parent)); return player; } void clapper_gtk_init_translations (void) { const gchar *clapper_gtk_ldir; if (initialized) return; if (!(clapper_gtk_ldir = g_getenv ("CLAPPER_GTK_OVERRIDE_LOCALEDIR"))) clapper_gtk_ldir = LOCALEDIR; bindtextdomain (GETTEXT_PACKAGE, clapper_gtk_ldir); bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); initialized = TRUE; } const gchar * clapper_gtk_get_icon_name_for_volume (gfloat volume) { return (volume <= 0.0f) ? "audio-volume-muted-symbolic" : (volume <= 0.3f) ? "audio-volume-low-symbolic" : (volume <= 0.7f) ? "audio-volume-medium-symbolic" : (volume <= 1.0f) ? "audio-volume-high-symbolic" : "audio-volume-overamplified-symbolic"; } const gchar * clapper_gtk_get_icon_name_for_speed (gfloat speed) { return (speed < 1.0f) ? "power-profile-power-saver-symbolic" : (speed == 1.0f) ? "power-profile-balanced-symbolic" : "power-profile-performance-symbolic"; } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-utils.h000066400000000000000000000023411474353110700224400ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include G_BEGIN_DECLS CLAPPER_GTK_API ClapperPlayer * clapper_gtk_get_player_from_ancestor (GtkWidget *widget); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-version.h.in000066400000000000000000000047441474353110700234030ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif /** * CLAPPER_GTK_MAJOR_VERSION: * * ClapperGtk major version component */ #define CLAPPER_GTK_MAJOR_VERSION (@CLAPPER_GTK_MAJOR_VERSION@) /** * CLAPPER_GTK_MINOR_VERSION: * * ClapperGtk minor version component */ #define CLAPPER_GTK_MINOR_VERSION (@CLAPPER_GTK_MINOR_VERSION@) /** * CLAPPER_GTK_MICRO_VERSION: * * ClapperGtk micro version component */ #define CLAPPER_GTK_MICRO_VERSION (@CLAPPER_GTK_MICRO_VERSION@) /** * CLAPPER_GTK_VERSION: * * ClapperGtk version */ #define CLAPPER_GTK_VERSION (@CLAPPER_GTK_VERSION@) /** * CLAPPER_GTK_VERSION_S: * * ClapperGtk version, encoded as a string */ #define CLAPPER_GTK_VERSION_S "@CLAPPER_GTK_VERSION@" #define CLAPPER_GTK_ENCODE_VERSION(major,minor,micro) \ ((major) << 24 | (minor) << 16 | (micro) << 8) /** * CLAPPER_GTK_VERSION_HEX: * * ClapperGtk version, encoded as an hexadecimal number, useful for integer comparisons. */ #define CLAPPER_GTK_VERSION_HEX \ (CLAPPER_GTK_ENCODE_VERSION (CLAPPER_GTK_MAJOR_VERSION, CLAPPER_GTK_MINOR_VERSION, CLAPPER_GTK_MICRO_VERSION)) #define CLAPPER_GTK_CHECK_VERSION(major, minor, micro) \ (CLAPPER_GTK_MAJOR_VERSION > (major) || \ (CLAPPER_GTK_MAJOR_VERSION == (major) && CLAPPER_GTK_MINOR_VERSION > (minor)) || \ (CLAPPER_GTK_MAJOR_VERSION == (major) && CLAPPER_GTK_MINOR_VERSION == (minor) && \ CLAPPER_GTK_MICRO_VERSION >= (micro))) clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-video-placeholder-private.h000066400000000000000000000025321474353110700263400ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include #include "clapper-gtk-container.h" G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_VIDEO_PLACEHOLDER (clapper_gtk_video_placeholder_get_type()) #define CLAPPER_GTK_VIDEO_PLACEHOLDER_CAST(obj) ((ClapperGtkVideoPlaceholder *)(obj)) G_DECLARE_FINAL_TYPE (ClapperGtkVideoPlaceholder, clapper_gtk_video_placeholder, CLAPPER_GTK, VIDEO_PLACEHOLDER, ClapperGtkContainer) G_GNUC_INTERNAL GtkWidget * clapper_gtk_video_placeholder_new (void); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-video-placeholder.c000066400000000000000000000110531474353110700246610ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-gtk-video-placeholder-private.h" #include "clapper-gtk-utils-private.h" #define NORMAL_SPACING 16 #define ADAPT_SPACING 8 #define GST_CAT_DEFAULT clapper_gtk_video_placeholder_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkVideoPlaceholder { ClapperGtkContainer parent; GtkWidget *box; GtkWidget *title_label; ClapperPlayer *player; }; #define parent_class clapper_gtk_video_placeholder_parent_class G_DEFINE_TYPE (ClapperGtkVideoPlaceholder, clapper_gtk_video_placeholder, CLAPPER_GTK_TYPE_CONTAINER) static void adapt_cb (ClapperGtkContainer *container, gboolean adapt, ClapperGtkVideoPlaceholder *self) { GST_DEBUG_OBJECT (self, "Adapted: %s", (adapt) ? "yes" : "no"); gtk_box_set_spacing (GTK_BOX (self->box), (adapt) ? ADAPT_SPACING : NORMAL_SPACING); if (adapt) { gtk_widget_add_css_class (GTK_WIDGET (self), "adapted"); gtk_widget_add_css_class (GTK_WIDGET (self->title_label), "title-2"); } else { gtk_widget_remove_css_class (GTK_WIDGET (self), "adapted"); gtk_widget_remove_css_class (GTK_WIDGET (self->title_label), "title-2"); } } static void _player_state_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideoPlaceholder *self) { gtk_widget_set_visible (self->box, clapper_player_get_state (player) > CLAPPER_PLAYER_STATE_STOPPED); } GtkWidget * clapper_gtk_video_placeholder_new (void) { return g_object_new (CLAPPER_GTK_TYPE_VIDEO_PLACEHOLDER, NULL); } static void clapper_gtk_video_placeholder_map (GtkWidget *widget) { ClapperGtkVideoPlaceholder *self = CLAPPER_GTK_VIDEO_PLACEHOLDER_CAST (widget); if ((self->player = clapper_gtk_get_player_from_ancestor (widget))) { g_signal_connect (self->player, "notify::state", G_CALLBACK (_player_state_changed_cb), self); _player_state_changed_cb (self->player, NULL, self); } GTK_WIDGET_CLASS (parent_class)->map (widget); } static void clapper_gtk_video_placeholder_unmap (GtkWidget *widget) { ClapperGtkVideoPlaceholder *self = CLAPPER_GTK_VIDEO_PLACEHOLDER_CAST (widget); if (self->player) { g_signal_handlers_disconnect_by_func (self->player, _player_state_changed_cb, self); self->player = NULL; } GTK_WIDGET_CLASS (parent_class)->unmap (widget); } static void clapper_gtk_video_placeholder_init (ClapperGtkVideoPlaceholder *self) { gtk_widget_init_template (GTK_WIDGET (self)); gtk_box_set_spacing (GTK_BOX (self->box), NORMAL_SPACING); } static void clapper_gtk_video_placeholder_dispose (GObject *object) { gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_VIDEO_PLACEHOLDER); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_video_placeholder_class_init (ClapperGtkVideoPlaceholderClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkvideoplaceholder", 0, "Clapper GTK Video Placeholder"); clapper_gtk_init_translations (); gobject_class->dispose = clapper_gtk_video_placeholder_dispose; widget_class->map = clapper_gtk_video_placeholder_map; widget_class->unmap = clapper_gtk_video_placeholder_unmap; gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-video-placeholder.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideoPlaceholder, box); gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideoPlaceholder, title_label); gtk_widget_class_bind_template_callback (widget_class, adapt_cb); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-video-placeholder"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-video.c000066400000000000000000001475231474353110700224150ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperGtkVideo: * * A ready to be used GTK video widget implementing Clapper API. * * #ClapperGtkVideo is the main widget exposed by `ClapperGtk` API. It both displays * videos played by [class@Clapper.Player] (exposed as its property) and manages * revealing and fading of any additional widgets overlaid on top of it. * * Other widgets provided by `ClapperGtk` library, once placed anywhere on video * (including nesting within another widget like [class@Gtk.Box]) will automatically * control #ClapperGtkVideo they were overlaid on top of. This allows to freely create * custom playback control panels best suited for specific application. Additionally, * pre-made widgets such as [class@ClapperGtk.SimpleControls] are also available. * * # Basic usage * * A typical use case is to embed video widget as part of your app where video playback * is needed. Get the [class@Clapper.Player] belonging to the video widget and start adding * new [class@Clapper.MediaItem] items to the [class@Clapper.Queue] for playback. * For more information please refer to the Clapper playback library documentation. * * #ClapperGtkVideo can automatically take care of revealing and later fading overlaid * content when interacting with the video. To do this, simply add your widgets with * [method@ClapperGtk.Video.add_fading_overlay]. If you want to display some static content * on top of video (or take care of visibility within overlaid widget itself) you can add * it to the video as a normal overlay with [method@ClapperGtk.Video.add_overlay]. * * # Actions * * #ClapperGtkVideo defines a set of built-in actions: * * ```yaml * - "video.toggle-play": toggle play/pause * - "video.play": start/resume playback * - "video.pause": pause playback * - "video.stop": stop playback * - "video.seek": seek to position (variant "d") * - "video.seek-custom": seek to position using seek method (variant "(di)") * - "video.toggle-mute": toggle mute state * - "video.set-mute": set mute state (variant "b") * - "video.volume-up": increase volume by 2% * - "video.volume-down": decrease volume by 2% * - "video.set-volume": set volume to specified value (variant "d") * - "video.speed-up": increase speed (from 0.05x - 2x range to nearest quarter) * - "video.speed-down": decrease speed (from 0.05x - 2x range to nearest quarter) * - "video.set-speed": set speed to specified value (variant "d") * - "video.previous-item": select previous item in queue * - "video.next-item": select next item in queue * - "video.select-item": select item at specified index in queue (variant "u") * ``` * * # ClapperGtkVideo as GtkBuildable * * #ClapperGtkVideo implementation of the [iface@Gtk.Buildable] interface supports * placing children as either normal overlay by specifying `overlay` or a fading * one by specifying `fading-overlay` as the `type` attribute of a `` element. * Position of overlaid content is determined by `valign/halign` properties. * * ```xml * * * * start * * * * * end * * * * ``` */ #include "config.h" #include #include "clapper-gtk-enums.h" #include "clapper-gtk-video.h" #include "clapper-gtk-lead-container.h" #include "clapper-gtk-status-private.h" #include "clapper-gtk-buffering-animation-private.h" #include "clapper-gtk-video-placeholder-private.h" #define PERCENTAGE_ROUND(a) (round ((gdouble) a / 0.01) * 0.01) #define DEFAULT_FADE_DELAY 3000 #define DEFAULT_TOUCH_FADE_DELAY 5000 #define DEFAULT_AUTO_INHIBIT FALSE #define MIN_MOTION_DELAY 100000 #define GST_CAT_DEFAULT clapper_gtk_video_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperGtkVideo { GtkWidget parent; GtkWidget *overlay; GtkWidget *status; GtkWidget *buffering_animation; GtkGesture *touch_gesture; GtkGesture *click_gesture; /* Props */ ClapperPlayer *player; guint fade_delay; guint touch_fade_delay; gboolean auto_inhibit; GPtrArray *overlays; GPtrArray *fading_overlays; gboolean buffering; gboolean showing_status; gulong notify_revealed_id; guint fade_timeout; gboolean reveal, revealed; guint inhibit_cookie; /* Current pointer coords and type */ gdouble x, y; gboolean is_touch; gboolean touching; gint64 last_motion_time; gboolean pending_toggle_play; }; static void clapper_gtk_video_add_child (GtkBuildable *buildable, GtkBuilder *builder, GObject *child, const char *type) { if (GTK_IS_WIDGET (child)) { if (g_strcmp0 (type, "overlay") == 0) clapper_gtk_video_add_overlay (CLAPPER_GTK_VIDEO (buildable), GTK_WIDGET (child)); else if (g_strcmp0 (type, "fading-overlay") == 0) clapper_gtk_video_add_fading_overlay (CLAPPER_GTK_VIDEO (buildable), GTK_WIDGET (child)); else GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type); } else { GtkBuildableIface *parent_iface = g_type_interface_peek_parent (GTK_BUILDABLE_GET_IFACE (buildable)); parent_iface->add_child (buildable, builder, child, type); } } static void _buildable_iface_init (GtkBuildableIface *iface) { iface->add_child = clapper_gtk_video_add_child; } #define parent_class clapper_gtk_video_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperGtkVideo, clapper_gtk_video, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, _buildable_iface_init)) enum { PROP_0, PROP_PLAYER, PROP_FADE_DELAY, PROP_TOUCH_FADE_DELAY, PROP_AUTO_INHIBIT, PROP_INHIBITED, PROP_LAST }; enum { SIGNAL_TOGGLE_FULLSCREEN, SIGNAL_SEEK_REQUEST, SIGNAL_LAST }; static gboolean provider_added = FALSE; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static guint signals[SIGNAL_LAST] = { 0, }; static void toggle_play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); switch (clapper_player_get_state (player)) { case CLAPPER_PLAYER_STATE_PLAYING: clapper_player_pause (player); break; case CLAPPER_PLAYER_STATE_STOPPED: case CLAPPER_PLAYER_STATE_PAUSED: clapper_player_play (player); break; default: break; } } static void play_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); clapper_player_play (player); } static void pause_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); clapper_player_pause (player); } static void stop_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); clapper_player_stop (player); } static void seek_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gdouble position = g_variant_get_double (parameter); clapper_player_seek (player, position); } static void seek_custom_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); ClapperPlayerSeekMethod method = CLAPPER_PLAYER_SEEK_METHOD_NORMAL; gdouble position = 0; g_variant_get (parameter, "(di)", &position, &method); clapper_player_seek_custom (player, position, method); } static void toggle_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); clapper_player_set_mute (player, !clapper_player_get_mute (player)); } static void set_mute_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gboolean mute = g_variant_get_boolean (parameter); clapper_player_set_mute (player, mute); } static void volume_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gdouble volume = (clapper_player_get_volume (player) + 0.02); if (volume > 2.0) volume = 2.0; clapper_player_set_volume (player, PERCENTAGE_ROUND (volume)); } static void volume_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gdouble volume = (clapper_player_get_volume (player) - 0.02); if (volume < 0) volume = 0; clapper_player_set_volume (player, PERCENTAGE_ROUND (volume)); } static void set_volume_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gdouble volume = g_variant_get_double (parameter); clapper_player_set_volume (player, volume); } static void speed_up_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gdouble dest, speed = clapper_player_get_speed (player); if (speed >= 2.0) return; dest = 0.25; while (speed >= dest) dest += 0.25; if (dest > 2.0) dest = 2.0; clapper_player_set_speed (player, dest); } static void speed_down_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gdouble dest, speed = clapper_player_get_speed (player); if (speed <= 0.05) return; dest = 2.0; while (speed <= dest) dest -= 0.25; if (dest < 0.05) dest = 0.05; clapper_player_set_speed (player, dest); } static void set_speed_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); gdouble speed = g_variant_get_double (parameter); clapper_player_set_speed (player, speed); } static void previous_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); clapper_queue_select_previous_item (clapper_player_get_queue (player)); } static void next_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); clapper_queue_select_next_item (clapper_player_get_queue (player)); } static void select_item_action_cb (GtkWidget *widget, const gchar *action_name, GVariant *parameter) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); ClapperPlayer *player = clapper_gtk_video_get_player (self); guint index = g_variant_get_uint32 (parameter); clapper_queue_select_index (clapper_player_get_queue (player), index); } static void _set_reveal_fading_overlays (ClapperGtkVideo *self, gboolean reveal) { GdkCursor *cursor = gdk_cursor_new_from_name ((reveal) ? "default" : "none", NULL); guint i; self->reveal = reveal; GST_LOG_OBJECT (self, "%s requested", (self->reveal) ? "Reveal" : "Fade"); gtk_widget_set_cursor (GTK_WIDGET (self), cursor); g_object_unref (cursor); for (i = 0; i < self->fading_overlays->len; ++i) { GtkRevealer *revealer = (GtkRevealer *) g_ptr_array_index (self->fading_overlays, i); if (reveal) gtk_widget_set_visible ((GtkWidget *) revealer, TRUE); gtk_revealer_set_reveal_child (revealer, reveal); } } static inline gboolean _is_on_leading_overlay (ClapperGtkVideo *self, ClapperGtkVideoActionMask blocked_action) { GtkWidget *video = (GtkWidget *) self; GtkWidget *tmp_widget = gtk_widget_pick (video, self->x, self->y, GTK_PICK_DEFAULT); gboolean is_leading = FALSE; GST_LOG_OBJECT (self, "Checking if is on leading overlay..."); while (tmp_widget && tmp_widget != video) { if (CLAPPER_GTK_IS_LEAD_CONTAINER (tmp_widget)) { ClapperGtkLeadContainer *lead_container = CLAPPER_GTK_LEAD_CONTAINER_CAST (tmp_widget); if (clapper_gtk_lead_container_get_leading (lead_container) && (clapper_gtk_lead_container_get_blocked_actions (lead_container) & blocked_action)) { is_leading = TRUE; break; } } tmp_widget = gtk_widget_get_parent (tmp_widget); } GST_LOG_OBJECT (self, "Is on leading overlay: %s", (is_leading) ? "yes" : "no"); return is_leading; } static inline gboolean _determine_can_fade (ClapperGtkVideo *self) { GtkWidget *video = (GtkWidget *) self; GtkRoot *root; GtkNative *native, *child_native; GtkWidget *focus_child; gboolean in_fading_overlay = FALSE; GST_LOG_OBJECT (self, "Checking if overlays can fade..."); if (self->is_touch) { if (self->touching) { GST_LOG_OBJECT (self, "Cannot fade while interacting with touchscreen"); return FALSE; } } else if (self->x > 0 && self->y > 0) { GtkWidget *tmp_widget = gtk_widget_pick (video, self->x, self->y, GTK_PICK_DEFAULT); guint i; if (!tmp_widget) { GST_LOG_OBJECT (self, "Can fade, since no widget under pointer"); return TRUE; } for (i = 0; i < self->fading_overlays->len; ++i) { GtkWidget *revealer = (GtkWidget *) g_ptr_array_index (self->fading_overlays, i); if (tmp_widget == revealer || gtk_widget_is_ancestor (tmp_widget, revealer)) { in_fading_overlay = TRUE; break; } } if (!in_fading_overlay) { GST_LOG_OBJECT (self, "Can fade, since pointer not within fading overlay"); return TRUE; } while (tmp_widget && tmp_widget != video) { GtkStateFlags state_flags = gtk_widget_get_state_flags (tmp_widget); if (GTK_IS_ACTIONABLE (tmp_widget) && (state_flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE))) { GST_LOG_OBJECT (self, "Cannot fade while on activatable widget"); return FALSE; } if ((state_flags & GTK_STATE_FLAG_DROP_ACTIVE)) { GST_LOG_OBJECT (self, "Cannot fade on drop-active widget"); return FALSE; } if (GTK_IS_ACCESSIBLE (tmp_widget) && gtk_widget_get_can_target (tmp_widget)) { GtkAccessibleRole role = gtk_accessible_get_accessible_role ((GtkAccessible *) tmp_widget); switch (role) { case GTK_ACCESSIBLE_ROLE_LIST: GST_LOG_OBJECT (self, "Cannot fade while browsing list"); return FALSE; case GTK_ACCESSIBLE_ROLE_SLIDER: case GTK_ACCESSIBLE_ROLE_SCROLLBAR: GST_LOG_OBJECT (self, "Cannot fade while on slider/scrollbar"); return FALSE; default: break; } } tmp_widget = gtk_widget_get_parent (tmp_widget); }; } root = gtk_widget_get_root (video); if (G_UNLIKELY (root == NULL)) return FALSE; focus_child = gtk_root_get_focus (root); if (!focus_child || !gtk_widget_has_focus (focus_child) || !gtk_widget_is_ancestor (focus_child, video)) { GST_LOG_OBJECT (self, "Can fade, since no focused child in video"); return TRUE; } native = gtk_widget_get_native (video); child_native = gtk_widget_get_native (focus_child); if (native != child_native) { GST_LOG_OBJECT (self, "Cannot fade while another surface is open"); return FALSE; } GST_LOG_OBJECT (self, "Can fade"); return TRUE; } static void _fade_overlay_delay_cb (ClapperGtkVideo *self) { GST_LOG_OBJECT (self, "Fade handler reached"); self->fade_timeout = 0; if (self->reveal) { gboolean can_fade = _determine_can_fade (self); GST_DEBUG_OBJECT (self, "Can fade overlays: %s", (can_fade) ? "yes" : "no"); if (can_fade) _set_reveal_fading_overlays (self, FALSE); } } static void _reset_fade_timeout (ClapperGtkVideo *self) { GST_TRACE_OBJECT (self, "Fade timeout reset"); g_clear_handle_id (&self->fade_timeout, g_source_remove); self->fade_timeout = g_timeout_add_once ( (self->is_touch) ? self->touch_fade_delay : self->fade_delay, (GSourceOnceFunc) _fade_overlay_delay_cb, self); } static void _window_is_active_cb (GtkWindow *window, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) { gboolean active = gtk_window_is_active (window); GST_DEBUG_OBJECT (self, "Window is now %sactive", (active) ? "" : "in"); if (!active) { /* Needs to set when drag starts during touch, * we do not get touch release then */ self->touching = FALSE; /* Ensure our overlays will fade eventually */ if (self->revealed && !self->fade_timeout) _reset_fade_timeout (self); } } static void _handle_motion (ClapperGtkVideo *self, GtkEventController *controller, gdouble x, gdouble y) { gint64 now; /* Start with points comparison as its faster, * otherwise we will check if threshold exceeded */ if (self->x == x && self->y == y) return; now = g_get_monotonic_time (); /* We do not want to reset timeout too often * (especially on high refresh rate screens). */ if (now - self->last_motion_time >= MIN_MOTION_DELAY) { GdkDevice *device = gtk_event_controller_get_current_event_device (controller); gboolean is_threshold = (ABS (self->x - x) > 1 || ABS (self->y - y) > 1); self->x = x; self->y = y; self->is_touch = (device && gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN); if (is_threshold) { if (!self->reveal && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS)) _set_reveal_fading_overlays (self, TRUE); /* Extend time until fade */ if (self->revealed) _reset_fade_timeout (self); } self->last_motion_time = now; } } static void _handle_motion_leave (ClapperGtkVideo *self) { GST_LOG_OBJECT (self, "Motion leave"); /* On leave we only reset coords to let overlays fade, * device is not expected to change here */ self->x = -1; self->y = -1; /* Ensure our overlays will fade eventually */ if (self->revealed && !self->fade_timeout) _reset_fade_timeout (self); } static void motion_enter_cb (GtkEventControllerMotion *motion, gdouble x, gdouble y, ClapperGtkVideo *self) { GdkDevice *device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (motion)); /* XXX: We do not update x/y coords here in order to not mislead us * that we are not on non-fading overlay when another surface is open */ self->is_touch = (device && gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN); /* Tap to reveal is handled elsewhere */ if (self->is_touch) return; if (!self->reveal && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS)) _set_reveal_fading_overlays (self, TRUE); /* Extend time until fade */ if (self->revealed) _reset_fade_timeout (self); } static void motion_cb (GtkEventControllerMotion *motion, gdouble x, gdouble y, ClapperGtkVideo *self) { _handle_motion (self, GTK_EVENT_CONTROLLER (motion), x, y); } static void motion_leave_cb (GtkEventControllerMotion *motion, ClapperGtkVideo *self) { _handle_motion_leave (self); } static void drop_motion_cb (GtkDropControllerMotion *drop_motion, gdouble x, gdouble y, ClapperGtkVideo *self) { /* We do not actually support D&D here, just want to track * drop motion events from it and reveal overlays as one * or more widgets overlaid may support current drop */ _handle_motion (self, GTK_EVENT_CONTROLLER (drop_motion), x, y); } static void drop_motion_leave_cb (GtkDropControllerMotion *drop_motion, ClapperGtkVideo *self) { _handle_motion_leave (self); } static void left_click_pressed_cb (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, ClapperGtkVideo *self) { GdkDevice *device; GST_LOG_OBJECT (self, "Left click pressed"); /* Need to always clear click timeout, * so we will not pause after double click */ self->pending_toggle_play = FALSE; device = gtk_gesture_get_device (GTK_GESTURE (click)); self->x = x; self->y = y; self->is_touch = (device && gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN); } static gboolean _touch_in_lr_area (ClapperGtkVideo *self, gboolean *forward) { gint video_w = gtk_widget_get_width (GTK_WIDGET (self)); gdouble area_w = (video_w / 4.); gboolean in_area; if ((in_area = (self->x <= area_w))) { if (forward) *forward = FALSE; } else if ((in_area = (self->x >= video_w - area_w))) { if (forward) *forward = TRUE; } if (in_area && forward) *forward ^= (gtk_widget_get_default_direction () == GTK_TEXT_DIR_RTL); GST_LOG_OBJECT (self, "Touch in area: %s (x: %.2lf, video_w: %i, area_w: %.0lf)", (in_area) ? "yes" : "no", self->x, video_w, area_w); return in_area; } static inline void _handle_single_click (ClapperGtkVideo *self, GtkGestureClick *click) { GdkDevice *device = gtk_gesture_get_device (GTK_GESTURE (click)); /* FIXME: Try GstNavigation first and do below logic only when not handled * by upstream elements (maybe use sequence claiming for that?) */ switch (gdk_device_get_source (device)) { case GDK_SOURCE_TOUCHSCREEN: /* First tap should only reveal overlays if fading/faded */ if (!self->reveal && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_REVEAL_OVERLAYS)) { _set_reveal_fading_overlays (self, TRUE); gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); break; } G_GNUC_FALLTHROUGH; default: if (!_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_TOGGLE_PLAY)) { self->pending_toggle_play = TRUE; gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); } break; } } static inline void _handle_double_click (ClapperGtkVideo *self, GtkGestureClick *click) { gboolean handled = FALSE; if (self->is_touch) { gboolean forward = FALSE; if (_touch_in_lr_area (self, &forward) && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST) && g_signal_handler_find (self, G_SIGNAL_MATCH_ID, signals[SIGNAL_SEEK_REQUEST], 0, NULL, NULL, NULL) != 0) { g_signal_emit (self, signals[SIGNAL_SEEK_REQUEST], 0, forward); handled = TRUE; } } if (!handled) { if ((handled = !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_TOGGLE_FULLSCREEN))) g_signal_emit (self, signals[SIGNAL_TOGGLE_FULLSCREEN], 0); } if (handled) gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); } static inline void _handle_nth_click (ClapperGtkVideo *self, GtkGestureClick *click) { gboolean forward = FALSE; if (_touch_in_lr_area (self, &forward) && !_is_on_leading_overlay (self, CLAPPER_GTK_VIDEO_ACTION_SEEK_REQUEST)) { g_signal_emit (self, signals[SIGNAL_SEEK_REQUEST], 0, forward); gtk_gesture_set_state (GTK_GESTURE (click), GTK_EVENT_SEQUENCE_CLAIMED); } } static void left_click_released_cb (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, ClapperGtkVideo *self) { GST_LOG_OBJECT (self, "Left click released"); if (self->x < 0 || self->y < 0) { GST_LOG_OBJECT (self, "Ignoring click release outside of video"); return; } self->x = x; self->y = y; switch (n_press) { case 1: _handle_single_click (self, click); break; case 2: _handle_double_click (self, click); break; default: _handle_nth_click (self, click); break; } /* Keep fading overlays revealed while clicking/tapping on video */ if (self->revealed) _reset_fade_timeout (self); } static void left_click_stopped_cb (GtkGestureClick *click, ClapperGtkVideo *self) { GST_LOG_OBJECT (self, "Left click stopped"); if (self->pending_toggle_play) { toggle_play_action_cb (GTK_WIDGET (self), NULL, NULL); self->pending_toggle_play = FALSE; } } static void touch_pressed_cb (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, ClapperGtkVideo *self) { GST_LOG_OBJECT (self, "Touch pressed"); self->is_touch = TRUE; self->touching = TRUE; if (self->revealed) _reset_fade_timeout (self); } static void touch_released_cb (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, ClapperGtkVideo *self) { GST_LOG_OBJECT (self, "Touch released"); self->touching = FALSE; /* Ensure our overlays will fade eventually */ if (self->revealed) _reset_fade_timeout (self); } static void _ensure_css_provider (void) { GdkDisplay *display; if (provider_added) return; display = gdk_display_get_default (); if (G_LIKELY (display != NULL)) { GtkCssProvider *provider = gtk_css_provider_new (); gtk_css_provider_load_from_resource (provider, CLAPPER_GTK_RESOURCE_PREFIX "/css/styles.css"); gtk_style_context_add_provider_for_display (display, (GtkStyleProvider *) provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION - 1); g_object_unref (provider); provider_added = TRUE; } } static inline void _set_inhibit_session (ClapperGtkVideo *self, gboolean inhibit) { GtkRoot *root; GApplication *app; gboolean inhibited = (self->inhibit_cookie != 0); if (inhibited == inhibit) return; GST_DEBUG_OBJECT (self, "Trying to %sinhibit session...", (inhibit) ? "" : "un"); root = gtk_widget_get_root (GTK_WIDGET (self)); if (!root && !GTK_IS_WINDOW (root)) { GST_WARNING_OBJECT (self, "Cannot %sinhibit session " "without root window", (inhibit) ? "" : "un"); return; } /* NOTE: Not using application from window prop, * as it goes away early when unrooting */ app = g_application_get_default (); if (!app && !GTK_IS_APPLICATION (app)) { GST_WARNING_OBJECT (self, "Cannot %sinhibit session " "without window application set", (inhibit) ? "" : "un"); return; } if (inhibited) { gtk_application_uninhibit (GTK_APPLICATION (app), self->inhibit_cookie); self->inhibit_cookie = 0; } if (inhibit) { self->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (app), GTK_WINDOW (root), GTK_APPLICATION_INHIBIT_IDLE, "Video is playing"); } GST_DEBUG_OBJECT (self, "Session %sinhibited", (inhibit) ? "" : "un"); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_INHIBITED]); } static inline void _set_buffering_animation_enabled (ClapperGtkVideo *self, gboolean enabled) { ClapperGtkBufferingAnimation *animation; if (self->buffering == enabled) return; animation = CLAPPER_GTK_BUFFERING_ANIMATION_CAST (self->buffering_animation); gtk_widget_set_visible (self->buffering_animation, enabled); if (enabled) clapper_gtk_buffering_animation_start (animation); else clapper_gtk_buffering_animation_stop (animation); self->buffering = enabled; } static void _player_state_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) { ClapperPlayerState state = clapper_player_get_state (player); if (self->auto_inhibit) _set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING); _set_buffering_animation_enabled (self, state == CLAPPER_PLAYER_STATE_BUFFERING); } static GtkWidget * _get_widget_from_video_sink (GstElement *vsink) { GtkWidget *widget = NULL; GParamSpec *pspec; if ((pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (vsink), "widget")) && pspec->value_type == GTK_TYPE_WIDGET) { GST_DEBUG ("Video sink provides a widget"); g_object_get (vsink, "widget", &widget, NULL); } else if ((pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (vsink), "paintable")) && pspec->value_type == GDK_TYPE_PAINTABLE) { GdkPaintable *paintable = NULL; GST_DEBUG ("Video sink provides a paintable"); g_object_get (vsink, "paintable", &paintable, NULL); widget = g_object_ref_sink (gtk_picture_new ()); gtk_picture_set_paintable (GTK_PICTURE (widget), paintable); g_object_unref (paintable); } return widget; } static void _video_sink_changed_cb (ClapperPlayer *player, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) { GstElement *vsink = clapper_player_get_video_sink (player); GtkWidget *widget = NULL; GST_DEBUG_OBJECT (self, "Video sink changed to: %" GST_PTR_FORMAT, vsink); if (vsink) { widget = _get_widget_from_video_sink (vsink); if (!widget && GST_IS_BIN (vsink)) { GstIterator *iter; GValue value = G_VALUE_INIT; iter = gst_bin_iterate_recurse (GST_BIN_CAST (vsink)); while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) { GstElement *element = g_value_get_object (&value); if (GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK)) widget = _get_widget_from_video_sink (element); g_value_unset (&value); if (widget) break; } gst_iterator_free (iter); } gst_object_unref (vsink); } if (!widget) { GST_DEBUG_OBJECT (self, "No widget from video sink, using placeholder"); widget = g_object_ref_sink (clapper_gtk_video_placeholder_new ()); } gtk_overlay_set_child (GTK_OVERLAY (self->overlay), widget); g_object_unref (widget); GST_DEBUG_OBJECT (self, "Set new video widget"); } static void _player_error_cb (ClapperPlayer *player, GError *error, const gchar *debug_info, ClapperGtkVideo *self) { /* FIXME: Handle authentication error (pop dialog to set credentials and retry) */ /* Buffering will not finish anymore if we were in middle of it */ _set_buffering_animation_enabled (self, FALSE); if (!self->showing_status) { clapper_gtk_status_set_error (CLAPPER_GTK_STATUS_CAST (self->status), error); self->showing_status = TRUE; } } static void _player_missing_plugin_cb (ClapperPlayer *player, const gchar *name, const gchar *installer_detail, ClapperGtkVideo *self) { /* Some media files have custom/proprietary metadata, * it should be safe to simply ignore these */ if (strstr (name, "meta/") != NULL) return; /* XXX: Playbin2 seems to not emit state change here, * so manually stop buffering animation just in case */ _set_buffering_animation_enabled (self, FALSE); /* XXX: Some content can still be played partially (e.g. without audio), * but it should be better to stop and notify user that something is missing */ clapper_player_stop (player); /* We might get "missing-plugin" followed by "error" signal. This boolean prevents * immediately overwriting status and lets user deal with problems in order. */ if (!self->showing_status) { clapper_gtk_status_set_missing_plugin (CLAPPER_GTK_STATUS_CAST (self->status), name); self->showing_status = TRUE; } } static void _queue_current_item_changed_cb (ClapperQueue *queue, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) { clapper_gtk_status_clear (CLAPPER_GTK_STATUS_CAST (self->status)); self->showing_status = FALSE; } static void _fading_overlay_revealed_cb (GtkRevealer *revealer, GParamSpec *pspec G_GNUC_UNUSED, ClapperGtkVideo *self) { self->revealed = gtk_revealer_get_child_revealed (revealer); /* Start fade timeout once fully revealed */ if (self->revealed) _reset_fade_timeout (self); } /** * clapper_gtk_video_new: * * Creates a new #ClapperGtkVideo instance. * * Newly created video widget will also set some default GStreamer elements * on its [class@Clapper.Player]. This includes Clapper own video sink and * a "scaletempo" element as audio filter. Both can still be changed after * construction by setting corresponding player properties. * * Returns: a new video #GtkWidget. */ GtkWidget * clapper_gtk_video_new (void) { return g_object_new (CLAPPER_GTK_TYPE_VIDEO, NULL); } /** * clapper_gtk_video_add_overlay: * @video: a #ClapperGtkVideo * @widget: a #GtkWidget * * Add another #GtkWidget to be overlaid on top of video. * * The position at which @widget is placed is determined from * [property@Gtk.Widget:halign] and [property@Gtk.Widget:valign] properties. * * This function will overlay @widget as-is meaning that widget is responsible * for managing its own visablity if needed. If you want to add a #GtkWidget * that will reveal and fade itself automatically when interacting with @video * (e.g. controls panel) you can use clapper_gtk_video_add_fading_overlay() * function for convenience. */ void clapper_gtk_video_add_overlay (ClapperGtkVideo *self, GtkWidget *widget) { g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); g_return_if_fail (GTK_IS_WIDGET (widget)); g_ptr_array_add (self->overlays, widget); gtk_overlay_add_overlay (GTK_OVERLAY (self->overlay), widget); } /** * clapper_gtk_video_add_fading_overlay: * @video: a #ClapperGtkVideo * @widget: a #GtkWidget * * Similiar as clapper_gtk_video_add_overlay() but will also automatically * add fading functionality to overlaid #GtkWidget for convenience. This will * make widget reveal itself when interacting with @video and fade otherwise. * Useful when placing widgets such as playback controls panels. */ void clapper_gtk_video_add_fading_overlay (ClapperGtkVideo *self, GtkWidget *widget) { GtkWidget *revealer; g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); g_return_if_fail (GTK_IS_WIDGET (widget)); revealer = gtk_revealer_new (); g_object_bind_property (revealer, "child-revealed", revealer, "visible", G_BINDING_DEFAULT); g_object_bind_property (widget, "halign", revealer, "halign", G_BINDING_SYNC_CREATE); g_object_bind_property (widget, "valign", revealer, "valign", G_BINDING_SYNC_CREATE); /* Since we reveal/fade all at once, one signal connection is enough */ if (self->notify_revealed_id == 0) { self->notify_revealed_id = g_signal_connect (revealer, "notify::child-revealed", G_CALLBACK (_fading_overlay_revealed_cb), self); } gtk_widget_set_visible (revealer, self->reveal); gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), self->reveal); gtk_revealer_set_transition_type (GTK_REVEALER (revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); gtk_revealer_set_transition_duration (GTK_REVEALER (revealer), 800); gtk_revealer_set_child (GTK_REVEALER (revealer), widget); g_ptr_array_add (self->fading_overlays, revealer); gtk_overlay_add_overlay (GTK_OVERLAY (self->overlay), revealer); } /** * clapper_gtk_video_get_player: * @video: a #ClapperGtkVideo * * Get #ClapperPlayer used by this #ClapperGtkVideo instance. * * Returns: (transfer none): a #ClapperPlayer used by video. */ ClapperPlayer * clapper_gtk_video_get_player (ClapperGtkVideo *self) { g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), NULL); return self->player; } /** * clapper_gtk_video_set_fade_delay: * @video: a #ClapperGtkVideo * @delay: a fade delay * * Set time in milliseconds after which fading overlays should fade. */ void clapper_gtk_video_set_fade_delay (ClapperGtkVideo *self, guint delay) { g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); g_return_if_fail (delay >= 1000); self->fade_delay = delay; g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_FADE_DELAY]); } /** * clapper_gtk_video_get_fade_delay: * @video: a #ClapperGtkVideo * * Get time in milliseconds after which fading overlays should fade. * * Returns: currently set fade delay. */ guint clapper_gtk_video_get_fade_delay (ClapperGtkVideo *self) { g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), 0); return self->fade_delay; } /** * clapper_gtk_video_set_touch_fade_delay: * @video: a #ClapperGtkVideo * @delay: a touch fade delay * * Set time in milliseconds after which fading overlays should fade * when using touchscreen. * * It is often useful to set this higher then normal fade delay property, * as in case of touch events user do not have a moving pointer that would * extend fade timeout, so he can have more time to decide what to press next. */ void clapper_gtk_video_set_touch_fade_delay (ClapperGtkVideo *self, guint delay) { g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); g_return_if_fail (delay >= 1); self->touch_fade_delay = delay; g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_TOUCH_FADE_DELAY]); } /** * clapper_gtk_video_get_touch_fade_delay: * @video: a #ClapperGtkVideo * * Get time in milliseconds after which fading overlays should fade * when revealed using touch device. * * Returns: currently set touch fade delay. */ guint clapper_gtk_video_get_touch_fade_delay (ClapperGtkVideo *self) { g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), 0); return self->touch_fade_delay; } /** * clapper_gtk_video_set_auto_inhibit: * @video: a #ClapperGtkVideo * @inhibit: whether to enable automatic session inhibit * * Set whether video should try to automatically inhibit session * from idling (and possibly screen going black) when video is playing. */ void clapper_gtk_video_set_auto_inhibit (ClapperGtkVideo *self, gboolean inhibit) { g_return_if_fail (CLAPPER_GTK_IS_VIDEO (self)); if (self->auto_inhibit != inhibit) { self->auto_inhibit = inhibit; /* Uninhibit if we were auto inhibited earlier */ if (!self->auto_inhibit) _set_inhibit_session (self, FALSE); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_AUTO_INHIBIT]); } } /** * clapper_gtk_video_get_auto_inhibit: * @video: a #ClapperGtkVideo * * Get whether automatic session inhibit is enabled. * * Returns: %TRUE if enabled, %FALSE otherwise. */ gboolean clapper_gtk_video_get_auto_inhibit (ClapperGtkVideo *self) { g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), FALSE); return self->auto_inhibit; } /** * clapper_gtk_video_get_inhibited: * @video: a #ClapperGtkVideo * * Get whether session is currently inhibited by * [property@ClapperGtk.Video:auto-inhibit]. * * Returns: %TRUE if inhibited, %FALSE otherwise. */ gboolean clapper_gtk_video_get_inhibited (ClapperGtkVideo *self) { g_return_val_if_fail (CLAPPER_GTK_IS_VIDEO (self), FALSE); return (self->inhibit_cookie != 0); } static void clapper_gtk_video_root (GtkWidget *widget) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); GtkRoot *root; _ensure_css_provider (); GTK_WIDGET_CLASS (parent_class)->root (widget); root = gtk_widget_get_root (widget); if (root && GTK_IS_WINDOW (root)) { GtkWindow *window = GTK_WINDOW (root); g_signal_connect (window, "notify::is-active", G_CALLBACK (_window_is_active_cb), self); _window_is_active_cb (window, NULL, self); } if (self->auto_inhibit) { ClapperPlayerState state = clapper_player_get_state (self->player); _set_inhibit_session (self, state == CLAPPER_PLAYER_STATE_PLAYING); } } static void clapper_gtk_video_unroot (GtkWidget *widget) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (widget); GtkRoot *root = gtk_widget_get_root (widget); if (root && GTK_IS_WINDOW (root)) { g_signal_handlers_disconnect_by_func (GTK_WINDOW (root), _window_is_active_cb, self); } _set_inhibit_session (self, FALSE); GTK_WIDGET_CLASS (parent_class)->unroot (widget); } static void clapper_gtk_video_init (ClapperGtkVideo *self) { self->overlay = gtk_overlay_new (); gtk_widget_set_overflow (self->overlay, GTK_OVERFLOW_HIDDEN); gtk_widget_set_parent (self->overlay, GTK_WIDGET (self)); self->overlays = g_ptr_array_new (); self->fading_overlays = g_ptr_array_new (); self->fade_delay = DEFAULT_FADE_DELAY; self->touch_fade_delay = DEFAULT_TOUCH_FADE_DELAY; self->auto_inhibit = DEFAULT_AUTO_INHIBIT; /* Ensure private types */ g_type_ensure (CLAPPER_GTK_TYPE_STATUS); g_type_ensure (CLAPPER_GTK_TYPE_BUFFERING_ANIMATION); gtk_widget_init_template (GTK_WIDGET (self)); gtk_gesture_group (self->touch_gesture, self->click_gesture); } static void clapper_gtk_video_constructed (GObject *object) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); GstElement *afilter, *vsink; ClapperQueue *queue; self->player = clapper_player_new (); queue = clapper_player_get_queue (self->player); g_signal_connect (self->player, "notify::state", G_CALLBACK (_player_state_changed_cb), self); g_signal_connect (self->player, "notify::video-sink", G_CALLBACK (_video_sink_changed_cb), self); vsink = gst_element_factory_make ("clappersink", NULL); /* FIXME: This is a temporary workaround for lack * of DMA_DRM negotiation support in sink itself */ if (G_LIKELY (vsink != NULL)) { guint major = 0, minor = 0, micro = 0, nano = 0; gst_version (&major, &minor, µ, &nano); if (major == 1 && minor >= 24) { GstElement *bin; if ((bin = gst_element_factory_make ("glsinkbin", NULL))) { g_object_set (bin, "sink", vsink, NULL); vsink = bin; } } clapper_player_set_video_sink (self->player, vsink); } afilter = gst_element_factory_make ("scaletempo", NULL); if (G_LIKELY (afilter != NULL)) clapper_player_set_audio_filter (self->player, afilter); g_signal_connect (self->player, "error", G_CALLBACK (_player_error_cb), self); g_signal_connect (self->player, "missing-plugin", G_CALLBACK (_player_missing_plugin_cb), self); g_signal_connect (queue, "notify::current-item", G_CALLBACK (_queue_current_item_changed_cb), self); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_gtk_video_dispose (GObject *object) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); if (self->notify_revealed_id != 0) { GtkRevealer *revealer = GTK_REVEALER (g_ptr_array_index (self->fading_overlays, 0)); g_signal_handler_disconnect (revealer, self->notify_revealed_id); self->notify_revealed_id = 0; } g_clear_handle_id (&self->fade_timeout, g_source_remove); /* Something else might still be holding a reference on the player, * thus we should disconnect everything before disposing template */ if (self->player) { ClapperQueue *queue = clapper_player_get_queue (self->player); g_signal_handlers_disconnect_by_func (self->player, _player_state_changed_cb, self); g_signal_handlers_disconnect_by_func (self->player, _video_sink_changed_cb, self); g_signal_handlers_disconnect_by_func (self->player, _player_error_cb, self); g_signal_handlers_disconnect_by_func (self->player, _player_missing_plugin_cb, self); g_signal_handlers_disconnect_by_func (queue, _queue_current_item_changed_cb, self); } gtk_widget_dispose_template (GTK_WIDGET (object), CLAPPER_GTK_TYPE_VIDEO); g_clear_pointer (&self->overlay, gtk_widget_unparent); gst_clear_object (&self->player); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_gtk_video_finalize (GObject *object) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); g_ptr_array_unref (self->overlays); g_ptr_array_unref (self->fading_overlays); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_gtk_video_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); switch (prop_id) { case PROP_PLAYER: g_value_set_object (value, clapper_gtk_video_get_player (self)); break; case PROP_FADE_DELAY: g_value_set_uint (value, clapper_gtk_video_get_fade_delay (self)); break; case PROP_TOUCH_FADE_DELAY: g_value_set_uint (value, clapper_gtk_video_get_touch_fade_delay (self)); break; case PROP_AUTO_INHIBIT: g_value_set_boolean (value, clapper_gtk_video_get_auto_inhibit (self)); break; case PROP_INHIBITED: g_value_set_boolean (value, clapper_gtk_video_get_inhibited (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_video_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperGtkVideo *self = CLAPPER_GTK_VIDEO_CAST (object); switch (prop_id) { case PROP_FADE_DELAY: clapper_gtk_video_set_fade_delay (self, g_value_get_uint (value)); break; case PROP_TOUCH_FADE_DELAY: clapper_gtk_video_set_touch_fade_delay (self, g_value_get_uint (value)); break; case PROP_AUTO_INHIBIT: clapper_gtk_video_set_auto_inhibit (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_gtk_video_class_init (ClapperGtkVideoClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GtkWidgetClass *widget_class = (GtkWidgetClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergtkvideo", GST_DEBUG_FG_MAGENTA, "Clapper GTK Video"); widget_class->root = clapper_gtk_video_root; widget_class->unroot = clapper_gtk_video_unroot; gobject_class->constructed = clapper_gtk_video_constructed; gobject_class->get_property = clapper_gtk_video_get_property; gobject_class->set_property = clapper_gtk_video_set_property; gobject_class->dispose = clapper_gtk_video_dispose; gobject_class->finalize = clapper_gtk_video_finalize; /** * ClapperGtkVideo:player: * * A #ClapperPlayer used by video. */ param_specs[PROP_PLAYER] = g_param_spec_object ("player", NULL, NULL, CLAPPER_TYPE_PLAYER, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkVideo:fade-delay: * * A delay in milliseconds before trying to fade all fading overlays. */ param_specs[PROP_FADE_DELAY] = g_param_spec_uint ("fade-delay", NULL, NULL, 1, G_MAXUINT, DEFAULT_FADE_DELAY, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkVideo:touch-fade-delay: * * A delay in milliseconds before trying to fade all fading overlays * after revealed using touchscreen. */ param_specs[PROP_TOUCH_FADE_DELAY] = g_param_spec_uint ("touch-fade-delay", NULL, NULL, 1, G_MAXUINT, DEFAULT_TOUCH_FADE_DELAY, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkVideo:auto-inhibit: * * Try to automatically inhibit session when video is playing. */ param_specs[PROP_AUTO_INHIBIT] = g_param_spec_boolean ("auto-inhibit", NULL, NULL, DEFAULT_AUTO_INHIBIT, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkVideo:inhibited: * * Get whether session is currently inhibited by the video. */ param_specs[PROP_INHIBITED] = g_param_spec_boolean ("inhibited", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperGtkVideo::toggle-fullscreen: * @video: a #ClapperGtkVideo * * A signal that user requested a change in fullscreen state of the video. * * Note that when going fullscreen from this signal, user will expect * for only video to be fullscreened and not the whole app window. * It is up to implementation to decide how to handle that. */ signals[SIGNAL_TOGGLE_FULLSCREEN] = g_signal_new ("toggle-fullscreen", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /** * ClapperGtkVideo::seek-request: * @video: a #ClapperGtkVideo * @forward: %TRUE if seek should be forward, %FALSE if backward * * A helper signal for implementing common seeking by double tap * on screen side for touchscreen devices. * * Note that @forward already takes into account RTL direction, * so implementation does not have to check. */ signals[SIGNAL_SEEK_REQUEST] = g_signal_new ("seek-request", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); gtk_widget_class_install_action (widget_class, "video.toggle-play", NULL, toggle_play_action_cb); gtk_widget_class_install_action (widget_class, "video.play", NULL, play_action_cb); gtk_widget_class_install_action (widget_class, "video.pause", NULL, pause_action_cb); gtk_widget_class_install_action (widget_class, "video.stop", NULL, stop_action_cb); gtk_widget_class_install_action (widget_class, "video.seek", "d", seek_action_cb); gtk_widget_class_install_action (widget_class, "video.seek-custom", "(di)", seek_custom_action_cb); gtk_widget_class_install_action (widget_class, "video.toggle-mute", NULL, toggle_mute_action_cb); gtk_widget_class_install_action (widget_class, "video.set-mute", "b", set_mute_action_cb); gtk_widget_class_install_action (widget_class, "video.volume-up", NULL, volume_up_action_cb); gtk_widget_class_install_action (widget_class, "video.volume-down", NULL, volume_down_action_cb); gtk_widget_class_install_action (widget_class, "video.set-volume", "d", set_volume_action_cb); gtk_widget_class_install_action (widget_class, "video.speed-up", NULL, speed_up_action_cb); gtk_widget_class_install_action (widget_class, "video.speed-down", NULL, speed_down_action_cb); gtk_widget_class_install_action (widget_class, "video.set-speed", "d", set_speed_action_cb); gtk_widget_class_install_action (widget_class, "video.previous-item", NULL, previous_item_action_cb); gtk_widget_class_install_action (widget_class, "video.next-item", NULL, next_item_action_cb); gtk_widget_class_install_action (widget_class, "video.select-item", "u", select_item_action_cb); gtk_widget_class_set_template_from_resource (widget_class, CLAPPER_GTK_RESOURCE_PREFIX "/ui/clapper-gtk-video.ui"); gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, status); gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, buffering_animation); gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, touch_gesture); gtk_widget_class_bind_template_child (widget_class, ClapperGtkVideo, click_gesture); gtk_widget_class_bind_template_callback (widget_class, left_click_pressed_cb); gtk_widget_class_bind_template_callback (widget_class, left_click_released_cb); gtk_widget_class_bind_template_callback (widget_class, left_click_stopped_cb); gtk_widget_class_bind_template_callback (widget_class, touch_pressed_cb); gtk_widget_class_bind_template_callback (widget_class, touch_released_cb); gtk_widget_class_bind_template_callback (widget_class, motion_enter_cb); gtk_widget_class_bind_template_callback (widget_class, motion_cb); gtk_widget_class_bind_template_callback (widget_class, motion_leave_cb); gtk_widget_class_bind_template_callback (widget_class, drop_motion_cb); gtk_widget_class_bind_template_callback (widget_class, drop_motion_leave_cb); gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GENERIC); gtk_widget_class_set_css_name (widget_class, "clapper-gtk-video"); } clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk-video.h000066400000000000000000000045371474353110700224170ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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_GTK_INSIDE__) && !defined(CLAPPER_GTK_COMPILATION) #error "Only can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_GTK_TYPE_VIDEO (clapper_gtk_video_get_type()) #define CLAPPER_GTK_VIDEO_CAST(obj) ((ClapperGtkVideo *)(obj)) CLAPPER_GTK_API G_DECLARE_FINAL_TYPE (ClapperGtkVideo, clapper_gtk_video, CLAPPER_GTK, VIDEO, GtkWidget) CLAPPER_GTK_API GtkWidget * clapper_gtk_video_new (void); CLAPPER_GTK_API void clapper_gtk_video_add_overlay (ClapperGtkVideo *video, GtkWidget *widget); CLAPPER_GTK_API void clapper_gtk_video_add_fading_overlay (ClapperGtkVideo *video, GtkWidget *widget); CLAPPER_GTK_API ClapperPlayer * clapper_gtk_video_get_player (ClapperGtkVideo *video); CLAPPER_GTK_API void clapper_gtk_video_set_fade_delay (ClapperGtkVideo *video, guint delay); CLAPPER_GTK_API guint clapper_gtk_video_get_fade_delay (ClapperGtkVideo *video); CLAPPER_GTK_API void clapper_gtk_video_set_touch_fade_delay (ClapperGtkVideo *video, guint delay); CLAPPER_GTK_API guint clapper_gtk_video_get_touch_fade_delay (ClapperGtkVideo *video); CLAPPER_GTK_API void clapper_gtk_video_set_auto_inhibit (ClapperGtkVideo *video, gboolean inhibit); CLAPPER_GTK_API gboolean clapper_gtk_video_get_auto_inhibit (ClapperGtkVideo *video); CLAPPER_GTK_API gboolean clapper_gtk_video_get_inhibited (ClapperGtkVideo *video); G_END_DECLS clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk.gresources.xml000066400000000000000000000015651474353110700240420ustar00rootroot00000000000000 ui/clapper-gtk-billboard.ui ui/clapper-gtk-extra-menu-button.ui ui/clapper-gtk-seek-bar.ui ui/clapper-gtk-simple-controls.ui ui/clapper-gtk-status.ui ui/clapper-gtk-stream-list-item.ui ui/clapper-gtk-title-header.ui ui/clapper-gtk-video.ui ui/clapper-gtk-video-placeholder.ui css/styles.css clapper-0.8.0/src/lib/clapper-gtk/clapper-gtk.h000066400000000000000000000033021474353110700213000ustar00rootroot00000000000000/* Clapper GTK Integration Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #define __CLAPPER_GTK_INSIDE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef __CLAPPER_GTK_INSIDE__ clapper-0.8.0/src/lib/clapper-gtk/css/000077500000000000000000000000001474353110700175105ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper-gtk/css/styles.css000066400000000000000000000100211474353110700215370ustar00rootroot00000000000000/* Adwaita OSD background color is unacceptable: * https://gitlab.gnome.org/GNOME/libadwaita/-/issues/454 */ clapper-gtk-video menubutton.osd, clapper-gtk-video button.osd, clapper-gtk-video box.osd, clapper-gtk-video clapper-gtk-title-header.osd, clapper-gtk-video .osd popover contents, clapper-gtk-video .osd popover arrow, clapper-gtk-video windowcontrols.osd button image { background-color: rgba(38,38,38,0.8); } clapper-gtk-video button.osd:hover, clapper-gtk-video button.osd:checked, clapper-gtk-video windowcontrols.osd button:hover image, clapper-gtk-video windowcontrols.osd button:checked image { background-color: rgba(63,63,63,0.8); } clapper-gtk-video button.osd:active, clapper-gtk-video windowcontrols.osd button:active image { background-color: rgba(82,82,82,0.8); } clapper-gtk-video box.osd listview.osd, clapper-gtk-video windowcontrols.osd, clapper-gtk-video clapper-gtk-status.osd { background-color: transparent; } clapper-gtk-video-placeholder { background-color: black; } clapper-gtk-status box, clapper-gtk-video-placeholder box { padding-left: 6px; padding-right: 6px; } clapper-gtk-status box image, clapper-gtk-video-placeholder box image { -gtk-icon-size: 128px; -gtk-icon-filter: opacity(0.6); } clapper-gtk-status.adapted box image, clapper-gtk-video-placeholder.adapted box image { -gtk-icon-size: 96px; } clapper-gtk-billboard .sidebox { padding: 12px; border-radius: 9999px; } clapper-gtk-billboard .sidebox:dir(ltr) { margin-left: 6px; } clapper-gtk-billboard .sidebox:dir(rtl) { margin-right: 6px; } clapper-gtk-billboard .sidebox .progressbox { margin-bottom: 6px; } clapper-gtk-billboard .sidebox .progressbox progressbar { min-height: 90px; } clapper-gtk-billboard .sidebox .progressbox.overamp progress { background: #c01c28; } clapper-gtk-billboard .sidebox .progresslabel:dir(ltr) { margin-left: 6px; } clapper-gtk-billboard .sidebox .progresslabel:dir(rtl) { margin-right: 6px; } clapper-gtk-billboard .messagebox { margin: 6px; padding: 12px; min-width: 92px; } clapper-gtk-billboard .messagebox image { -gtk-icon-size: 48px; } menubutton.circular, dropdown.circular button.toggle { border-radius: 9999px; } .rounded { border-radius: 19px; } clapper-gtk-title-header { margin: 6px; min-height: 38px; } clapper-gtk-title-header label { margin-left: 12px; margin-right: 12px; } clapper-gtk-simple-controls .centerbox { margin-bottom: 2px; } clapper-gtk-simple-controls .mainbox { margin-left: 6px; margin-right: 6px; margin-bottom: 4px; } clapper-gtk-simple-controls .mainbox button { margin: 2px; } clapper-gtk-simple-controls .mainbox scale { margin-top: 2px; margin-bottom: 2px; } clapper-gtk-simple-controls .mainbox popover button { margin: 0px; } clapper-gtk-simple-controls .fullscreenbutton { margin-bottom: 6px; } clapper-gtk-simple-controls .fullscreenbutton:dir(ltr) { margin-right: 6px; } clapper-gtk-simple-controls .fullscreenbutton:dir(rtl) { margin-left: 6px; } clapper-gtk-seek-bar scale trough highlight { min-height: 6px; } clapper-gtk-seek-bar label { margin-left: 2px; margin-right: 2px; } clapper-gtk-extra-menu-button popover .spinsidebutton { min-width: 28px; min-height: 28px; } clapper-gtk-extra-menu-button popover .spinsidebutton:dir(ltr) { margin-right: 2px; } clapper-gtk-extra-menu-button popover .spinsidebutton:dir(rtl) { margin-left: 2px; } clapper-gtk-extra-menu-button popover spinbutton { border-radius: 9999px; } clapper-gtk-extra-menu-button popover spinbutton button.up:dir(ltr) { border-radius: 0px 9999px 9999px 0px; } clapper-gtk-extra-menu-button popover spinbutton button.up:dir(rtl) { border-radius: 9999px 0px 0px 9999px; } clapper-gtk-extra-menu-button popover spinbutton button.up:dir(ltr) { padding-right: 6px; } clapper-gtk-extra-menu-button popover spinbutton button.up:dir(rtl) { padding-left: 6px; } clapper-gtk-menu-scrolled-window listview row { padding-left: 6px; padding-right: 6px; } clapper-gtk-menu-scrolled-window listview row checkbutton label { margin-left: 2px; margin-right: 2px; } clapper-0.8.0/src/lib/clapper-gtk/meson.build000066400000000000000000000135351474353110700210710ustar00rootroot00000000000000clappergtk_dep = dependency('', required: false) clappergtk_option = get_option('clapper-gtk') clappergtk_api_name = meson.project_name() + '-gtk' + clapper_version_suffix clappergtk_resource_prefix = '/com/github/rafostar/Clapper/clapper-gtk' build_clappergtk = false if clappergtk_option.disabled() subdir_done() endif clappergtk_deps = [ clapper_dep, gst_dep, gst_clapper_sink_dep, gtk4_dep, glib_dep, gobject_dep, libm, ] foreach dep : clappergtk_deps if not dep.found() if clappergtk_option.enabled() error('clapper-gtk option was enabled, but required dependencies were not found') endif subdir_done() endif endforeach subdir('po') config_h = configuration_data() config_h.set_quoted('GETTEXT_PACKAGE', meson.project_name() + '-gtk') config_h.set_quoted('LOCALEDIR', join_paths (prefix, localedir)) config_h.set_quoted('CLAPPER_GTK_RESOURCE_PREFIX', clappergtk_resource_prefix) configure_file( output: 'config.h', configuration: config_h, ) visibility_conf = configuration_data() visibility_conf.set( 'CLAPPER_API', 'CLAPPER_GTK', ) clappergtk_visibility_header = configure_file( input: '../shared/clapper-api-visibility.h.in', output: 'clapper-gtk-visibility.h', configuration: visibility_conf, ) version_conf = configuration_data() version_conf.set( 'CLAPPER_GTK_VERSION', meson.project_version(), ) version_conf.set( 'CLAPPER_GTK_MAJOR_VERSION', version_array[0].to_int(), ) version_conf.set( 'CLAPPER_GTK_MINOR_VERSION', version_array[1].to_int(), ) version_conf.set( 'CLAPPER_GTK_MICRO_VERSION', version_array[2].to_int(), ) clappergtk_version_header = configure_file( input: 'clapper-gtk-version.h.in', output: 'clapper-gtk-version.h', configuration: version_conf, ) clappergtk_resources = gnome.compile_resources( 'clapper-gtk-resources', 'clapper-gtk.gresources.xml', c_name: 'clapper_gtk', ) # Include the generated headers clappergtk_conf_inc = [ include_directories('.'), include_directories('..'), ] clappergtk_headers = [ 'clapper-gtk.h', 'clapper-gtk-enums.h', 'clapper-gtk-billboard.h', 'clapper-gtk-container.h', 'clapper-gtk-extra-menu-button.h', 'clapper-gtk-lead-container.h', 'clapper-gtk-next-item-button.h', 'clapper-gtk-previous-item-button.h', 'clapper-gtk-seek-bar.h', 'clapper-gtk-simple-controls.h', 'clapper-gtk-title-header.h', 'clapper-gtk-title-label.h', 'clapper-gtk-toggle-fullscreen-button.h', 'clapper-gtk-toggle-play-button.h', 'clapper-gtk-utils.h', 'clapper-gtk-video.h', clappergtk_version_header, clappergtk_visibility_header, ] clappergtk_sources = [ 'clapper-gtk-billboard.c', 'clapper-gtk-buffering-animation.c', 'clapper-gtk-buffering-paintable.c', 'clapper-gtk-container.c', 'clapper-gtk-extra-menu-button.c', 'clapper-gtk-lead-container.c', 'clapper-gtk-limited-layout.c', 'clapper-gtk-next-item-button.c', 'clapper-gtk-previous-item-button.c', 'clapper-gtk-seek-bar.c', 'clapper-gtk-simple-controls.c', 'clapper-gtk-status.c', 'clapper-gtk-stream-check-button.c', 'clapper-gtk-title-header.c', 'clapper-gtk-title-label.c', 'clapper-gtk-toggle-fullscreen-button.c', 'clapper-gtk-toggle-play-button.c', 'clapper-gtk-utils.c', 'clapper-gtk-video.c', 'clapper-gtk-video-placeholder.c', clappergtk_resources, ] clappergtk_c_args = [ '-DG_LOG_DOMAIN="ClapperGtk"', '-DCLAPPER_GTK_COMPILATION', '-DGST_USE_UNSTABLE_API', ] if get_option('default_library') == 'static' clappergtk_c_args += ['-DCLAPPER_GTK_STATIC_COMPILATION'] endif clappergtk_headers_dir = join_paths(includedir, clapper_api_name, 'clapper-gtk') clappergtk_enums = gnome.mkenums_simple( 'clapper-gtk-enum-types', sources: clappergtk_headers, decorator: 'CLAPPER_GTK_API', header_prefix: '#include ', identifier_prefix: 'ClapperGtk', symbol_prefix: 'clapper_gtk', install_header: true, install_dir: clappergtk_headers_dir, ) clappergtk_lib = library( clappergtk_api_name, clappergtk_sources + clappergtk_enums, dependencies: clappergtk_deps, include_directories: clappergtk_conf_inc, c_args: clappergtk_c_args, version: clapper_version, install: true, ) install_headers(clappergtk_headers, install_dir: clappergtk_headers_dir, ) build_clappergtk = true if build_gir clappergtk_gir = gnome.generate_gir(clappergtk_lib, sources: [ clappergtk_sources, clappergtk_headers, clappergtk_enums, ], extra_args: [ gir_init_section, '--quiet', '--warn-all', '-DCLAPPER_GTK_COMPILATION', '-DGST_USE_UNSTABLE_API', ], nsversion: version_array[0] + '.0', namespace: 'ClapperGtk', identifier_prefix: 'ClapperGtk', symbol_prefix: 'clapper_gtk', export_packages: clappergtk_api_name, install: true, includes: [ clapper_gir[0], 'Gst-1.0', 'Gtk-4.0', 'GLib-2.0', 'GObject-2.0', ], header: join_paths(meson.project_name() + '-gtk', 'clapper-gtk.h'), ) endif if build_vapi if not build_gir if get_option('vapi').enabled() error('Cannot build "vapi" without "introspection"') endif else gnome.generate_vapi(clappergtk_api_name, sources: clappergtk_gir[0], packages: clapper_pkg_reqs + [ clapper_vapi, 'gtk4', ], install: true, ) endif endif pkgconfig.generate(clappergtk_lib, subdirs: [clappergtk_api_name], filebase: clappergtk_api_name, name: meson.project_name() + '-gtk', version: meson.project_version(), description: 'Clapper GTK integration library', requires: [ clapper_lib, 'gstreamer-1.0', 'gtk4', 'glib-2.0', 'gobject-2.0', ], ) clappergtk_dep = declare_dependency( link_with: clappergtk_lib, include_directories: clappergtk_conf_inc, dependencies: clappergtk_deps, sources: [ clappergtk_visibility_header, clappergtk_version_header, clappergtk_enums[1], ], ) clapper-0.8.0/src/lib/clapper-gtk/po/000077500000000000000000000000001474353110700173365ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper-gtk/po/LINGUAS000066400000000000000000000001361474353110700203630ustar00rootroot00000000000000ar ast ca cs de es eu fa fi fr he hi hr hu it ja lt nl no oc pl pt pt_BR ro ru sk sv tr zh_CN clapper-0.8.0/src/lib/clapper-gtk/po/POTFILES000066400000000000000000000004621474353110700205100ustar00rootroot00000000000000src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c src/lib/clapper-gtk/clapper-gtk-status.c src/lib/clapper-gtk/clapper-gtk-stream-check-button.c src/lib/clapper-gtk/clapper-gtk-title-label.c clapper-0.8.0/src/lib/clapper-gtk/po/af.po000066400000000000000000000045361474353110700202740ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Afrikaans\n" "Language: af_ZA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: af\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/ar.po000066400000000000000000000047571474353110700203150ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Arabic\n" "Language: ar_SA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ar\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "الصوت" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "التَّرْجَمَةً" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "غير محدّد" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "قنوات" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/ast.po000066400000000000000000000045561474353110700204770ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Asturian\n" "Language: ast_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ast\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Sotítulos" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/ca.po000066400000000000000000000051241474353110700202630ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Catalan\n" "Language: ca_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ca\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Vídeo" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Àudio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Subtítols" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Mostra els subtítols" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Obre…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Silencia" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Reinicia" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "No es pot reproduir aquest contingut" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "La teva instal·lació del Gstreamer li manca un connector: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Manca un connector" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Sense determinar" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Canals" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Sense contingut multimèdia" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Títol desconegut" clapper-0.8.0/src/lib/clapper-gtk/po/clapper-gtk.pot000066400000000000000000000045531474353110700223020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the clapper-gtk package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: clapper-gtk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-01-07 19:03+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/cs.po000066400000000000000000000050641474353110700203100ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Czech\n" "Language: cs_CZ\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: cs\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Video" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Zvuk" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Titulky" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Zobrazit Titulky" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Otevřít…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Ztlumit" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Resetovat" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Nepřehratelný Obsah" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "Vaší instalaci GStreamer chybí plugin: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Chybějící Plugin" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Neurčeno" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanály" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Žádná média" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Neznámý název" clapper-0.8.0/src/lib/clapper-gtk/po/da.po000066400000000000000000000045331474353110700202670ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Danish\n" "Language: da_DK\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: da\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/de.po000066400000000000000000000052151474353110700202710ustar00rootroot00000000000000msgid "" 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-13 17:42\n" "Last-Translator: \n" "Language-Team: German\n" "Language: de_DE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: de\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Video" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Untertitel" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Untertitel anzeigen" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Öffnen…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Stummschalten" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Zurücksetzen" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Externe Videoausgabe" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "Der verwendete Videoabfluss kann nicht in einem Anwendungsfenster eingebettet werden" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Medium nicht abspielbar" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "GStreamer fehlt folgendes Plugin: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Fehlendes Plugin" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Unbestimmt" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Audio-Kanäle" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Keine Medien" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Unbekannter Titel" clapper-0.8.0/src/lib/clapper-gtk/po/el.po000066400000000000000000000045321474353110700203020ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Greek\n" "Language: el_GR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: el\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/es.po000066400000000000000000000052551474353110700203140ustar00rootroot00000000000000msgid "" 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-11 17:41\n" "Last-Translator: \n" "Language-Team: Spanish\n" "Language: es_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: es-ES\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Vídeo" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Subtítulos" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Mostrar subtítulos" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Abrir…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Silenciar" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Restablecer" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Salida de vídeo externa" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "La salida de vídeo utilizada no se puede mostrar en la ventana de la aplicación" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Contenido no reproducible" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "A tu instalación de GStreamer le falta un complemento: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Complemento no disponible" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Indeterminado" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Canales" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "No hay medios" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Título desconocido" clapper-0.8.0/src/lib/clapper-gtk/po/eu.po000066400000000000000000000046001474353110700203070ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Basque\n" "Language: eu_ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: eu\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audioa" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Azpitituluak" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Zehaztugabea" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanalak" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/fa.po000066400000000000000000000054741474353110700202760ustar00rootroot00000000000000msgid "" 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-13 15:41\n" "Last-Translator: \n" "Language-Team: Persian\n" "Language: fa_IR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: fa\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "ویدیو" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "صدا" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "زیرنویس‌ها" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "نمایش زیرنویس" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "گشودن…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "خموش" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "بازنشانی" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "خروجی ویدیویی خارجی" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "دستگاه ویدیویی استفاده شده نمی‌تواند درون پنجرهٔ برنامه تعبیه شود" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "محتوای غیرقابل پخش" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "نصب جی‌استریمرتان افزایه‌ای را ندارد: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "افزایه ناموجود" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "تشخیص داده نشده" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "کانال‌ها" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "بدون رسانه" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "عنوان ناشناس" clapper-0.8.0/src/lib/clapper-gtk/po/fi.po000066400000000000000000000051101474353110700202710ustar00rootroot00000000000000msgid "" 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-12 13:02\n" "Last-Translator: \n" "Language-Team: Finnish\n" "Language: fi_FI\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: fi\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Video" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Ääni" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Tekstitykset" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Näytä tekstitykset" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Avaa…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Mykistä" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Nollaa" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Ulkoinen videon ulostulo" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Sisältöä ei voi toistaa" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "GStreamer-asennuksesta puuttuu liitännäinen: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Puuttuva liitännäinen" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Määrittelemätön" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "kanavaa" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Ei mediaa" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Tuntematon nimi" clapper-0.8.0/src/lib/clapper-gtk/po/fr.po000066400000000000000000000052351474353110700203120ustar00rootroot00000000000000msgid "" 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-18 09:13\n" "Last-Translator: \n" "Language-Team: French\n" "Language: fr_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: fr\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Vidéo" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Sous-titres" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Afficher les sous-titres" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Ouvrir…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Mettre en sourdine" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Réinitialiser" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Sortie vidéo externe" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "La sortie vidéo utilisée ne peut pas être affichée dans la fenêtre de l’application" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Contenu illisible" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "Il manque un greffon à votre installation : %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Greffon manquant" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Indéterminé" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Canaux" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Aucun média" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Titre inconnu" clapper-0.8.0/src/lib/clapper-gtk/po/he.po000066400000000000000000000046741474353110700203050ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Hebrew\n" "Language: he_IL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: he\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "שמע" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "כתוביות" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "לא מוגדר" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "ערוצים" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/hi.po000066400000000000000000000054371474353110700203070ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Hindi\n" "Language: hi_IN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: hi\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "वीडियो" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "ऑडियो" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "उपशीर्षक" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "उपशीर्षक दिखाएं" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "खोलें…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "मूक" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "रीसेट" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "चलाने योग्य सामग्री नहीं" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "आपके GStreamer स्थापना में एक प्लगइन गायब है: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "गायब प्लगइन" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "अनिर्धारित" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "चैनल" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "मीडिया नहीं" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "अज्ञात शीर्षक" clapper-0.8.0/src/lib/clapper-gtk/po/hr.po000066400000000000000000000047041474353110700203140ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Croatian\n" "Language: hr_HR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: hr\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Titlovi" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Neodređeno" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanali" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/hu.po000066400000000000000000000051201474353110700203100ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Hungarian\n" "Language: hu_HU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: hu\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Kép" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Hang" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Feliratok" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Feliratok megjelenítése" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Megnyitás…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Némítás" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Visszaállítás" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Nem lejátszható tartalom" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "A telepített GStreamer -ből hiányzik egy bővítmény: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Hiányzó bővítmény" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Meghatározatlan" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Csatornák" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Nincs média" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Ismeretlen cím" clapper-0.8.0/src/lib/clapper-gtk/po/it.po000066400000000000000000000045771474353110700203270ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Italian\n" "Language: it_IT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: it\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Sottotitoli" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Indeterminato" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Canali" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/ja.po000066400000000000000000000046001474353110700202700ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Japanese\n" "Language: ja_JP\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ja\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "オ−ディオ" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "字幕" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "不明" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "チャンネル" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/ko.po000066400000000000000000000045241474353110700203140ustar00rootroot00000000000000msgid "" 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:31\n" "Last-Translator: \n" "Language-Team: Korean\n" "Language: ko_KR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ko\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/lt.po000066400000000000000000000047111474353110700203200ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Lithuanian\n" "Language: lt_LT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && (n%100>19 || n%100<11) ? 0 : (n%10>=2 && n%10<=9) && (n%100>19 || n%100<11) ? 1 : n%1!=0 ? 2: 3);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: lt\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Titrai" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/meson.build000066400000000000000000000002051474353110700214750ustar00rootroot00000000000000i18n.gettext(meson.project_name() + '-gtk', preset: 'glib') devenv.set('CLAPPER_GTK_OVERRIDE_LOCALEDIR', meson.current_build_dir()) clapper-0.8.0/src/lib/clapper-gtk/po/nl.po000066400000000000000000000045731474353110700203200ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Dutch\n" "Language: nl_NL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: nl\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Ondertiteling" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "onbekend" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanalen" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/no.po000066400000000000000000000050331474353110700203130ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Norwegian\n" "Language: no_NO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: no\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Video" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Lyd" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Undertekstning" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Vis undertekster" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Åpne…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Demp" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Tilbakestill" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Uavspillelig innhold" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "GStreamer-innstallasjon din mangler et tillegg: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Manglende tillegg" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Ubestemt" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanaler" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Ingen medier" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Ukjent tittel" clapper-0.8.0/src/lib/clapper-gtk/po/oc.po000066400000000000000000000045331474353110700203040ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Occitan\n" "Language: oc_FR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: oc\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/pl.po000066400000000000000000000054001474353110700203100ustar00rootroot00000000000000msgid "" 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-19 14:18\n" "Last-Translator: \n" "Language-Team: Polish\n" "Language: pl_PL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: pl\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Wideo" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Napisy" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Pokaż napisy" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Otwórz…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Wycisz" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Reset" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Zewnętrzne wyjście wideo" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "Użyty zlew wideo nie może być osadzony w oknie aplikacji" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Nieodtwarzalna zawartość" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "W twojej instalacji GStreamera brakuje pluginu: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Brakujący plugin" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Nieokreślony" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanały" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Brak mediów" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Nieznany tytuł" clapper-0.8.0/src/lib/clapper-gtk/po/pt.po000066400000000000000000000052441474353110700203260ustar00rootroot00000000000000msgid "" 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-14 14:47\n" "Last-Translator: \n" "Language-Team: Portuguese\n" "Language: pt_PT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: pt-PT\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Vídeo" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Áudio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Legendas" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Mostrar legendas" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Abrir…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Silenciar" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Repor" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Saída de vídeo externo" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "A saída de vídeo utilizada não pode ser incorporada na janela da aplicação" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Conteúdo não reproduzível" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "A sua instalação do GStreamer está com um plugin em falta: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Plugin em falta" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Indeterminado" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Canais" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Nenhum ficheiro" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Título desconhecido" clapper-0.8.0/src/lib/clapper-gtk/po/pt_BR.po000066400000000000000000000052521474353110700207100ustar00rootroot00000000000000msgid "" 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-12 03:17\n" "Last-Translator: \n" "Language-Team: Portuguese, Brazilian\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: pt-BR\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Vídeo" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Áudio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Legendas" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Mostrar legendas" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Abrir…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Desativar som" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Redefinir" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Saída de vídeo externa" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "A sink de vídeo usada não pode ser incorporada à janela do aplicativo" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Conteúdo não reproduzível" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "Há um plugin faltando na sua instalação do GStreamer: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Plugin faltante" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Indeterminado" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Canais" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Nenhuma mídia" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Título desconhecido" clapper-0.8.0/src/lib/clapper-gtk/po/ro.po000066400000000000000000000052601474353110700203210ustar00rootroot00000000000000msgid "" 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-20 18:48\n" "Last-Translator: \n" "Language-Team: Romanian\n" "Language: ro_RO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ro\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Videoclip" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Audio" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Subtitrări" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Arată subtitrările" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Deschide…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Mut" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Resetați" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Ieșire video externă" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "Chiuveta video utilizată nu poate fi încorporat în fereastra aplicației" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Conținut neredabil" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "Instalarea dvs. de GStreamer lipsește un plugin: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Plugin lipsă" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Nedeterminat" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Canale" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Fără media" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Titlu necunoscut" clapper-0.8.0/src/lib/clapper-gtk/po/ru.po000066400000000000000000000056101474353110700203260ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Russian\n" "Language: ru_RU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: ru\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Видео" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Аудио" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Субтитры" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Показать субтитры" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Открыть…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Без звука" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Сброс" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Невоспроизводимый контент" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "В вашей установке GStreamer отсутствует модуль: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Отсутствующий плагин" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Неопределенный" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Каналы" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Нет медиафайлов" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Неизвестное название" clapper-0.8.0/src/lib/clapper-gtk/po/sk.po000066400000000000000000000046221474353110700203170ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Slovak\n" "Language: sk_SK\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: sk\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Zvuk" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Titulky" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Neurčené" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanály" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/sr.po000066400000000000000000000046611474353110700203310ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Serbian (Cyrillic)\n" "Language: sr_SP\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: sr\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/sv.po000066400000000000000000000045761474353110700203420ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Swedish\n" "Language: sv_SE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: sv-SE\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Ljud" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Undertexter" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Obestämd" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanaler" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/tr.po000066400000000000000000000051511474353110700203250ustar00rootroot00000000000000msgid "" 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-11 22:52\n" "Last-Translator: \n" "Language-Team: Turkish\n" "Language: tr_TR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: tr\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "Video" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "Ses" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "Alt Yazılar" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "Alt Yazıları Göster" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "Aç…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "Sessiz" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "Sıfırla" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "Dış video çıktısı" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "Kullanılan video alıcısı uygulama penceresi içine gömülemez" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "Oynatılamaz İçerik" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "GStreamer kurulumunda eksik eklenti: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "Eksik Eklenti" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "Belirsiz" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "Kanallar" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "Ortam yok" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "Bilinmeyen başlık" clapper-0.8.0/src/lib/clapper-gtk/po/uk.po000066400000000000000000000050031474353110700203130ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Ukrainian\n" "Language: uk_UA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: uk\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/vi.po000066400000000000000000000045301474353110700203160ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Vietnamese\n" "Language: vi_VN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: vi\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/po/zh_CN.po000066400000000000000000000050121474353110700206750ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: zh-CN\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "视频" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "音频" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "字幕" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "显示字幕" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "打开…" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "静音" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "重置" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "无法播放的内容" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "您的 GStreamer 安装缺少一个插件: %s" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "缺少插件" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "未确定" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "声道" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "无媒体" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "未知标题" clapper-0.8.0/src/lib/clapper-gtk/po/zh_TW.po000066400000000000000000000045441474353110700207400ustar00rootroot00000000000000msgid "" 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" "Last-Translator: \n" "Language-Team: Chinese Traditional\n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Crowdin-Project: clapper\n" "X-Crowdin-Project-ID: 473374\n" "X-Crowdin-Language: zh-TW\n" "X-Crowdin-File: /master/src/lib/clapper-gtk/po/clapper-gtk.pot\n" "X-Crowdin-File-ID: 46\n" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:9 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:228 msgid "Video" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:17 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:229 msgid "Audio" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:25 #: src/lib/clapper-gtk/clapper-gtk-extra-menu-button.c:230 msgid "Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:28 msgid "Show Subtitles" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:32 msgid "Open…" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:75 msgid "Mute" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui:119 msgid "Reset" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:25 msgid "External video output" msgstr "" #: src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui:37 msgid "Used video sink cannot be embedded within application window" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:79 msgid "Unplayable Content" msgstr "" #. TRANSLATORS: Please do not try to translate "GStreamer" (it is a library name). #: src/lib/clapper-gtk/clapper-gtk-status.c:89 #, c-format msgid "Your GStreamer installation is missing a plugin: %s" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-status.c:90 msgid "Missing Plugin" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:126 msgid "Undetermined" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-stream-check-button.c:82 msgid "Channels" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:91 msgid "No media" msgstr "" #: src/lib/clapper-gtk/clapper-gtk-title-label.c:103 msgid "Unknown title" msgstr "" clapper-0.8.0/src/lib/clapper-gtk/ui/000077500000000000000000000000001474353110700173355ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-billboard.ui000066400000000000000000000176061474353110700240470ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-extra-menu-button.ui000066400000000000000000000310651474353110700255060ustar00rootroot00000000000000
top-box Video
video-streams
Audio
audio-streams
Subtitles
Show Subtitles clappergtk.subtitle-stream-enabled Open… clappergtk.open-subtitle-stream action-disabled
subtitle-streams
clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-seek-bar.ui000066400000000000000000000070201474353110700235730ustar00rootroot00000000000000 0 0 0 0 8 clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-simple-controls.ui000066400000000000000000000164061474353110700252440ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-status.ui000066400000000000000000000032641474353110700234330ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-stream-list-item.ui000066400000000000000000000007031474353110700253030ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-title-header.ui000066400000000000000000000013341474353110700244530ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-video-placeholder.ui000066400000000000000000000034351474353110700254760ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper-gtk/ui/clapper-gtk-video.ui000066400000000000000000000034521474353110700232150ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper/000077500000000000000000000000001474353110700161355ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper/clapper-app-bus-private.h000066400000000000000000000041411474353110700227510ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_APP_BUS (clapper_app_bus_get_type()) #define CLAPPER_APP_BUS_CAST(obj) ((ClapperAppBus *)(obj)) /** * ClapperAppBus: */ G_DECLARE_FINAL_TYPE (ClapperAppBus, clapper_app_bus, CLAPPER, APP_BUS, GstBus) void clapper_app_bus_initialize (void); ClapperAppBus * clapper_app_bus_new (void); void clapper_app_bus_forward_message (ClapperAppBus *app_bus, GstMessage *msg); void clapper_app_bus_post_prop_notify (ClapperAppBus *app_bus, GstObject *src, GParamSpec *pspec); void clapper_app_bus_post_refresh_streams (ClapperAppBus *app_bus, GstObject *src); void clapper_app_bus_post_refresh_timeline (ClapperAppBus *app_bus, GstObject *src); void clapper_app_bus_post_simple_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id); void clapper_app_bus_post_object_desc_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GstObject *object, const gchar *desc); void clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, const gchar *desc, const gchar *details); void clapper_app_bus_post_error_signal (ClapperAppBus *app_bus, GstObject *src, guint signal_id, GError *error, const gchar *debug_info); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-app-bus.c000066400000000000000000000247301474353110700213020ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-player-private.h" #include "clapper-media-item-private.h" #include "clapper-timeline-private.h" #define GST_CAT_DEFAULT clapper_app_bus_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAppBus { GstBus parent; }; #define parent_class clapper_app_bus_parent_class G_DEFINE_TYPE (ClapperAppBus, clapper_app_bus, GST_TYPE_BUS); enum { CLAPPER_APP_BUS_STRUCTURE_UNKNOWN = 0, CLAPPER_APP_BUS_STRUCTURE_PROP_NOTIFY, CLAPPER_APP_BUS_STRUCTURE_REFRESH_STREAMS, CLAPPER_APP_BUS_STRUCTURE_REFRESH_TIMELINE, CLAPPER_APP_BUS_STRUCTURE_SIMPLE_SIGNAL, CLAPPER_APP_BUS_STRUCTURE_OBJECT_DESC_SIGNAL, CLAPPER_APP_BUS_STRUCTURE_DESC_WITH_DETAILS_SIGNAL, CLAPPER_APP_BUS_STRUCTURE_ERROR_SIGNAL }; static ClapperBusQuark _structure_quarks[] = { {"unknown", 0}, {"prop-notify", 0}, {"refresh-streams", 0}, {"refresh-timeline", 0}, {"simple-signal", 0}, {"object-desc-signal", 0}, {"desc-with-details-signal", 0}, {"error-signal", 0}, {NULL, 0} }; enum { CLAPPER_APP_BUS_FIELD_UNKNOWN = 0, CLAPPER_APP_BUS_FIELD_PSPEC, CLAPPER_APP_BUS_FIELD_SIGNAL_ID, CLAPPER_APP_BUS_FIELD_OBJECT, CLAPPER_APP_BUS_FIELD_DESC, CLAPPER_APP_BUS_FIELD_DETAILS, CLAPPER_APP_BUS_FIELD_ERROR, CLAPPER_APP_BUS_FIELD_DEBUG_INFO }; static ClapperBusQuark _field_quarks[] = { {"unknown", 0}, {"pspec", 0}, {"signal-id", 0}, {"object", 0}, {"desc", 0}, {"details", 0}, {"error", 0}, {"debug-info", 0}, {NULL, 0} }; #define _STRUCTURE_QUARK(q) (_structure_quarks[CLAPPER_APP_BUS_STRUCTURE_##q].quark) #define _FIELD_QUARK(q) (_field_quarks[CLAPPER_APP_BUS_FIELD_##q].quark) #define _MESSAGE_SRC_GOBJECT(msg) ((GObject *) GST_MESSAGE_SRC (msg)) void clapper_app_bus_initialize (void) { gint i; for (i = 0; _structure_quarks[i].name; ++i) _structure_quarks[i].quark = g_quark_from_static_string (_structure_quarks[i].name); for (i = 0; _field_quarks[i].name; ++i) _field_quarks[i].quark = g_quark_from_static_string (_field_quarks[i].name); } void clapper_app_bus_forward_message (ClapperAppBus *self, GstMessage *msg) { gst_bus_post (GST_BUS_CAST (self), gst_message_ref (msg)); } /* FIXME: It should be faster to wait for gst_message_new_property_notify() from * playbin bus and forward them to app bus instead of connecting to notify * signals of playbin, so change into using gst_message_new_property_notify() here too */ void clapper_app_bus_post_prop_notify (ClapperAppBus *self, GstObject *src, GParamSpec *pspec) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (PROP_NOTIFY), _FIELD_QUARK (PSPEC), G_TYPE_PARAM, pspec, NULL); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); } static inline void _handle_prop_notify_msg (GstMessage *msg, const GstStructure *structure) { GParamSpec *pspec = NULL; gst_structure_id_get (structure, _FIELD_QUARK (PSPEC), G_TYPE_PARAM, &pspec, NULL); g_object_notify_by_pspec (_MESSAGE_SRC_GOBJECT (msg), pspec); g_param_spec_unref (pspec); } void clapper_app_bus_post_refresh_streams (ClapperAppBus *self, GstObject *src) { GstStructure *structure = gst_structure_new_id_empty (_STRUCTURE_QUARK (REFRESH_STREAMS)); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); } static inline void _handle_refresh_streams_msg (GstMessage *msg, const GstStructure *structure) { ClapperPlayer *player = CLAPPER_PLAYER_CAST (GST_MESSAGE_SRC (msg)); clapper_player_refresh_streams (player); } void clapper_app_bus_post_refresh_timeline (ClapperAppBus *self, GstObject *src) { GstStructure *structure = gst_structure_new_id_empty (_STRUCTURE_QUARK (REFRESH_TIMELINE)); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); } static inline void _handle_refresh_timeline_msg (GstMessage *msg, const GstStructure *structure) { ClapperMediaItem *item = CLAPPER_MEDIA_ITEM_CAST (GST_MESSAGE_SRC (msg)); ClapperTimeline *timeline = clapper_media_item_get_timeline (item); clapper_timeline_refresh (timeline); } void clapper_app_bus_post_simple_signal (ClapperAppBus *self, GstObject *src, guint signal_id) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (SIMPLE_SIGNAL), _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id, NULL); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); } static inline void _handle_simple_signal_msg (GstMessage *msg, const GstStructure *structure) { guint signal_id = 0; gst_structure_id_get (structure, _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id, NULL); g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0); } void clapper_app_bus_post_object_desc_signal (ClapperAppBus *self, GstObject *src, guint signal_id, GstObject *object, const gchar *desc) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (OBJECT_DESC_SIGNAL), _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id, _FIELD_QUARK (OBJECT), GST_TYPE_OBJECT, object, _FIELD_QUARK (DESC), G_TYPE_STRING, desc, NULL); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); } static inline void _handle_object_desc_signal_msg (GstMessage *msg, const GstStructure *structure) { guint signal_id = 0; GstObject *object = NULL; gchar *desc = NULL; gst_structure_id_get (structure, _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id, _FIELD_QUARK (OBJECT), GST_TYPE_OBJECT, &object, _FIELD_QUARK (DESC), G_TYPE_STRING, &desc, NULL); g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0, object, desc); gst_object_unref (object); g_free (desc); } void clapper_app_bus_post_desc_with_details_signal (ClapperAppBus *self, GstObject *src, guint signal_id, const gchar *desc, const gchar *details) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL), _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id, _FIELD_QUARK (DESC), G_TYPE_STRING, desc, _FIELD_QUARK (DETAILS), G_TYPE_STRING, details, NULL); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); } static inline void _handle_desc_with_details_signal_msg (GstMessage *msg, const GstStructure *structure) { guint signal_id = 0; gchar *desc = NULL, *details = NULL; gst_structure_id_get (structure, _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id, _FIELD_QUARK (DESC), G_TYPE_STRING, &desc, _FIELD_QUARK (DETAILS), G_TYPE_STRING, &details, NULL); g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0, desc, details); g_free (desc); g_free (details); } void clapper_app_bus_post_error_signal (ClapperAppBus *self, GstObject *src, guint signal_id, GError *error, const gchar *debug_info) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (ERROR_SIGNAL), _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, signal_id, _FIELD_QUARK (ERROR), G_TYPE_ERROR, error, _FIELD_QUARK (DEBUG_INFO), G_TYPE_STRING, debug_info, NULL); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application (src, structure)); } static inline void _handle_error_signal_msg (GstMessage *msg, const GstStructure *structure) { guint signal_id = 0; GError *error = NULL; gchar *debug_info = NULL; gst_structure_id_get (structure, _FIELD_QUARK (SIGNAL_ID), G_TYPE_UINT, &signal_id, _FIELD_QUARK (ERROR), G_TYPE_ERROR, &error, _FIELD_QUARK (DEBUG_INFO), G_TYPE_STRING, &debug_info, NULL); g_signal_emit (_MESSAGE_SRC_GOBJECT (msg), signal_id, 0, error, debug_info); g_clear_error (&error); g_free (debug_info); } static gboolean clapper_app_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSED) { if (G_LIKELY (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_APPLICATION)) { const GstStructure *structure = gst_message_get_structure (msg); GQuark quark = gst_structure_get_name_id (structure); if (quark == _STRUCTURE_QUARK (PROP_NOTIFY)) _handle_prop_notify_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (REFRESH_STREAMS)) _handle_refresh_streams_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (REFRESH_TIMELINE)) _handle_refresh_timeline_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (SIMPLE_SIGNAL)) _handle_simple_signal_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (OBJECT_DESC_SIGNAL)) _handle_object_desc_signal_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (ERROR_SIGNAL)) _handle_error_signal_msg (msg, structure); else if (quark == _STRUCTURE_QUARK (DESC_WITH_DETAILS_SIGNAL)) _handle_desc_with_details_signal_msg (msg, structure); } return G_SOURCE_CONTINUE; } /* * clapper_app_bus_new: * * Returns: (transfer full): a new #ClapperAppBus instance */ ClapperAppBus * clapper_app_bus_new (void) { GstBus *app_bus; app_bus = GST_BUS_CAST (g_object_new (CLAPPER_TYPE_APP_BUS, NULL)); gst_object_ref_sink (app_bus); gst_bus_add_watch (app_bus, (GstBusFunc) clapper_app_bus_message_func, NULL); return CLAPPER_APP_BUS_CAST (app_bus); } static void clapper_app_bus_init (ClapperAppBus *self) { } static void clapper_app_bus_finalize (GObject *object) { ClapperAppBus *self = CLAPPER_APP_BUS_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_app_bus_class_init (ClapperAppBusClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperappbus", 0, "Clapper App Bus"); gobject_class->finalize = clapper_app_bus_finalize; } clapper-0.8.0/src/lib/clapper/clapper-audio-stream-private.h000066400000000000000000000020211474353110700237670ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-audio-stream.h" G_BEGIN_DECLS G_GNUC_INTERNAL ClapperStream * clapper_audio_stream_new (GstStream *gst_stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-audio-stream.c000066400000000000000000000265531474353110700223320ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperAudioStream: * * Represents an audio stream within media. */ #include #include "clapper-audio-stream-private.h" #include "clapper-stream-private.h" #define GST_CAT_DEFAULT clapper_audio_stream_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperAudioStream { ClapperStream parent; gchar *codec; guint bitrate; gchar *sample_format; gint sample_rate; gint channels; gchar *lang_code; gchar *lang_name; }; #define parent_class clapper_audio_stream_parent_class G_DEFINE_TYPE (ClapperAudioStream, clapper_audio_stream, CLAPPER_TYPE_STREAM); enum { PROP_0, PROP_CODEC, PROP_BITRATE, PROP_SAMPLE_FORMAT, PROP_SAMPLE_RATE, PROP_CHANNELS, PROP_LANG_CODE, PROP_LANG_NAME, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void _update_using_caps (ClapperAudioStream *self, GstCaps *caps) { ClapperStream *stream = CLAPPER_STREAM_CAST (self); GstStructure *structure; gint sample_rate = 0, channels = 0; if (gst_caps_get_size (caps) == 0) return; structure = gst_caps_get_structure (caps, 0); clapper_stream_set_string_prop (stream, param_specs[PROP_SAMPLE_FORMAT], &self->sample_format, gst_structure_get_string (structure, "format")); gst_structure_get_int (structure, "rate", &sample_rate); clapper_stream_set_int_prop (stream, param_specs[PROP_SAMPLE_RATE], &self->sample_rate, sample_rate); gst_structure_get_int (structure, "channels", &channels); clapper_stream_set_int_prop (stream, param_specs[PROP_CHANNELS], &self->channels, channels); } static void _update_using_tags (ClapperAudioStream *self, GstTagList *tags) { ClapperStream *stream = CLAPPER_STREAM_CAST (self); gchar *codec = NULL, *lang_code = NULL, *lang_name = NULL; guint bitrate = 0; gst_tag_list_get_string_index (tags, GST_TAG_AUDIO_CODEC, 0, &codec); clapper_stream_take_string_prop (stream, param_specs[PROP_CODEC], &self->codec, codec); gst_tag_list_get_uint_index (tags, GST_TAG_BITRATE, 0, &bitrate); clapper_stream_set_uint_prop (stream, param_specs[PROP_BITRATE], &self->bitrate, bitrate); /* Prefer code (and name from it), fallback to extracted name */ if (!gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_CODE, 0, &lang_code)) gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_NAME, 0, &lang_name); clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_CODE], &self->lang_code, lang_code); clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_NAME], &self->lang_name, lang_name); } ClapperStream * clapper_audio_stream_new (GstStream *gst_stream) { ClapperAudioStream *audio_stream; audio_stream = g_object_new (CLAPPER_TYPE_AUDIO_STREAM, "stream-type", CLAPPER_STREAM_TYPE_AUDIO, NULL); gst_object_ref_sink (audio_stream); clapper_stream_set_gst_stream (CLAPPER_STREAM_CAST (audio_stream), gst_stream); return CLAPPER_STREAM_CAST (audio_stream); } /** * clapper_audio_stream_get_codec: * @stream: a #ClapperAudioStream * * Get codec used to encode @stream. * * Returns: (transfer full) (nullable): the audio codec of stream * or %NULL if undetermined. */ gchar * clapper_audio_stream_get_codec (ClapperAudioStream *self) { gchar *codec; g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL); GST_OBJECT_LOCK (self); codec = g_strdup (self->codec); GST_OBJECT_UNLOCK (self); return codec; } /** * clapper_audio_stream_get_bitrate: * @stream: a #ClapperAudioStream * * Get bitrate of audio @stream. * * Returns: the bitrate of audio stream. */ guint clapper_audio_stream_get_bitrate (ClapperAudioStream *self) { guint bitrate; g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), 0); GST_OBJECT_LOCK (self); bitrate = self->bitrate; GST_OBJECT_UNLOCK (self); return bitrate; } /** * clapper_audio_stream_get_sample_format: * @stream: a #ClapperAudioStream * * Get sample format of audio @stream. * * Returns: (transfer full) (nullable): the sample format of stream * or %NULL if undetermined. */ gchar * clapper_audio_stream_get_sample_format (ClapperAudioStream *self) { gchar *sample_format; g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL); GST_OBJECT_LOCK (self); sample_format = g_strdup (self->sample_format); GST_OBJECT_UNLOCK (self); return sample_format; } /** * clapper_audio_stream_get_sample_rate: * @stream: a #ClapperAudioStream * * Get sample rate of audio @stream (in Hz). * * Returns: the sample rate of audio stream. */ gint clapper_audio_stream_get_sample_rate (ClapperAudioStream *self) { gint sample_rate; g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), 0); GST_OBJECT_LOCK (self); sample_rate = self->sample_rate; GST_OBJECT_UNLOCK (self); return sample_rate; } /** * clapper_audio_stream_get_channels: * @stream: a #ClapperAudioStream * * Get number of audio channels in @stream. * * Returns: the number of audio channels. */ gint clapper_audio_stream_get_channels (ClapperAudioStream *self) { gint channels; g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), 0); GST_OBJECT_LOCK (self); channels = self->channels; GST_OBJECT_UNLOCK (self); return channels; } /** * clapper_audio_stream_get_lang_code: * @stream: a #ClapperAudioStream * * Get an ISO-639 language code of the @stream. * * Returns: (transfer full) (nullable): the language code of audio stream. */ gchar * clapper_audio_stream_get_lang_code (ClapperAudioStream *self) { gchar *lang_code; g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL); GST_OBJECT_LOCK (self); lang_code = g_strdup (self->lang_code); GST_OBJECT_UNLOCK (self); return lang_code; } /** * clapper_audio_stream_get_lang_name: * @stream: a #ClapperAudioStream * * Get language name of the @stream. * * This function will try to return a translated string into current * locale if possible, with a fallback to a name extracted from tags. * * Returns: (transfer full) (nullable): the language name of audio stream. */ gchar * clapper_audio_stream_get_lang_name (ClapperAudioStream *self) { gchar *lang_name = NULL; g_return_val_if_fail (CLAPPER_IS_AUDIO_STREAM (self), NULL); GST_OBJECT_LOCK (self); /* Prefer from code as its translated to user locale, * otherwise try to fallback to the one sent in tags */ if (self->lang_code) lang_name = g_strdup (gst_tag_get_language_name (self->lang_code)); if (!lang_name) lang_name = g_strdup (self->lang_name); GST_OBJECT_UNLOCK (self); return lang_name; } static void clapper_audio_stream_init (ClapperAudioStream *self) { } static void clapper_audio_stream_finalize (GObject *object) { ClapperAudioStream *self = CLAPPER_AUDIO_STREAM_CAST (object); g_free (self->codec); g_free (self->sample_format); g_free (self->lang_code); g_free (self->lang_name); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_audio_stream_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperAudioStream *self = CLAPPER_AUDIO_STREAM_CAST (object); switch (prop_id) { case PROP_CODEC: g_value_take_string (value, clapper_audio_stream_get_codec (self)); break; case PROP_BITRATE: g_value_set_uint (value, clapper_audio_stream_get_bitrate (self)); break; case PROP_SAMPLE_FORMAT: g_value_take_string (value, clapper_audio_stream_get_sample_format (self)); break; case PROP_SAMPLE_RATE: g_value_set_int (value, clapper_audio_stream_get_sample_rate (self)); break; case PROP_CHANNELS: g_value_set_int (value, clapper_audio_stream_get_channels (self)); break; case PROP_LANG_CODE: g_value_take_string (value, clapper_audio_stream_get_lang_code (self)); break; case PROP_LANG_NAME: g_value_take_string (value, clapper_audio_stream_get_lang_name (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_audio_stream_internal_stream_updated (ClapperStream *stream, GstCaps *caps, GstTagList *tags) { ClapperAudioStream *self = CLAPPER_AUDIO_STREAM_CAST (stream); CLAPPER_STREAM_CLASS (parent_class)->internal_stream_updated (stream, caps, tags); if (caps) _update_using_caps (self, caps); if (tags) _update_using_tags (self, tags); } static void clapper_audio_stream_class_init (ClapperAudioStreamClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperStreamClass *stream_class = (ClapperStreamClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperaudiostream", 0, "Clapper Audio Stream"); gobject_class->get_property = clapper_audio_stream_get_property; gobject_class->finalize = clapper_audio_stream_finalize; stream_class->internal_stream_updated = clapper_audio_stream_internal_stream_updated; /** * ClapperAudioStream:codec: * * Stream codec. */ param_specs[PROP_CODEC] = g_param_spec_string ("codec", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperAudioStream:bitrate: * * Stream bitrate. */ param_specs[PROP_BITRATE] = g_param_spec_uint ("bitrate", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperAudioStream:sample-format: * * Stream sample format. */ param_specs[PROP_SAMPLE_FORMAT] = g_param_spec_string ("sample-format", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperAudioStream:sample-rate: * * Stream sample rate (in Hz). */ param_specs[PROP_SAMPLE_RATE] = g_param_spec_int ("sample-rate", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperAudioStream:channels: * * Stream number of audio channels. */ param_specs[PROP_CHANNELS] = g_param_spec_int ("channels", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperAudioStream:lang-code: * * Stream language code in ISO-639 format. */ param_specs[PROP_LANG_CODE] = g_param_spec_string ("lang-code", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperAudioStream:lang-name: * * Stream language name. */ param_specs[PROP_LANG_NAME] = g_param_spec_string ("lang-name", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/clapper-audio-stream.h000066400000000000000000000037001474353110700223240ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_AUDIO_STREAM (clapper_audio_stream_get_type()) #define CLAPPER_AUDIO_STREAM_CAST(obj) ((ClapperAudioStream *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperAudioStream, clapper_audio_stream, CLAPPER, AUDIO_STREAM, ClapperStream) CLAPPER_API gchar * clapper_audio_stream_get_codec (ClapperAudioStream *stream); CLAPPER_API guint clapper_audio_stream_get_bitrate (ClapperAudioStream *stream); CLAPPER_API gchar * clapper_audio_stream_get_sample_format (ClapperAudioStream *stream); CLAPPER_API gint clapper_audio_stream_get_sample_rate (ClapperAudioStream *stream); CLAPPER_API gint clapper_audio_stream_get_channels (ClapperAudioStream *stream); CLAPPER_API gchar * clapper_audio_stream_get_lang_code (ClapperAudioStream *stream); CLAPPER_API gchar * clapper_audio_stream_get_lang_name (ClapperAudioStream *stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-bus-private.h000066400000000000000000000017201474353110700221730ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 G_BEGIN_DECLS typedef struct { const gchar *name; GQuark quark; } ClapperBusQuark; G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-enhancers-loader-private.h000066400000000000000000000025751474353110700246250ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_enhancers_loader_initialize (void); 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); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-enhancers-loader.c000066400000000000000000000301231474353110700231360ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #ifdef G_OS_WIN32 #include static HMODULE _enhancers_dll_handle = NULL; #endif #include "clapper-enhancers-loader-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 #define GST_CAT_DEFAULT clapper_enhancers_loader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); static PeasEngine *_engine = NULL; static GMutex load_lock; static inline void _import_enhancers (const gchar *enhancers_path) { gchar **dir_paths = g_strsplit (enhancers_path, G_SEARCHPATH_SEPARATOR_S, 0); guint i; for (i = 0; dir_paths[i]; ++i) peas_engine_add_search_path (_engine, dir_paths[i], NULL); g_strfreev (dir_paths); } /* * clapper_enhancers_loader_initialize: * * Initializes #PeasEngine with directories that store enhancers. */ void clapper_enhancers_loader_initialize (void) { const gchar *enhancers_path; gchar *custom_path = NULL; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancersloader", 0, "Clapper Enhancer Loader"); enhancers_path = g_getenv ("CLAPPER_ENHANCERS_PATH"); #ifdef G_OS_WIN32 if (!enhancers_path || *enhancers_path == '\0') { gchar *win_base_dir; 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); enhancers_path = custom_path; // assign temporarily g_free (win_base_dir); } #endif if (!enhancers_path || *enhancers_path == '\0') enhancers_path = CLAPPER_ENHANCERS_PATH; GST_INFO ("Initializing Clapper enhancers with path: \"%s\"", enhancers_path); _engine = peas_engine_new (); /* Peas loaders are loaded lazily, so it should be fine * to just enable them all here (even if not installed) */ peas_engine_enable_loader (_engine, "python"); peas_engine_enable_loader (_engine, "gjs"); _import_enhancers (enhancers_path); /* Support loading additional enhancers from non-default directory */ enhancers_path = g_getenv ("CLAPPER_ENHANCERS_EXTRA_PATH"); if (enhancers_path && *enhancers_path != '\0') { GST_INFO ("Enhancers extra path: \"%s\"", enhancers_path); _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); 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); } GST_INFO ("Clapper enhancers initialized, found: %u", n_items); } 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: * @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 * * 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. * * 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) { GObject *enhancer = NULL; PeasPluginInfo *info; const gchar *scheme = g_uri_get_scheme (uri); const gchar *host = g_uri_get_host (uri); if ((info = clapper_enhancers_loader_get_info (iface_type, scheme, host))) { 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); } return enhancer; } clapper-0.8.0/src/lib/clapper/clapper-enums-private.h000066400000000000000000000051541474353110700225360ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 G_BEGIN_DECLS typedef enum { CLAPPER_PLAYER_PLAY_FLAG_VIDEO = (1 << 0), CLAPPER_PLAYER_PLAY_FLAG_AUDIO = (1 << 1), CLAPPER_PLAYER_PLAY_FLAG_TEXT = (1 << 2), CLAPPER_PLAYER_PLAY_FLAG_VIS = (1 << 3), CLAPPER_PLAYER_PLAY_FLAG_SOFT_VOLUME = (1 << 4), CLAPPER_PLAYER_PLAY_FLAG_NATIVE_AUDIO = (1 << 5), CLAPPER_PLAYER_PLAY_FLAG_NATIVE_VIDEO = (1 << 6), CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD = (1 << 7), CLAPPER_PLAYER_PLAY_FLAG_BUFFERING = (1 << 8), CLAPPER_PLAYER_PLAY_FLAG_DEINTERLACE = (1 << 9), CLAPPER_PLAYER_PLAY_FLAG_SOFT_COLORBALANCE = (1 << 10), CLAPPER_PLAYER_PLAY_FLAG_FORCE_FILTERS = (1 << 11), CLAPPER_PLAYER_PLAY_FLAG_FORCE_SW_DECODERS = (1 << 12) } ClapperPlayerPlayFlags; typedef enum { CLAPPER_FEATURES_MANAGER_EVENT_UNKNOWN = 0, CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_ADDED, CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_PROPERTY_CHANGED, CLAPPER_FEATURES_MANAGER_EVENT_STATE_CHANGED, CLAPPER_FEATURES_MANAGER_EVENT_POSITION_CHANGED, CLAPPER_FEATURES_MANAGER_EVENT_SPEED_CHANGED, CLAPPER_FEATURES_MANAGER_EVENT_VOLUME_CHANGED, CLAPPER_FEATURES_MANAGER_EVENT_MUTE_CHANGED, CLAPPER_FEATURES_MANAGER_EVENT_PLAYED_ITEM_CHANGED, CLAPPER_FEATURES_MANAGER_EVENT_ITEM_UPDATED, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_ADDED, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REMOVED, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_CLEARED, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED } ClapperFeaturesManagerEvent; typedef enum { CLAPPER_QUEUE_ITEM_CHANGE_NORMAL = 1, CLAPPER_QUEUE_ITEM_CHANGE_INSTANT = 2, CLAPPER_QUEUE_ITEM_CHANGE_GAPLESS = 3, } ClapperQueueItemChangeMode; G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-enums.h000066400000000000000000000117511474353110700210660ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include G_BEGIN_DECLS /** * ClapperPlayerState: * @CLAPPER_PLAYER_STATE_STOPPED: Player is stopped. * @CLAPPER_PLAYER_STATE_BUFFERING: Player is buffering. * @CLAPPER_PLAYER_STATE_PAUSED: Player is paused. * @CLAPPER_PLAYER_STATE_PLAYING: Player is playing. */ typedef enum { CLAPPER_PLAYER_STATE_STOPPED = 0, CLAPPER_PLAYER_STATE_BUFFERING, CLAPPER_PLAYER_STATE_PAUSED, CLAPPER_PLAYER_STATE_PLAYING, } ClapperPlayerState; /** * ClapperPlayerSeekMethod: * @CLAPPER_PLAYER_SEEK_METHOD_ACCURATE: Seek to exact position (slow). * @CLAPPER_PLAYER_SEEK_METHOD_NORMAL: Seek to approximated position. * @CLAPPER_PLAYER_SEEK_METHOD_FAST: Seek to position of nearest keyframe (fast). */ typedef enum { CLAPPER_PLAYER_SEEK_METHOD_ACCURATE = 0, CLAPPER_PLAYER_SEEK_METHOD_NORMAL, CLAPPER_PLAYER_SEEK_METHOD_FAST, } ClapperPlayerSeekMethod; /** * ClapperQueueProgressionMode: * @CLAPPER_QUEUE_PROGRESSION_NONE: Queue will not change current item after playback finishes. * @CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE: Queue selects items one after another until the end. * When end of queue is reached, this mode will continue one another item is added to the queue, * playing it if player autoplay property is set, otherwise current player state is kept. * @CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM: Queue keeps repeating current media item. * @CLAPPER_QUEUE_PROGRESSION_CAROUSEL: Queue starts from beginning after last media item. * @CLAPPER_QUEUE_PROGRESSION_SHUFFLE: Queue selects a random media item after current one. * Shuffle mode will avoid reselecting previously shuffled items as long as possible. * After it runs out of unused items, shuffling begins anew. */ typedef enum { CLAPPER_QUEUE_PROGRESSION_NONE = 0, CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE, CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM, CLAPPER_QUEUE_PROGRESSION_CAROUSEL, CLAPPER_QUEUE_PROGRESSION_SHUFFLE, } ClapperQueueProgressionMode; /** * ClapperMarkerType: * @CLAPPER_MARKER_TYPE_UNKNOWN: Unknown marker type. * @CLAPPER_MARKER_TYPE_TITLE: A title marker in timeline. * @CLAPPER_MARKER_TYPE_CHAPTER: A chapter marker in timeline. * @CLAPPER_MARKER_TYPE_TRACK: A track marker in timeline. * @CLAPPER_STREAM_TYPE_CUSTOM_1: A custom marker 1 for free usage by application. * @CLAPPER_STREAM_TYPE_CUSTOM_2: A custom marker 2 for free usage by application. * @CLAPPER_STREAM_TYPE_CUSTOM_3: A custom marker 3 for free usage by application. */ typedef enum { CLAPPER_MARKER_TYPE_UNKNOWN = 0, CLAPPER_MARKER_TYPE_TITLE, CLAPPER_MARKER_TYPE_CHAPTER, CLAPPER_MARKER_TYPE_TRACK, CLAPPER_MARKER_TYPE_CUSTOM_1 = 101, CLAPPER_MARKER_TYPE_CUSTOM_2 = 102, CLAPPER_MARKER_TYPE_CUSTOM_3 = 103, } ClapperMarkerType; /** * ClapperStreamType: * @CLAPPER_STREAM_TYPE_UNKNOWN: Unknown stream type. * @CLAPPER_STREAM_TYPE_VIDEO: Stream is a #ClapperVideoStream. * @CLAPPER_STREAM_TYPE_AUDIO: Stream is a #ClapperAudioStream. * @CLAPPER_STREAM_TYPE_SUBTITLE: Stream is a #ClapperSubtitleStream. */ typedef enum { CLAPPER_STREAM_TYPE_UNKNOWN = 0, CLAPPER_STREAM_TYPE_VIDEO, CLAPPER_STREAM_TYPE_AUDIO, CLAPPER_STREAM_TYPE_SUBTITLE, } ClapperStreamType; /** * ClapperDiscovererDiscoveryMode: * @CLAPPER_DISCOVERER_DISCOVERY_ALWAYS: Run discovery for every single media item added to [class@Clapper.Queue]. * This mode is useful when application presents a list of items to select from to the user before playback. * It will scan every single item in queue, so user can have an updated list of items when selecting what to play. * @CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT: Only run discovery on an item if it is not a currently selected item in [class@Clapper.Queue]. * This mode is optimal when application always plays (or at least goes into paused) after selecting item from queue. * It will skip discovery of such items since they will be discovered by [class@Clapper.Player] anyway. */ typedef enum { CLAPPER_DISCOVERER_DISCOVERY_ALWAYS = 0, CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT, } ClapperDiscovererDiscoveryMode; G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-extractable-private.h000066400000000000000000000022021474353110700236740ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-extractable.h" #include "clapper-harvest.h" G_BEGIN_DECLS G_GNUC_INTERNAL gboolean clapper_extractable_extract (ClapperExtractable *extractable, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-extractable.c000066400000000000000000000036441474353110700222320ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperExtractable: * * An interface for creating enhancers that resolve given URI into something playable. * * Since: 0.8 */ #include #include "clapper-extractable-private.h" #include "clapper-harvest-private.h" G_DEFINE_INTERFACE (ClapperExtractable, clapper_extractable, G_TYPE_OBJECT); static gboolean clapper_extractable_default_extract (ClapperExtractable *self, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error) { if (*error == NULL) { g_set_error (error, GST_CORE_ERROR, GST_CORE_ERROR_NOT_IMPLEMENTED, "Extractable object did not implement extract function"); } return FALSE; } static void clapper_extractable_default_init (ClapperExtractableInterface *iface) { iface->extract = clapper_extractable_default_extract; } gboolean clapper_extractable_extract (ClapperExtractable *self, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error) { ClapperExtractableInterface *iface = CLAPPER_EXTRACTABLE_GET_IFACE (self); return iface->extract (self, uri, harvest, cancellable, error); } clapper-0.8.0/src/lib/clapper/clapper-extractable.h000066400000000000000000000040441474353110700222320ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_EXTRACTABLE (clapper_extractable_get_type()) #define CLAPPER_EXTRACTABLE_CAST(obj) ((ClapperExtractable *)(obj)) CLAPPER_API G_DECLARE_INTERFACE (ClapperExtractable, clapper_extractable, CLAPPER, EXTRACTABLE, GObject) /** * ClapperExtractableInterface: * @parent_iface: The parent interface structure. * @extract: Extract data and fill harvest. */ struct _ClapperExtractableInterface { GTypeInterface parent_iface; /** * ClapperExtractableInterface::extract: * @extractable: a #ClapperExtractable * @uri: a #GUri * @cancellable: a #GCancellable object * @error: a #GError * * Extract data and fill harvest. * * Returns: whether extraction was successful. * * Since: 0.8 */ gboolean (* extract) (ClapperExtractable *extractable, GUri *uri, ClapperHarvest *harvest, GCancellable *cancellable, GError **error); /*< private >*/ gpointer padding[8]; }; G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-feature-private.h000066400000000000000000000047501474353110700230430ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_feature_call_prepare (ClapperFeature *feature); G_GNUC_INTERNAL void clapper_feature_call_unprepare (ClapperFeature *feature); G_GNUC_INTERNAL void clapper_feature_call_property_changed (ClapperFeature *feature, GParamSpec *pspec); G_GNUC_INTERNAL void clapper_feature_call_state_changed (ClapperFeature *feature, ClapperPlayerState state); G_GNUC_INTERNAL void clapper_feature_call_position_changed (ClapperFeature *feature, gdouble position); G_GNUC_INTERNAL void clapper_feature_call_speed_changed (ClapperFeature *feature, gdouble speed); G_GNUC_INTERNAL void clapper_feature_call_volume_changed (ClapperFeature *feature, gdouble volume); G_GNUC_INTERNAL void clapper_feature_call_mute_changed (ClapperFeature *feature, gboolean mute); G_GNUC_INTERNAL void clapper_feature_call_played_item_changed (ClapperFeature *feature, ClapperMediaItem *item); G_GNUC_INTERNAL void clapper_feature_call_item_updated (ClapperFeature *feature, ClapperMediaItem *item); G_GNUC_INTERNAL void clapper_feature_call_queue_item_added (ClapperFeature *feature, ClapperMediaItem *item, guint index); G_GNUC_INTERNAL void clapper_feature_call_queue_item_removed (ClapperFeature *feature, ClapperMediaItem *item, guint index); G_GNUC_INTERNAL void clapper_feature_call_queue_item_repositioned (ClapperFeature *self, guint before, guint after); G_GNUC_INTERNAL void clapper_feature_call_queue_cleared (ClapperFeature *feature); G_GNUC_INTERNAL void clapper_feature_call_queue_progression_changed (ClapperFeature *feature, ClapperQueueProgressionMode mode); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-feature.c000066400000000000000000000153631474353110700213700ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperFeature: * * A base class for creating new features for the player. * * Feature objects are meant for adding additional functionalities that * are supposed to either act on playback/properties changes and/or change * them themselves due to some external signal/event. * * For reacting to playback changes subclass should override this class * virtual functions logic, while for controlling playback implementation * may call [method@Gst.Object.get_parent] to acquire a weak reference on * a parent [class@Clapper.Player] object feature was added to. */ #include "clapper-feature.h" #include "clapper-feature-private.h" #include "clapper-player-private.h" #define GST_CAT_DEFAULT clapper_feature_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); typedef struct _ClapperFeaturePrivate ClapperFeaturePrivate; struct _ClapperFeaturePrivate { gboolean prepared; }; #define parent_class clapper_feature_parent_class G_DEFINE_TYPE_WITH_PRIVATE (ClapperFeature, clapper_feature, GST_TYPE_OBJECT); #define CALL_WITH_ARGS(_feature,_vfunc,...) \ ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (_feature); \ if (priv->prepared) { \ ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (_feature); \ if (feature_class->_vfunc) \ feature_class->_vfunc (_feature, __VA_ARGS__); } #define CALL_WITHOUT_ARGS(_feature,_vfunc) \ ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (_feature); \ if (priv->prepared) { \ ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (_feature); \ if (feature_class->_vfunc) \ feature_class->_vfunc (_feature); } void clapper_feature_call_prepare (ClapperFeature *self) { ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (self); if (!priv->prepared) { ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (self); gboolean prepared = TRUE; // mark subclass without prepare method as prepared if (feature_class->prepare) prepared = feature_class->prepare (self); priv->prepared = prepared; } } void clapper_feature_call_unprepare (ClapperFeature *self) { ClapperFeaturePrivate *priv = clapper_feature_get_instance_private (self); if (priv->prepared) { ClapperFeatureClass *feature_class = CLAPPER_FEATURE_GET_CLASS (self); gboolean unprepared = TRUE; // mark subclass without unprepare method as unprepared if (feature_class->unprepare) unprepared = feature_class->unprepare (self); priv->prepared = !unprepared; } } void clapper_feature_call_property_changed (ClapperFeature *self, GParamSpec *pspec) { CALL_WITH_ARGS (self, property_changed, pspec); } void clapper_feature_call_state_changed (ClapperFeature *self, ClapperPlayerState state) { CALL_WITH_ARGS (self, state_changed, state); } void clapper_feature_call_position_changed (ClapperFeature *self, gdouble position) { CALL_WITH_ARGS (self, position_changed, position); } void clapper_feature_call_speed_changed (ClapperFeature *self, gdouble speed) { CALL_WITH_ARGS (self, speed_changed, speed); } void clapper_feature_call_volume_changed (ClapperFeature *self, gdouble volume) { CALL_WITH_ARGS (self, volume_changed, volume); } void clapper_feature_call_mute_changed (ClapperFeature *self, gboolean mute) { CALL_WITH_ARGS (self, mute_changed, mute); } void clapper_feature_call_played_item_changed (ClapperFeature *self, ClapperMediaItem *item) { CALL_WITH_ARGS (self, played_item_changed, item); } void clapper_feature_call_item_updated (ClapperFeature *self, ClapperMediaItem *item) { CALL_WITH_ARGS (self, item_updated, item); } void clapper_feature_call_queue_item_added (ClapperFeature *self, ClapperMediaItem *item, guint index) { CALL_WITH_ARGS (self, queue_item_added, item, index); } void clapper_feature_call_queue_item_removed (ClapperFeature *self, ClapperMediaItem *item, guint index) { CALL_WITH_ARGS (self, queue_item_removed, item, index); } void clapper_feature_call_queue_item_repositioned (ClapperFeature *self, guint before, guint after) { CALL_WITH_ARGS (self, queue_item_repositioned, before, after); } void clapper_feature_call_queue_cleared (ClapperFeature *self) { CALL_WITHOUT_ARGS (self, queue_cleared); } void clapper_feature_call_queue_progression_changed (ClapperFeature *self, ClapperQueueProgressionMode mode) { CALL_WITH_ARGS (self, queue_progression_changed, mode); } static void clapper_feature_init (ClapperFeature *self) { } static void clapper_feature_dispatch_properties_changed (GObject *object, guint n_pspecs, GParamSpec **pspecs) { ClapperPlayer *player; if ((player = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (object))))) { ClapperFeaturesManager *features_manager; if ((features_manager = clapper_player_get_features_manager (player))) { guint i; for (i = 0; i < n_pspecs; ++i) { clapper_features_manager_trigger_property_changed (features_manager, CLAPPER_FEATURE_CAST (object), pspecs[i]); } } gst_object_unref (player); } G_OBJECT_CLASS (parent_class)->dispatch_properties_changed (object, n_pspecs, pspecs); } static void clapper_feature_finalize (GObject *object) { ClapperFeature *self = CLAPPER_FEATURE_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_feature_class_init (ClapperFeatureClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperfeature", 0, "Clapper Feature"); gobject_class->dispatch_properties_changed = clapper_feature_dispatch_properties_changed; gobject_class->finalize = clapper_feature_finalize; } clapper-0.8.0/src/lib/clapper/clapper-feature.h000066400000000000000000000161661474353110700213770ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_FEATURE (clapper_feature_get_type()) #define CLAPPER_FEATURE_CAST(obj) ((ClapperFeature *)(obj)) CLAPPER_API G_DECLARE_DERIVABLE_TYPE (ClapperFeature, clapper_feature, CLAPPER, FEATURE, GstObject) /** * ClapperFeatureClass: * @parent_class: The object class structure. * @prepare: Prepare feature for operation (optional). * @unprepare: Revert the changes done in @prepare (optional). * @property_changed: A property of @feature changed its value. * @state_changed: Player state was changed. * @position_changed: Player position was changed. * @speed_changed: Player speed was changed. * @volume_changed: Player volume was changed. * @mute_changed: Player mute state was changed. * @played_item_changed: Currently playing media item got changed. * @item_updated: An item in queue got updated. * @queue_item_added: An item was added to the queue. * @queue_item_removed: An item was removed from queue. * @queue_item_reposition: An item changed position within queue. * @queue_cleared: All items were removed from queue. * @queue_progression_changed: Progression mode of the queue was changed. */ struct _ClapperFeatureClass { GstObjectClass parent_class; /** * ClapperFeatureClass::prepare: * @feature: a #ClapperFeature * * Prepare feature for operation (optional). * * This is different from init() as its called from features thread once * feature is added to the player, so it can already access it parent using * gst_object_get_parent(). If it fails, no other method will be called. * * Returns: %TRUE on success, %FALSE otherwise. */ gboolean (* prepare) (ClapperFeature *feature); /** * ClapperFeatureClass::unprepare: * @feature: a #ClapperFeature * * Revert the changes done in @prepare (optional). * * Returns: %TRUE on success, %FALSE otherwise. */ gboolean (* unprepare) (ClapperFeature *feature); /** * ClapperFeatureClass::property_changed: * @feature: a #ClapperFeature * @pspec: a #GParamSpec * * A property of @feature changed its value. * * Useful for reconfiguring @feature, since unlike "notify" signal * this is always called from the thread that feature works on and * only after feature was prepared. */ void (* property_changed) (ClapperFeature *feature, GParamSpec *pspec); /** * ClapperFeatureClass::state_changed: * @feature: a #ClapperFeature * @state: a #ClapperPlayerState * * Player state was changed. */ void (* state_changed) (ClapperFeature *feature, ClapperPlayerState state); /** * ClapperFeatureClass::position_changed: * @feature: a #ClapperFeature * @position: a decimal number with current position in seconds * * Player position was changed. */ void (* position_changed) (ClapperFeature *feature, gdouble position); /** * ClapperFeatureClass::speed_changed: * @feature: a #ClapperFeature * @speed: the playback speed multiplier * * Player speed was changed. */ void (* speed_changed) (ClapperFeature *feature, gdouble speed); /** * ClapperFeatureClass::volume_changed: * @feature: a #ClapperFeature * @volume: the volume level * * Player volume was changed. */ void (* volume_changed) (ClapperFeature *feature, gdouble volume); /** * ClapperFeatureClass::mute_changed: * @feature: a #ClapperFeature * @mute: %TRUE if player is muted, %FALSE otherwise * * Player mute state was changed. */ void (* mute_changed) (ClapperFeature *feature, gboolean mute); /** * ClapperFeatureClass::played_item_changed: * @feature: a #ClapperFeature * @item: a #ClapperMediaItem that is now playing * * New media item started playing. All following events (such as position changes) * will be related to this @item from now on. */ void (* played_item_changed) (ClapperFeature *feature, ClapperMediaItem *item); /** * ClapperFeatureClass::item_updated: * @feature: a #ClapperFeature * @item: a #ClapperMediaItem that was updated * * An item in queue got updated. This might be (or not) currently * played item. Implementations can get parent player object * if they want to check that from its queue. */ void (* item_updated) (ClapperFeature *feature, ClapperMediaItem *item); /** * ClapperFeatureClass::queue_item_added: * @feature: a #ClapperFeature * @item: a #ClapperMediaItem that was added * @index: position at which @item was placed in queue * * An item was added to the queue. */ void (* queue_item_added) (ClapperFeature *feature, ClapperMediaItem *item, guint index); /** * ClapperFeatureClass::queue_item_removed: * @feature: a #ClapperFeature * @item: a #ClapperMediaItem that was removed * @index: position from which @item was removed in queue * * An item was removed from queue. */ void (* queue_item_removed) (ClapperFeature *feature, ClapperMediaItem *item, guint index); /** * ClapperFeatureClass::queue_item_repositioned: * @feature: a #ClapperFeature * @before: position from which #ClapperMediaItem was removed * @after: position at which #ClapperMediaItem was inserted after removal * * An item changed position within queue. */ void (* queue_item_repositioned) (ClapperFeature *feature, guint before, guint after); /** * ClapperFeatureClass::queue_cleared: * @feature: a #ClapperFeature * * All items were removed from queue. Note that in such event * @queue_item_removed will NOT be called for each item for performance reasons. * You probably want to implement this function if you also implemented item removal. */ void (* queue_cleared) (ClapperFeature *feature); /** * ClapperFeatureClass::queue_progression_changed: * @feature: a #ClapperFeature * @mode: a #ClapperQueueProgressionMode * * Progression mode of the queue was changed. */ void (* queue_progression_changed) (ClapperFeature *feature, ClapperQueueProgressionMode mode); /*< private >*/ gpointer padding[8]; }; G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-features-bus-private.h000066400000000000000000000030201474353110700240020ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-features-manager-private.h" #include "clapper-enums-private.h" G_BEGIN_DECLS #define CLAPPER_TYPE_FEATURES_BUS (clapper_features_bus_get_type()) #define CLAPPER_FEATURES_BUS_CAST(obj) ((ClapperFeaturesBus *)(obj)) /** * ClapperFeaturesBus: */ G_DECLARE_FINAL_TYPE (ClapperFeaturesBus, clapper_features_bus, CLAPPER, FEATURES_BUS, GstBus) void clapper_features_bus_initialize (void); ClapperFeaturesBus * clapper_features_bus_new (void); void clapper_features_bus_post_event (ClapperFeaturesBus *features_bus, ClapperFeaturesManager *src, ClapperFeaturesManagerEvent event, GValue *value, GValue *extra_value); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-features-bus.c000066400000000000000000000117611474353110700223400ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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-bus-private.h" #include "clapper-features-manager-private.h" #include "clapper-features-bus-private.h" #define GST_CAT_DEFAULT clapper_features_bus_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperFeaturesBus { GstBus parent; }; #define parent_class clapper_features_bus_parent_class G_DEFINE_TYPE (ClapperFeaturesBus, clapper_features_bus, GST_TYPE_BUS); enum { CLAPPER_FEATURES_BUS_STRUCTURE_UNKNOWN = 0, CLAPPER_FEATURES_BUS_STRUCTURE_EVENT }; static ClapperBusQuark _structure_quarks[] = { {"unknown", 0}, {"event", 0}, {NULL, 0} }; enum { CLAPPER_FEATURES_BUS_FIELD_UNKNOWN = 0, CLAPPER_FEATURES_BUS_FIELD_EVENT, CLAPPER_FEATURES_BUS_FIELD_VALUE, CLAPPER_FEATURES_BUS_FIELD_EXTRA_VALUE }; static ClapperBusQuark _field_quarks[] = { {"unknown", 0}, {"event", 0}, {"value", 0}, {"extra-value", 0}, {NULL, 0} }; #define _STRUCTURE_QUARK(q) (_structure_quarks[CLAPPER_FEATURES_BUS_STRUCTURE_##q].quark) #define _FIELD_QUARK(q) (_field_quarks[CLAPPER_FEATURES_BUS_FIELD_##q].quark) #define _MESSAGE_SRC_CLAPPER_FEATURES_MANAGER(msg) ((ClapperFeaturesManager *) GST_MESSAGE_SRC (msg)) void clapper_features_bus_initialize (void) { gint i; for (i = 0; _structure_quarks[i].name; ++i) _structure_quarks[i].quark = g_quark_from_static_string (_structure_quarks[i].name); for (i = 0; _field_quarks[i].name; ++i) _field_quarks[i].quark = g_quark_from_static_string (_field_quarks[i].name); } void clapper_features_bus_post_event (ClapperFeaturesBus *self, ClapperFeaturesManager *src, ClapperFeaturesManagerEvent event, GValue *value, GValue *extra_value) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (EVENT), _FIELD_QUARK (EVENT), G_TYPE_ENUM, event, NULL); if (value) gst_structure_id_take_value (structure, _FIELD_QUARK (VALUE), value); if (extra_value) gst_structure_id_take_value (structure, _FIELD_QUARK (EXTRA_VALUE), extra_value); gst_bus_post (GST_BUS_CAST (self), gst_message_new_application ( GST_OBJECT_CAST (src), structure)); } static inline void _handle_event_msg (GstMessage *msg, const GstStructure *structure, ClapperFeaturesManager *features_manager) { ClapperFeaturesManagerEvent event = CLAPPER_FEATURES_MANAGER_EVENT_UNKNOWN; const GValue *value = gst_structure_id_get_value (structure, _FIELD_QUARK (VALUE)); const GValue *extra_value = gst_structure_id_get_value (structure, _FIELD_QUARK (EXTRA_VALUE)); gst_structure_id_get (structure, _FIELD_QUARK (EVENT), G_TYPE_ENUM, &event, NULL); clapper_features_manager_handle_event (features_manager, event, value, extra_value); } static gboolean clapper_features_bus_message_func (GstBus *bus, GstMessage *msg, gpointer user_data G_GNUC_UNUSED) { if (G_LIKELY (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_APPLICATION)) { ClapperFeaturesManager *features_manager = _MESSAGE_SRC_CLAPPER_FEATURES_MANAGER (msg); const GstStructure *structure = gst_message_get_structure (msg); GQuark quark = gst_structure_get_name_id (structure); if (quark == _STRUCTURE_QUARK (EVENT)) _handle_event_msg (msg, structure, features_manager); } return G_SOURCE_CONTINUE; } /* * clapper_features_bus_new: * * Returns: (transfer full): a new #ClapperFeaturesBus instance. */ ClapperFeaturesBus * clapper_features_bus_new (void) { GstBus *features_bus; features_bus = GST_BUS_CAST (g_object_new (CLAPPER_TYPE_FEATURES_BUS, NULL)); gst_object_ref_sink (features_bus); gst_bus_add_watch (features_bus, (GstBusFunc) clapper_features_bus_message_func, NULL); return CLAPPER_FEATURES_BUS_CAST (features_bus); } static void clapper_features_bus_init (ClapperFeaturesBus *self) { } static void clapper_features_bus_finalize (GObject *object) { ClapperFeaturesBus *self = CLAPPER_FEATURES_BUS_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_features_bus_class_init (ClapperFeaturesBusClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperfeaturesbus", 0, "Clapper Features Bus"); gobject_class->finalize = clapper_features_bus_finalize; } clapper-0.8.0/src/lib/clapper/clapper-features-manager-private.h000066400000000000000000000064621474353110700246400ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 "clapper-enums-private.h" #include "clapper-threaded-object.h" #include "clapper-feature.h" G_BEGIN_DECLS #define CLAPPER_TYPE_FEATURES_MANAGER (clapper_features_manager_get_type()) #define CLAPPER_FEATURES_MANAGER_CAST(obj) ((ClapperFeaturesManager *)(obj)) G_DECLARE_FINAL_TYPE (ClapperFeaturesManager, clapper_features_manager, CLAPPER, FEATURES_MANAGER, ClapperThreadedObject) G_GNUC_INTERNAL ClapperFeaturesManager * clapper_features_manager_new (void); G_GNUC_INTERNAL void clapper_features_manager_add_feature (ClapperFeaturesManager *features, ClapperFeature *feature, GstObject *parent); G_GNUC_INTERNAL void clapper_features_manager_trigger_property_changed (ClapperFeaturesManager *self, ClapperFeature *feature, GParamSpec *pspec); G_GNUC_INTERNAL void clapper_features_manager_trigger_state_changed (ClapperFeaturesManager *features, ClapperPlayerState state); G_GNUC_INTERNAL void clapper_features_manager_trigger_position_changed (ClapperFeaturesManager *features, gdouble position); G_GNUC_INTERNAL void clapper_features_manager_trigger_speed_changed (ClapperFeaturesManager *features, gdouble speed); G_GNUC_INTERNAL void clapper_features_manager_trigger_volume_changed (ClapperFeaturesManager *features, gdouble volume); G_GNUC_INTERNAL void clapper_features_manager_trigger_mute_changed (ClapperFeaturesManager *features, gboolean mute); G_GNUC_INTERNAL void clapper_features_manager_trigger_played_item_changed (ClapperFeaturesManager *features, ClapperMediaItem *item); G_GNUC_INTERNAL void clapper_features_manager_trigger_item_updated (ClapperFeaturesManager *features, ClapperMediaItem *item); G_GNUC_INTERNAL void clapper_features_manager_trigger_queue_item_added (ClapperFeaturesManager *features, ClapperMediaItem *item, guint index); G_GNUC_INTERNAL void clapper_features_manager_trigger_queue_item_removed (ClapperFeaturesManager *features, ClapperMediaItem *item, guint index); G_GNUC_INTERNAL void clapper_features_manager_trigger_queue_item_repositioned (ClapperFeaturesManager *features, guint before, guint after); G_GNUC_INTERNAL void clapper_features_manager_trigger_queue_cleared (ClapperFeaturesManager *features); G_GNUC_INTERNAL void clapper_features_manager_trigger_queue_progression_changed (ClapperFeaturesManager *features, ClapperQueueProgressionMode mode); G_GNUC_INTERNAL void clapper_features_manager_handle_event (ClapperFeaturesManager *features, ClapperFeaturesManagerEvent event, const GValue *value, const GValue *extra_value); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-features-manager.c000066400000000000000000000277021474353110700231630ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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-features-manager-private.h" #include "clapper-features-bus-private.h" #include "clapper-feature-private.h" #define GST_CAT_DEFAULT clapper_features_manager_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperFeaturesManager { ClapperThreadedObject parent; GPtrArray *features; ClapperFeaturesBus *bus; }; #define parent_class clapper_features_manager_parent_class G_DEFINE_TYPE (ClapperFeaturesManager, clapper_features_manager, CLAPPER_TYPE_THREADED_OBJECT); static inline void _post_object (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, GObject *data) { GValue value = G_VALUE_INIT; g_value_init (&value, G_TYPE_OBJECT); g_value_set_object (&value, data); clapper_features_bus_post_event (self->bus, self, event, &value, NULL); } static inline void _post_int (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, gint data) { GValue value = G_VALUE_INIT; g_value_init (&value, G_TYPE_INT); g_value_set_int (&value, data); clapper_features_bus_post_event (self->bus, self, event, &value, NULL); } static inline void _post_double (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, gdouble data) { GValue value = G_VALUE_INIT; g_value_init (&value, G_TYPE_DOUBLE); g_value_set_double (&value, data); clapper_features_bus_post_event (self->bus, self, event, &value, NULL); } static inline void _post_boolean (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, gboolean data) { GValue value = G_VALUE_INIT; g_value_init (&value, G_TYPE_BOOLEAN); g_value_set_boolean (&value, data); clapper_features_bus_post_event (self->bus, self, event, &value, NULL); } static inline void _post_item_added_or_removed (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, ClapperMediaItem *item, guint index) { GValue value = G_VALUE_INIT; GValue extra_value = G_VALUE_INIT; g_value_init (&value, G_TYPE_OBJECT); g_value_set_object (&value, (GObject *) item); g_value_init (&extra_value, G_TYPE_UINT); g_value_set_uint (&extra_value, index); clapper_features_bus_post_event (self->bus, self, event, &value, &extra_value); } static inline void _post_item_reposition (ClapperFeaturesManager *self, guint data_1, guint data_2) { GValue value = G_VALUE_INIT; GValue extra_value = G_VALUE_INIT; g_value_init (&value, G_TYPE_UINT); g_value_set_uint (&value, data_1); g_value_init (&extra_value, G_TYPE_UINT); g_value_set_uint (&extra_value, data_2); clapper_features_bus_post_event (self->bus, self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED, &value, &extra_value); } /* * clapper_features_manager_new: * * Returns: (transfer full): a new #ClapperFeaturesManager instance. */ ClapperFeaturesManager * clapper_features_manager_new (void) { ClapperFeaturesManager *features_manager; features_manager = g_object_new (CLAPPER_TYPE_FEATURES_MANAGER, NULL); gst_object_ref_sink (features_manager); return features_manager; } void clapper_features_manager_add_feature (ClapperFeaturesManager *self, ClapperFeature *feature, GstObject *parent) { GValue value = G_VALUE_INIT; GValue extra_value = G_VALUE_INIT; g_value_init (&value, G_TYPE_OBJECT); g_value_set_object (&value, G_OBJECT (feature)); g_value_init (&extra_value, G_TYPE_OBJECT); g_value_set_object (&extra_value, G_OBJECT (parent)); clapper_features_bus_post_event (self->bus, self, CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_ADDED, &value, &extra_value); } void clapper_features_manager_trigger_property_changed (ClapperFeaturesManager *self, ClapperFeature *feature, GParamSpec *pspec) { GValue value = G_VALUE_INIT; GValue extra_value = G_VALUE_INIT; g_value_init (&value, G_TYPE_OBJECT); g_value_set_object (&value, G_OBJECT (feature)); g_value_init (&extra_value, G_TYPE_PARAM); g_value_set_param (&extra_value, pspec); clapper_features_bus_post_event (self->bus, self, CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_PROPERTY_CHANGED, &value, &extra_value); } void clapper_features_manager_trigger_state_changed (ClapperFeaturesManager *self, ClapperPlayerState state) { _post_int (self, CLAPPER_FEATURES_MANAGER_EVENT_STATE_CHANGED, state); } void clapper_features_manager_trigger_position_changed (ClapperFeaturesManager *self, gdouble position) { _post_double (self, CLAPPER_FEATURES_MANAGER_EVENT_POSITION_CHANGED, position); } void clapper_features_manager_trigger_speed_changed (ClapperFeaturesManager *self, gdouble speed) { _post_double (self, CLAPPER_FEATURES_MANAGER_EVENT_SPEED_CHANGED, speed); } void clapper_features_manager_trigger_volume_changed (ClapperFeaturesManager *self, gdouble volume) { _post_double (self, CLAPPER_FEATURES_MANAGER_EVENT_VOLUME_CHANGED, volume); } void clapper_features_manager_trigger_mute_changed (ClapperFeaturesManager *self, gboolean mute) { _post_boolean (self, CLAPPER_FEATURES_MANAGER_EVENT_MUTE_CHANGED, mute); } void clapper_features_manager_trigger_played_item_changed (ClapperFeaturesManager *self, ClapperMediaItem *item) { _post_object (self, CLAPPER_FEATURES_MANAGER_EVENT_PLAYED_ITEM_CHANGED, (GObject *) item); } void clapper_features_manager_trigger_item_updated (ClapperFeaturesManager *self, ClapperMediaItem *item) { _post_object (self, CLAPPER_FEATURES_MANAGER_EVENT_ITEM_UPDATED, (GObject *) item); } void clapper_features_manager_trigger_queue_item_added (ClapperFeaturesManager *self, ClapperMediaItem *item, guint index) { _post_item_added_or_removed (self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_ADDED, item, index); } void clapper_features_manager_trigger_queue_item_removed (ClapperFeaturesManager *self, ClapperMediaItem *item, guint index) { _post_item_added_or_removed (self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REMOVED, item, index); } void clapper_features_manager_trigger_queue_item_repositioned (ClapperFeaturesManager *self, guint before, guint after) { _post_item_reposition (self, before, after); } void clapper_features_manager_trigger_queue_cleared (ClapperFeaturesManager *self) { clapper_features_bus_post_event (self->bus, self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_CLEARED, NULL, NULL); } void clapper_features_manager_trigger_queue_progression_changed (ClapperFeaturesManager *self, ClapperQueueProgressionMode mode) { _post_int (self, CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED, mode); } void clapper_features_manager_handle_event (ClapperFeaturesManager *self, ClapperFeaturesManagerEvent event, const GValue *value, const GValue *extra_value) { guint i; switch (event) { case CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_ADDED:{ ClapperFeature *feature = g_value_get_object (value); GstObject *parent = g_value_get_object (extra_value); if (!g_ptr_array_find (self->features, feature, NULL)) { g_ptr_array_add (self->features, gst_object_ref (feature)); gst_object_set_parent (GST_OBJECT_CAST (feature), parent); clapper_feature_call_prepare (feature); } /* Nothing more to do */ return; } default: break; } for (i = 0; i < self->features->len; ++i) { ClapperFeature *feature = g_ptr_array_index (self->features, i); switch (event) { case CLAPPER_FEATURES_MANAGER_EVENT_FEATURE_PROPERTY_CHANGED:{ ClapperFeature *event_feature = g_value_get_object (value); if (feature == event_feature) { clapper_feature_call_property_changed (feature, g_value_get_param (extra_value)); } break; } case CLAPPER_FEATURES_MANAGER_EVENT_STATE_CHANGED: clapper_feature_call_state_changed (feature, g_value_get_int (value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_POSITION_CHANGED: clapper_feature_call_position_changed (feature, g_value_get_double (value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_SPEED_CHANGED: clapper_feature_call_speed_changed (feature, g_value_get_double (value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_VOLUME_CHANGED: clapper_feature_call_volume_changed (feature, g_value_get_double (value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_MUTE_CHANGED: clapper_feature_call_mute_changed (feature, g_value_get_boolean (value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_PLAYED_ITEM_CHANGED: clapper_feature_call_played_item_changed (feature, CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value))); break; case CLAPPER_FEATURES_MANAGER_EVENT_ITEM_UPDATED: clapper_feature_call_item_updated (feature, CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value))); break; case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_ADDED: clapper_feature_call_queue_item_added (feature, CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)), g_value_get_uint (extra_value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REMOVED: clapper_feature_call_queue_item_removed (feature, CLAPPER_MEDIA_ITEM_CAST (g_value_get_object (value)), g_value_get_uint (extra_value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_ITEM_REPOSITIONED: clapper_feature_call_queue_item_repositioned (feature, g_value_get_uint (value), g_value_get_uint (extra_value)); break; case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_CLEARED: clapper_feature_call_queue_cleared (feature); break; case CLAPPER_FEATURES_MANAGER_EVENT_QUEUE_PROGRESSION_CHANGED: clapper_feature_call_queue_progression_changed (feature, g_value_get_int (value)); break; default: break; } } } static void clapper_features_manager_thread_start (ClapperThreadedObject *threaded_object) { ClapperFeaturesManager *self = CLAPPER_FEATURES_MANAGER_CAST (threaded_object); GST_TRACE_OBJECT (threaded_object, "Features manager thread start"); self->features = g_ptr_array_new_with_free_func ( (GDestroyNotify) gst_object_unref); self->bus = clapper_features_bus_new (); } static void clapper_features_manager_thread_stop (ClapperThreadedObject *threaded_object) { ClapperFeaturesManager *self = CLAPPER_FEATURES_MANAGER_CAST (threaded_object); guint i; GST_TRACE_OBJECT (threaded_object, "Features manager thread stop"); gst_bus_set_flushing (GST_BUS_CAST (self->bus), TRUE); gst_bus_remove_watch (GST_BUS_CAST (self->bus)); gst_clear_object (&self->bus); for (i = 0; i < self->features->len; ++i) { ClapperFeature *feature = g_ptr_array_index (self->features, i); clapper_feature_call_unprepare (feature); gst_object_unparent (GST_OBJECT_CAST (feature)); } g_ptr_array_unref (self->features); } static void clapper_features_manager_init (ClapperFeaturesManager *self) { } static void clapper_features_manager_class_init (ClapperFeaturesManagerClass *klass) { ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperfeaturesmanager", 0, "Clapper Features Manager"); threaded_object->thread_start = clapper_features_manager_thread_start; threaded_object->thread_stop = clapper_features_manager_thread_stop; } clapper-0.8.0/src/lib/clapper/clapper-functionalities-availability.h.in000066400000000000000000000024471474353110700262160ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif /** * CLAPPER_WITH_ENHANCERS_LOADER: * * Check if Clapper was compiled with Enhancers Loader functionality. * * Alternatively, apps before compiling can also check whether `pkgconfig` * variable named `functionalities` contains `enhancers-loader` string. * * Since: 0.8 */ #define CLAPPER_WITH_ENHANCERS_LOADER (@CLAPPER_WITH_ENHANCERS_LOADER@) clapper-0.8.0/src/lib/clapper/clapper-harvest-private.h000066400000000000000000000022651474353110700230630ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-harvest.h" G_BEGIN_DECLS G_GNUC_INTERNAL 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_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-harvest.c000066400000000000000000000267211474353110700214110ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperHarvest: * * An object storing data from enhancers that implement [iface@Clapper.Extractable] interface. * * Since: 0.8 */ /* * NOTE: We cannot simply expose GstMiniObjects for * implementations to assemble TagList/Toc themselves, see: * https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/2867 */ #include "clapper-harvest-private.h" #define GST_CAT_DEFAULT clapper_harvest_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperHarvest { GstObject parent; GstCaps *caps; GstBuffer *buffer; gsize buf_size; GstTagList *tags; GstToc *toc; GstStructure *headers; guint16 n_chapters; guint16 n_tracks; }; #define parent_class clapper_harvest_parent_class G_DEFINE_TYPE (ClapperHarvest, clapper_harvest, GST_TYPE_OBJECT); static inline void _ensure_tags (ClapperHarvest *self) { if (!self->tags) { self->tags = gst_tag_list_new_empty (); gst_tag_list_set_scope (self->tags, GST_TAG_SCOPE_GLOBAL); } } static inline void _ensure_toc (ClapperHarvest *self) { if (!self->toc) self->toc = gst_toc_new (GST_TOC_SCOPE_GLOBAL); } static inline void _ensure_headers (ClapperHarvest *self) { if (!self->headers) self->headers = gst_structure_new_empty ("request-headers"); } ClapperHarvest * clapper_harvest_new (void) { ClapperHarvest *harvest; harvest = g_object_new (CLAPPER_TYPE_HARVEST, NULL); gst_object_ref_sink (harvest); return harvest; } gboolean clapper_harvest_unpack (ClapperHarvest *self, GstBuffer **buffer, gsize *buf_size, GstCaps **caps, GstTagList **tags, GstToc **toc, GstStructure **headers) { /* Not filled or already unpacked */ if (!self->buffer) return FALSE; *buffer = self->buffer; self->buffer = NULL; *buf_size = self->buf_size; self->buf_size = 0; *caps = self->caps; self->caps = NULL; *tags = self->tags; self->tags = NULL; *toc = self->toc; self->toc = NULL; *headers = self->headers; self->headers = NULL; return TRUE; } /** * clapper_harvest_fill: * @harvest: a #ClapperHarvest * @media_type: media mime type * @data: (array length=size) (element-type guint8) (transfer full): data to fill @harvest * @size: allocated size of @data * * Fill harvest with extracted data. It can be anything that GStreamer * can parse and play such as single URI or a streaming manifest. * * Calling again this function will replace previously filled content. * * Commonly used media types are: * * * `application/dash+xml` * * * `application/x-hls` * * * `text/uri-list` * * Returns: %TRUE when filled successfully, %FALSE if taken data was empty. * * Since: 0.8 */ gboolean clapper_harvest_fill (ClapperHarvest *self, const gchar *media_type, gpointer data, gsize size) { g_return_val_if_fail (CLAPPER_IS_HARVEST (self), FALSE); g_return_val_if_fail (media_type != NULL, FALSE); g_return_val_if_fail (data != NULL, FALSE); if (!data || size == 0) { g_free (data); return FALSE; } if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) { gboolean is_printable = (strcmp (media_type, "application/dash+xml") == 0) || (strcmp (media_type, "application/x-hls") == 0) || (strcmp (media_type, "text/uri-list") == 0); if (is_printable) { gchar *data_str; data_str = g_new0 (gchar, size + 1); memcpy (data_str, data, size); GST_DEBUG_OBJECT (self, "Filled with data:\n%s", data_str); g_free (data_str); } } gst_clear_buffer (&self->buffer); gst_clear_caps (&self->caps); self->buffer = gst_buffer_new_wrapped (data, size); self->buf_size = size; self->caps = gst_caps_new_simple (media_type, "source", G_TYPE_STRING, "clapper-harvest", NULL); return TRUE; } /** * clapper_harvest_fill_with_text: * @harvest: a #ClapperHarvest * @media_type: media mime type * @text: (transfer full): data to fill @harvest as %NULL terminated string * * A convenience method to fill @harvest using a %NULL terminated string. * * For more info, see [method@Clapper.Harvest.fill] documentation. * * Returns: %TRUE when filled successfully, %FALSE if taken data was empty. * * Since: 0.8 */ gboolean clapper_harvest_fill_with_text (ClapperHarvest *self, const gchar *media_type, gchar *text) { g_return_val_if_fail (text != NULL, FALSE); return clapper_harvest_fill (self, media_type, text, strlen (text)); } /** * clapper_harvest_fill_with_bytes: * @harvest: a #ClapperHarvest * @media_type: media mime type * @bytes: (transfer full): a #GBytes to fill @harvest * * A convenience method to fill @harvest with data from #GBytes. * * For more info, see [method@Clapper.Harvest.fill] documentation. * * Returns: %TRUE when filled successfully, %FALSE if taken data was empty. * * Since: 0.8 */ gboolean clapper_harvest_fill_with_bytes (ClapperHarvest *self, const gchar *media_type, GBytes *bytes) { gpointer data; gsize size = 0; g_return_val_if_fail (bytes != NULL, FALSE); data = g_bytes_unref_to_data (bytes, &size); return clapper_harvest_fill (self, media_type, data, size); } /** * clapper_harvest_tags_add: * @harvest: a #ClapperHarvest * @tag: a name of tag to set * @...: %NULL-terminated list of arguments * * Append one or more tags into the tag list. * * Variable arguments should be in the form of tag and value pairs. * * Since: 0.8 */ void clapper_harvest_tags_add (ClapperHarvest *self, const gchar *tag, ...) { va_list args; g_return_if_fail (CLAPPER_IS_HARVEST (self)); g_return_if_fail (tag != NULL); _ensure_tags (self); va_start (args, tag); gst_tag_list_add_valist (self->tags, GST_TAG_MERGE_APPEND, tag, args); va_end (args); } /** * clapper_harvest_tags_add_value: (rename-to clapper_harvest_tags_add) * @harvest: a #ClapperHarvest * @tag: a name of tag to set * @value: a #GValue of tag * * Append another tag into the tag list using #GValue. * * Since: 0.8 */ void clapper_harvest_tags_add_value (ClapperHarvest *self, const gchar *tag, const GValue *value) { g_return_if_fail (CLAPPER_IS_HARVEST (self)); g_return_if_fail (tag != NULL); g_return_if_fail (G_IS_VALUE (value)); _ensure_tags (self); gst_tag_list_add_value (self->tags, GST_TAG_MERGE_APPEND, tag, value); } /** * clapper_harvest_toc_add: * @harvest: a #ClapperHarvest * @type: a #GstTocEntryType * @title: an entry title * @start: entry start time in seconds * @end: entry end time in seconds or -1 if none * * Append a chapter or track name into table of contents. * * Since: 0.8 */ void clapper_harvest_toc_add (ClapperHarvest *self, GstTocEntryType type, const gchar *title, gdouble start, gdouble end) { GstTocEntry *entry, *subentry; GstClockTime start_time, end_time; gchar edition[3]; // 2 + 1 gchar id[14]; // 7 + 1 + 5 + 1 const gchar *id_prefix; guint16 nth_entry; g_return_if_fail (CLAPPER_IS_HARVEST (self)); g_return_if_fail (type == GST_TOC_ENTRY_TYPE_CHAPTER || type == GST_TOC_ENTRY_TYPE_TRACK); g_return_if_fail (title != NULL); g_return_if_fail (start >= 0 && end >= start); switch (type) { case GST_TOC_ENTRY_TYPE_CHAPTER: id_prefix = "chapter"; nth_entry = ++(self->n_chapters); break; case GST_TOC_ENTRY_TYPE_TRACK: id_prefix = "track"; nth_entry = ++(self->n_tracks); break; default: g_assert_not_reached (); return; } start_time = start * GST_SECOND; end_time = (end >= 0) ? end * GST_SECOND : GST_CLOCK_TIME_NONE; 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\"" " (%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ")", id, title, start_time, end_time); subentry = gst_toc_entry_new (type, id); gst_toc_entry_set_tags (subentry, gst_tag_list_new (GST_TAG_TITLE, title, NULL)); gst_toc_entry_set_start_stop_times (subentry, start_time, end_time); _ensure_toc (self); find_entry: if (!(entry = gst_toc_find_entry (self->toc, edition))) { GstTocEntry *toc_entry; toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, edition); gst_toc_entry_set_start_stop_times (toc_entry, 0, GST_CLOCK_TIME_NONE); gst_toc_append_entry (self->toc, toc_entry); // transfer full and must be writable goto find_entry; } gst_toc_entry_append_sub_entry (entry, subentry); } /** * clapper_harvest_headers_set: * @harvest: a #ClapperHarvest * @key: a header name * @...: %NULL-terminated list of arguments * * Set one or more request headers named with @key to specified `value`. * * Arguments should be %NULL terminated list of `key+value` string pairs. * * Setting again the same key will update its value to the new one. * * Since: 0.8 */ void clapper_harvest_headers_set (ClapperHarvest *self, const gchar *key, ...) { va_list args; g_return_if_fail (CLAPPER_IS_HARVEST (self)); g_return_if_fail (key != NULL); _ensure_headers (self); va_start (args, key); while (key != NULL) { const gchar *val = va_arg (args, const gchar *); GST_DEBUG_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 *); } va_end (args); } /** * clapper_harvest_headers_set_value: (rename-to clapper_harvest_headers_set) * @harvest: a #ClapperHarvest * @key: a header name * @value: a string #GValue of header * * Set another header in the request headers list using #GValue. * * Setting again the same key will update its value to the new one. * * Since: 0.8 */ void clapper_harvest_headers_set_value (ClapperHarvest *self, const gchar *key, const GValue *value) { g_return_if_fail (CLAPPER_IS_HARVEST (self)); g_return_if_fail (key != NULL); g_return_if_fail (G_IS_VALUE (value) && G_VALUE_HOLDS_STRING (value)); _ensure_headers (self); GST_DEBUG_OBJECT (self, "Set header, \"%s\": \"%s\"", key, g_value_get_string (value)); gst_structure_set_value (self->headers, key, value); } static void clapper_harvest_init (ClapperHarvest *self) { } static void clapper_harvest_finalize (GObject *object) { ClapperHarvest *self = CLAPPER_HARVEST_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); gst_clear_caps (&self->caps); gst_clear_buffer (&self->buffer); if (self->tags) gst_tag_list_unref (self->tags); if (self->toc) gst_toc_unref (self->toc); if (self->headers) gst_structure_free (self->headers); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_harvest_class_init (ClapperHarvestClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperharvest", 0, "Clapper Harvest"); gobject_class->finalize = clapper_harvest_finalize; } clapper-0.8.0/src/lib/clapper/clapper-harvest.h000066400000000000000000000044201474353110700214060ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_HARVEST (clapper_harvest_get_type()) #define CLAPPER_HARVEST_CAST(obj) ((ClapperHarvest *)(obj)) G_DECLARE_FINAL_TYPE (ClapperHarvest, clapper_harvest, CLAPPER, HARVEST, GstObject) CLAPPER_API gboolean clapper_harvest_fill (ClapperHarvest *harvest, const gchar *media_type, gpointer data, gsize size); CLAPPER_API gboolean clapper_harvest_fill_with_text (ClapperHarvest *harvest, const gchar *media_type, gchar *text); CLAPPER_API gboolean clapper_harvest_fill_with_bytes (ClapperHarvest *harvest, const gchar *media_type, GBytes *bytes); CLAPPER_API void clapper_harvest_tags_add (ClapperHarvest *harvest, const gchar *tag, ...) G_GNUC_NULL_TERMINATED; CLAPPER_API void clapper_harvest_tags_add_value (ClapperHarvest *harvest, const gchar *tag, const GValue *value); CLAPPER_API void clapper_harvest_toc_add (ClapperHarvest *harvest, GstTocEntryType type, const gchar *title, gdouble start, gdouble end); CLAPPER_API void clapper_harvest_headers_set (ClapperHarvest *harvest, const gchar *key, ...) G_GNUC_NULL_TERMINATED; CLAPPER_API void clapper_harvest_headers_set_value (ClapperHarvest *harvest, const gchar *key, const GValue *value); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-marker-private.h000066400000000000000000000022311474353110700226610ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-enums.h" #include "clapper-marker.h" G_BEGIN_DECLS G_GNUC_INTERNAL ClapperMarker * clapper_marker_new_internal (ClapperMarkerType marker_type, const gchar *name, gdouble start, gdouble end); G_GNUC_INTERNAL gboolean clapper_marker_is_internal (ClapperMarker *marker); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-marker.c000066400000000000000000000215401474353110700212100ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperMarker: * * Represents a point in timeline. * * Markers are a convienient way of marking points of interest within a * [class@Clapper.Timeline] of [class@Clapper.MediaItem]. Use them * to indicate certain areas on the timeline. * * Markers are reference counted immutable objects. Once a marker is created * it can only be inserted into a single [class@Clapper.Timeline] at a time. * * Please note that markers are independent of [property@Clapper.MediaItem:duration] * and applications should not assume that all markers must have start/end times * lower or equal the item duration. This is not the case in e.g. live streams * where duration is unknown, but markers are still allowed to mark entries * (like EPG titles for example). * * Remember that [class@Clapper.Player] will also automatically insert certain * markers extracted from media such as video chapters. Clapper will never * "touch" the ones created by the application. If you want to differentiate * your own markers, applications can define and create markers with one of * the custom types from [enum@Clapper.MarkerType] enum. * * Example: * * ```c * #define MY_APP_MARKER (CLAPPER_MARKER_TYPE_CUSTOM_1) * * ClapperMarker *marker = clapper_marker_new (MY_APP_MARKER, title, start, end); * ``` * * ```c * ClapperMarkerType marker_type = clapper_marker_get_marker_type (marker); * * if (marker_type == MY_APP_MARKER) { * // Do something with your custom marker * } * ``` */ #include "clapper-marker-private.h" #define GST_CAT_DEFAULT clapper_marker_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperMarker { GstObject parent; ClapperMarkerType marker_type; gchar *title; gdouble start; gdouble end; gboolean is_internal; }; enum { PROP_0, PROP_MARKER_TYPE, PROP_TITLE, PROP_START, PROP_END, PROP_LAST }; #define parent_class clapper_marker_parent_class G_DEFINE_TYPE (ClapperMarker, clapper_marker, GST_TYPE_OBJECT); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; /** * clapper_marker_new: * @marker_type: a #ClapperMarkerType * @title: (nullable): title of the marker * @start: a start position of the marker * @end: an end position of the marker or [const@Clapper.MARKER_NO_END] if none * * Creates a new #ClapperMarker with given params. * * It is considered a programmer error trying to set an ending * point that is before the starting one. If end is unknown or * not defined a special [const@Clapper.MARKER_NO_END] value * should be used. * * Returns: (transfer full): a new #ClapperMarker. */ ClapperMarker * clapper_marker_new (ClapperMarkerType marker_type, const gchar *title, gdouble start, gdouble end) { ClapperMarker *marker; marker = g_object_new (CLAPPER_TYPE_MARKER, "marker-type", marker_type, "title", title, "start", start, "end", end, NULL); gst_object_ref_sink (marker); return marker; } ClapperMarker * clapper_marker_new_internal (ClapperMarkerType marker_type, const gchar *title, gdouble start, gdouble end) { ClapperMarker *marker; marker = clapper_marker_new (marker_type, title, start, end); marker->is_internal = TRUE; return marker; } /** * clapper_marker_get_marker_type: * @marker: a #ClapperMarker * * Get the #ClapperMarkerType of @marker. * * Returns: type of marker. */ ClapperMarkerType clapper_marker_get_marker_type (ClapperMarker *self) { g_return_val_if_fail (CLAPPER_IS_MARKER (self), CLAPPER_MARKER_TYPE_UNKNOWN); return self->marker_type; } /** * clapper_marker_get_title: * @marker: a #ClapperMarker * * Get the title of @marker. * * Returns: (nullable): the marker title. */ const gchar * clapper_marker_get_title (ClapperMarker *self) { g_return_val_if_fail (CLAPPER_IS_MARKER (self), NULL); return self->title; } /** * clapper_marker_get_start: * @marker: a #ClapperMarker * * Get the start position (in seconds) of @marker. * * Returns: marker start. */ gdouble clapper_marker_get_start (ClapperMarker *self) { g_return_val_if_fail (CLAPPER_IS_MARKER (self), 0); return self->start; } /** * clapper_marker_get_end: * @marker: a #ClapperMarker * * Get the end position (in seconds) of @marker. * * Returns: marker end. */ gdouble clapper_marker_get_end (ClapperMarker *self) { g_return_val_if_fail (CLAPPER_IS_MARKER (self), CLAPPER_MARKER_NO_END); return self->end; } gboolean clapper_marker_is_internal (ClapperMarker *self) { return self->is_internal; } static void clapper_marker_init (ClapperMarker *self) { self->marker_type = CLAPPER_MARKER_TYPE_UNKNOWN; self->end = CLAPPER_MARKER_NO_END; } static void clapper_marker_constructed (GObject *object) { ClapperMarker *self = CLAPPER_MARKER_CAST (object); G_OBJECT_CLASS (parent_class)->constructed (object); GST_TRACE_OBJECT (self, "Created new marker" ", type: %i, title: \"%s\", start: %lf, end: %lf", self->marker_type, GST_STR_NULL (self->title), self->start, self->end); } static void clapper_marker_finalize (GObject *object) { ClapperMarker *self = CLAPPER_MARKER_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_free (self->title); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_marker_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperMarker *self = CLAPPER_MARKER_CAST (object); switch (prop_id) { case PROP_MARKER_TYPE: g_value_set_enum (value, clapper_marker_get_marker_type (self)); break; case PROP_TITLE: g_value_set_string (value, clapper_marker_get_title (self)); break; case PROP_START: g_value_set_double (value, clapper_marker_get_start (self)); break; case PROP_END: g_value_set_double (value, clapper_marker_get_end (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_marker_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperMarker *self = CLAPPER_MARKER_CAST (object); switch (prop_id) { case PROP_MARKER_TYPE: self->marker_type = g_value_get_enum (value); break; case PROP_TITLE: self->title = g_value_dup_string (value); break; case PROP_START: self->start = g_value_get_double (value); break; case PROP_END: self->end = g_value_get_double (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_marker_class_init (ClapperMarkerClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappermarker", 0, "Clapper Marker"); gobject_class->constructed = clapper_marker_constructed; gobject_class->get_property = clapper_marker_get_property; gobject_class->set_property = clapper_marker_set_property; gobject_class->finalize = clapper_marker_finalize; /** * ClapperMarker:marker-type: * * Type of stream. */ param_specs[PROP_MARKER_TYPE] = g_param_spec_enum ("marker-type", NULL, NULL, CLAPPER_TYPE_MARKER_TYPE, CLAPPER_MARKER_TYPE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMarker:title: * * Title of marker. */ param_specs[PROP_TITLE] = g_param_spec_string ("title", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMarker:start: * * Starting time of marker. */ param_specs[PROP_START] = g_param_spec_double ("start", NULL, NULL, 0, G_MAXDOUBLE, 0, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMarker:end: * * Ending time of marker. */ param_specs[PROP_END] = g_param_spec_double ("end", NULL, NULL, -1, G_MAXDOUBLE, CLAPPER_MARKER_NO_END, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/clapper-marker.h000066400000000000000000000042551474353110700212210ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_MARKER (clapper_marker_get_type()) #define CLAPPER_MARKER_CAST(obj) ((ClapperMarker *)(obj)) /* NOTE: #ClapperMarker are immutable objects that cannot be derived, * otherwise #ClapperFeaturesManager would not be able to announce media * item changed caused by changes within them */ CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperMarker, clapper_marker, CLAPPER, MARKER, GstObject) /** * CLAPPER_MARKER_NO_END: * * The value used to indicate that marker does not have an ending time specified */ #define CLAPPER_MARKER_NO_END ((gdouble) -1) // Needs a cast from int, otherwise GIR is generated incorrectly CLAPPER_API ClapperMarker * clapper_marker_new (ClapperMarkerType marker_type, const gchar *title, gdouble start, gdouble end); CLAPPER_API ClapperMarkerType clapper_marker_get_marker_type (ClapperMarker *marker); CLAPPER_API const gchar * clapper_marker_get_title (ClapperMarker *marker); CLAPPER_API gdouble clapper_marker_get_start (ClapperMarker *marker); CLAPPER_API gdouble clapper_marker_get_end (ClapperMarker *marker); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-media-item-private.h000066400000000000000000000034031474353110700234150ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-media-item.h" #include "clapper-player-private.h" #include "clapper-app-bus-private.h" G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_media_item_update_from_tag_list (ClapperMediaItem *item, const GstTagList *tags, ClapperPlayer *player); G_GNUC_INTERNAL void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDiscovererInfo *info); G_GNUC_INTERNAL gboolean clapper_media_item_set_duration (ClapperMediaItem *item, gdouble duration, ClapperAppBus *app_bus); G_GNUC_INTERNAL void clapper_media_item_set_cache_location (ClapperMediaItem *item, const gchar *location); G_GNUC_INTERNAL const gchar * clapper_media_item_get_playback_uri (ClapperMediaItem *item); G_GNUC_INTERNAL void clapper_media_item_set_used (ClapperMediaItem *item, gboolean used); G_GNUC_INTERNAL gboolean clapper_media_item_get_used (ClapperMediaItem *item); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-media-item.c000066400000000000000000000452011474353110700217420ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperMediaItem: * * Represents a media item. * * A newly created media item must be added to player [class@Clapper.Queue] * first in order to be played. */ #include "clapper-media-item-private.h" #include "clapper-timeline-private.h" #include "clapper-player-private.h" #include "clapper-playbin-bus-private.h" #include "clapper-features-manager-private.h" #include "clapper-utils-private.h" #define GST_CAT_DEFAULT clapper_media_item_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperMediaItem { GstObject parent; gchar *uri; gchar *suburi; ClapperTimeline *timeline; guint id; gchar *title; gchar *container_format; gdouble duration; gchar *cache_uri; /* For shuffle */ gboolean used; }; enum { PROP_0, PROP_ID, PROP_URI, PROP_SUBURI, PROP_CACHE_LOCATION, PROP_TITLE, PROP_CONTAINER_FORMAT, PROP_DURATION, PROP_TIMELINE, PROP_LAST }; #define parent_class clapper_media_item_parent_class G_DEFINE_TYPE (ClapperMediaItem, clapper_media_item, GST_TYPE_OBJECT); static guint _item_id = 0; static GMutex id_lock; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; /** * clapper_media_item_new: * @uri: a media URI * * Creates new #ClapperMediaItem from URI. * * Use one of the URI protocols supported by plugins in #GStreamer * installation. For local files you can use either "file" protocol * or [ctor@Clapper.MediaItem.new_from_file] method. * * It is considered a programmer error trying to create new media item from * invalid URI. If URI is valid, but unsupported by installed plugins on user * system, [class@Clapper.Player] will emit a [signal@Clapper.Player::missing-plugin] * signal upon playback. * * Returns: (transfer full): a new #ClapperMediaItem. */ ClapperMediaItem * clapper_media_item_new (const gchar *uri) { ClapperMediaItem *item; g_return_val_if_fail (uri != NULL, NULL); item = g_object_new (CLAPPER_TYPE_MEDIA_ITEM, "uri", uri, NULL); gst_object_ref_sink (item); g_mutex_lock (&id_lock); item->id = _item_id; _item_id++; g_mutex_unlock (&id_lock); /* FIXME: Set initial container format from file extension parsing */ GST_TRACE_OBJECT (item, "New media item, ID: %u, URI: %s, title: %s", item->id, item->uri, item->title); return item; } /** * clapper_media_item_new_from_file: * @file: a #GFile * * Creates new #ClapperMediaItem from #GFile. * * Same as [ctor@Clapper.MediaItem.new], but uses a [iface@Gio.File] * for convenience in some situations instead of an URI. * * Returns: (transfer full): a new #ClapperMediaItem. */ ClapperMediaItem * clapper_media_item_new_from_file (GFile *file) { ClapperMediaItem *item; gchar *uri; g_return_val_if_fail (G_IS_FILE (file), NULL); uri = clapper_utils_uri_from_file (file); item = clapper_media_item_new (uri); g_free (uri); return item; } /** * clapper_media_item_new_cached: * @uri: a media URI * @location: (type filename) (nullable): a path to downloaded file * * Same as [ctor@Clapper.MediaItem.new], but allows to provide * a location of a cache file where particular media at @uri * is supposed to be found. * * File at @location existence will be checked upon starting playback * of created item. If cache file is not found, media item @uri will be * used as fallback. In this case when [property@Clapper.Player:download-enabled] * is set to %TRUE, item will be downloaded and cached again if possible. * * Returns: (transfer full): a new #ClapperMediaItem. * * Since: 0.8 */ ClapperMediaItem * clapper_media_item_new_cached (const gchar *uri, const gchar *location) { ClapperMediaItem *item = clapper_media_item_new (uri); if (location && G_LIKELY (item != NULL)) clapper_media_item_set_cache_location (item, location); return item; } /** * clapper_media_item_get_id: * @item: a #ClapperMediaItem * * Get the unique ID of #ClapperMediaItem. * * Returns: an ID of #ClapperMediaItem. */ guint clapper_media_item_get_id (ClapperMediaItem *self) { g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), G_MAXUINT); return self->id; } /** * clapper_media_item_get_uri: * @item: a #ClapperMediaItem * * Get the URI of #ClapperMediaItem. * * Returns: an URI of #ClapperMediaItem. */ const gchar * clapper_media_item_get_uri (ClapperMediaItem *self) { g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL); return self->uri; } /** * clapper_media_item_set_suburi: * @item: a #ClapperMediaItem * @suburi: an additional URI * * Set the additional URI of #ClapperMediaItem. * * This is typically used to add an external subtitles URI to the @item. */ void clapper_media_item_set_suburi (ClapperMediaItem *self, const gchar *suburi) { gboolean changed; GST_OBJECT_LOCK (self); changed = g_set_str (&self->suburi, suburi); GST_OBJECT_UNLOCK (self); if (changed) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); if (player) { clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_SUBURI]); clapper_playbin_bus_post_item_suburi_change (player->bus, self); gst_object_unref (player); } } } /** * clapper_media_item_get_suburi: * @item: a #ClapperMediaItem * * Get the additional URI of #ClapperMediaItem. * * Returns: (transfer full) (nullable): an additional URI of #ClapperMediaItem. */ gchar * clapper_media_item_get_suburi (ClapperMediaItem *self) { gchar *suburi; g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL); GST_OBJECT_LOCK (self); suburi = g_strdup (self->suburi); GST_OBJECT_UNLOCK (self); return suburi; } static gboolean clapper_media_item_take_title (ClapperMediaItem *self, gchar *title, ClapperAppBus *app_bus) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = g_strcmp0 (self->title, title) != 0)) { g_free (self->title); self->title = title; } GST_OBJECT_UNLOCK (self); if (changed) clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_TITLE]); else g_free (title); return changed; } /** * clapper_media_item_get_title: * @item: a #ClapperMediaItem * * Get media item title. * * The title can be either text detected by media discovery once it * completes. Otherwise whenever possible this will try to return a title * extracted from media URI e.g. basename without extension for local files. * * Returns: (transfer full) (nullable): media title. */ gchar * clapper_media_item_get_title (ClapperMediaItem *self) { gchar *title; g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL); GST_OBJECT_LOCK (self); title = g_strdup (self->title); GST_OBJECT_UNLOCK (self); return title; } static gboolean clapper_media_item_take_container_format (ClapperMediaItem *self, gchar *container_format, ClapperAppBus *app_bus) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = g_strcmp0 (self->container_format, container_format) != 0)) { g_free (self->container_format); self->container_format = container_format; } GST_OBJECT_UNLOCK (self); if (changed) clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CONTAINER_FORMAT]); else g_free (container_format); return changed; } /** * clapper_media_item_get_container_format: * @item: a #ClapperMediaItem * * Get media item container format. * * Returns: (transfer full) (nullable): media container format. */ gchar * clapper_media_item_get_container_format (ClapperMediaItem *self) { gchar *container_format; g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL); GST_OBJECT_LOCK (self); container_format = g_strdup (self->container_format); GST_OBJECT_UNLOCK (self); return container_format; } gboolean clapper_media_item_set_duration (ClapperMediaItem *self, gdouble duration, ClapperAppBus *app_bus) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = !G_APPROX_VALUE (self->duration, duration, FLT_EPSILON))) self->duration = duration; GST_OBJECT_UNLOCK (self); if (changed) { GST_DEBUG_OBJECT (self, "Duration: %" GST_TIME_FORMAT, GST_TIME_ARGS (duration * GST_SECOND)); clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_DURATION]); } return changed; } /** * clapper_media_item_get_duration: * @item: a #ClapperMediaItem * * Get media item duration as decimal number in seconds. * * Returns: media duration. */ gdouble clapper_media_item_get_duration (ClapperMediaItem *self) { gdouble duration; g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), 0); GST_OBJECT_LOCK (self); duration = self->duration; GST_OBJECT_UNLOCK (self); return duration; } /** * clapper_media_item_get_timeline: * @item: a #ClapperMediaItem * * Get the [class@Clapper.Timeline] assosiated with @item. * * Returns: (transfer none): a #ClapperTimeline of item. */ ClapperTimeline * clapper_media_item_get_timeline (ClapperMediaItem *self) { g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (self), NULL); return self->timeline; } static gboolean clapper_media_item_update_from_container_tags (ClapperMediaItem *self, const GstTagList *tags, ClapperAppBus *app_bus) { gchar *string = NULL; gboolean changed = FALSE; if (gst_tag_list_get_string (tags, GST_TAG_CONTAINER_FORMAT, &string)) changed |= clapper_media_item_take_container_format (self, string, app_bus); if (gst_tag_list_get_string (tags, GST_TAG_TITLE, &string)) changed |= clapper_media_item_take_title (self, string, app_bus); return changed; } void clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagList *tags, ClapperPlayer *player) { GstTagScope scope = gst_tag_list_get_scope (tags); if (scope == GST_TAG_SCOPE_GLOBAL) { gboolean changed = clapper_media_item_update_from_container_tags (self, tags, player->app_bus); if (changed) { ClapperFeaturesManager *features_manager; if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } } } void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDiscovererInfo *info) { ClapperPlayer *player; GstDiscovererStreamInfo *sinfo; GstClockTime duration; gdouble val_dbl; gboolean changed = FALSE; if (!(player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) return; for (sinfo = gst_discoverer_info_get_stream_info (info); sinfo != NULL; sinfo = gst_discoverer_stream_info_get_next (sinfo)) { const GstTagList *tags; if (GST_IS_DISCOVERER_CONTAINER_INFO (sinfo)) { GstDiscovererContainerInfo *cinfo = (GstDiscovererContainerInfo *) sinfo; if ((tags = gst_discoverer_container_info_get_tags (cinfo))) changed |= clapper_media_item_update_from_container_tags (self, tags, player->app_bus); } gst_discoverer_stream_info_unref (sinfo); } duration = gst_discoverer_info_get_duration (info); if (G_UNLIKELY (duration == GST_CLOCK_TIME_NONE)) duration = 0; val_dbl = (gdouble) duration / GST_SECOND; changed |= clapper_media_item_set_duration (self, val_dbl, player->app_bus); if (changed) { ClapperFeaturesManager *features_manager; if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, self); } gst_object_unref (player); } /* XXX: Must be set from player thread or upon construction */ void clapper_media_item_set_cache_location (ClapperMediaItem *self, const gchar *location) { g_clear_pointer (&self->cache_uri, g_free); if (location) self->cache_uri = g_filename_to_uri (location, NULL, NULL); GST_DEBUG_OBJECT (self, "Set cache URI: \"%s\"", GST_STR_NULL (self->cache_uri)); } /* XXX: Can only be read from player thread. * Returns cache URI if available, item URI otherwise. */ inline const gchar * clapper_media_item_get_playback_uri (ClapperMediaItem *self) { if (self->cache_uri) { GFile *file = g_file_new_for_uri (self->cache_uri); gboolean exists; /* It is an app error if it removes files in non-stopped state, * and this function is only called when starting playback */ exists = g_file_query_exists (file, NULL); g_object_unref (file); if (exists) return self->cache_uri; /* Do not test file existence next time */ clapper_media_item_set_cache_location (self, NULL); } return self->uri; } void clapper_media_item_set_used (ClapperMediaItem *self, gboolean used) { GST_OBJECT_LOCK (self); self->used = used; GST_OBJECT_UNLOCK (self); } gboolean clapper_media_item_get_used (ClapperMediaItem *self) { gboolean used; GST_OBJECT_LOCK (self); used = self->used; GST_OBJECT_UNLOCK (self); return used; } static void clapper_media_item_init (ClapperMediaItem *self) { self->timeline = clapper_timeline_new (); gst_object_set_parent (GST_OBJECT_CAST (self->timeline), GST_OBJECT_CAST (self)); } static void clapper_media_item_constructed (GObject *object) { ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object); /* Be safe when someone incorrectly constructs item without URI */ if (G_UNLIKELY (self->uri == NULL)) self->uri = g_strdup ("file://"); self->title = clapper_utils_title_from_uri (self->uri); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_media_item_finalize (GObject *object) { ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_free (self->uri); g_free (self->title); g_free (self->container_format); gst_object_unparent (GST_OBJECT_CAST (self->timeline)); gst_object_unref (self->timeline); g_free (self->cache_uri); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_media_item_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object); switch (prop_id) { case PROP_URI: self->uri = g_value_dup_string (value); break; case PROP_SUBURI: clapper_media_item_set_suburi (self, g_value_get_string (value)); break; case PROP_CACHE_LOCATION: clapper_media_item_set_cache_location (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_media_item_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperMediaItem *self = CLAPPER_MEDIA_ITEM_CAST (object); switch (prop_id) { case PROP_ID: g_value_set_uint (value, clapper_media_item_get_id (self)); break; case PROP_URI: g_value_set_string (value, clapper_media_item_get_uri (self)); break; case PROP_SUBURI: g_value_take_string (value, clapper_media_item_get_suburi (self)); break; case PROP_TITLE: g_value_take_string (value, clapper_media_item_get_title (self)); break; case PROP_CONTAINER_FORMAT: g_value_take_string (value, clapper_media_item_get_container_format (self)); break; case PROP_DURATION: g_value_set_double (value, clapper_media_item_get_duration (self)); break; case PROP_TIMELINE: g_value_set_object (value, clapper_media_item_get_timeline (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_media_item_class_init (ClapperMediaItemClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappermediaitem", 0, "Clapper Media Item"); gobject_class->constructed = clapper_media_item_constructed; gobject_class->set_property = clapper_media_item_set_property; gobject_class->get_property = clapper_media_item_get_property; gobject_class->finalize = clapper_media_item_finalize; /** * ClapperMediaItem:id: * * Media Item ID. */ param_specs[PROP_ID] = g_param_spec_uint ("id", NULL, NULL, 0, G_MAXUINT, G_MAXUINT, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMediaItem:uri: * * Media URI. */ param_specs[PROP_URI] = g_param_spec_string ("uri", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMediaItem:suburi: * * Media additional URI. */ param_specs[PROP_SUBURI] = g_param_spec_string ("suburi", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMediaItem:cache-location: * * Media downloaded cache file location. * * Since: 0.8 */ param_specs[PROP_CACHE_LOCATION] = g_param_spec_string ("cache-location", NULL, NULL, NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMediaItem:title: * * Media title. */ param_specs[PROP_TITLE] = g_param_spec_string ("title", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMediaItem:container-format: * * Media container format. */ param_specs[PROP_CONTAINER_FORMAT] = g_param_spec_string ("container-format", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMediaItem:duration: * * Media duration as a decimal number in seconds. */ param_specs[PROP_DURATION] = g_param_spec_double ("duration", NULL, NULL, 0, G_MAXDOUBLE, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMediaItem:timeline: * * Media timeline. */ param_specs[PROP_TIMELINE] = g_param_spec_object ("timeline", NULL, NULL, CLAPPER_TYPE_TIMELINE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/clapper-media-item.h000066400000000000000000000044331474353110700217510ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_MEDIA_ITEM (clapper_media_item_get_type()) #define CLAPPER_MEDIA_ITEM_CAST(obj) ((ClapperMediaItem *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperMediaItem, clapper_media_item, CLAPPER, MEDIA_ITEM, GstObject) CLAPPER_API ClapperMediaItem * clapper_media_item_new (const gchar *uri); CLAPPER_API ClapperMediaItem * clapper_media_item_new_from_file (GFile *file); CLAPPER_API ClapperMediaItem * clapper_media_item_new_cached (const gchar *uri, const gchar *location); CLAPPER_API guint clapper_media_item_get_id (ClapperMediaItem *item); CLAPPER_API const gchar * clapper_media_item_get_uri (ClapperMediaItem *item); CLAPPER_API void clapper_media_item_set_suburi (ClapperMediaItem *item, const gchar *suburi); CLAPPER_API gchar * clapper_media_item_get_suburi (ClapperMediaItem *item); CLAPPER_API gchar * clapper_media_item_get_title (ClapperMediaItem *item); CLAPPER_API gchar * clapper_media_item_get_container_format (ClapperMediaItem *item); CLAPPER_API gdouble clapper_media_item_get_duration (ClapperMediaItem *item); CLAPPER_API ClapperTimeline * clapper_media_item_get_timeline (ClapperMediaItem *item); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-playbin-bus-private.h000066400000000000000000000037431474353110700236360ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-enums-private.h" #include "clapper-player.h" #include "clapper-media-item.h" G_BEGIN_DECLS void clapper_playbin_bus_initialize (void); gboolean clapper_playbin_bus_message_func (GstBus *bus, GstMessage *msg, ClapperPlayer *player); void clapper_playbin_bus_post_set_volume (GstBus *bus, GstElement *playbin, gdouble volume); void clapper_playbin_bus_post_set_prop (GstBus *bus, GstObject *src, const gchar *name, GValue *value); void clapper_playbin_bus_post_set_play_flag (GstBus *bus, ClapperPlayerPlayFlags flag, gboolean enabled); void clapper_playbin_bus_post_request_state (GstBus *bus, ClapperPlayer *player, GstState state); void clapper_playbin_bus_post_seek (GstBus *bus, gdouble position, ClapperPlayerSeekMethod flags); void clapper_playbin_bus_post_rate_change (GstBus *bus, gdouble rate); void clapper_playbin_bus_post_stream_change (GstBus *bus); void clapper_playbin_bus_post_current_item_change (GstBus *bus, ClapperMediaItem *current_item, ClapperQueueItemChangeMode mode); void clapper_playbin_bus_post_item_suburi_change (GstBus *bus, ClapperMediaItem *item); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-playbin-bus.c000066400000000000000000001202301474353110700221500ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-bus-private.h" #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-player-private.h" #include "clapper-queue-private.h" #include "clapper-media-item-private.h" #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 #define GST_CAT_DEFAULT clapper_playbin_bus_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); enum { CLAPPER_PLAYBIN_BUS_STRUCTURE_UNKNOWN = 0, CLAPPER_PLAYBIN_BUS_STRUCTURE_SET_PROP, CLAPPER_PLAYBIN_BUS_STRUCTURE_SET_PLAY_FLAG, CLAPPER_PLAYBIN_BUS_STRUCTURE_SEEK, CLAPPER_PLAYBIN_BUS_STRUCTURE_RATE_CHANGE, CLAPPER_PLAYBIN_BUS_STRUCTURE_STREAM_CHANGE, CLAPPER_PLAYBIN_BUS_STRUCTURE_CURRENT_ITEM_CHANGE, CLAPPER_PLAYBIN_BUS_STRUCTURE_ITEM_SUBURI_CHANGE }; static ClapperBusQuark _structure_quarks[] = { {"unknown", 0}, {"set-prop", 0}, {"set-play-flag", 0}, {"seek", 0}, {"rate-change", 0}, {"stream-change", 0}, {"current-item-change", 0}, {"item-suburi-change", 0}, {NULL, 0} }; enum { CLAPPER_PLAYBIN_BUS_FIELD_UNKNOWN = 0, CLAPPER_PLAYBIN_BUS_FIELD_NAME, CLAPPER_PLAYBIN_BUS_FIELD_VALUE, CLAPPER_PLAYBIN_BUS_FIELD_FLAG, CLAPPER_PLAYBIN_BUS_FIELD_POSITION, CLAPPER_PLAYBIN_BUS_FIELD_RATE, CLAPPER_PLAYBIN_BUS_FIELD_SEEK_METHOD, CLAPPER_PLAYBIN_BUS_FIELD_MEDIA_ITEM, CLAPPER_PLAYBIN_BUS_FIELD_ITEM_CHANGE_MODE }; static ClapperBusQuark _field_quarks[] = { {"unknown", 0}, {"name", 0}, {"value", 0}, {"flag", 0}, {"position", 0}, {"rate", 0}, {"seek-method", 0}, {"media-item", 0}, {"item-change-mode", 0}, {NULL, 0} }; #define _STRUCTURE_QUARK(q) (_structure_quarks[CLAPPER_PLAYBIN_BUS_STRUCTURE_##q].quark) #define _FIELD_QUARK(q) (_field_quarks[CLAPPER_PLAYBIN_BUS_FIELD_##q].quark) #define _FIELD_NAME(q) (_field_quarks[CLAPPER_PLAYBIN_BUS_FIELD_##q].name) #define _MESSAGE_SRC_GOBJECT(msg) ((GObject *) GST_MESSAGE_SRC (msg)) void clapper_playbin_bus_initialize (void) { guint i; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperplaybinbus", 0, "Clapper Playbin Bus"); for (i = 0; _structure_quarks[i].name; ++i) _structure_quarks[i].quark = g_quark_from_static_string (_structure_quarks[i].name); for (i = 0; _field_quarks[i].name; ++i) _field_quarks[i].quark = g_quark_from_static_string (_field_quarks[i].name); } /* static gboolean _set_object_prop (GQuark field_id, const GValue *value, GstObject *object) { const gchar *prop_name = g_quark_to_string (field_id); GST_DEBUG ("Setting %s property: %s", GST_OBJECT_NAME (object), prop_name); g_object_set_property (G_OBJECT (object), prop_name, value); return G_SOURCE_CONTINUE; } */ static inline void dump_dot_file (ClapperPlayer *player, const gchar *name) { gchar full_name[40]; g_snprintf (full_name, sizeof (full_name), "clapper.%p.%s", player, name); GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN_CAST (player->playbin), GST_DEBUG_GRAPH_SHOW_ALL, full_name); } static void _perform_flush_seek (ClapperPlayer *player) { GstEvent *seek_event; GstSeekFlags flags = GST_SEEK_FLAG_FLUSH; gint64 position = GST_CLOCK_TIME_NONE; gdouble rate = clapper_player_get_speed (player); if (rate != 1.0) flags |= GST_SEEK_FLAG_TRICKMODE; if (gst_element_query (player->playbin, player->position_query)) gst_query_parse_position (player->position_query, NULL, &position); if (rate >= 0) { seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE); } else { seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0), GST_SEEK_TYPE_SET, position); } clapper_player_remove_tick_source (player); GST_DEBUG_OBJECT (player, "Flush seeking with rate %.2lf to: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (position)); if (!gst_element_send_event (player->playbin, seek_event)) GST_WARNING_OBJECT (player, "Could not perform a flush seek"); } static void _update_current_duration (ClapperPlayer *player) { gint64 duration; if (!gst_element_query_duration (player->playbin, GST_FORMAT_TIME, &duration)) return; if (G_UNLIKELY (duration < 0)) duration = 0; if (G_LIKELY (player->played_item != NULL)) { gdouble duration_dbl = (gdouble) duration / GST_SECOND; if (clapper_media_item_set_duration (player->played_item, duration_dbl, player->app_bus)) { ClapperFeaturesManager *features_manager; if ((features_manager = clapper_player_get_features_manager (player))) clapper_features_manager_trigger_item_updated (features_manager, player->played_item); } } } static inline void _handle_warning_msg (GstMessage *msg, ClapperPlayer *player) { GError *error = NULL; gchar *debug_info = NULL; guint signal_id; gst_message_parse_warning (msg, &error, &debug_info); GST_WARNING_OBJECT (player, "Warning: %s", error->message); dump_dot_file (player, "WARNING"); signal_id = g_signal_lookup ("warning", CLAPPER_TYPE_PLAYER); clapper_app_bus_post_error_signal (player->app_bus, GST_OBJECT_CAST (player), signal_id, error, debug_info); g_clear_error (&error); g_free (debug_info); } static inline void _handle_error_msg (GstMessage *msg, ClapperPlayer *player) { GError *error = NULL; gchar *debug_info = NULL; guint signal_id; gst_message_parse_error (msg, &error, &debug_info); GST_ERROR_OBJECT (player, "Error: %s", error->message); dump_dot_file (player, "ERROR"); GST_OBJECT_LOCK (player); player->had_error = TRUE; GST_OBJECT_UNLOCK (player); /* Remove position query, since there was an error */ clapper_player_remove_tick_source (player); /* After error we should go to READY, so all elements will stop processing buffers */ gst_element_set_state (player->playbin, GST_STATE_READY); signal_id = g_signal_lookup ("error", CLAPPER_TYPE_PLAYER); clapper_app_bus_post_error_signal (player->app_bus, GST_OBJECT_CAST (player), signal_id, error, debug_info); g_clear_error (&error); g_free (debug_info); } static inline void _handle_buffering_msg (GstMessage *msg, ClapperPlayer *player) { gint percent; gboolean is_buffering; gst_message_parse_buffering (msg, &percent); GST_LOG_OBJECT (player, "Buffering: %i%%", percent); is_buffering = (percent < 100); /* If no change return */ if (player->is_buffering == is_buffering) return; player->is_buffering = is_buffering; /* When buffering we need to manually refresh to enter buffering state * while later playbin PLAYING state message will trigger leave */ if (player->is_buffering || player->target_state < GST_STATE_PLAYING) clapper_player_handle_playbin_state_changed (player); /* TODO: Review this code later */ if (player->target_state > GST_STATE_PAUSED) { GstStateChangeReturn ret; ret = gst_element_set_state (player->playbin, (is_buffering) ? GST_STATE_PAUSED : GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) GST_FIXME_OBJECT (player, "HANDLE BUFFERING STATE CHANGE ERROR"); } } void clapper_playbin_bus_post_set_volume (GstBus *bus, GstElement *playbin, gdouble volume) { GValue value = G_VALUE_INIT; gdouble volume_linear; volume_linear = gst_stream_volume_convert_volume ( GST_STREAM_VOLUME_FORMAT_CUBIC, GST_STREAM_VOLUME_FORMAT_LINEAR, volume); g_value_init (&value, G_TYPE_DOUBLE); g_value_set_double (&value, volume_linear); clapper_playbin_bus_post_set_prop (bus, GST_OBJECT_CAST (playbin), "volume", &value); } /* GValue is transfer-full!!! */ void clapper_playbin_bus_post_set_prop (GstBus *bus, GstObject *src, const gchar *name, GValue *value) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (SET_PROP), _FIELD_QUARK (NAME), G_TYPE_STRING, name, NULL); gst_structure_id_take_value (structure, _FIELD_QUARK (VALUE), value); gst_bus_post (bus, gst_message_new_application (src, structure)); } static inline void _handle_set_prop_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer *player) { const gchar *prop_name = gst_structure_get_string (structure, _FIELD_NAME (NAME)); const GValue *value = gst_structure_id_get_value (structure, _FIELD_QUARK (VALUE)); /* We cannot change some playbin properties, until pipeline is running. * Notify user about change immediatelly and we will apply value on preroll. */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT_CAST (player->playbin) && player->current_state <= GST_STATE_READY) { if (strcmp (prop_name, "volume") == 0) { clapper_player_handle_playbin_volume_changed (player, value); return; } else if (strcmp (prop_name, "mute") == 0) { clapper_player_handle_playbin_mute_changed (player, value); return; } } GST_DEBUG ("Setting %s property: %s", GST_OBJECT_NAME (GST_MESSAGE_SRC (msg)), prop_name); g_object_set_property (_MESSAGE_SRC_GOBJECT (msg), prop_name, value); } void clapper_playbin_bus_post_set_play_flag (GstBus *bus, ClapperPlayerPlayFlags flag, gboolean enabled) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (SET_PLAY_FLAG), _FIELD_QUARK (FLAG), G_TYPE_FLAGS, flag, _FIELD_QUARK (VALUE), G_TYPE_BOOLEAN, enabled, NULL); gst_bus_post (bus, gst_message_new_application (NULL, structure)); } static inline void _handle_set_play_flag_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer *player) { ClapperPlayerPlayFlags flag = 0; gboolean enabled, enable = FALSE; gint flags = 0; gst_structure_id_get (structure, _FIELD_QUARK (FLAG), G_TYPE_FLAGS, &flag, _FIELD_QUARK (VALUE), G_TYPE_BOOLEAN, &enable, NULL); g_object_get (player->playbin, "flags", &flags, NULL); enabled = ((flags & flag) == flag); if (enabled != enable) { if (enable) flags |= flag; else flags &= ~flag; GST_DEBUG_OBJECT (player, "%sabling play flag: %i", (enable) ? "En" : "Dis", flag); g_object_set (player->playbin, "flags", flags, NULL); } } void clapper_playbin_bus_post_request_state (GstBus *bus, ClapperPlayer *player, GstState state) { gst_bus_post (bus, gst_message_new_request_state (GST_OBJECT_CAST (player), state)); } static inline void _handle_request_state_msg (GstMessage *msg, ClapperPlayer *player) { GstState state; gst_message_parse_request_state (msg, &state); if (state > GST_STATE_READY) { gboolean has_item; GST_OBJECT_LOCK (player); has_item = (player->played_item || player->pending_item); GST_OBJECT_UNLOCK (player); if (!has_item) return; } /* If message came from player, update user requested target state */ if (GST_MESSAGE_SRC (msg) == GST_OBJECT_CAST (player)) player->target_state = state; /* FIXME: Also ignore play/pause call for live content */ /* Ignore play/pause state requests if we are buffering, * just update target state for later */ if (player->is_buffering && state > GST_STATE_READY) return; GST_DEBUG_OBJECT (player, "Changing state to: %s", gst_element_state_get_name (state)); gst_element_set_state (player->playbin, state); } void clapper_playbin_bus_post_seek (GstBus *bus, gdouble position, ClapperPlayerSeekMethod seek_method) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (SEEK), _FIELD_QUARK (POSITION), G_TYPE_INT64, (gint64) (position * GST_SECOND), _FIELD_QUARK (SEEK_METHOD), G_TYPE_ENUM, seek_method, NULL); gst_bus_post (bus, gst_message_new_application (NULL, structure)); } static inline void _handle_seek_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer *player) { GstEvent *seek_event; gint64 position = 0; gdouble rate; ClapperPlayerSeekMethod seek_method = CLAPPER_PLAYER_SEEK_METHOD_NORMAL; GstSeekFlags flags = GST_SEEK_FLAG_FLUSH; /* We should ignore seek if pipeline is going to be stopped */ if (player->target_state < GST_STATE_PAUSED) return; gst_structure_id_get (structure, _FIELD_QUARK (POSITION), G_TYPE_INT64, &position, _FIELD_QUARK (SEEK_METHOD), G_TYPE_ENUM, &seek_method, NULL); /* If we are starting playback, do a seek after preroll */ if (player->current_state < GST_STATE_PAUSED) { player->pending_position = (gdouble) position / GST_SECOND; return; } switch (seek_method) { case CLAPPER_PLAYER_SEEK_METHOD_FAST: flags |= (GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_SNAP_NEAREST); break; case CLAPPER_PLAYER_SEEK_METHOD_NORMAL: break; case CLAPPER_PLAYER_SEEK_METHOD_ACCURATE: flags |= GST_SEEK_FLAG_ACCURATE; break; default: g_assert_not_reached (); break; } rate = clapper_player_get_speed (player); if (rate != 1.0) flags |= GST_SEEK_FLAG_TRICKMODE; if (rate >= 0) { seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, position, GST_SEEK_TYPE_SET, GST_CLOCK_TIME_NONE); } else { seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, GST_SEEK_TYPE_SET, G_GINT64_CONSTANT (0), GST_SEEK_TYPE_SET, position); } GST_DEBUG ("Seeking with rate %.2lf to: %" GST_TIME_FORMAT, rate, GST_TIME_ARGS (position)); clapper_player_remove_tick_source (player); if (!(player->seeking = gst_element_send_event (player->playbin, seek_event))) { /* FIXME: Should we maybe call _handle_error_msg with * some error here? Or will playbin post such message for us? */ GST_ERROR ("Could not seek"); } } void clapper_playbin_bus_post_rate_change (GstBus *bus, gdouble rate) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (RATE_CHANGE), _FIELD_QUARK (RATE), G_TYPE_DOUBLE, rate, NULL); gst_bus_post (bus, gst_message_new_application (NULL, structure)); } static inline void _handle_rate_change_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer *player) { GstEvent *seek_event; gint64 position = GST_CLOCK_TIME_NONE; GstSeekType seek_type = GST_SEEK_TYPE_NONE; GstSeekFlags flags = GST_SEEK_FLAG_NONE; gdouble /* current_rate,*/ rate = 1.0; gst_structure_id_get (structure, _FIELD_QUARK (RATE), G_TYPE_DOUBLE, &rate, NULL); if (player->speed_changing && player->requested_speed != 0) { player->pending_speed = rate; return; } /* We cannot perform playback rate changes until pipeline is running. * Notify user about change immediatelly and we will apply value on preroll. */ if (player->current_state < GST_STATE_PAUSED || player->target_state < GST_STATE_PAUSED) { clapper_player_handle_playbin_rate_changed (player, rate); return; } /* FIXME: Using GST_SEEK_FLAG_INSTANT_RATE_CHANGE, audio-filter stops * working with playbin2 and seek event fails with playbin3 :-( */ #if 0 /* We can only do instant rate changes without flushing when * playback direction stays the same. Otherwise get most current * position, so we can seek back as close to it as possible. */ current_rate = clapper_player_get_speed (player); if ((rate < 0 && current_rate < 0) || (rate > 0 && current_rate > 0)) { #else if (FALSE) { #endif flags |= GST_SEEK_FLAG_INSTANT_RATE_CHANGE; } else { seek_type = GST_SEEK_TYPE_SET; flags |= GST_SEEK_FLAG_FLUSH; if (gst_element_query (player->playbin, player->position_query)) gst_query_parse_position (player->position_query, NULL, &position); } /* Round playback rate to 1.0 */ if (G_APPROX_VALUE (rate, 1.0, FLT_EPSILON)) rate = 1.0; if (rate != 1.0) flags |= GST_SEEK_FLAG_TRICKMODE; if (rate >= 0) { seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, seek_type, position, seek_type, GST_CLOCK_TIME_NONE); } else { seek_event = gst_event_new_seek (rate, GST_FORMAT_TIME, flags, seek_type, (position < 0) ? GST_CLOCK_TIME_NONE : G_GINT64_CONSTANT (0), seek_type, position); } GST_DEBUG_OBJECT (player, "Changing rate to: %.2lf", rate); /* Similarly as in normal seek */ if ((flags & GST_SEEK_FLAG_INSTANT_RATE_CHANGE) == 0) clapper_player_remove_tick_source (player); if (gst_element_send_event (player->playbin, seek_event)) { if ((flags & GST_SEEK_FLAG_INSTANT_RATE_CHANGE) == 0) { player->requested_speed = rate; player->speed_changing = TRUE; } else { player->requested_speed = 0; player->pending_speed = 0; player->speed_changing = FALSE; clapper_player_handle_playbin_rate_changed (player, rate); } } else { /* FIXME: Should we maybe call _handle_error_msg with * some error here? Or will playbin post such message for us? */ GST_ERROR ("Could not change rate"); } } static inline void _handle_state_changed_msg (GstMessage *msg, ClapperPlayer *player) { GstState old_state, pending_state; gboolean preroll, eos; /* We only care about our parent bin state changes */ if (GST_MESSAGE_SRC (msg) != GST_OBJECT_CAST (player->playbin)) return; gst_message_parse_state_changed (msg, &old_state, &player->current_state, &pending_state); GST_LOG_OBJECT (player, "State changed, old: %i, current: %i, pending: %i", old_state, player->current_state, pending_state); dump_dot_file (player, gst_element_state_get_name (player->current_state)); /* Seek operation is progressing as expected. Return as we do not * want to change ClapperPlayerState when seeking or rate changing. */ if ((player->seeking || player->speed_changing) && player->current_state > GST_STATE_READY) return; if ((eos = (player->pending_eos && player->current_state == GST_STATE_PAUSED))) player->pending_eos = FALSE; g_atomic_int_set (&player->eos, (gint) eos); if (player->current_state <= GST_STATE_READY) clapper_player_reset (player, FALSE); if (player->current_state == GST_STATE_PLAYING) clapper_player_add_tick_source (player); else clapper_player_remove_tick_source (player); /* Notify user about current position either right before or after * changed playback (so it does not look like seek after paused) */ if (player->current_state < old_state) clapper_player_refresh_position (player); clapper_player_handle_playbin_state_changed (player); if (player->current_state > old_state) clapper_player_refresh_position (player); preroll = (old_state == GST_STATE_READY && player->current_state == GST_STATE_PAUSED && (pending_state == GST_STATE_VOID_PENDING || pending_state == GST_STATE_PLAYING)); if (preroll) { gdouble speed; GST_DEBUG ("Setting cached playbin props after preroll"); clapper_player_set_volume (player, clapper_player_get_volume (player)); clapper_player_set_mute (player, clapper_player_get_mute (player)); speed = clapper_player_get_speed (player); /* Playback always starts with normal speed and from zero. * When not changed do not post seek event. */ if (!G_APPROX_VALUE (speed, 1.0, FLT_EPSILON)) clapper_player_set_speed (player, speed); if (!G_APPROX_VALUE (player->pending_position, 0, FLT_EPSILON)) { clapper_player_seek (player, player->pending_position); player->pending_position = 0; } _update_current_duration (player); if (!player->use_playbin3) clapper_player_playbin_update_current_decoders (player); } } void clapper_playbin_bus_post_current_item_change (GstBus *bus, ClapperMediaItem *current_item, ClapperQueueItemChangeMode mode) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (CURRENT_ITEM_CHANGE), _FIELD_QUARK (MEDIA_ITEM), CLAPPER_TYPE_MEDIA_ITEM, current_item, _FIELD_QUARK (ITEM_CHANGE_MODE), G_TYPE_ENUM, mode, NULL); gst_bus_post (bus, gst_message_new_application (NULL, structure)); } static inline void _handle_current_item_change_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer *player) { ClapperMediaItem *current_item = NULL; ClapperQueueItemChangeMode mode = CLAPPER_QUEUE_ITEM_CHANGE_NORMAL; gst_structure_id_get (structure, _FIELD_QUARK (MEDIA_ITEM), CLAPPER_TYPE_MEDIA_ITEM, ¤t_item, _FIELD_QUARK (ITEM_CHANGE_MODE), G_TYPE_ENUM, &mode, NULL); player->pending_position = 0; // We store pending position for played item, so reset if (player->current_state < GST_STATE_READY || mode == CLAPPER_QUEUE_ITEM_CHANGE_NORMAL) gst_element_set_state (player->playbin, GST_STATE_READY); clapper_player_set_pending_item (player, current_item, mode); if (!current_item) { player->target_state = GST_STATE_READY; } else { GST_OBJECT_LOCK (player); if (player->autoplay) player->target_state = GST_STATE_PLAYING; GST_OBJECT_UNLOCK (player); } if ((mode == CLAPPER_QUEUE_ITEM_CHANGE_NORMAL && player->target_state > GST_STATE_READY) || player->current_state != player->target_state) gst_element_set_state (player->playbin, player->target_state); gst_clear_object (¤t_item); } void clapper_playbin_bus_post_item_suburi_change (GstBus *bus, ClapperMediaItem *item) { GstStructure *structure = gst_structure_new_id (_STRUCTURE_QUARK (ITEM_SUBURI_CHANGE), _FIELD_QUARK (MEDIA_ITEM), CLAPPER_TYPE_MEDIA_ITEM, item, NULL); gst_bus_post (bus, gst_message_new_application (NULL, structure)); } static inline void _handle_item_suburi_change_msg (GstMessage *msg, const GstStructure *structure, ClapperPlayer *player) { ClapperMediaItem *item = NULL; gst_structure_id_get (structure, _FIELD_QUARK (MEDIA_ITEM), CLAPPER_TYPE_MEDIA_ITEM, &item, NULL); if (item == player->played_item) { gst_element_set_state (player->playbin, GST_STATE_READY); clapper_player_set_pending_item (player, item, CLAPPER_QUEUE_ITEM_CHANGE_NORMAL); gst_element_set_state (player->playbin, player->target_state); } gst_object_unref (item); } void clapper_playbin_bus_post_stream_change (GstBus *bus) { GstStructure *structure = gst_structure_new_id_empty (_STRUCTURE_QUARK (STREAM_CHANGE)); gst_bus_post (bus, gst_message_new_application (NULL, structure)); } static inline void _handle_stream_change_msg (GstMessage *msg, const GstStructure *structure G_GNUC_UNUSED, ClapperPlayer *player) { GST_DEBUG_OBJECT (player, "Requested stream change"); if (player->use_playbin3) { GList *list = NULL; ClapperStreamList *vstream_list, *astream_list, *sstream_list; ClapperStream *vstream = NULL, *astream = NULL, *sstream = NULL; vstream_list = clapper_player_get_video_streams (player); if ((vstream = clapper_stream_list_get_current_stream (vstream_list))) { GstStream *gst_stream = clapper_stream_get_gst_stream (vstream); list = g_list_append (list, (gpointer) gst_stream_get_stream_id (gst_stream)); } astream_list = clapper_player_get_audio_streams (player); if ((astream = clapper_stream_list_get_current_stream (astream_list))) { GstStream *gst_stream = clapper_stream_get_gst_stream (astream); list = g_list_append (list, (gpointer) gst_stream_get_stream_id (gst_stream)); } sstream_list = clapper_player_get_subtitle_streams (player); if ((sstream = clapper_stream_list_get_current_stream (sstream_list))) { GstStream *gst_stream = clapper_stream_get_gst_stream (sstream); list = g_list_append (list, (gpointer) gst_stream_get_stream_id (gst_stream)); } if (list) { if (gst_element_send_event (player->playbin, gst_event_new_select_streams (list)) && player->current_state >= GST_STATE_PAUSED) { /* XXX: I am not sure if we "officially" need to flush seek after select * streams, but as of GStreamer 1.22 it doesn't work otherwise. */ player->pending_flush = TRUE; } g_list_free (list); } /* Need to hold ref until after event is * sent to ensure ID pointer lifespan */ gst_clear_object (&vstream); gst_clear_object (&astream); gst_clear_object (&sstream); } else { ClapperStreamList *stream_list; gint current_video = -1, current_audio = -1, current_text = -1; guint index; g_object_get (player->playbin, "current-video", ¤t_video, "current-audio", ¤t_audio, "current-text", ¤t_text, NULL); stream_list = clapper_player_get_video_streams (player); index = clapper_stream_list_get_current_index (stream_list); if (index != (guint) current_video) g_object_set (player->playbin, "current-video", index, NULL); stream_list = clapper_player_get_audio_streams (player); index = clapper_stream_list_get_current_index (stream_list); if (index != (guint) current_audio) g_object_set (player->playbin, "current-audio", index, NULL); stream_list = clapper_player_get_subtitle_streams (player); index = clapper_stream_list_get_current_index (stream_list); if (index != (guint) current_text) g_object_set (player->playbin, "current-text", index, NULL); } } static inline void _handle_app_msg (GstMessage *msg, ClapperPlayer *player) { const GstStructure *structure = gst_message_get_structure (msg); GQuark quark = gst_structure_get_name_id (structure); if (quark == _STRUCTURE_QUARK (SET_PROP)) _handle_set_prop_msg (msg, structure, player); else if (quark == _STRUCTURE_QUARK (SET_PLAY_FLAG)) _handle_set_play_flag_msg (msg, structure, player); else if (quark == _STRUCTURE_QUARK (SEEK)) _handle_seek_msg (msg, structure, player); else if (quark == _STRUCTURE_QUARK (RATE_CHANGE)) _handle_rate_change_msg (msg, structure, player); else if (quark == _STRUCTURE_QUARK (STREAM_CHANGE)) _handle_stream_change_msg (msg, structure, player); else if (quark == _STRUCTURE_QUARK (CURRENT_ITEM_CHANGE)) _handle_current_item_change_msg (msg, structure, player); else if (quark == _STRUCTURE_QUARK (ITEM_SUBURI_CHANGE)) _handle_item_suburi_change_msg (msg, structure, player); } static inline void _handle_element_msg (GstMessage *msg, ClapperPlayer *player) { if (gst_is_missing_plugin_message (msg)) { gchar *name, *details; guint signal_id; name = gst_missing_plugin_message_get_description (msg); details = gst_missing_plugin_message_get_installer_detail (msg); signal_id = g_signal_lookup ("missing-plugin", CLAPPER_TYPE_PLAYER); clapper_app_bus_post_desc_with_details_signal (player->app_bus, GST_OBJECT_CAST (player), signal_id, name, details); g_free (name); g_free (details); } else if (gst_message_has_name (msg, "GstCacheDownloadComplete")) { ClapperMediaItem *downloaded_item = NULL; const GstStructure *structure; const gchar *location; guint signal_id; GST_OBJECT_LOCK (player); /* Short video might be fully downloaded before playback starts */ if (player->pending_item) downloaded_item = gst_object_ref (player->pending_item); else if (player->played_item) downloaded_item = gst_object_ref (player->played_item); GST_OBJECT_UNLOCK (player); if (G_UNLIKELY (downloaded_item == NULL)) { GST_WARNING_OBJECT (player, "Download completed without media item set"); return; } structure = gst_message_get_structure (msg); location = gst_structure_get_string (structure, "location"); signal_id = g_signal_lookup ("download-complete", CLAPPER_TYPE_PLAYER); GST_INFO_OBJECT (player, "Download of %" GST_PTR_FORMAT " complete: %s", downloaded_item, location); clapper_media_item_set_cache_location (downloaded_item, location); clapper_app_bus_post_object_desc_signal (player->app_bus, GST_OBJECT_CAST (player), signal_id, GST_OBJECT_CAST (downloaded_item), location); gst_object_unref (downloaded_item); } } static inline void _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)) return; gst_message_parse_tag (msg, &tags); 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) { if (player->pending_tags) { gst_tag_list_unref (player->pending_tags); } player->pending_tags = gst_tag_list_ref (tags); } else if (G_LIKELY (player->played_item != NULL)) { clapper_media_item_update_from_tag_list (player->played_item, tags, player); } gst_tag_list_unref (tags); } static inline void _handle_toc_msg (GstMessage *msg, ClapperPlayer *player) { GstObject *src = GST_MESSAGE_SRC (msg); GstToc *toc = NULL; gboolean from_enhancer_src, updated = FALSE; /* TOC messages should only be posted by sink elements */ if (G_UNLIKELY (!src)) return; /* Either new TOC was found or previous one was updated */ gst_message_parse_toc (msg, &toc, &updated); GST_DEBUG_OBJECT (player, "Got TOC (%" GST_PTR_FORMAT ")" " 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) { if (player->pending_toc) { gst_toc_unref (player->pending_toc); } player->pending_toc = gst_toc_ref (toc); } else if (G_LIKELY (player->played_item != NULL)) { ClapperTimeline *timeline; timeline = clapper_media_item_get_timeline (player->played_item); if (clapper_timeline_set_toc (timeline, toc, updated)) { clapper_app_bus_post_refresh_timeline (player->app_bus, GST_OBJECT_CAST (player->played_item)); } } gst_toc_unref (toc); } static inline void _handle_property_notify_msg (GstMessage *msg, ClapperPlayer *player) { GstObject *src = NULL; const gchar *prop_name = NULL; const GValue *value = NULL; gst_message_parse_property_notify (msg, &src, &prop_name, &value); GST_DEBUG ("Received info about changed %s property: %s", GST_OBJECT_NAME (src), prop_name); /* Since we manually need to request elements to post this message, * any other element posting this is unlikely */ if (G_UNLIKELY (src != GST_OBJECT_CAST (player->playbin))) return; if (strcmp (prop_name, "volume") == 0) clapper_player_handle_playbin_volume_changed (player, value); else if (strcmp (prop_name, "mute") == 0) clapper_player_handle_playbin_mute_changed (player, value); else if (strcmp (prop_name, "flags") == 0) clapper_player_handle_playbin_flags_changed (player, value); else if (strcmp (prop_name, "av-offset") == 0) clapper_player_handle_playbin_av_offset_changed (player, value); else if (strcmp (prop_name, "text-offset") == 0) clapper_player_handle_playbin_text_offset_changed (player, value); else clapper_player_handle_playbin_common_prop_changed (player, prop_name); } static inline void _handle_stream_collection_msg (GstMessage *msg, ClapperPlayer *player) { GstStreamCollection *collection = NULL; GST_INFO_OBJECT (player, "Stream collection"); gst_message_parse_stream_collection (msg, &collection); clapper_player_take_stream_collection (player, collection); } static inline void _handle_streams_selected_msg (GstMessage *msg, ClapperPlayer *player) { /* NOTE: Streams selected message carries whole collection * and allows reading actually selected streams from it * via gst_message_streams_selected_* methods */ GST_INFO_OBJECT (player, "Streams selected"); if (player->use_playbin3) { guint i, n_streams = gst_message_streams_selected_get_size (msg); for (i = 0; i < n_streams; ++i) { GstStream *stream = gst_message_streams_selected_get_stream (msg, i); GstStreamType stream_type = gst_stream_get_stream_type (stream); if ((stream_type & GST_STREAM_TYPE_VIDEO) == GST_STREAM_TYPE_VIDEO) { if (!clapper_player_find_active_decoder_with_stream_id (player, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, gst_stream_get_stream_id (stream))) GST_DEBUG_OBJECT (player, "Active video decoder not found"); } else if ((stream_type & GST_STREAM_TYPE_AUDIO) == GST_STREAM_TYPE_AUDIO) { if (!clapper_player_find_active_decoder_with_stream_id (player, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, gst_stream_get_stream_id (stream))) GST_DEBUG_OBJECT (player, "Active audio decoder not found"); } } } else { /* In playbin2 we do not know real stream IDs, so * we iterate in search for all active ones */ clapper_player_playbin_update_current_decoders (player); } if (player->pending_flush) { player->pending_flush = FALSE; if (player->current_state >= GST_STATE_PAUSED) _perform_flush_seek (player); } } static inline void _handle_stream_start_msg (GstMessage *msg, ClapperPlayer *player) { guint group = 0; gboolean changed; /* We only care about our parent bin start which * happens after all sinks have started */ if (GST_MESSAGE_SRC (msg) != GST_OBJECT_CAST (player->playbin)) return; if (!gst_message_parse_group_id (msg, &group)) return; GST_INFO_OBJECT (player, "Stream start, group: %u", group); GST_OBJECT_LOCK (player); /* This should never happen, but better be safe */ if (G_UNLIKELY (player->pending_item == NULL)) { GST_ERROR_OBJECT (player, "Starting some stream, but there was no pending one!"); GST_OBJECT_UNLOCK (player); return; } changed = gst_object_replace ((GstObject **) &player->played_item, GST_OBJECT_CAST (player->pending_item)); gst_clear_object (&player->pending_item); GST_OBJECT_UNLOCK (player); if (G_LIKELY (changed)) { clapper_queue_handle_played_item_changed (player->queue, player->played_item, player->app_bus); if (clapper_player_get_have_features (player)) clapper_features_manager_trigger_played_item_changed (player->features_manager, player->played_item); } clapper_app_bus_post_refresh_streams (player->app_bus, GST_OBJECT_CAST (player)); /* Update position on start after announcing item change, * since we will not do this on state change when gapless */ clapper_player_refresh_position (player); /* With playbin2 we update all decoders at once after stream start */ if (!player->use_playbin3) clapper_player_playbin_update_current_decoders (player); if (player->pending_tags) { if (G_LIKELY (player->played_item != NULL)) clapper_media_item_update_from_tag_list (player->played_item, player->pending_tags, player); gst_clear_tag_list (&player->pending_tags); } if (player->pending_toc) { if (G_LIKELY (player->played_item != NULL)) { ClapperTimeline *timeline = clapper_media_item_get_timeline (player->played_item); if (clapper_timeline_set_toc (timeline, player->pending_toc, FALSE)) { clapper_app_bus_post_refresh_timeline (player->app_bus, GST_OBJECT_CAST (player->played_item)); } } gst_toc_unref (player->pending_toc); player->pending_toc = NULL; } } static inline void _handle_duration_changed_msg (GstMessage *msg G_GNUC_UNUSED, ClapperPlayer *player) { _update_current_duration (player); } static inline void _handle_async_done_msg (GstMessage *msg G_GNUC_UNUSED, ClapperPlayer *player) { if (player->seeking) { guint signal_id; player->seeking = FALSE; GST_DEBUG_OBJECT (player, "Seek done"); signal_id = g_signal_lookup ("seek-done", CLAPPER_TYPE_PLAYER); /* Update current position first, then announce seek done */ clapper_player_refresh_position (player); clapper_app_bus_post_simple_signal (player->app_bus, GST_OBJECT_CAST (player), signal_id); } if (player->speed_changing) { if (player->pending_speed != 0) { GST_DEBUG_OBJECT (player, "Changing rate to pending value: %.2lf -> %.2lf", player->speed, player->pending_speed); clapper_player_set_speed (player, player->pending_speed); player->pending_speed = 0; } else { clapper_player_handle_playbin_rate_changed (player, player->requested_speed); player->speed_changing = FALSE; } player->requested_speed = 0; } } static inline void _handle_latency_msg (GstMessage *msg G_GNUC_UNUSED, ClapperPlayer *player) { GST_LOG_OBJECT (player, "Latency changed"); gst_bin_recalculate_latency (GST_BIN_CAST (player->playbin)); } static inline void _handle_clock_lost_msg (GstMessage *msg, ClapperPlayer *player) { GstStateChangeReturn ret; if (player->target_state != GST_STATE_PLAYING) return; GST_DEBUG_OBJECT (player, "Clock lost"); ret = gst_element_set_state (player->playbin, GST_STATE_PAUSED); if (ret != GST_STATE_CHANGE_FAILURE) ret = gst_element_set_state (player->playbin, GST_STATE_PLAYING); if (ret == GST_STATE_CHANGE_FAILURE) { GstMessage *msg; GError *error; error = g_error_new (GST_CORE_ERROR, GST_CORE_ERROR_STATE_CHANGE, "Could not recover with changing state after clock was lost"); msg = gst_message_new_error (GST_OBJECT (player), error, NULL); _handle_error_msg (msg, player); g_error_free (error); gst_message_unref (msg); } } static inline void _handle_eos_msg (GstMessage *msg G_GNUC_UNUSED, ClapperPlayer *player) { gboolean had_error; /* EOS happens after "about-to-finish" if URI did not change. * Changing items should be done in former one while pausing * after playback here. */ GST_INFO_OBJECT (player, "EOS"); /* This is also used in another thread */ GST_OBJECT_LOCK (player); had_error = player->had_error; GST_OBJECT_UNLOCK (player); /* Error handling already changes state to READY */ if (G_UNLIKELY (had_error)) return; if (!clapper_queue_handle_eos (player->queue, player)) { player->pending_eos = TRUE; gst_element_set_state (player->playbin, GST_STATE_PAUSED); } } gboolean clapper_playbin_bus_message_func (GstBus *bus, GstMessage *msg, ClapperPlayer *player) { switch (GST_MESSAGE_TYPE (msg)) { case GST_MESSAGE_BUFFERING: _handle_buffering_msg (msg, player); break; case GST_MESSAGE_REQUEST_STATE: _handle_request_state_msg (msg, player); break; case GST_MESSAGE_STATE_CHANGED: _handle_state_changed_msg (msg, player); break; case GST_MESSAGE_APPLICATION: _handle_app_msg (msg, player); break; case GST_MESSAGE_ELEMENT: _handle_element_msg (msg, player); break; case GST_MESSAGE_TAG: _handle_tag_msg (msg, player); break; case GST_MESSAGE_TOC: _handle_toc_msg (msg, player); break; case GST_MESSAGE_PROPERTY_NOTIFY: _handle_property_notify_msg (msg, player); break; case GST_MESSAGE_STREAM_COLLECTION: _handle_stream_collection_msg (msg, player); break; case GST_MESSAGE_STREAMS_SELECTED: _handle_streams_selected_msg (msg, player); break; case GST_MESSAGE_STREAM_START: _handle_stream_start_msg (msg, player); break; case GST_MESSAGE_DURATION_CHANGED: _handle_duration_changed_msg (msg, player); break; case GST_MESSAGE_ASYNC_DONE: _handle_async_done_msg (msg, player); break; case GST_MESSAGE_LATENCY: _handle_latency_msg (msg, player); break; case GST_MESSAGE_CLOCK_LOST: _handle_clock_lost_msg (msg, player); break; case GST_MESSAGE_EOS: _handle_eos_msg (msg, player); break; case GST_MESSAGE_WARNING: _handle_warning_msg (msg, player); break; case GST_MESSAGE_ERROR: _handle_error_msg (msg, player); break; default: break; } return G_SOURCE_CONTINUE; } clapper-0.8.0/src/lib/clapper/clapper-player-private.h000066400000000000000000000122611474353110700227000ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-player.h" #include "clapper-queue.h" #include "clapper-enums.h" #include "clapper-app-bus-private.h" #include "clapper-features-manager-private.h" G_BEGIN_DECLS #define clapper_player_set_have_features(player,have) (g_atomic_int_set (&player->have_features, (gint) have)) #define clapper_player_get_have_features(player) (g_atomic_int_get (&player->have_features) == 1) #define clapper_player_get_features_manager(player) (clapper_player_get_have_features(player) ? player->features_manager : NULL) struct _ClapperPlayer { ClapperThreadedObject parent; ClapperQueue *queue; ClapperStreamList *video_streams; ClapperStreamList *audio_streams; ClapperStreamList *subtitle_streams; ClapperFeaturesManager *features_manager; gint have_features; // atomic integer /* 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; /* Will eventually become our "played_item", can be set from * different thread, thus needs a lock */ ClapperMediaItem *pending_item; /* Pending tags/toc that arrive before stream start. * To be applied to "played_item", thus no lock needed. */ GstTagList *pending_tags; GstToc *pending_toc; GstElement *playbin; GstBus *bus; ClapperAppBus *app_bus; GSource *tick_source; GstQuery *position_query; /* Must only be used from player thread */ GstState current_state; // reported from playbin GstState target_state; // state requested by user gboolean is_buffering; gdouble pending_position; // store seek before playback gdouble requested_speed, pending_speed; // store speed for consecutive rate changes /* Stream collection */ GstStreamCollection *collection; gulong stream_notify_id; /* Extra params */ gboolean use_playbin3; // when using playbin3 gboolean had_error; // so we do not do stuff after error gboolean seeking; // during seek operation gboolean speed_changing; // during rate change operation gboolean pending_eos; // when pausing due to EOS gboolean pending_flush; // after another stream selection gint eos; // atomic integer /* Set adaptive props immediately */ GstElement *adaptive_demuxer; /* Playbin2 compat */ gint n_video, n_audio, n_text; /* Props */ gboolean autoplay; gboolean mute; gdouble volume; gdouble speed; gdouble position; ClapperPlayerState state; GstElement *video_decoder; GstElement *audio_decoder; gboolean video_enabled; gboolean audio_enabled; gboolean subtitles_enabled; gchar *download_dir; gboolean download_enabled; guint start_bitrate; guint min_bitrate; guint max_bitrate; guint bandwidth; gdouble audio_offset; gdouble subtitle_offset; }; ClapperPlayer * clapper_player_get_from_ancestor (GstObject *object); gboolean clapper_player_refresh_position (ClapperPlayer *player); void clapper_player_add_tick_source (ClapperPlayer *player); void clapper_player_remove_tick_source (ClapperPlayer *player); void clapper_player_handle_playbin_state_changed (ClapperPlayer *player); void clapper_player_handle_playbin_volume_changed (ClapperPlayer *player, const GValue *value); void clapper_player_handle_playbin_mute_changed (ClapperPlayer *player, const GValue *value); void clapper_player_handle_playbin_flags_changed (ClapperPlayer *player, const GValue *value); void clapper_player_handle_playbin_av_offset_changed (ClapperPlayer *player, const GValue *value); void clapper_player_handle_playbin_text_offset_changed (ClapperPlayer *player, const GValue *value); void clapper_player_handle_playbin_common_prop_changed (ClapperPlayer *player, const gchar *prop_name); void clapper_player_handle_playbin_rate_changed (ClapperPlayer *player, gdouble speed); void clapper_player_set_pending_item (ClapperPlayer *player, ClapperMediaItem *pending_item, ClapperQueueItemChangeMode mode); void clapper_player_take_stream_collection (ClapperPlayer *player, GstStreamCollection *collection); void clapper_player_refresh_streams (ClapperPlayer *player); gboolean clapper_player_find_active_decoder_with_stream_id (ClapperPlayer *player, GstElementFactoryListType type, const gchar *stream_id); void clapper_player_playbin_update_current_decoders (ClapperPlayer *player); void clapper_player_reset (ClapperPlayer *player, gboolean pending_dispose); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-player.c000066400000000000000000002476431474353110700212410ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperPlayer: * * The media player object used for playback. * * #ClapperPlayer was written in an easy to use way, so no special GStreamer * experience is needed to get started with making various playback applications. * * Scheduling media for playback is done using a [class@Clapper.Queue] upon which * player operates. * * Player uses `GStreamer` internally and handles playback on a separate thread, while * serializing all events/commands between player and the thread it was created upon * (usually main app thread). This makes it very easy to integrate with UI toolkits * that operate on a single thread like (but not limited to) GTK. * * To listen for property changes, you can connect to property "notify" signal. */ #include #include "clapper-player.h" #include "clapper-player-private.h" #include "clapper-playbin-bus-private.h" #include "clapper-app-bus-private.h" #include "clapper-queue-private.h" #include "clapper-media-item-private.h" #include "clapper-stream-list-private.h" #include "clapper-stream-private.h" #include "clapper-video-stream-private.h" #include "clapper-audio-stream-private.h" #include "clapper-subtitle-stream-private.h" #include "clapper-enums-private.h" #include "clapper-utils-private.h" #include "../shared/clapper-shared-utils-private.h" #define DEFAULT_AUTOPLAY FALSE #define DEFAULT_MUTE FALSE #define DEFAULT_VOLUME 1.0 #define DEFAULT_SPEED 1.0 #define DEFAULT_STATE CLAPPER_PLAYER_STATE_STOPPED #define DEFAULT_VIDEO_ENABLED TRUE #define DEFAULT_AUDIO_ENABLED TRUE #define DEFAULT_SUBTITLES_ENABLED TRUE #define DEFAULT_DOWNLOAD_ENABLED FALSE #define DEFAULT_ADAPTIVE_START_BITRATE 1600000 #define GST_CAT_DEFAULT clapper_player_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define parent_class clapper_player_parent_class G_DEFINE_TYPE (ClapperPlayer, clapper_player, CLAPPER_TYPE_THREADED_OBJECT); enum { PROP_0, PROP_QUEUE, PROP_VIDEO_STREAMS, PROP_AUDIO_STREAMS, PROP_SUBTITLE_STREAMS, PROP_AUTOPLAY, PROP_POSITION, PROP_SPEED, PROP_STATE, PROP_MUTE, PROP_VOLUME, PROP_VIDEO_SINK, PROP_AUDIO_SINK, PROP_VIDEO_FILTER, PROP_AUDIO_FILTER, PROP_CURRENT_VIDEO_DECODER, PROP_CURRENT_AUDIO_DECODER, PROP_VIDEO_ENABLED, PROP_AUDIO_ENABLED, PROP_SUBTITLES_ENABLED, PROP_DOWNLOAD_DIR, PROP_DOWNLOAD_ENABLED, PROP_ADAPTIVE_START_BITRATE, PROP_ADAPTIVE_MIN_BITRATE, PROP_ADAPTIVE_MAX_BITRATE, PROP_ADAPTIVE_BANDWIDTH, PROP_AUDIO_OFFSET, PROP_SUBTITLE_OFFSET, PROP_SUBTITLE_FONT_DESC, PROP_LAST }; enum { SIGNAL_SEEK_DONE, SIGNAL_DOWNLOAD_COMPLETE, SIGNAL_MISSING_PLUGIN, SIGNAL_WARNING, SIGNAL_ERROR, SIGNAL_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static guint signals[SIGNAL_LAST] = { 0, }; /* Properties we expose through API, thus we want to emit notify signals for them */ static const gchar *playbin_watchlist[] = { "volume", "mute", "flags", "audio-sink", "video-sink", "audio-filter", "video-filter", "av-offset", "text-offset", NULL }; gboolean clapper_player_refresh_position (ClapperPlayer *self) { gint64 position = GST_CLOCK_TIME_NONE; gdouble position_dbl; gboolean changed; if (gst_element_query (self->playbin, self->position_query)) gst_query_parse_position (self->position_query, NULL, &position); if (position < 0) position = 0; position_dbl = (gdouble) position / GST_SECOND; GST_OBJECT_LOCK (self); if ((changed = !G_APPROX_VALUE (self->position, position_dbl, FLT_EPSILON))) self->position = position_dbl; GST_OBJECT_UNLOCK (self); if (changed) { GST_LOG_OBJECT (self, "Position: %" CLAPPER_TIME_MS_FORMAT, CLAPPER_TIME_MS_ARGS (position_dbl)); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_POSITION]); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_position_changed (self->features_manager, position_dbl); } return G_SOURCE_CONTINUE; } void clapper_player_add_tick_source (ClapperPlayer *self) { GST_OBJECT_LOCK (self); if (!self->tick_source) { self->tick_source = clapper_shared_utils_context_timeout_add_full ( clapper_threaded_object_get_context (CLAPPER_THREADED_OBJECT_CAST (self)), G_PRIORITY_DEFAULT_IDLE, 100, (GSourceFunc) clapper_player_refresh_position, self, NULL); GST_TRACE_OBJECT (self, "Added tick source"); } GST_OBJECT_UNLOCK (self); } void clapper_player_remove_tick_source (ClapperPlayer *self) { GST_OBJECT_LOCK (self); if (self->tick_source) { g_source_destroy (self->tick_source); g_clear_pointer (&self->tick_source, g_source_unref); GST_TRACE_OBJECT (self, "Removed tick source"); } GST_OBJECT_UNLOCK (self); } void clapper_player_handle_playbin_state_changed (ClapperPlayer *self) { ClapperPlayerState state; gboolean changed; if (self->is_buffering) { state = CLAPPER_PLAYER_STATE_BUFFERING; } else { switch (self->current_state) { case GST_STATE_PLAYING: state = CLAPPER_PLAYER_STATE_PLAYING; break; case GST_STATE_PAUSED: state = CLAPPER_PLAYER_STATE_PAUSED; break; default: state = CLAPPER_PLAYER_STATE_STOPPED; break; } } GST_OBJECT_LOCK (self); if ((changed = self->state != state)) self->state = state; GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "State changed, now: %i", state); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_STATE]); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_state_changed (self->features_manager, state); } } /* Not using common_prop_changed() because needs linear -> cubic conversion * before applying and can only be applied during playback */ void clapper_player_handle_playbin_volume_changed (ClapperPlayer *self, const GValue *value) { gdouble volume, volume_linear; gboolean changed; volume_linear = g_value_get_double (value); GST_DEBUG_OBJECT (self, "Playbin volume changed, linear: %lf", volume_linear); volume = gst_stream_volume_convert_volume ( GST_STREAM_VOLUME_FORMAT_LINEAR, GST_STREAM_VOLUME_FORMAT_CUBIC, volume_linear); GST_OBJECT_LOCK (self); if ((changed = !G_APPROX_VALUE (self->volume, volume, FLT_EPSILON))) self->volume = volume; GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Volume: %.2lf", volume); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_VOLUME]); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_volume_changed (self->features_manager, volume); } } /* Not using common_prop_changed() because can only be applied during playback */ void clapper_player_handle_playbin_mute_changed (ClapperPlayer *self, const GValue *value) { gboolean mute, changed; mute = g_value_get_boolean (value); GST_DEBUG_OBJECT (self, "Playbin mute changed"); GST_OBJECT_LOCK (self); if ((changed = self->mute != mute)) self->mute = mute; GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Mute: %s", (mute) ? "yes" : "no"); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_MUTE]); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_mute_changed (self->features_manager, mute); } } void clapper_player_handle_playbin_flags_changed (ClapperPlayer *self, const GValue *value) { gint flags; gboolean video_enabled, audio_enabled, subtitles_enabled, download_enabled; gboolean video_changed, audio_changed, subtitles_changed, download_changed; flags = g_value_get_flags (value); video_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_VIDEO) == CLAPPER_PLAYER_PLAY_FLAG_VIDEO); audio_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_AUDIO) == CLAPPER_PLAYER_PLAY_FLAG_AUDIO); subtitles_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_TEXT) == CLAPPER_PLAYER_PLAY_FLAG_TEXT); download_enabled = ((flags & CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD) == CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD); GST_OBJECT_LOCK (self); if ((video_changed = self->video_enabled != video_enabled)) self->video_enabled = video_enabled; if ((audio_changed = self->audio_enabled != audio_enabled)) self->audio_enabled = audio_enabled; if ((subtitles_changed = self->subtitles_enabled != subtitles_enabled)) self->subtitles_enabled = subtitles_enabled; if ((download_changed = self->download_enabled != download_enabled)) self->download_enabled = download_enabled; GST_OBJECT_UNLOCK (self); if (video_changed) { GST_INFO_OBJECT (self, "Video enabled: %s", (video_enabled) ? "yes" : "no"); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_VIDEO_ENABLED]); } if (audio_changed) { GST_INFO_OBJECT (self, "Audio enabled: %s", (audio_enabled) ? "yes" : "no"); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_AUDIO_ENABLED]); } if (subtitles_changed) { GST_INFO_OBJECT (self, "Subtitles enabled: %s", (subtitles_enabled) ? "yes" : "no"); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_SUBTITLES_ENABLED]); } if (download_changed) { GST_INFO_OBJECT (self, "Download enabled: %s", (download_enabled) ? "yes" : "no"); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_DOWNLOAD_ENABLED]); } } void clapper_player_handle_playbin_av_offset_changed (ClapperPlayer *self, const GValue *value) { gdouble offset = (gdouble) g_value_get_int64 (value) / GST_SECOND; gboolean changed; GST_OBJECT_LOCK (self); if ((changed = !G_APPROX_VALUE (self->audio_offset, offset, FLT_EPSILON))) self->audio_offset = offset; GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Audio offset: %.2lf", offset); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_AUDIO_OFFSET]); } } void clapper_player_handle_playbin_text_offset_changed (ClapperPlayer *self, const GValue *value) { gdouble offset = (gdouble) g_value_get_int64 (value) / GST_SECOND; gboolean changed; GST_OBJECT_LOCK (self); if ((changed = !G_APPROX_VALUE (self->subtitle_offset, offset, FLT_EPSILON))) self->subtitle_offset = offset; GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Subtitles offset: %.2lf", offset); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_SUBTITLE_OFFSET]); } } void clapper_player_handle_playbin_common_prop_changed (ClapperPlayer *self, const gchar *prop_name) { GObjectClass *gobject_class = G_OBJECT_GET_CLASS (self); GParamSpec *pspec = g_object_class_find_property (gobject_class, prop_name); if (G_LIKELY (pspec != NULL)) { GST_DEBUG_OBJECT (self, "Playbin %s changed", prop_name); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), pspec); } } void clapper_player_handle_playbin_rate_changed (ClapperPlayer *self, gdouble speed) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = !G_APPROX_VALUE (self->speed, speed, FLT_EPSILON))) self->speed = speed; GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Speed: %.2lf", speed); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_SPEED]); if (clapper_player_get_have_features (self)) clapper_features_manager_trigger_speed_changed (self->features_manager, speed); } } static void clapper_player_set_current_video_decoder (ClapperPlayer *self, GstElement *element) { gboolean changed; GST_OBJECT_LOCK (self); changed = gst_object_replace ((GstObject **) &self->video_decoder, GST_OBJECT_CAST (element)); GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Current video decoder: %" GST_PTR_FORMAT, element); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_VIDEO_DECODER]); } } static void clapper_player_set_current_audio_decoder (ClapperPlayer *self, GstElement *element) { gboolean changed; GST_OBJECT_LOCK (self); changed = gst_object_replace ((GstObject **) &self->audio_decoder, GST_OBJECT_CAST (element)); GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Current audio decoder: %" GST_PTR_FORMAT, element); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_AUDIO_DECODER]); } } void clapper_player_set_pending_item (ClapperPlayer *self, ClapperMediaItem *pending_item, ClapperQueueItemChangeMode mode) { const gchar *uri = NULL; gchar *suburi = NULL; /* We cannot do gapless/instant with pending suburi in place, * do a check and if necessary use normal mode instead */ if (mode > CLAPPER_QUEUE_ITEM_CHANGE_NORMAL) { g_object_get (self->playbin, "suburi", &suburi, NULL); if (suburi) { mode = CLAPPER_QUEUE_ITEM_CHANGE_NORMAL; g_clear_pointer (&suburi, g_free); } } /* Might be NULL (e.g. after queue is cleared) */ if (pending_item) { uri = clapper_media_item_get_playback_uri (pending_item); suburi = clapper_media_item_get_suburi (pending_item); } GST_INFO_OBJECT (self, "Changing item with mode %u, URI: \"%s\", SUBURI: \"%s\"", mode, GST_STR_NULL (uri), GST_STR_NULL (suburi)); /* We need to lock here, as this function is also called from "about-to-finish" * signal which comes from different thread and we need to change URIs in it ASAP, * so we cannot schedule an invoke of another thread there */ GST_OBJECT_LOCK (self); gst_object_replace ((GstObject **) &self->pending_item, GST_OBJECT_CAST (pending_item)); GST_OBJECT_UNLOCK (self); /* GStreamer does not support changing suburi in gapless/instant mode */ if (mode == CLAPPER_QUEUE_ITEM_CHANGE_NORMAL) g_object_set (self->playbin, "suburi", suburi, NULL); if (uri) { if (mode == CLAPPER_QUEUE_ITEM_CHANGE_INSTANT) g_object_set (self->playbin, "instant-uri", TRUE, NULL); g_object_set (self->playbin, "uri", uri, NULL); if (mode == CLAPPER_QUEUE_ITEM_CHANGE_INSTANT) g_object_set (self->playbin, "instant-uri", FALSE, NULL); } g_free (suburi); } static void _stream_notify_cb (GstStreamCollection *collection, GstStream *gst_stream, GParamSpec *pspec, ClapperPlayer *self) { GstStreamType stream_type; ClapperStream *stream = NULL; const gchar *pspec_name = g_param_spec_get_name (pspec); GstCaps *caps = NULL; GstTagList *tags = NULL; if (pspec_name == g_intern_string ("caps")) caps = gst_stream_get_caps (gst_stream); else if (pspec_name == g_intern_string ("tags")) tags = gst_stream_get_tags (gst_stream); else return; stream_type = gst_stream_get_stream_type (gst_stream); if ((stream_type & GST_STREAM_TYPE_VIDEO) == GST_STREAM_TYPE_VIDEO) { stream = clapper_stream_list_get_stream_for_gst_stream (self->video_streams, gst_stream); } else if ((stream_type & GST_STREAM_TYPE_AUDIO) == GST_STREAM_TYPE_AUDIO) { stream = clapper_stream_list_get_stream_for_gst_stream (self->audio_streams, gst_stream); } else if ((stream_type & GST_STREAM_TYPE_TEXT) == GST_STREAM_TYPE_TEXT) { stream = clapper_stream_list_get_stream_for_gst_stream (self->subtitle_streams, gst_stream); } if (G_LIKELY (stream != NULL)) { ClapperStreamClass *stream_class = CLAPPER_STREAM_GET_CLASS (stream); stream_class->internal_stream_updated (stream, caps, tags); gst_object_unref (stream); } gst_clear_caps (&caps); gst_clear_tag_list (&tags); } void clapper_player_take_stream_collection (ClapperPlayer *self, GstStreamCollection *collection) { GST_OBJECT_LOCK (self); if (self->stream_notify_id != 0) { g_signal_handler_disconnect (self->collection, self->stream_notify_id); self->stream_notify_id = 0; } gst_clear_object (&self->collection); self->collection = collection; GST_OBJECT_UNLOCK (self); } /* * Must be called from main thread! */ void clapper_player_refresh_streams (ClapperPlayer *self) { GList *vstreams = NULL, *astreams = NULL, *sstreams = NULL; guint i, n_streams; GST_TRACE_OBJECT (self, "Removing all obsolete streams"); GST_OBJECT_LOCK (self); /* We should not be connected here anymore, but better be safe */ if (G_LIKELY (self->stream_notify_id == 0)) { /* Initial update is done upon stream construction, thus * we do not have to call this callback here after connecting * (also why we connect it before constructing our streams). */ self->stream_notify_id = g_signal_connect (self->collection, "stream-notify", G_CALLBACK (_stream_notify_cb), self); } n_streams = gst_stream_collection_get_size (self->collection); for (i = 0; i < n_streams; ++i) { GstStream *gst_stream = gst_stream_collection_get_stream (self->collection, i); GstStreamType stream_type = gst_stream_get_stream_type (gst_stream); GST_LOG_OBJECT (self, "Found %" GST_PTR_FORMAT, gst_stream); if ((stream_type & GST_STREAM_TYPE_VIDEO) == GST_STREAM_TYPE_VIDEO) { vstreams = g_list_append (vstreams, clapper_video_stream_new (gst_stream)); } else if ((stream_type & GST_STREAM_TYPE_AUDIO) == GST_STREAM_TYPE_AUDIO) { astreams = g_list_append (astreams, clapper_audio_stream_new (gst_stream)); } else if ((stream_type & GST_STREAM_TYPE_TEXT) == GST_STREAM_TYPE_TEXT) { sstreams = g_list_append (sstreams, clapper_subtitle_stream_new (gst_stream)); } else { GST_WARNING_OBJECT (self, "Unhandled stream type: %s", gst_stream_type_get_name (stream_type)); } } GST_OBJECT_UNLOCK (self); clapper_stream_list_replace_streams (self->video_streams, vstreams); clapper_stream_list_replace_streams (self->audio_streams, astreams); clapper_stream_list_replace_streams (self->subtitle_streams, sstreams); /* We only want to do this once for all stream lists, so * playbin will select the same streams as we initially did */ clapper_playbin_bus_post_stream_change (self->bus); if (vstreams) g_list_free (vstreams); if (astreams) g_list_free (astreams); if (sstreams) g_list_free (sstreams); } static gboolean _iterate_decoder_pads (ClapperPlayer *self, GstElement *element, const gchar *stream_id, GstElementFactoryListType type) { GstIterator *iter; GValue value = G_VALUE_INIT; gboolean found = FALSE; iter = gst_element_iterate_src_pads (element); while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) { GstPad *decoder_pad = g_value_get_object (&value); gchar *decoder_sid = gst_pad_get_stream_id (decoder_pad); GST_DEBUG_OBJECT (self, "Decoder stream: %s", decoder_sid); if ((found = (g_strcmp0 (decoder_sid, stream_id) == 0))) { GST_DEBUG_OBJECT (self, "Found decoder for stream: %s", stream_id); if ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) == GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO) clapper_player_set_current_video_decoder (self, element); else if ((type & GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) == GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO) clapper_player_set_current_audio_decoder (self, element); } g_free (decoder_sid); g_value_unset (&value); if (found) break; } gst_iterator_free (iter); return found; } gboolean clapper_player_find_active_decoder_with_stream_id (ClapperPlayer *self, GstElementFactoryListType type, const gchar *stream_id) { GstIterator *iter; GValue value = G_VALUE_INIT; gboolean found = FALSE; GST_DEBUG_OBJECT (self, "Searching for decoder with stream: %s", stream_id); type |= GST_ELEMENT_FACTORY_TYPE_DECODER; iter = gst_bin_iterate_recurse (GST_BIN_CAST (self->playbin)); while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) { GstElement *element = g_value_get_object (&value); GstElementFactory *factory = gst_element_get_factory (element); if (factory && gst_element_factory_list_is_type (factory, type)) found = _iterate_decoder_pads (self, element, stream_id, type); g_value_unset (&value); if (found) break; } gst_iterator_free (iter); return found; } /* For playbin2 only */ void clapper_player_playbin_update_current_decoders (ClapperPlayer *self) { GstIterator *iter; GValue value = G_VALUE_INIT; gboolean found_video = FALSE, found_audio = FALSE; iter = gst_bin_iterate_all_by_element_factory_name ( GST_BIN_CAST (self->playbin), "input-selector"); while (gst_iterator_next (iter, &value) == GST_ITERATOR_OK) { GstElement *element = g_value_get_object (&value); GstPad *active_pad; g_object_get (element, "active-pad", &active_pad, NULL); if (active_pad) { gchar *stream_id; stream_id = gst_pad_get_stream_id (active_pad); gst_object_unref (active_pad); if (stream_id) { if (!found_video) { found_video = clapper_player_find_active_decoder_with_stream_id (self, GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO, stream_id); } if (!found_audio) { found_audio = clapper_player_find_active_decoder_with_stream_id (self, GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, stream_id); } g_free (stream_id); } } g_value_unset (&value); if (found_video && found_audio) break; } gst_iterator_free (iter); if (!found_video) GST_DEBUG_OBJECT (self, "Active video decoder not found"); if (!found_audio) GST_DEBUG_OBJECT (self, "Active audio decoder not found"); } static void _adaptive_demuxer_bandwidth_changed_cb (GstElement *adaptive_demuxer, GParamSpec *pspec G_GNUC_UNUSED, ClapperPlayer *self) { guint bandwidth = 0; gboolean changed; g_object_get (adaptive_demuxer, "current-bandwidth", &bandwidth, NULL); /* Skip uncalculated bandwidth from * new adaptive demuxer instance */ if (bandwidth == 0) return; GST_OBJECT_LOCK (self); if ((changed = bandwidth != self->bandwidth)) self->bandwidth = bandwidth; GST_OBJECT_UNLOCK (self); if (changed) { GST_LOG_OBJECT (self, "Adaptive bandwidth: %u", bandwidth); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_ADAPTIVE_BANDWIDTH]); } } void clapper_player_reset (ClapperPlayer *self, gboolean pending_dispose) { GST_OBJECT_LOCK (self); GST_DEBUG_OBJECT (self, "Reset"); self->had_error = FALSE; self->pending_flush = FALSE; gst_clear_object (&self->played_item); if (pending_dispose) { gst_clear_object (&self->video_decoder); gst_clear_object (&self->audio_decoder); } if (self->adaptive_demuxer) { g_signal_handlers_disconnect_by_func (self->adaptive_demuxer, _adaptive_demuxer_bandwidth_changed_cb, self); gst_clear_object (&self->adaptive_demuxer); } GST_OBJECT_UNLOCK (self); gst_clear_tag_list (&self->pending_tags); if (self->pending_toc) { gst_toc_unref (self->pending_toc); self->pending_toc = NULL; } /* Emit notify when we are not going to be disposed */ if (!pending_dispose) { /* Clear current decoders (next item might not have video/audio track) */ clapper_player_set_current_video_decoder (self, NULL); clapper_player_set_current_audio_decoder (self, NULL); } } static inline gchar * _make_download_template (ClapperPlayer *self) { gchar *download_template = NULL; GST_OBJECT_LOCK (self); if (self->download_enabled && self->download_dir) { if (g_mkdir_with_parents (self->download_dir, 0755) == 0) { download_template = g_build_filename (self->download_dir, "XXXXXX", NULL); } else { GST_ERROR_OBJECT (self, "Could not create download dir: \"%s\"", self->download_dir); } } GST_OBJECT_UNLOCK (self); return download_template; } static void _element_setup_cb (GstElement *playbin, GstElement *element, ClapperPlayer *self) { GstElementFactory *factory = gst_element_get_factory (element); const gchar *factory_name; if (G_UNLIKELY (factory == NULL)) return; 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")) { gchar *download_template; /* Only set props if we have download template */ if ((download_template = _make_download_template (self))) { g_object_set (element, "temp-template", download_template, "temp-remove", FALSE, NULL); g_free (download_template); } } else if (factory_name == g_intern_static_string ("dashdemux2") || factory_name == g_intern_static_string ("hlsdemux2")) { guint start_bitrate, min_bitrate, max_bitrate; GST_OBJECT_LOCK (self); start_bitrate = self->start_bitrate; min_bitrate = self->min_bitrate; max_bitrate = self->max_bitrate; if (self->adaptive_demuxer) { g_signal_handlers_disconnect_by_func (self->adaptive_demuxer, _adaptive_demuxer_bandwidth_changed_cb, self); } gst_object_replace ((GstObject **) &self->adaptive_demuxer, GST_OBJECT_CAST (element)); if (self->adaptive_demuxer) { g_signal_connect (self->adaptive_demuxer, "notify::current-bandwidth", G_CALLBACK (_adaptive_demuxer_bandwidth_changed_cb), self); } GST_OBJECT_UNLOCK (self); g_object_set (element, "low-watermark-time", 3 * GST_SECOND, "high-watermark-time", 10 * GST_SECOND, "start-bitrate", start_bitrate, "min-bitrate", min_bitrate, "max-bitrate", max_bitrate, NULL); } } static void _about_to_finish_cb (GstElement *playbin, ClapperPlayer *self) { gboolean had_error; GST_INFO_OBJECT (self, "About to finish"); /* This signal comes from different thread */ GST_OBJECT_LOCK (self); had_error = self->had_error; GST_OBJECT_UNLOCK (self); /* We do not want to progress playlist after error */ if (G_UNLIKELY (had_error)) return; clapper_queue_handle_about_to_finish (self->queue, self); } static void _playbin_streams_changed_cb (GstElement *playbin, ClapperPlayer *self) { GstStreamCollection *collection = gst_stream_collection_new (NULL); gint i; GST_DEBUG_OBJECT (self, "Playbin streams changed"); g_object_get (playbin, "n-video", &self->n_video, NULL); for (i = 0; i < self->n_video; ++i) { gst_stream_collection_add_stream (collection, gst_stream_new (NULL, NULL, GST_STREAM_TYPE_VIDEO, GST_STREAM_FLAG_NONE)); } g_object_get (playbin, "n-audio", &self->n_audio, NULL); for (i = 0; i < self->n_audio; ++i) { gst_stream_collection_add_stream (collection, gst_stream_new (NULL, NULL, GST_STREAM_TYPE_AUDIO, GST_STREAM_FLAG_NONE)); } g_object_get (playbin, "n-text", &self->n_text, NULL); for (i = 0; i < self->n_text; ++i) { gst_stream_collection_add_stream (collection, gst_stream_new (NULL, NULL, GST_STREAM_TYPE_TEXT, GST_STREAM_FLAG_NONE)); } clapper_player_take_stream_collection (self, collection); } static void _playbin_tags_changed (ClapperPlayer *self, gint index, gint global_index) { GstStream *gst_stream; GstStreamType stream_type; GstTagList *tags = NULL; GstPad *pad = NULL; GstCaps *caps = NULL; gst_stream = gst_stream_collection_get_stream (self->collection, global_index); stream_type = gst_stream_get_stream_type (gst_stream); if ((stream_type & GST_STREAM_TYPE_VIDEO) == GST_STREAM_TYPE_VIDEO) { g_signal_emit_by_name (self->playbin, "get-video-tags", index, &tags); g_signal_emit_by_name (self->playbin, "get-video-pad", index, &pad); } else if ((stream_type & GST_STREAM_TYPE_AUDIO) == GST_STREAM_TYPE_AUDIO) { g_signal_emit_by_name (self->playbin, "get-audio-tags", index, &tags); g_signal_emit_by_name (self->playbin, "get-audio-pad", index, &pad); } else if ((stream_type & GST_STREAM_TYPE_TEXT) == GST_STREAM_TYPE_TEXT) { g_signal_emit_by_name (self->playbin, "get-text-tags", index, &tags); g_signal_emit_by_name (self->playbin, "get-text-pad", index, &pad); } gst_stream_set_tags (gst_stream, tags); gst_clear_tag_list (&tags); if (G_LIKELY (pad != NULL)) { caps = gst_pad_get_current_caps (pad); gst_object_unref (pad); } gst_stream_set_caps (gst_stream, caps); gst_clear_caps (&caps); } static void _playbin_video_tags_changed_cb (GstElement *playbin, gint index, ClapperPlayer *self) { GST_DEBUG_OBJECT (self, "Video stream %i tags changed", index); _playbin_tags_changed (self, index, index); } static void _playbin_audio_tags_changed_cb (GstElement *playbin, gint index, ClapperPlayer *self) { GST_DEBUG_OBJECT (self, "Audio stream %i tags changed", index); _playbin_tags_changed (self, index, self->n_video + index); } static void _playbin_text_tags_changed_cb (GstElement *playbin, gint index, ClapperPlayer *self) { GST_DEBUG_OBJECT (self, "Subtitle stream %i tags changed", index); _playbin_tags_changed (self, index, self->n_video + self->n_audio + index); } static void _playbin_selected_streams_changed_cb (GstElement *playbin, GParamSpec *pspec G_GNUC_UNUSED, ClapperPlayer *self) { GstMessage *msg; gint current_video = 0, current_audio = 0, current_text = 0; gboolean success = TRUE; msg = gst_message_new_streams_selected ( GST_OBJECT_CAST (playbin), self->collection); g_object_get (playbin, "current-video", ¤t_video, "current-audio", ¤t_audio, "current-text", ¤t_text, NULL); GST_DEBUG_OBJECT (self, "Selected streams changed, video: %i, audio: %i, text: %i", current_video, current_audio, current_text); /* We cannot play text stream only, skip streams selected for now */ if (current_video < 0 && current_audio < 0) { success = FALSE; goto finish; } if (current_video >= 0) { GstStream *gst_stream = gst_stream_collection_get_stream (self->collection, current_video); if (gst_stream) gst_message_streams_selected_add (msg, gst_stream); else success = FALSE; } if (current_audio >= 0) { GstStream *gst_stream = gst_stream_collection_get_stream (self->collection, self->n_video + current_audio); if (gst_stream) gst_message_streams_selected_add (msg, gst_stream); else success = FALSE; } if (current_text >= 0) { GstStream *gst_stream = gst_stream_collection_get_stream (self->collection, self->n_video + self->n_audio + current_text); if (gst_stream) gst_message_streams_selected_add (msg, gst_stream); else success = FALSE; } finish: /* Since "current-*" is changed one at a time from signal emissions, * we might fail here to assemble everything until last signal */ if (success) gst_bus_post (self->bus, msg); else gst_message_unref (msg); } ClapperPlayer * clapper_player_get_from_ancestor (GstObject *object) { GstObject *parent = gst_object_get_parent (object); while (parent) { GstObject *tmp; if (CLAPPER_IS_PLAYER (parent)) return CLAPPER_PLAYER_CAST (parent); tmp = gst_object_get_parent (parent); gst_object_unref (parent); parent = tmp; } return NULL; } /** * clapper_player_new: * * Creates a new #ClapperPlayer instance. * * Returns: (transfer full): a new #ClapperPlayer instance. */ ClapperPlayer * clapper_player_new (void) { ClapperPlayer *player; player = g_object_new (CLAPPER_TYPE_PLAYER, NULL); gst_object_ref_sink (player); return player; } /** * clapper_player_get_queue: * @player: a #ClapperPlayer * * Get the #ClapperQueue of the player. * * The queue belongs to the player and can be accessed for as long * as #ClapperPlayer object instance it belongs to is alive. * * Returns: (transfer none): the #ClapperQueue of the player. */ ClapperQueue * clapper_player_get_queue (ClapperPlayer *self) { g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); return self->queue; } /** * clapper_player_get_video_streams: * @player: a #ClapperPlayer * * Get a list of video streams within media item. * * Returns: (transfer none): a #ClapperStreamList of video #ClapperStream. */ ClapperStreamList * clapper_player_get_video_streams (ClapperPlayer *self) { g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); return self->video_streams; } /** * clapper_player_get_audio_streams: * @player: a #ClapperPlayer * * Get a list of audio streams within media item. * * Returns: (transfer none): a #ClapperStreamList of audio #ClapperStream. */ ClapperStreamList * clapper_player_get_audio_streams (ClapperPlayer *self) { g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); return self->audio_streams; } /** * clapper_player_get_subtitle_streams: * @player: a #ClapperPlayer * * Get a list of subtitle streams within media item. * * Returns: (transfer none): a #ClapperStreamList of subtitle #ClapperStream. */ ClapperStreamList * clapper_player_get_subtitle_streams (ClapperPlayer *self) { g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); return self->subtitle_streams; } /** * clapper_player_set_autoplay: * @player: a #ClapperPlayer * @enabled: %TRUE to enable autoplay, %FALSE otherwise. * * Set the autoplay state of the player. * * When autoplay is enabled, player will always try to start * playback after current media item changes. When disabled * current playback state is preserved when changing items. */ void clapper_player_set_autoplay (ClapperPlayer *self, gboolean autoplay) { gboolean changed; g_return_if_fail (CLAPPER_IS_PLAYER (self)); GST_OBJECT_LOCK (self); if ((changed = self->autoplay != autoplay)) self->autoplay = autoplay; GST_OBJECT_UNLOCK (self); if (changed) { clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_AUTOPLAY]); } } /** * clapper_player_get_autoplay: * @player: a #ClapperPlayer * * Get the autoplay value. * * Returns: %TRUE if autoplay is enabled, %FALSE otherwise. */ gboolean clapper_player_get_autoplay (ClapperPlayer *self) { gboolean autoplay; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), DEFAULT_AUTOPLAY); GST_OBJECT_LOCK (self); autoplay = self->autoplay; GST_OBJECT_UNLOCK (self); return autoplay; } /** * clapper_player_get_position: * @player: a #ClapperPlayer * * Get the current player playback position. * * The returned value is in seconds as a decimal number. * * Returns: the position of the player. */ gdouble clapper_player_get_position (ClapperPlayer *self) { gdouble position; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), 0); GST_OBJECT_LOCK (self); position = self->position; GST_OBJECT_UNLOCK (self); return position; } /** * clapper_player_get_state: * @player: a #ClapperPlayer * * Get the current #ClapperPlayerState. * * Returns: the #ClapperPlayerState of the player. */ ClapperPlayerState clapper_player_get_state (ClapperPlayer *self) { ClapperPlayerState state; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), DEFAULT_STATE); GST_OBJECT_LOCK (self); state = self->state; GST_OBJECT_UNLOCK (self); return state; } /** * clapper_player_set_mute: * @player: a #ClapperPlayer * @mute: %TRUE if player should be muted, %FALSE otherwise. * * Set the mute state of the player. */ void clapper_player_set_mute (ClapperPlayer *self, gboolean mute) { GValue value = G_VALUE_INIT; g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_value_init (&value, G_TYPE_BOOLEAN); g_value_set_boolean (&value, mute); clapper_playbin_bus_post_set_prop (self->bus, GST_OBJECT_CAST (self->playbin), "mute", &value); } /** * clapper_player_get_mute: * @player: a #ClapperPlayer * * Get the mute state of the player. * * Returns: %TRUE if player is muted, %FALSE otherwise. */ gboolean clapper_player_get_mute (ClapperPlayer *self) { gboolean mute; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), DEFAULT_MUTE); GST_OBJECT_LOCK (self); mute = self->mute; GST_OBJECT_UNLOCK (self); return mute; } /** * clapper_player_set_volume: * @player: a #ClapperPlayer * @volume: the volume level. * * Set the volume of the player. * * The value should be within 0 - 2.0 range, where 1.0 is 100% * volume and anything above results with an overamplification. */ void clapper_player_set_volume (ClapperPlayer *self, gdouble volume) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (volume >= 0 && volume <= 2.0); clapper_playbin_bus_post_set_volume (self->bus, self->playbin, volume); } /** * clapper_player_get_volume: * @player: a #ClapperPlayer * * Get the volume of the player. * * Returns: current volume level. */ gdouble clapper_player_get_volume (ClapperPlayer *self) { gdouble volume; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), DEFAULT_VOLUME); GST_OBJECT_LOCK (self); volume = self->volume; GST_OBJECT_UNLOCK (self); return volume; } /** * clapper_player_set_speed: * @player: a #ClapperPlayer * @speed: the playback speed multiplier. * * Set the speed multiplier of the player. */ void clapper_player_set_speed (ClapperPlayer *self, gdouble speed) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (speed != 0); clapper_playbin_bus_post_rate_change (self->bus, speed); } /** * clapper_player_get_speed: * @player: a #ClapperPlayer * * Get the speed of the player used for playback. * * Returns: the playback speed multiplier. */ gdouble clapper_player_get_speed (ClapperPlayer *self) { gdouble speed; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), DEFAULT_SPEED); GST_OBJECT_LOCK (self); speed = self->speed; GST_OBJECT_UNLOCK (self); return speed; } /* XXX: Also serialized into player thread, so action order like stop() -> set_sink() -> play() is not racy */ static void clapper_player_set_playbin_prop_element (ClapperPlayer *self, const gchar *prop_name, GstElement *element) { GValue value = G_VALUE_INIT; g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (element == NULL || GST_IS_ELEMENT (element)); g_value_init (&value, GST_TYPE_ELEMENT); g_value_set_object (&value, element); clapper_playbin_bus_post_set_prop (self->bus, GST_OBJECT_CAST (self->playbin), prop_name, &value); } static GstElement * clapper_player_get_playbin_prop_element (ClapperPlayer *self, const gchar *prop_name) { GstElement *element = NULL; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); g_object_get (self->playbin, prop_name, &element, NULL); return element; } /** * clapper_player_set_video_sink: * @player: a #ClapperPlayer * @element: (nullable): a #GstElement or %NULL to use default. * * Set #GstElement to be used as video sink. */ void clapper_player_set_video_sink (ClapperPlayer *self, GstElement *element) { clapper_player_set_playbin_prop_element (self, "video-sink", element); } /** * clapper_player_get_video_sink: * @player: a #ClapperPlayer * * Get #GstElement used as video sink. * * Returns: (transfer full): #GstElement set as video sink. */ GstElement * clapper_player_get_video_sink (ClapperPlayer *self) { return clapper_player_get_playbin_prop_element (self, "video-sink"); } /** * clapper_player_set_audio_sink: * @player: a #ClapperPlayer * @element: (nullable): a #GstElement or %NULL to use default. * * Set #GstElement to be used as audio sink. */ void clapper_player_set_audio_sink (ClapperPlayer *self, GstElement *element) { clapper_player_set_playbin_prop_element (self, "audio-sink", element); } /** * clapper_player_get_audio_sink: * @player: a #ClapperPlayer * * Get #GstElement used as audio sink. * * Returns: (transfer full): #GstElement set as audio sink. */ GstElement * clapper_player_get_audio_sink (ClapperPlayer *self) { return clapper_player_get_playbin_prop_element (self, "audio-sink"); } /** * clapper_player_set_video_filter: * @player: a #ClapperPlayer * @element: (nullable): a #GstElement or %NULL for none. * * Set #GstElement to be used as video filter. */ void clapper_player_set_video_filter (ClapperPlayer *self, GstElement *element) { clapper_player_set_playbin_prop_element (self, "video-filter", element); } /** * clapper_player_get_video_filter: * @player: a #ClapperPlayer * * Get #GstElement used as video filter. * * Returns: (transfer full): #GstElement set as video filter. */ GstElement * clapper_player_get_video_filter (ClapperPlayer *self) { return clapper_player_get_playbin_prop_element (self, "video-filter"); } /** * clapper_player_set_audio_filter: * @player: a #ClapperPlayer * @element: (nullable): a #GstElement or %NULL for none. * * Set #GstElement to be used as audio filter. */ void clapper_player_set_audio_filter (ClapperPlayer *self, GstElement *element) { clapper_player_set_playbin_prop_element (self, "audio-filter", element); } /** * clapper_player_get_audio_filter: * @player: a #ClapperPlayer * * Get #GstElement used as audio filter. * * Returns: (transfer full): #GstElement set as audio filter. */ GstElement * clapper_player_get_audio_filter (ClapperPlayer *self) { return clapper_player_get_playbin_prop_element (self, "audio-filter"); } /** * clapper_player_get_current_video_decoder: * @player: a #ClapperPlayer * * Get #GstElement currently used as video decoder. * * Returns: (transfer full): #GstElement currently used as video decoder. */ GstElement * clapper_player_get_current_video_decoder (ClapperPlayer *self) { GstElement *element = NULL; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); GST_OBJECT_LOCK (self); if (self->video_decoder) element = gst_object_ref (self->video_decoder); GST_OBJECT_UNLOCK (self); return element; } /** * clapper_player_get_current_audio_decoder: * @player: a #ClapperPlayer * * Get #GstElement currently used as audio decoder. * * Returns: (transfer full): #GstElement currently used as audio decoder. */ GstElement * clapper_player_get_current_audio_decoder (ClapperPlayer *self) { GstElement *element = NULL; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); GST_OBJECT_LOCK (self); if (self->audio_decoder) element = gst_object_ref (self->audio_decoder); GST_OBJECT_UNLOCK (self); return element; } /** * clapper_player_set_video_enabled: * @player: a #ClapperPlayer * @enabled: whether enabled * * Set whether enable video stream. */ void clapper_player_set_video_enabled (ClapperPlayer *self, gboolean enabled) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); clapper_playbin_bus_post_set_play_flag (self->bus, CLAPPER_PLAYER_PLAY_FLAG_VIDEO, enabled); } /** * clapper_player_get_video_enabled: * @player: a #ClapperPlayer * * Get whether video stream is enabled. * * Returns: %TRUE if enabled, %FALSE otherwise. */ gboolean clapper_player_get_video_enabled (ClapperPlayer *self) { gboolean enabled; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), FALSE); GST_OBJECT_LOCK (self); enabled = self->video_enabled; GST_OBJECT_UNLOCK (self); return enabled; } /** * clapper_player_set_audio_enabled: * @player: a #ClapperPlayer * @enabled: whether enabled * * Set whether enable audio stream. */ void clapper_player_set_audio_enabled (ClapperPlayer *self, gboolean enabled) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); clapper_playbin_bus_post_set_play_flag (self->bus, CLAPPER_PLAYER_PLAY_FLAG_AUDIO, enabled); } /** * clapper_player_get_audio_enabled: * @player: a #ClapperPlayer * * Get whether audio stream is enabled. * * Returns: %TRUE if enabled, %FALSE otherwise. */ gboolean clapper_player_get_audio_enabled (ClapperPlayer *self) { gboolean enabled; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), FALSE); GST_OBJECT_LOCK (self); enabled = self->audio_enabled; GST_OBJECT_UNLOCK (self); return enabled; } /** * clapper_player_set_subtitles_enabled: * @player: a #ClapperPlayer * @enabled: whether enabled * * Set whether subtitles should be shown if any. */ void clapper_player_set_subtitles_enabled (ClapperPlayer *self, gboolean enabled) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); clapper_playbin_bus_post_set_play_flag (self->bus, CLAPPER_PLAYER_PLAY_FLAG_TEXT, enabled); } /** * clapper_player_get_subtitles_enabled: * @player: a #ClapperPlayer * * Get whether subtitles are to be shown when available. * * Returns: %TRUE if enabled, %FALSE otherwise. */ gboolean clapper_player_get_subtitles_enabled (ClapperPlayer *self) { gboolean enabled; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), FALSE); GST_OBJECT_LOCK (self); enabled = self->subtitles_enabled; GST_OBJECT_UNLOCK (self); return enabled; } /** * clapper_player_set_download_dir: * @player: a #ClapperPlayer * @path: (type filename): the path of a directory to use for media downloads * * Set a directory that @player will use to store downloads. * * See [property@Clapper.Player:download-enabled] description for more * info how this works. * * Since: 0.8 */ void clapper_player_set_download_dir (ClapperPlayer *self, const gchar *path) { gboolean changed; g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (path != NULL); GST_OBJECT_LOCK (self); changed = g_set_str (&self->download_dir, path); GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Current download dir: %s", path); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_DOWNLOAD_DIR]); } } /** * clapper_player_get_download_dir: * @player: a #ClapperPlayer * * Get path to a directory set for media downloads. * * Returns: (type filename) (transfer full) (nullable): the path of a directory * set for media downloads or %NULL if no directory was set yet. * * Since: 0.8 */ gchar * clapper_player_get_download_dir (ClapperPlayer *self) { gchar *download_dir; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); GST_OBJECT_LOCK (self); download_dir = g_strdup (self->download_dir); GST_OBJECT_UNLOCK (self); return download_dir; } /** * clapper_player_set_download_enabled: * @player: a #ClapperPlayer * @enabled: whether enabled * * Set whether player should attempt progressive download buffering. * * For this to actually work a [property@Clapper.Player:download-dir] * must also be set. * * Since: 0.8 */ void clapper_player_set_download_enabled (ClapperPlayer *self, gboolean enabled) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); clapper_playbin_bus_post_set_play_flag (self->bus, CLAPPER_PLAYER_PLAY_FLAG_DOWNLOAD, enabled); } /** * clapper_player_get_download_enabled: * @player: a #ClapperPlayer * * Get whether progressive download buffering is enabled. * * Returns: %TRUE if enabled, %FALSE otherwise. * * Since: 0.8 */ gboolean clapper_player_get_download_enabled (ClapperPlayer *self) { gboolean enabled; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), FALSE); GST_OBJECT_LOCK (self); enabled = self->download_enabled; GST_OBJECT_UNLOCK (self); return enabled; } static void _set_adaptive_bitrate (ClapperPlayer *self, guint *internal_ptr, const gchar *prop_name, guint bitrate, GParamSpec *pspec) { GstElement *element = NULL; gboolean changed; if (!self->use_playbin3) { GST_WARNING_OBJECT (self, "Setting adaptive-%s when using playbin2" " has no effect", prop_name); } GST_OBJECT_LOCK (self); if ((changed = (*internal_ptr != bitrate))) { *internal_ptr = bitrate; if (self->adaptive_demuxer) element = gst_object_ref (self->adaptive_demuxer); } GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Set adaptive-%s: %u", prop_name, bitrate); if (element) g_object_set (element, prop_name, bitrate, NULL); clapper_app_bus_post_prop_notify (self->app_bus, GST_OBJECT_CAST (self), pspec); } gst_clear_object (&element); } /** * clapper_player_set_adaptive_start_bitrate: * @player: a #ClapperPlayer * @bitrate: a bitrate to set (bits/s) * * Set initial bitrate to select when starting adaptive * streaming such as DASH or HLS. * * Since: 0.8 */ void clapper_player_set_adaptive_start_bitrate (ClapperPlayer *self, guint bitrate) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); _set_adaptive_bitrate (self, &self->start_bitrate, "start-bitrate", bitrate, param_specs[PROP_ADAPTIVE_START_BITRATE]); } /** * clapper_player_get_adaptive_start_bitrate: * @player: a #ClapperPlayer * * Get currently set initial bitrate (bits/s) for adaptive streaming. * * Returns: the start bitrate value. * * Since: 0.8 */ guint clapper_player_get_adaptive_start_bitrate (ClapperPlayer *self) { guint bitrate; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), 0); GST_OBJECT_LOCK (self); bitrate = self->start_bitrate; GST_OBJECT_UNLOCK (self); return bitrate; } /** * clapper_player_set_adaptive_min_bitrate: * @player: a #ClapperPlayer * @bitrate: a bitrate to set (bits/s) * * Set minimal bitrate to select for adaptive streaming * such as DASH or HLS. * * Since: 0.8 */ void clapper_player_set_adaptive_min_bitrate (ClapperPlayer *self, guint bitrate) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); _set_adaptive_bitrate (self, &self->min_bitrate, "min-bitrate", bitrate, param_specs[PROP_ADAPTIVE_MIN_BITRATE]); } /** * clapper_player_get_adaptive_min_bitrate: * @player: a #ClapperPlayer * * Get currently set minimal bitrate (bits/s) for adaptive streaming. * * Returns: the minimal bitrate value. * * Since: 0.8 */ guint clapper_player_get_adaptive_min_bitrate (ClapperPlayer *self) { guint bitrate; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), 0); GST_OBJECT_LOCK (self); bitrate = self->min_bitrate; GST_OBJECT_UNLOCK (self); return bitrate; } /** * clapper_player_set_adaptive_max_bitrate: * @player: a #ClapperPlayer * @bitrate: a bitrate to set (bits/s) * * Set maximal bitrate to select for adaptive streaming * such as DASH or HLS. * * Since: 0.8 */ void clapper_player_set_adaptive_max_bitrate (ClapperPlayer *self, guint bitrate) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); _set_adaptive_bitrate (self, &self->max_bitrate, "max-bitrate", bitrate, param_specs[PROP_ADAPTIVE_MAX_BITRATE]); } /** * clapper_player_get_adaptive_max_bitrate: * @player: a #ClapperPlayer * * Get currently set maximal bitrate (bits/s) for adaptive streaming. * * Returns: the maximal bitrate value. * * Since: 0.8 */ guint clapper_player_get_adaptive_max_bitrate (ClapperPlayer *self) { guint bitrate; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), 0); GST_OBJECT_LOCK (self); bitrate = self->max_bitrate; GST_OBJECT_UNLOCK (self); return bitrate; } /** * clapper_player_get_adaptive_bandwidth: * @player: a #ClapperPlayer * * Get last fragment download bandwidth (bits/s) during * adaptive streaming. * * Returns: the adaptive bandwidth. * * Since: 0.8 */ guint clapper_player_get_adaptive_bandwidth (ClapperPlayer *self) { guint bandwidth; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), 0); GST_OBJECT_LOCK (self); bandwidth = self->bandwidth; GST_OBJECT_UNLOCK (self); return bandwidth; } /** * clapper_player_set_audio_offset: * @player: a #ClapperPlayer * @offset: a decimal audio offset (in seconds) * * Set synchronisation offset between the audio stream and video. * * Positive values make the audio ahead of the video and negative * values make the audio go behind the video. */ void clapper_player_set_audio_offset (ClapperPlayer *self, gdouble offset) { GValue value = G_VALUE_INIT; g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (offset >= G_MININT64 && offset <= G_MAXINT64); g_value_init (&value, G_TYPE_INT64); g_value_set_int64 (&value, (gint64) (offset * GST_SECOND)); clapper_playbin_bus_post_set_prop (self->bus, GST_OBJECT_CAST (self->playbin), "av-offset", &value); } /** * clapper_player_get_audio_offset: * @player: a #ClapperPlayer * * Get the currently set audio stream offset. * * The returned value is in seconds as a decimal number. * * Returns: the audio stream offset. */ gdouble clapper_player_get_audio_offset (ClapperPlayer *self) { gdouble offset; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), 0); GST_OBJECT_LOCK (self); offset = self->audio_offset; GST_OBJECT_UNLOCK (self); return offset; } /** * clapper_player_set_subtitle_offset: * @player: a #ClapperPlayer * @offset: a decimal subtitle stream offset (in seconds) * * Set synchronisation offset between the subtitle stream and video. * * Positive values make the subtitles ahead of the video and negative * values make the subtitles go behind the video. */ void clapper_player_set_subtitle_offset (ClapperPlayer *self, gdouble offset) { GValue value = G_VALUE_INIT; g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (offset >= G_MININT64 && offset <= G_MAXINT64); g_value_init (&value, G_TYPE_INT64); g_value_set_int64 (&value, (gint64) (offset * GST_SECOND)); clapper_playbin_bus_post_set_prop (self->bus, GST_OBJECT_CAST (self->playbin), "text-offset", &value); } /** * clapper_player_get_subtitle_offset: * @player: a #ClapperPlayer * * Get the currently set subtitle stream offset. * * The returned value is in seconds as a decimal number. * * Returns: the subtitle stream offset. */ gdouble clapper_player_get_subtitle_offset (ClapperPlayer *self) { gdouble offset; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), 0); GST_OBJECT_LOCK (self); offset = self->subtitle_offset; GST_OBJECT_UNLOCK (self); return offset; } /** * clapper_player_set_subtitle_font_desc: * @player: a #ClapperPlayer * @font_desc: Font description * * Set Pango font description to be used for subtitle stream rendering. */ void clapper_player_set_subtitle_font_desc (ClapperPlayer *self, const gchar *font_desc) { GValue value = G_VALUE_INIT; g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_value_init (&value, G_TYPE_STRING); g_value_set_string (&value, font_desc); clapper_playbin_bus_post_set_prop (self->bus, GST_OBJECT_CAST (self->playbin), "subtitle-font-desc", &value); } /** * clapper_player_get_subtitle_font_desc: * @player: a #ClapperPlayer * * Get the currently set font description used for subtitle stream rendering. * * Returns: (transfer full): the subtitle font description. */ gchar * clapper_player_get_subtitle_font_desc (ClapperPlayer *self) { gchar *font_desc = NULL; g_return_val_if_fail (CLAPPER_IS_PLAYER (self), NULL); g_object_get (self->playbin, "subtitle-font-desc", &font_desc, NULL); return font_desc; } /** * clapper_player_play: * @player: a #ClapperPlayer * * Either start or resume the playback of current media item. * * This function will queue a request for the underlaying #GStreamer * pipeline to go into `PLAYING` state. */ void clapper_player_play (ClapperPlayer *self) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); clapper_playbin_bus_post_request_state (self->bus, self, GST_STATE_PLAYING); } /** * clapper_player_pause: * @player: a #ClapperPlayer * * Pause the playback of current media item. * * This function will queue a request for the underlaying #GStreamer * pipeline to go into `PAUSED` state, thus can also be used on a not * yet started video to go into `PAUSED` state first. */ void clapper_player_pause (ClapperPlayer *self) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); clapper_playbin_bus_post_request_state (self->bus, self, GST_STATE_PAUSED); } /** * clapper_player_stop: * @player: a #ClapperPlayer * * Stop the playback of current media item. * * This function will queue a request for the underlaying #GStreamer * pipeline to go into `READY` state. */ void clapper_player_stop (ClapperPlayer *self) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); clapper_playbin_bus_post_request_state (self->bus, self, GST_STATE_READY); } /** * clapper_player_seek: * @player: a #ClapperPlayer * @position: a decimal number with position to seek to (in seconds) * * Request the player to perform a seek operation. * * This function will use [enum@Clapper.PlayerSeekMethod.NORMAL] as a * seeking method. If you wish to specify what method to use per seeking * request, use [method@Clapper.Player.seek_custom] instead. * * Note that seeking requests are per selected media item. Seeking * requests will be ignored if player is stopped. You need to at least * call [method@Clapper.Player.pause] before seeking and then your requested * seek will be handled if item could be played. */ void clapper_player_seek (ClapperPlayer *self, gdouble position) { clapper_player_seek_custom (self, position, CLAPPER_PLAYER_SEEK_METHOD_NORMAL); } /** * clapper_player_seek_custom: * @player: a #ClapperPlayer * @position: a decimal number with position to seek to (in seconds) * @method: a #ClapperPlayerSeekMethod * * Request the player to perform a seek operation. * * Same as [method@Clapper.Player.seek], but also allows to specify * [enum@Clapper.PlayerSeekMethod] to use for seek. */ void clapper_player_seek_custom (ClapperPlayer *self, gdouble position, ClapperPlayerSeekMethod method) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (position >= 0); clapper_playbin_bus_post_seek (self->bus, position, method); } /** * clapper_player_add_feature: * @player: a #ClapperPlayer * @feature: a #ClapperFeature * * Add another #ClapperFeature to the player. */ void clapper_player_add_feature (ClapperPlayer *self, ClapperFeature *feature) { g_return_if_fail (CLAPPER_IS_PLAYER (self)); g_return_if_fail (CLAPPER_IS_FEATURE (feature)); GST_OBJECT_LOCK (self); if (!self->features_manager) self->features_manager = clapper_features_manager_new (); GST_OBJECT_UNLOCK (self); /* Once a feature is added, we always have features manager object * and we can avoid player object locking to check that by using * clapper_player_get_have_features() which is atomic */ clapper_player_set_have_features (self, TRUE); clapper_features_manager_add_feature (self->features_manager, feature, GST_OBJECT (self)); } static void clapper_player_thread_start (ClapperThreadedObject *threaded_object) { ClapperPlayer *self = CLAPPER_PLAYER_CAST (threaded_object); const gchar *env, *playbin_str; gint i; GST_TRACE_OBJECT (threaded_object, "Player thread start"); if (!(env = g_getenv ("USE_PLAYBIN3"))) // global GStreamer override if (!(env = g_getenv ("CLAPPER_USE_PLAYBIN3"))) // Clapper override env = g_getenv ("GST_CLAPPER_USE_PLAYBIN3"); // compat self->use_playbin3 = (!env || g_str_has_prefix (env, "1")); playbin_str = (self->use_playbin3) ? "playbin3" : "playbin"; if (!(self->playbin = gst_element_factory_make (playbin_str, NULL))) { g_error ("Clapper: \"%s\" element not found, please check your setup", playbin_str); g_assert_not_reached (); return; } gst_object_ref_sink (self->playbin); for (i = 0; playbin_watchlist[i]; ++i) gst_element_add_property_notify_watch (self->playbin, playbin_watchlist[i], TRUE); g_signal_connect (self->playbin, "element-setup", G_CALLBACK (_element_setup_cb), self); g_signal_connect (self->playbin, "about-to-finish", G_CALLBACK (_about_to_finish_cb), self); if (!self->use_playbin3) { g_signal_connect (self->playbin, "video-changed", G_CALLBACK (_playbin_streams_changed_cb), self); g_signal_connect (self->playbin, "audio-changed", G_CALLBACK (_playbin_streams_changed_cb), self); g_signal_connect (self->playbin, "text-changed", G_CALLBACK (_playbin_streams_changed_cb), self); g_signal_connect (self->playbin, "video-tags-changed", G_CALLBACK (_playbin_video_tags_changed_cb), self); g_signal_connect (self->playbin, "audio-tags-changed", G_CALLBACK (_playbin_audio_tags_changed_cb), self); g_signal_connect (self->playbin, "text-tags-changed", G_CALLBACK (_playbin_text_tags_changed_cb), self); g_signal_connect (self->playbin, "notify::current-video", G_CALLBACK (_playbin_selected_streams_changed_cb), self); g_signal_connect (self->playbin, "notify::current-audio", G_CALLBACK (_playbin_selected_streams_changed_cb), self); g_signal_connect (self->playbin, "notify::current-text", G_CALLBACK (_playbin_selected_streams_changed_cb), self); } self->bus = gst_element_get_bus (self->playbin); gst_bus_add_watch (self->bus, (GstBusFunc) clapper_playbin_bus_message_func, self); } static void clapper_player_thread_stop (ClapperThreadedObject *threaded_object) { ClapperPlayer *self = CLAPPER_PLAYER_CAST (threaded_object); GST_TRACE_OBJECT (threaded_object, "Player thread stop"); clapper_player_remove_tick_source (self); gst_bus_set_flushing (self->bus, TRUE); gst_bus_remove_watch (self->bus); gst_bus_set_flushing (GST_BUS_CAST (self->app_bus), TRUE); gst_bus_remove_watch (GST_BUS_CAST (self->app_bus)); clapper_player_reset (self, TRUE); gst_element_set_state (self->playbin, GST_STATE_NULL); gst_clear_object (&self->bus); gst_clear_object (&self->app_bus); gst_clear_object (&self->playbin); gst_clear_object (&self->collection); } static void clapper_player_init (ClapperPlayer *self) { self->queue = clapper_queue_new (); gst_object_set_parent (GST_OBJECT_CAST (self->queue), GST_OBJECT_CAST (self)); self->video_streams = clapper_stream_list_new (); gst_object_set_parent (GST_OBJECT_CAST (self->video_streams), GST_OBJECT_CAST (self)); self->audio_streams = clapper_stream_list_new (); gst_object_set_parent (GST_OBJECT_CAST (self->audio_streams), GST_OBJECT_CAST (self)); self->subtitle_streams = clapper_stream_list_new (); gst_object_set_parent (GST_OBJECT_CAST (self->subtitle_streams), GST_OBJECT_CAST (self)); self->position_query = gst_query_new_position (GST_FORMAT_TIME); self->current_state = GST_STATE_NULL; self->target_state = GST_STATE_READY; self->autoplay = DEFAULT_AUTOPLAY; self->mute = DEFAULT_MUTE; self->volume = DEFAULT_VOLUME; self->speed = DEFAULT_SPEED; self->state = DEFAULT_STATE; self->video_enabled = DEFAULT_VIDEO_ENABLED; self->audio_enabled = DEFAULT_AUDIO_ENABLED; self->subtitles_enabled = DEFAULT_SUBTITLES_ENABLED; self->download_enabled = DEFAULT_DOWNLOAD_ENABLED; self->start_bitrate = DEFAULT_ADAPTIVE_START_BITRATE; } static void clapper_player_constructed (GObject *object) { ClapperPlayer *self = CLAPPER_PLAYER_CAST (object); GST_OBJECT_LOCK (self); self->app_bus = clapper_app_bus_new (); GST_OBJECT_UNLOCK (self); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_player_dispose (GObject *object) { ClapperPlayer *self = CLAPPER_PLAYER_CAST (object); GST_OBJECT_LOCK (self); if (self->stream_notify_id != 0) { g_signal_handler_disconnect (self->collection, self->stream_notify_id); self->stream_notify_id = 0; } GST_OBJECT_UNLOCK (self); /* Parent class will wait for player thread to stop running */ G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_player_finalize (GObject *object) { ClapperPlayer *self = CLAPPER_PLAYER_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); gst_object_unparent (GST_OBJECT_CAST (self->queue)); gst_object_unref (self->queue); gst_object_unparent (GST_OBJECT_CAST (self->video_streams)); gst_object_unref (self->video_streams); gst_object_unparent (GST_OBJECT_CAST (self->audio_streams)); gst_object_unref (self->audio_streams); gst_object_unparent (GST_OBJECT_CAST (self->subtitle_streams)); gst_object_unref (self->subtitle_streams); gst_query_unref (self->position_query); gst_clear_object (&self->collection); gst_clear_object (&self->features_manager); gst_clear_object (&self->pending_item); gst_clear_object (&self->played_item); g_free (self->download_dir); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_player_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperPlayer *self = CLAPPER_PLAYER_CAST (object); switch (prop_id) { case PROP_QUEUE: g_value_set_object (value, clapper_player_get_queue (self)); break; case PROP_VIDEO_STREAMS: g_value_set_object (value, clapper_player_get_video_streams (self)); break; case PROP_AUDIO_STREAMS: g_value_set_object (value, clapper_player_get_audio_streams (self)); break; case PROP_SUBTITLE_STREAMS: g_value_set_object (value, clapper_player_get_subtitle_streams (self)); break; case PROP_AUTOPLAY: g_value_set_boolean (value, clapper_player_get_autoplay (self)); break; case PROP_POSITION: g_value_set_double (value, clapper_player_get_position (self)); break; case PROP_SPEED: g_value_set_double (value, clapper_player_get_speed (self)); break; case PROP_STATE: g_value_set_enum (value, clapper_player_get_state (self)); break; case PROP_MUTE: g_value_set_boolean (value, clapper_player_get_mute (self)); break; case PROP_VOLUME: g_value_set_double (value, clapper_player_get_volume (self)); break; case PROP_AUDIO_SINK: g_value_take_object (value, clapper_player_get_audio_sink (self)); break; case PROP_VIDEO_SINK: g_value_take_object (value, clapper_player_get_video_sink (self)); break; case PROP_AUDIO_FILTER: g_value_take_object (value, clapper_player_get_audio_filter (self)); break; case PROP_VIDEO_FILTER: g_value_take_object (value, clapper_player_get_video_filter (self)); break; case PROP_CURRENT_AUDIO_DECODER: g_value_take_object (value, clapper_player_get_current_audio_decoder (self)); break; case PROP_CURRENT_VIDEO_DECODER: g_value_take_object (value, clapper_player_get_current_video_decoder (self)); break; case PROP_VIDEO_ENABLED: g_value_set_boolean (value, clapper_player_get_video_enabled (self)); break; case PROP_AUDIO_ENABLED: g_value_set_boolean (value, clapper_player_get_audio_enabled (self)); break; case PROP_SUBTITLES_ENABLED: g_value_set_boolean (value, clapper_player_get_subtitles_enabled (self)); break; case PROP_DOWNLOAD_DIR: g_value_take_string (value, clapper_player_get_download_dir (self)); break; case PROP_DOWNLOAD_ENABLED: g_value_set_boolean (value, clapper_player_get_download_enabled (self)); break; case PROP_ADAPTIVE_START_BITRATE: g_value_set_uint (value, clapper_player_get_adaptive_start_bitrate (self)); break; case PROP_ADAPTIVE_MIN_BITRATE: g_value_set_uint (value, clapper_player_get_adaptive_min_bitrate (self)); break; case PROP_ADAPTIVE_MAX_BITRATE: g_value_set_uint (value, clapper_player_get_adaptive_max_bitrate (self)); break; case PROP_ADAPTIVE_BANDWIDTH: g_value_set_uint (value, clapper_player_get_adaptive_bandwidth (self)); break; case PROP_AUDIO_OFFSET: g_value_set_double (value, clapper_player_get_audio_offset (self)); break; case PROP_SUBTITLE_OFFSET: g_value_set_double (value, clapper_player_get_subtitle_offset (self)); break; case PROP_SUBTITLE_FONT_DESC: g_value_take_string (value, clapper_player_get_subtitle_font_desc (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_player_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperPlayer *self = CLAPPER_PLAYER_CAST (object); switch (prop_id) { case PROP_AUTOPLAY: clapper_player_set_autoplay (self, g_value_get_boolean (value)); break; case PROP_SPEED: clapper_player_set_speed (self, g_value_get_double (value)); break; case PROP_MUTE: clapper_player_set_mute (self, g_value_get_boolean (value)); break; case PROP_VOLUME: clapper_player_set_volume (self, g_value_get_double (value)); break; case PROP_AUDIO_SINK: clapper_player_set_audio_sink (self, g_value_get_object (value)); break; case PROP_VIDEO_SINK: clapper_player_set_video_sink (self, g_value_get_object (value)); break; case PROP_AUDIO_FILTER: clapper_player_set_audio_filter (self, g_value_get_object (value)); break; case PROP_VIDEO_FILTER: clapper_player_set_video_filter (self, g_value_get_object (value)); break; case PROP_VIDEO_ENABLED: clapper_player_set_video_enabled (self, g_value_get_boolean (value)); break; case PROP_AUDIO_ENABLED: clapper_player_set_audio_enabled (self, g_value_get_boolean (value)); break; case PROP_SUBTITLES_ENABLED: clapper_player_set_subtitles_enabled (self, g_value_get_boolean (value)); break; case PROP_DOWNLOAD_DIR: clapper_player_set_download_dir (self, g_value_get_string (value)); break; case PROP_DOWNLOAD_ENABLED: clapper_player_set_download_enabled (self, g_value_get_boolean (value)); break; case PROP_ADAPTIVE_START_BITRATE: clapper_player_set_adaptive_start_bitrate (self, g_value_get_uint (value)); break; case PROP_ADAPTIVE_MIN_BITRATE: clapper_player_set_adaptive_min_bitrate (self, g_value_get_uint (value)); break; case PROP_ADAPTIVE_MAX_BITRATE: clapper_player_set_adaptive_max_bitrate (self, g_value_get_uint (value)); break; case PROP_AUDIO_OFFSET: clapper_player_set_audio_offset (self, g_value_get_double (value)); break; case PROP_SUBTITLE_OFFSET: clapper_player_set_subtitle_offset (self, g_value_get_double (value)); break; case PROP_SUBTITLE_FONT_DESC: clapper_player_set_subtitle_font_desc (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_player_class_init (ClapperPlayerClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperplayer", 0, "Clapper Player"); gobject_class->constructed = clapper_player_constructed; gobject_class->get_property = clapper_player_get_property; gobject_class->set_property = clapper_player_set_property; gobject_class->dispose = clapper_player_dispose; gobject_class->finalize = clapper_player_finalize; /** * ClapperPlayer:queue: * * Clapper playback queue. */ param_specs[PROP_QUEUE] = g_param_spec_object ("queue", NULL, NULL, CLAPPER_TYPE_QUEUE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:video-streams: * * List of currently available video streams. */ param_specs[PROP_VIDEO_STREAMS] = g_param_spec_object ("video-streams", NULL, NULL, CLAPPER_TYPE_STREAM_LIST, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:audio-streams: * * List of currently available audio streams. */ param_specs[PROP_AUDIO_STREAMS] = g_param_spec_object ("audio-streams", NULL, NULL, CLAPPER_TYPE_STREAM_LIST, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:subtitle-streams: * * List of currently available subtitle streams. */ param_specs[PROP_SUBTITLE_STREAMS] = g_param_spec_object ("subtitle-streams", NULL, NULL, CLAPPER_TYPE_STREAM_LIST, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:autoplay: * * Always try to start playback after media item changes. */ param_specs[PROP_AUTOPLAY] = g_param_spec_boolean ("autoplay", NULL, NULL, DEFAULT_AUTOPLAY, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:position: * * Current playback position as a decimal number in seconds. */ param_specs[PROP_POSITION] = g_param_spec_double ("position", NULL, NULL, 0, G_MAXDOUBLE, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:speed: * * Current playback speed. */ param_specs[PROP_SPEED] = g_param_spec_double ("speed", NULL, NULL, G_MINDOUBLE, G_MAXDOUBLE, DEFAULT_SPEED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:state: * * Current playback state. */ param_specs[PROP_STATE] = g_param_spec_enum ("state", NULL, NULL, CLAPPER_TYPE_PLAYER_STATE, DEFAULT_STATE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:mute: * * Mute audio without changing volume. */ param_specs[PROP_MUTE] = g_param_spec_boolean ("mute", NULL, NULL, DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:volume: * * Current volume as a decimal number (1.0 = 100%). * * Note that #ClapperPlayer uses a CUBIC volume scale, meaning * that this property value reflects human hearing level and can * be easily bound to volume sliders as-is. */ param_specs[PROP_VOLUME] = g_param_spec_double ("volume", NULL, NULL, 0, 2.0, DEFAULT_VOLUME, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:video-sink: * * Video sink to use (autovideosink by default). */ param_specs[PROP_VIDEO_SINK] = g_param_spec_object ("video-sink", NULL, NULL, GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:audio-sink: * * Audio sink to use (autoaudiosink by default). */ param_specs[PROP_AUDIO_SINK] = g_param_spec_object ("audio-sink", NULL, NULL, GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:video-filter: * * Optional video filter to use (none by default). */ param_specs[PROP_VIDEO_FILTER] = g_param_spec_object ("video-filter", NULL, NULL, GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:audio-filter: * * Optional audio filter to use (none by default). */ param_specs[PROP_AUDIO_FILTER] = g_param_spec_object ("audio-filter", NULL, NULL, GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:current-video-decoder: * * Currently used video decoder. */ param_specs[PROP_CURRENT_VIDEO_DECODER] = g_param_spec_object ("current-video-decoder", NULL, NULL, GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:current-audio-decoder: * * Currently used audio decoder. */ param_specs[PROP_CURRENT_AUDIO_DECODER] = g_param_spec_object ("current-audio-decoder", NULL, NULL, GST_TYPE_ELEMENT, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:video-enabled: * * Whether video stream is enabled. */ param_specs[PROP_VIDEO_ENABLED] = g_param_spec_boolean ("video-enabled", NULL, NULL, DEFAULT_VIDEO_ENABLED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:audio-enabled: * * Whether audio stream is enabled. */ param_specs[PROP_AUDIO_ENABLED] = g_param_spec_boolean ("audio-enabled", NULL, NULL, DEFAULT_AUDIO_ENABLED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:subtitles-enabled: * * Whether subtitles stream is enabled. */ param_specs[PROP_SUBTITLES_ENABLED] = g_param_spec_boolean ("subtitles-enabled", NULL, NULL, DEFAULT_SUBTITLES_ENABLED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:download-dir: * * A directory that @player will use to download network content * when [property@Clapper.Player:download-enabled] is set to %TRUE. * * If directory at @path does not exist, it will be automatically created. * * Since: 0.8 */ param_specs[PROP_DOWNLOAD_DIR] = g_param_spec_string ("download-dir", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:download-enabled: * * Whether progressive download buffering is enabled. * * If progressive download is enabled and [property@Clapper.Player:download-dir] * is set, streamed network content will be cached to the disk space instead * of memory whenever possible. This allows for faster seeking through * currently played media. * * Not every type of content is download applicable. Mainly applies to * web content that does not use adaptive streaming. * * Once data that media item URI points to is fully downloaded, player * will emit [signal@Clapper.Player::download-complete] signal with a * location of downloaded file. * * Playing again the exact same [class@Clapper.MediaItem] object that was * previously fully downloaded will cause player to automatically use that * cached file if it still exists, avoiding any further network requests. * * Please note that player will not delete nor manage downloaded content. * It is up to application to cleanup data in created cache directory * (e.g. before app exits), in order to remove any downloads that app * is not going to use next time it is run and incomplete ones. * * Since: 0.8 */ param_specs[PROP_DOWNLOAD_ENABLED] = g_param_spec_boolean ("download-enabled", NULL, NULL, DEFAULT_DOWNLOAD_ENABLED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:adaptive-start-bitrate: * * An initial bitrate (bits/s) to select during * starting adaptive streaming such as DASH or HLS. * * If value is higher than lowest available bitrate in streaming * manifest, then lowest possible bitrate will be selected. * * Since: 0.8 */ param_specs[PROP_ADAPTIVE_START_BITRATE] = g_param_spec_uint ("adaptive-start-bitrate", NULL, NULL, 0, G_MAXUINT, DEFAULT_ADAPTIVE_START_BITRATE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:adaptive-min-bitrate: * * A minimal allowed bitrate (bits/s) during adaptive streaming * such as DASH or HLS. * * Setting this will prevent streaming from entering lower qualities * (even when connection speed cannot keep up). When set together with * [property@Clapper.Player:adaptive-max-bitrate] it can be used to * enforce some specific quality. * * Since: 0.8 */ param_specs[PROP_ADAPTIVE_MIN_BITRATE] = g_param_spec_uint ("adaptive-min-bitrate", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:adaptive-max-bitrate: * * A maximal allowed bitrate (bits/s) during adaptive streaming * such as DASH or HLS (`0` for unlimited). * * Setting this will prevent streaming from entering qualities with * higher bandwidth than the one set. When set together with * [property@Clapper.Player:adaptive-min-bitrate] it can be used to * enforce some specific quality. * * Since: 0.8 */ param_specs[PROP_ADAPTIVE_MAX_BITRATE] = g_param_spec_uint ("adaptive-max-bitrate", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:adaptive-bandwidth: * * Last fragment download bandwidth (bits/s) during adaptive streaming. * * This property only changes when adaptive streaming and later stays * at the last value until streaming some adaptive content again. * * Apps can use this to determine and set an optimal value for * [property@Clapper.Player:adaptive-start-bitrate]. * * Since: 0.8 */ param_specs[PROP_ADAPTIVE_BANDWIDTH] = g_param_spec_uint ("adaptive-bandwidth", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:audio-offset: * * Audio stream offset relative to video. */ param_specs[PROP_AUDIO_OFFSET] = g_param_spec_double ("audio-offset", NULL, NULL, G_MININT64, G_MAXINT64, 0, // NOTE: Gstreamer has gint64 range G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:subtitle-offset: * * Subtitle stream offset relative to video. */ param_specs[PROP_SUBTITLE_OFFSET] = g_param_spec_double ("subtitle-offset", NULL, NULL, G_MININT64, G_MAXINT64, 0, // NOTE: Gstreamer has gint64 range G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer:subtitle-font-desc: * * Subtitle stream font description. */ param_specs[PROP_SUBTITLE_FONT_DESC] = g_param_spec_string ("subtitle-font-desc", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperPlayer::seek-done: * @player: a #ClapperPlayer * * A seeking operation has finished. Player is now at playback position after seek. */ signals[SIGNAL_SEEK_DONE] = g_signal_new ("seek-done", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); /** * ClapperPlayer::download-complete: * @player: a #ClapperPlayer * @item: a #ClapperMediaItem * @location: (type filename): a path to downloaded file * * Media was fully downloaded to local cache directory. This signal will * be only emitted when progressive download buffering is enabled by * setting [property@Clapper.Player:download-enabled] property to %TRUE. * * Since: 0.8 */ signals[SIGNAL_DOWNLOAD_COMPLETE] = g_signal_new ("download-complete", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, CLAPPER_TYPE_MEDIA_ITEM, G_TYPE_STRING); /** * ClapperPlayer::missing-plugin: * @player: a #ClapperPlayer * @name: a localised string describing the missing feature, for use in * error dialogs and the like. * @installer_detail: (nullable): a string containing all the details about the missing * element to be passed to an external installer called via either * gst_install_plugins_async() or gst_install_plugins_sync() function. * * A #GStreamer plugin or one of its features needed for playback is missing. * * The @description and @installer_detail can be used to present the user more info * about what is missing and prompt him to install it with an external installer. */ signals[SIGNAL_MISSING_PLUGIN] = g_signal_new ("missing-plugin", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); /** * ClapperPlayer::warning: * @player: a #ClapperPlayer * @error: a #GError * @debug_info: (nullable): an additional debug message. * * These are some usually more minor error messages that should * be treated like warnings. Should not generally prevent/stop playback. */ signals[SIGNAL_WARNING] = g_signal_new ("warning", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, G_TYPE_STRING); /** * ClapperPlayer::error: * @player: a #ClapperPlayer * @error: a #GError * @debug_info: (nullable): an additional debug message. * * These are normal error messages. Upon emitting this signal, * playback will stop due to the error. */ signals[SIGNAL_ERROR] = g_signal_new ("error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_ERROR, G_TYPE_STRING); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); threaded_object->thread_start = clapper_player_thread_start; threaded_object->thread_stop = clapper_player_thread_stop; } clapper-0.8.0/src/lib/clapper/clapper-player.h000066400000000000000000000140141474353110700212260ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_PLAYER (clapper_player_get_type()) #define CLAPPER_PLAYER_CAST(obj) ((ClapperPlayer *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperPlayer, clapper_player, CLAPPER, PLAYER, ClapperThreadedObject) CLAPPER_API ClapperPlayer * clapper_player_new (void); CLAPPER_API ClapperQueue * clapper_player_get_queue (ClapperPlayer *player); CLAPPER_API ClapperStreamList * clapper_player_get_video_streams (ClapperPlayer *player); CLAPPER_API ClapperStreamList * clapper_player_get_audio_streams (ClapperPlayer *player); CLAPPER_API ClapperStreamList * clapper_player_get_subtitle_streams (ClapperPlayer *player); CLAPPER_API void clapper_player_set_autoplay (ClapperPlayer *player, gboolean enabled); CLAPPER_API gboolean clapper_player_get_autoplay (ClapperPlayer *player); CLAPPER_API gdouble clapper_player_get_position (ClapperPlayer *player); CLAPPER_API ClapperPlayerState clapper_player_get_state (ClapperPlayer *player); CLAPPER_API void clapper_player_set_mute (ClapperPlayer *player, gboolean mute); CLAPPER_API gboolean clapper_player_get_mute (ClapperPlayer *player); CLAPPER_API void clapper_player_set_volume (ClapperPlayer *player, gdouble volume); CLAPPER_API gdouble clapper_player_get_volume (ClapperPlayer *player); CLAPPER_API void clapper_player_set_speed (ClapperPlayer *player, gdouble speed); CLAPPER_API gdouble clapper_player_get_speed (ClapperPlayer *player); CLAPPER_API void clapper_player_set_video_sink (ClapperPlayer *player, GstElement *element); CLAPPER_API GstElement * clapper_player_get_video_sink (ClapperPlayer *player); CLAPPER_API void clapper_player_set_audio_sink (ClapperPlayer *player, GstElement *element); CLAPPER_API GstElement * clapper_player_get_audio_sink (ClapperPlayer *player); CLAPPER_API void clapper_player_set_video_filter (ClapperPlayer *player, GstElement *element); CLAPPER_API GstElement * clapper_player_get_video_filter (ClapperPlayer *player); CLAPPER_API void clapper_player_set_audio_filter (ClapperPlayer *player, GstElement *element); CLAPPER_API GstElement * clapper_player_get_audio_filter (ClapperPlayer *player); CLAPPER_API GstElement * clapper_player_get_current_video_decoder (ClapperPlayer *player); CLAPPER_API GstElement * clapper_player_get_current_audio_decoder (ClapperPlayer *player); CLAPPER_API void clapper_player_set_video_enabled (ClapperPlayer *player, gboolean enabled); CLAPPER_API gboolean clapper_player_get_video_enabled (ClapperPlayer *player); CLAPPER_API void clapper_player_set_audio_enabled (ClapperPlayer *player, gboolean enabled); CLAPPER_API gboolean clapper_player_get_audio_enabled (ClapperPlayer *player); CLAPPER_API void clapper_player_set_subtitles_enabled (ClapperPlayer *player, gboolean enabled); CLAPPER_API gboolean clapper_player_get_subtitles_enabled (ClapperPlayer *player); CLAPPER_API void clapper_player_set_download_dir (ClapperPlayer *player, const gchar *path); CLAPPER_API gchar * clapper_player_get_download_dir (ClapperPlayer *player); CLAPPER_API void clapper_player_set_download_enabled (ClapperPlayer *player, gboolean enabled); CLAPPER_API gboolean clapper_player_get_download_enabled (ClapperPlayer *player); CLAPPER_API void clapper_player_set_adaptive_start_bitrate (ClapperPlayer *player, guint bitrate); CLAPPER_API guint clapper_player_get_adaptive_start_bitrate (ClapperPlayer *player); CLAPPER_API void clapper_player_set_adaptive_min_bitrate (ClapperPlayer *player, guint bitrate); CLAPPER_API guint clapper_player_get_adaptive_min_bitrate (ClapperPlayer *player); CLAPPER_API void clapper_player_set_adaptive_max_bitrate (ClapperPlayer *player, guint bitrate); CLAPPER_API guint clapper_player_get_adaptive_max_bitrate (ClapperPlayer *player); CLAPPER_API guint clapper_player_get_adaptive_bandwidth (ClapperPlayer *player); CLAPPER_API void clapper_player_set_audio_offset (ClapperPlayer *player, gdouble offset); CLAPPER_API gdouble clapper_player_get_audio_offset (ClapperPlayer *player); CLAPPER_API void clapper_player_set_subtitle_offset (ClapperPlayer *player, gdouble offset); CLAPPER_API gdouble clapper_player_get_subtitle_offset (ClapperPlayer *player); CLAPPER_API void clapper_player_set_subtitle_font_desc (ClapperPlayer *player, const gchar *font_desc); CLAPPER_API gchar * clapper_player_get_subtitle_font_desc (ClapperPlayer *player); CLAPPER_API void clapper_player_play (ClapperPlayer *player); CLAPPER_API void clapper_player_pause (ClapperPlayer *player); CLAPPER_API void clapper_player_stop (ClapperPlayer *player); CLAPPER_API void clapper_player_seek (ClapperPlayer *player, gdouble position); CLAPPER_API void clapper_player_seek_custom (ClapperPlayer *player, gdouble position, ClapperPlayerSeekMethod method); CLAPPER_API void clapper_player_add_feature (ClapperPlayer *player, ClapperFeature *feature); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-queue-private.h000066400000000000000000000025241474353110700225310ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-queue.h" #include "clapper-media-item.h" #include "clapper-player.h" #include "clapper-app-bus-private.h" G_BEGIN_DECLS ClapperQueue * clapper_queue_new (void); void clapper_queue_handle_played_item_changed (ClapperQueue *queue, ClapperMediaItem *played_item, ClapperAppBus *app_bus); void clapper_queue_handle_about_to_finish (ClapperQueue *queue, ClapperPlayer *player); gboolean clapper_queue_handle_eos (ClapperQueue *queue, ClapperPlayer *player); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-queue.c000066400000000000000000001106601474353110700210550ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperQueue: * * A queue of media to be played. */ #include #include "clapper-queue-private.h" #include "clapper-media-item-private.h" #include "clapper-player-private.h" #include "clapper-playbin-bus-private.h" #define CLAPPER_QUEUE_GET_REC_LOCK(obj) (&CLAPPER_QUEUE_CAST(obj)->rec_lock) #define CLAPPER_QUEUE_REC_LOCK(obj) g_rec_mutex_lock (CLAPPER_QUEUE_GET_REC_LOCK(obj)) #define CLAPPER_QUEUE_REC_UNLOCK(obj) g_rec_mutex_unlock (CLAPPER_QUEUE_GET_REC_LOCK(obj)) #define DEFAULT_PROGRESSION_MODE CLAPPER_QUEUE_PROGRESSION_NONE #define DEFAULT_GAPLESS FALSE #define DEFAULT_INSTANT FALSE #define GST_CAT_DEFAULT clapper_queue_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperQueue { GstObject parent; GRecMutex rec_lock; GPtrArray *items; ClapperMediaItem *current_item; guint current_index; ClapperQueueProgressionMode progression_mode; gboolean gapless; gboolean instant; /* Avoid scenario when "gapless" prop is changed * between "about-to-finish" and "EOS" */ gboolean handled_gapless; }; enum { PROP_0, PROP_CURRENT_ITEM, PROP_CURRENT_INDEX, PROP_N_ITEMS, PROP_PROGRESSION_MODE, PROP_GAPLESS, PROP_INSTANT, PROP_LAST }; static GType clapper_queue_list_model_get_item_type (GListModel *model) { return CLAPPER_TYPE_MEDIA_ITEM; } static guint clapper_queue_list_model_get_n_items (GListModel *model) { ClapperQueue *self = CLAPPER_QUEUE_CAST (model); guint n_items; CLAPPER_QUEUE_REC_LOCK (self); n_items = self->items->len; CLAPPER_QUEUE_REC_UNLOCK (self); return n_items; } static gpointer clapper_queue_list_model_get_item (GListModel *model, guint index) { ClapperQueue *self = CLAPPER_QUEUE_CAST (model); ClapperMediaItem *item = NULL; CLAPPER_QUEUE_REC_LOCK (self); if (G_LIKELY (index < self->items->len)) { GST_LOG_OBJECT (self, "Reading queue item: %u", index); item = g_object_ref (g_ptr_array_index (self->items, index)); } CLAPPER_QUEUE_REC_UNLOCK (self); return item; } static void clapper_queue_list_model_iface_init (GListModelInterface *iface) { iface->get_item_type = clapper_queue_list_model_get_item_type; iface->get_n_items = clapper_queue_list_model_get_n_items; iface->get_item = clapper_queue_list_model_get_item; } #define parent_class clapper_queue_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperQueue, clapper_queue, GST_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_queue_list_model_iface_init)); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void _announce_model_update (ClapperQueue *self, guint index, guint removed, guint added, ClapperMediaItem *changed_item) { GST_DEBUG_OBJECT (self, "Announcing model update, index: %u, removed: %u, added: %u", index, removed, added); /* We handle reposition separately */ if (removed != added) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); if (player && clapper_player_get_have_features (player)) { if (added == 1) // addition clapper_features_manager_trigger_queue_item_added (player->features_manager, changed_item, index); else if (removed == 1) // removal clapper_features_manager_trigger_queue_item_removed (player->features_manager, changed_item, index); else if (removed > 1 && added == 0) // queue cleared clapper_features_manager_trigger_queue_cleared (player->features_manager); else g_assert_not_reached (); } gst_clear_object (&player); } g_list_model_items_changed (G_LIST_MODEL (self), index, removed, added); if (removed != added) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_ITEMS]); } static void _announce_reposition (ClapperQueue *self, guint before, guint after) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Announcing item reposition: %u -> %u", before, after); if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) { if (clapper_player_get_have_features (player)) clapper_features_manager_trigger_queue_item_repositioned (player->features_manager, before, after); gst_object_unref (player); } } /* * Notify about current index change. This is needed only if some items * are added/removed before current selection, otherwise if selection * also changes use _announce_current_item_and_index_change() instead. */ static void _announce_current_index_change (ClapperQueue *self) { gboolean is_main_thread = g_main_context_is_owner (g_main_context_default ()); GST_DEBUG_OBJECT (self, "Announcing current index change from %smain thread, now: %u", (is_main_thread) ? "" : "non-", self->current_index); if (is_main_thread) { g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_INDEX]); } else { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); if (G_LIKELY (player != NULL)) { clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]); gst_object_unref (player); } } } /* * Notify about both current item and its index changes. * Needs to be called while holding CLAPPER_QUEUE_REC_LOCK. */ static void _announce_current_item_and_index_change (ClapperQueue *self) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); gboolean instant, is_main_thread; if (G_UNLIKELY (player == NULL)) return; is_main_thread = g_main_context_is_owner (g_main_context_default ()); GST_DEBUG_OBJECT (self, "Announcing current item change from %smain thread," " now: %" GST_PTR_FORMAT " (index: %u)", (is_main_thread) ? "" : "non-", self->current_item, self->current_index); GST_OBJECT_LOCK (self); instant = self->instant; GST_OBJECT_UNLOCK (self); clapper_playbin_bus_post_current_item_change (player->bus, self->current_item, (instant) ? CLAPPER_QUEUE_ITEM_CHANGE_INSTANT : CLAPPER_QUEUE_ITEM_CHANGE_NORMAL); if (is_main_thread) { g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_ITEM]); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_INDEX]); } else { clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_ITEM]); clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]); } gst_object_unref (player); } static inline gboolean _replace_current_item_unlocked (ClapperQueue *self, ClapperMediaItem *item, guint index) { if (gst_object_replace ((GstObject **) &self->current_item, GST_OBJECT_CAST (item))) { self->current_index = index; if (self->current_item) clapper_media_item_set_used (self->current_item, TRUE); GST_TRACE_OBJECT (self, "Current item replaced, now: %" GST_PTR_FORMAT, self->current_item); return TRUE; } return FALSE; } static void _reset_shuffle_unlocked (ClapperQueue *self) { guint i; for (i = 0; i < self->items->len; ++i) { ClapperMediaItem *item = g_ptr_array_index (self->items, i); clapper_media_item_set_used (item, FALSE); } } static ClapperMediaItem * _get_next_item_unlocked (ClapperQueue *self, ClapperQueueProgressionMode mode) { ClapperMediaItem *next_item = NULL; GST_DEBUG_OBJECT (self, "Handling progression mode: %u", mode); if (self->current_index == CLAPPER_QUEUE_INVALID_POSITION) { GST_DEBUG_OBJECT (self, "No current item, can not advance"); return NULL; } switch (mode) { case CLAPPER_QUEUE_PROGRESSION_NONE: break; case CLAPPER_QUEUE_PROGRESSION_CAROUSEL: next_item = g_ptr_array_index (self->items, 0); G_GNUC_FALLTHROUGH; case CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE: if (self->current_index + 1 < self->items->len) next_item = g_ptr_array_index (self->items, self->current_index + 1); break; case CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM: next_item = self->current_item; break; case CLAPPER_QUEUE_PROGRESSION_SHUFFLE:{ GList *unused = NULL; GRand *rand = g_rand_new (); guint i; for (i = 0; i < self->items->len; ++i) { ClapperMediaItem *item = g_ptr_array_index (self->items, i); if (!clapper_media_item_get_used (item)) unused = g_list_append (unused, item); } if (unused) { next_item = g_list_nth_data (unused, g_rand_int_range (rand, 0, g_list_length (unused))); g_list_free (unused); } else { _reset_shuffle_unlocked (self); next_item = g_ptr_array_index (self->items, g_rand_int_range (rand, 0, self->items->len)); } g_rand_free (rand); break; } default: g_assert_not_reached (); break; } if (next_item) gst_object_ref (next_item); return next_item; } /* * For gapless we need to manually replace current item in queue when it starts * playing and emit notify about change, this function will do that if necessary */ void clapper_queue_handle_played_item_changed (ClapperQueue *self, ClapperMediaItem *played_item, ClapperAppBus *app_bus) { guint index = 0; gboolean changed = FALSE; CLAPPER_QUEUE_REC_LOCK (self); /* Item is often the same here (when selected from queue), * so compare pointers first to avoid iterating queue */ if (played_item != self->current_item && g_ptr_array_find (self->items, played_item, &index)) changed = _replace_current_item_unlocked (self, played_item, index); CLAPPER_QUEUE_REC_UNLOCK (self); if (changed) { clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_ITEM]); clapper_app_bus_post_prop_notify (app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]); } } void clapper_queue_handle_about_to_finish (ClapperQueue *self, ClapperPlayer *player) { ClapperMediaItem *next_item; ClapperQueueProgressionMode progression_mode; GST_INFO_OBJECT (self, "Handling \"about-to-finish\""); GST_OBJECT_LOCK (self); if (!(self->handled_gapless = self->gapless)) { GST_OBJECT_UNLOCK (self); return; } progression_mode = self->progression_mode; GST_OBJECT_UNLOCK (self); CLAPPER_QUEUE_REC_LOCK (self); next_item = _get_next_item_unlocked (self, progression_mode); CLAPPER_QUEUE_REC_UNLOCK (self); if (next_item) { clapper_player_set_pending_item (player, next_item, CLAPPER_QUEUE_ITEM_CHANGE_GAPLESS); gst_object_unref (next_item); } } gboolean clapper_queue_handle_eos (ClapperQueue *self, ClapperPlayer *player) { ClapperMediaItem *next_item = NULL; ClapperQueueProgressionMode progression_mode; gboolean handled_eos = FALSE; /* On gapless "about-to-finish" selects next item instead and * we can reach EOS only if there was either nothing to select or * some playback error ocurred */ GST_INFO_OBJECT (self, "Handling EOS"); GST_OBJECT_LOCK (self); if (self->handled_gapless) { self->handled_gapless = FALSE; // reset GST_OBJECT_UNLOCK (self); return FALSE; } progression_mode = self->progression_mode; GST_OBJECT_UNLOCK (self); CLAPPER_QUEUE_REC_LOCK (self); if ((next_item = _get_next_item_unlocked (self, progression_mode))) { if (next_item == self->current_item) clapper_player_seek (player, 0); else clapper_queue_select_item (self, next_item); handled_eos = TRUE; gst_object_unref (next_item); } CLAPPER_QUEUE_REC_UNLOCK (self); return handled_eos; } /* * clapper_queue_new: * * Returns: (transfer full): a new #ClapperQueue instance */ ClapperQueue * clapper_queue_new (void) { ClapperQueue *queue; queue = g_object_new (CLAPPER_TYPE_QUEUE, NULL); gst_object_ref_sink (queue); return queue; } /** * clapper_queue_add_item: * @queue: a #ClapperQueue * @item: a #ClapperMediaItem * * Add another #ClapperMediaItem to the end of queue. * * If item is already in queue, this function will do nothing, * so it is safe to call multiple times if unsure. */ void clapper_queue_add_item (ClapperQueue *self, ClapperMediaItem *item) { clapper_queue_insert_item (self, item, -1); } /** * clapper_queue_insert_item: * @queue: a #ClapperQueue * @item: a #ClapperMediaItem * @index: the index to place @item in queue, -1 to append * * Insert another #ClapperMediaItem at @index position to the queue. * * If item is already in queue, this function will do nothing, * so it is safe to call multiple times if unsure. */ void clapper_queue_insert_item (ClapperQueue *self, ClapperMediaItem *item, gint index) { g_return_if_fail (CLAPPER_IS_QUEUE (self)); g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item)); g_return_if_fail (index >= -1); CLAPPER_QUEUE_REC_LOCK (self); if (!g_ptr_array_find (self->items, item, NULL)) { guint prev_length = self->items->len; g_ptr_array_insert (self->items, index, gst_object_ref (item)); gst_object_set_parent (GST_OBJECT_CAST (item), GST_OBJECT_CAST (self)); /* In append we inserted at array length */ if (index < 0) index = prev_length; _announce_model_update (self, index, 0, 1, item); /* If has selection and inserting before it */ if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION && (guint) index <= self->current_index) { self->current_index++; _announce_current_index_change (self); } else if (prev_length == 0 && _replace_current_item_unlocked (self, item, 0)) { /* If queue was empty, auto select first item and announce it */ _announce_current_item_and_index_change (self); } else if (self->current_index == prev_length - 1 && clapper_queue_get_progression_mode (self) == CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); gboolean after_eos = (gboolean) g_atomic_int_get (&player->eos); /* In consecutive progression automatically select next item * if we were after EOS of last queue item */ if (after_eos && _replace_current_item_unlocked (self, item, index)) _announce_current_item_and_index_change (self); gst_object_unref (player); } } CLAPPER_QUEUE_REC_UNLOCK (self); } /** * clapper_queue_reposition_item: * @queue: a #ClapperQueue * @item: a #ClapperMediaItem * @index: the index to place @item in queue, -1 to place at the end * * Change position of one #ClapperMediaItem within the queue. * * Note that the @index is the new position you expect item to be * after whole reposition operation is finished. * * If item is not in the queue, this function will do nothing. */ void clapper_queue_reposition_item (ClapperQueue *self, ClapperMediaItem *item, gint index) { guint index_old = 0; g_return_if_fail (CLAPPER_IS_QUEUE (self)); g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item)); g_return_if_fail (index >= -1); CLAPPER_QUEUE_REC_LOCK (self); if (g_ptr_array_find (self->items, item, &index_old)) { ClapperMediaItem *removed_item; guint index_new, start_index, end_index, n_changed; index_new = (index < 0) ? self->items->len - 1 : (guint) index; GST_DEBUG_OBJECT (self, "Reposition item %u -> %u, is_current: %s", index_old, index_new, (item == self->current_item) ? "yes" : "no"); removed_item = g_ptr_array_steal_index (self->items, index_old); g_ptr_array_insert (self->items, index_new, removed_item); _announce_reposition (self, index_old, index_new); if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION) { guint before = self->current_index; if (index_old > self->current_index && index_new <= self->current_index) self->current_index++; // Moved before current item else if (index_old < self->current_index && index_new >= self->current_index) self->current_index--; // Moved after current item else if (index_old == self->current_index) self->current_index = index_new; // Moved current item if (self->current_index != before) _announce_current_index_change (self); } start_index = MIN (index_old, index_new); end_index = MAX (index_old, index_new); n_changed = end_index - start_index + 1; _announce_model_update (self, start_index, n_changed, n_changed, item); } CLAPPER_QUEUE_REC_UNLOCK (self); } /** * clapper_queue_remove_item: * @queue: a #ClapperQueue * @item: a #ClapperMediaItem * * Removes #ClapperMediaItem from the queue. * * If item either was never in the queue or was removed from * it earlier, this function will do nothing, so it is safe * to call multiple times if unsure. */ void clapper_queue_remove_item (ClapperQueue *self, ClapperMediaItem *item) { guint index = 0; g_return_if_fail (CLAPPER_IS_QUEUE (self)); g_return_if_fail (CLAPPER_IS_MEDIA_ITEM (item)); CLAPPER_QUEUE_REC_LOCK (self); if (g_ptr_array_find (self->items, item, &index)) clapper_queue_remove_index (self, index); CLAPPER_QUEUE_REC_UNLOCK (self); } /** * clapper_queue_remove_index: * @queue: a #ClapperQueue * @index: an item index * * Removes #ClapperMediaItem at @index from the queue. */ void clapper_queue_remove_index (ClapperQueue *self, guint index) { ClapperMediaItem *item = clapper_queue_steal_index (self, index); gst_clear_object (&item); } /** * clapper_queue_steal_index: * @queue: a #ClapperQueue * @index: an item index * * Removes #ClapperMediaItem at @index from the queue. * * Returns: (transfer full) (nullable): The removed #ClapperMediaItem at @index. */ ClapperMediaItem * clapper_queue_steal_index (ClapperQueue *self, guint index) { ClapperMediaItem *removed_item = NULL; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), NULL); g_return_val_if_fail (index != CLAPPER_QUEUE_INVALID_POSITION, NULL); CLAPPER_QUEUE_REC_LOCK (self); if (index < self->items->len) { if (index == self->current_index && _replace_current_item_unlocked (self, NULL, CLAPPER_QUEUE_INVALID_POSITION)) { _announce_current_item_and_index_change (self); } else if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION && index < self->current_index) { /* If has selection and removed before it */ self->current_index--; _announce_current_index_change (self); } removed_item = g_ptr_array_steal_index (self->items, index); gst_object_unparent (GST_OBJECT_CAST (removed_item)); _announce_model_update (self, index, 1, 0, removed_item); } CLAPPER_QUEUE_REC_UNLOCK (self); return removed_item; } /** * clapper_queue_clear: * @queue: a #ClapperQueue * * Removes all media items from the queue. * * If queue is empty, this function will do nothing, * so it is safe to call multiple times if unsure. */ void clapper_queue_clear (ClapperQueue *self) { guint n_items; g_return_if_fail (CLAPPER_IS_QUEUE (self)); CLAPPER_QUEUE_REC_LOCK (self); n_items = self->items->len; if (n_items > 0) { if (_replace_current_item_unlocked (self, NULL, CLAPPER_QUEUE_INVALID_POSITION)) _announce_current_item_and_index_change (self); g_ptr_array_remove_range (self->items, 0, n_items); _announce_model_update (self, 0, n_items, 0, NULL); } CLAPPER_QUEUE_REC_UNLOCK (self); } /** * clapper_queue_select_item: * @queue: a #ClapperQueue * @item: (nullable): a #ClapperMediaItem or %NULL to unselect * * Selects #ClapperMediaItem from @queue as current one or * unselects currently selected item when @item is %NULL. * * Returns: %TRUE if item could be selected/unselected, * %FALSE if it was not in the queue. */ gboolean clapper_queue_select_item (ClapperQueue *self, ClapperMediaItem *item) { gboolean success = FALSE; guint index = 0; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); g_return_val_if_fail (item == NULL || CLAPPER_IS_MEDIA_ITEM (item), FALSE); CLAPPER_QUEUE_REC_LOCK (self); if (!item) success = clapper_queue_select_index (self, CLAPPER_QUEUE_INVALID_POSITION); else if (g_ptr_array_find (self->items, item, &index)) success = clapper_queue_select_index (self, index); CLAPPER_QUEUE_REC_UNLOCK (self); return success; } /** * clapper_queue_select_index: * @queue: a #ClapperQueue * @index: an item index or [const@Clapper.QUEUE_INVALID_POSITION] to unselect * * Selects #ClapperMediaItem at @index from @queue as current one or * unselects currently selected index when @index is [const@Clapper.QUEUE_INVALID_POSITION]. * * Returns: %TRUE if item at @index could be selected/unselected, * %FALSE if index was out of queue range. */ gboolean clapper_queue_select_index (ClapperQueue *self, guint index) { ClapperMediaItem *item = NULL; gboolean success; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); CLAPPER_QUEUE_REC_LOCK (self); if (index != CLAPPER_QUEUE_INVALID_POSITION && index < self->items->len) item = g_ptr_array_index (self->items, index); if ((success = (index == CLAPPER_QUEUE_INVALID_POSITION || index < self->items->len))) { if (_replace_current_item_unlocked (self, item, index)) _announce_current_item_and_index_change (self); } CLAPPER_QUEUE_REC_UNLOCK (self); return success; } /** * clapper_queue_select_next_item: * @queue: a #ClapperQueue * * Selects next #ClapperMediaItem from @queue for playback. * * Note that this will try to select next item in the order * of the queue, regardless of [enum@Clapper.QueueProgressionMode] set. * * Returns: %TRUE if there was another media item in queue, %FALSE otherwise. */ gboolean clapper_queue_select_next_item (ClapperQueue *self) { gboolean success = FALSE; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); CLAPPER_QUEUE_REC_LOCK (self); if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION && self->current_index < self->items->len - 1) { GST_DEBUG_OBJECT (self, "Selecting next queue item"); success = clapper_queue_select_index (self, self->current_index + 1); } CLAPPER_QUEUE_REC_UNLOCK (self); return success; } /** * clapper_queue_select_previous_item: * @queue: a #ClapperQueue * * Selects previous #ClapperMediaItem from @queue for playback. * * Note that this will try to select previous item in the order * of the queue, regardless of [enum@Clapper.QueueProgressionMode] set. * * Returns: %TRUE if there was previous media item in queue, %FALSE otherwise. */ gboolean clapper_queue_select_previous_item (ClapperQueue *self) { gboolean success = FALSE; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); CLAPPER_QUEUE_REC_LOCK (self); if (self->current_index != CLAPPER_QUEUE_INVALID_POSITION && self->current_index > 0) { GST_DEBUG_OBJECT (self, "Selecting previous queue item"); success = clapper_queue_select_index (self, self->current_index - 1); } CLAPPER_QUEUE_REC_UNLOCK (self); return success; } /** * clapper_queue_get_item: (skip) * @queue: a #ClapperQueue * @index: an item index * * Get the #ClapperMediaItem 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. * * This function is not available in bindings as they already * inherit `get_item()` method from [iface@Gio.ListModel] interface. * * Returns: (transfer full) (nullable): The #ClapperMediaItem at @index. */ ClapperMediaItem * clapper_queue_get_item (ClapperQueue *self, guint index) { g_return_val_if_fail (CLAPPER_IS_QUEUE (self), NULL); return g_list_model_get_item (G_LIST_MODEL (self), index); } /** * clapper_queue_get_current_item: * @queue: a #ClapperQueue * * Get the currently selected #ClapperMediaItem. * * Returns: (transfer full) (nullable): The current #ClapperMediaItem. */ ClapperMediaItem * clapper_queue_get_current_item (ClapperQueue *self) { ClapperMediaItem *item = NULL; /* XXX: For updating media item during playback we should * use `player->played_item` instead to not be racy when * changing and updating current item at the same time */ g_return_val_if_fail (CLAPPER_IS_QUEUE (self), NULL); CLAPPER_QUEUE_REC_LOCK (self); if (self->current_item) item = gst_object_ref (self->current_item); CLAPPER_QUEUE_REC_UNLOCK (self); return item; } /** * clapper_queue_get_current_index: * @queue: a #ClapperQueue * * Get index of the currently selected #ClapperMediaItem. * * Returns: Current item index or [const@Clapper.QUEUE_INVALID_POSITION] * when nothing is selected. */ guint clapper_queue_get_current_index (ClapperQueue *self) { guint index; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), CLAPPER_QUEUE_INVALID_POSITION); CLAPPER_QUEUE_REC_LOCK (self); index = self->current_index; CLAPPER_QUEUE_REC_UNLOCK (self); return index; } /** * clapper_queue_item_is_current: * @queue: a #ClapperQueue * @item: a #ClapperMediaItem to check * * Checks if given #ClapperMediaItem is currently selected. * * Returns: %TRUE if @item is a current media item, %FALSE otherwise. */ gboolean clapper_queue_item_is_current (ClapperQueue *self, ClapperMediaItem *item) { gboolean is_current; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (item), FALSE); CLAPPER_QUEUE_REC_LOCK (self); is_current = (item == self->current_item); CLAPPER_QUEUE_REC_UNLOCK (self); return is_current; } /** * clapper_queue_find_item: * @queue: a #ClapperQueue * @item: a #ClapperMediaItem to search for * @index: (optional) (out): return location for the index of * the element, if found * * Get the index of #ClapperMediaItem within #ClapperQueue. * * Returns: %TRUE if @item is one of the elements of queue. */ gboolean clapper_queue_find_item (ClapperQueue *self, ClapperMediaItem *item, guint *index) { gboolean found; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); g_return_val_if_fail (CLAPPER_IS_MEDIA_ITEM (item), FALSE); CLAPPER_QUEUE_REC_LOCK (self); found = g_ptr_array_find (self->items, item, index); CLAPPER_QUEUE_REC_UNLOCK (self); return found; } /** * clapper_queue_get_n_items: (skip) * @queue: a #ClapperQueue * * Get the number of items in #ClapperQueue. * * 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. * * This function is not available in bindings as they already * inherit get_n_items() method from #GListModel interface. * * Returns: The number of items in #ClapperQueue. */ guint clapper_queue_get_n_items (ClapperQueue *self) { g_return_val_if_fail (CLAPPER_IS_QUEUE (self), 0); return g_list_model_get_n_items (G_LIST_MODEL (self)); } /** * clapper_queue_set_progression_mode: * @queue: a #ClapperQueue * @mode: a #ClapperQueueProgressionMode * * Set the #ClapperQueueProgressionMode of the #ClapperQueue. * * Changing the mode set will alter next item selection at the * end of playback. For possible values and their descriptions, * see #ClapperQueueProgressionMode documentation. */ void clapper_queue_set_progression_mode (ClapperQueue *self, ClapperQueueProgressionMode mode) { gboolean changed; g_return_if_fail (CLAPPER_IS_QUEUE (self)); GST_OBJECT_LOCK (self); if ((changed = self->progression_mode != mode)) self->progression_mode = mode; GST_OBJECT_UNLOCK (self); if (changed) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); /* Start shuffle from the current item, allowing * reselecting past items already used without it */ if (mode == CLAPPER_QUEUE_PROGRESSION_SHUFFLE) { CLAPPER_QUEUE_REC_LOCK (self); _reset_shuffle_unlocked (self); if (self->current_item) clapper_media_item_set_used (self->current_item, TRUE); CLAPPER_QUEUE_REC_UNLOCK (self); } clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_PROGRESSION_MODE]); if (clapper_player_get_have_features (player)) clapper_features_manager_trigger_queue_progression_changed (player->features_manager, mode); gst_object_unref (player); } } /** * clapper_queue_get_progression_mode: * @queue: a #ClapperQueue * * Get the #ClapperQueueProgressionMode of the #ClapperQueue. * * Returns: a currently set #ClapperQueueProgressionMode. */ ClapperQueueProgressionMode clapper_queue_get_progression_mode (ClapperQueue *self) { ClapperQueueProgressionMode mode; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), DEFAULT_PROGRESSION_MODE); GST_OBJECT_LOCK (self); mode = self->progression_mode; GST_OBJECT_UNLOCK (self); return mode; } /** * clapper_queue_set_gapless: * @queue: a #ClapperQueue * @gapless: %TRUE to enable, %FALSE otherwise. * * Set #ClapperQueue progression to be gapless. * * Gapless playback will try to re-use as much as possible of underlying * GStreamer elements when #ClapperQueue progresses, removing any * potential gap in the data. * * Enabling this option mostly makes sense when used together with * [property@Clapper.Queue:progression-mode] property set to * [enum@Clapper.QueueProgressionMode.CONSECUTIVE]. * * NOTE: This feature within GStreamer is rather new and * might still cause playback issues. Disabled by default. */ void clapper_queue_set_gapless (ClapperQueue *self, gboolean gapless) { gboolean changed; g_return_if_fail (CLAPPER_IS_QUEUE (self)); GST_OBJECT_LOCK (self); if ((changed = self->gapless != gapless)) self->gapless = gapless; GST_OBJECT_UNLOCK (self); if (changed) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_GAPLESS]); gst_object_unref (player); } } /** * clapper_queue_get_gapless: * @queue: a #ClapperQueue * * Get if #ClapperQueue is set to use gapless progression. * * Returns: %TRUE if enabled, %FALSE otherwise. */ gboolean clapper_queue_get_gapless (ClapperQueue *self) { gboolean gapless; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); GST_OBJECT_LOCK (self); gapless = self->gapless; GST_OBJECT_UNLOCK (self); return gapless; } /** * clapper_queue_set_instant: * @queue: a #ClapperQueue * @instant: %TRUE to enable, %FALSE otherwise. * * Set #ClapperQueue media item changes to be instant. * * Instant will try to re-use as much as possible of underlying * GStreamer elements when #ClapperMediaItem is selected, allowing * media item change requests to be faster. * * NOTE: This feature within GStreamer is rather new and * might still cause playback issues. Disabled by default. */ void clapper_queue_set_instant (ClapperQueue *self, gboolean instant) { gboolean changed; g_return_if_fail (CLAPPER_IS_QUEUE (self)); GST_OBJECT_LOCK (self); if ((changed = self->instant != instant)) self->instant = instant; GST_OBJECT_UNLOCK (self); if (changed) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_INSTANT]); gst_object_unref (player); } } /** * clapper_queue_get_instant: * @queue: a #ClapperQueue * * Get if #ClapperQueue is set to use instant media item changes. * * Returns: %TRUE if enabled, %FALSE otherwise. */ gboolean clapper_queue_get_instant (ClapperQueue *self) { gboolean instant; g_return_val_if_fail (CLAPPER_IS_QUEUE (self), FALSE); GST_OBJECT_LOCK (self); instant = self->instant; GST_OBJECT_UNLOCK (self); return instant; } static void _item_remove_func (ClapperMediaItem *item) { gst_object_unparent (GST_OBJECT_CAST (item)); gst_object_unref (item); } static void clapper_queue_init (ClapperQueue *self) { g_rec_mutex_init (&self->rec_lock); self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) _item_remove_func); self->current_index = CLAPPER_QUEUE_INVALID_POSITION; self->progression_mode = DEFAULT_PROGRESSION_MODE; self->gapless = DEFAULT_GAPLESS; self->instant = DEFAULT_INSTANT; } static void clapper_queue_finalize (GObject *object) { ClapperQueue *self = CLAPPER_QUEUE_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_rec_mutex_clear (&self->rec_lock); gst_clear_object (&self->current_item); g_ptr_array_unref (self->items); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_queue_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperQueue *self = CLAPPER_QUEUE_CAST (object); switch (prop_id) { case PROP_CURRENT_ITEM: g_value_take_object (value, clapper_queue_get_current_item (self)); break; case PROP_CURRENT_INDEX: g_value_set_uint (value, clapper_queue_get_current_index (self)); break; case PROP_N_ITEMS: g_value_set_uint (value, clapper_queue_get_n_items (self)); break; case PROP_PROGRESSION_MODE: g_value_set_enum (value, clapper_queue_get_progression_mode (self)); break; case PROP_GAPLESS: g_value_set_boolean (value, clapper_queue_get_gapless (self)); break; case PROP_INSTANT: g_value_set_boolean (value, clapper_queue_get_instant (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_queue_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperQueue *self = CLAPPER_QUEUE_CAST (object); switch (prop_id) { case PROP_CURRENT_INDEX: clapper_queue_select_index (self, g_value_get_uint (value)); break; case PROP_PROGRESSION_MODE: clapper_queue_set_progression_mode (self, g_value_get_enum (value)); break; case PROP_GAPLESS: clapper_queue_set_gapless (self, g_value_get_boolean (value)); break; case PROP_INSTANT: clapper_queue_set_instant (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_queue_class_init (ClapperQueueClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperqueue", 0, "Clapper Queue"); gobject_class->get_property = clapper_queue_get_property; gobject_class->set_property = clapper_queue_set_property; gobject_class->finalize = clapper_queue_finalize; /** * ClapperQueue:current-item: * * Currently selected media item for playback. */ param_specs[PROP_CURRENT_ITEM] = g_param_spec_object ("current-item", NULL, NULL, CLAPPER_TYPE_MEDIA_ITEM, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperQueue:current-index: * * Index of currently selected media item for playback. */ param_specs[PROP_CURRENT_INDEX] = g_param_spec_uint ("current-index", NULL, NULL, 0, G_MAXUINT, CLAPPER_QUEUE_INVALID_POSITION, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperQueue:n-items: * * Number of media items in the queue. */ param_specs[PROP_N_ITEMS] = g_param_spec_uint ("n-items", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperQueue:progression-mode: * * Queue progression mode. */ param_specs[PROP_PROGRESSION_MODE] = g_param_spec_enum ("progression-mode", NULL, NULL, CLAPPER_TYPE_QUEUE_PROGRESSION_MODE, DEFAULT_PROGRESSION_MODE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperQueue:gapless: * * Use gapless progression. */ param_specs[PROP_GAPLESS] = g_param_spec_boolean ("gapless", NULL, NULL, DEFAULT_GAPLESS, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperQueue:instant: * * Use instant media item changes. */ param_specs[PROP_INSTANT] = g_param_spec_boolean ("instant", NULL, NULL, DEFAULT_INSTANT, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/clapper-queue.h000066400000000000000000000067771474353110700210770ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_QUEUE (clapper_queue_get_type()) #define CLAPPER_QUEUE_CAST(obj) ((ClapperQueue *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperQueue, clapper_queue, CLAPPER, QUEUE, GstObject) /** * CLAPPER_QUEUE_INVALID_POSITION: * * The value used to refer to an invalid position in a #ClapperQueue */ #define CLAPPER_QUEUE_INVALID_POSITION ((guint) 0xffffffff) CLAPPER_API void clapper_queue_add_item (ClapperQueue *queue, ClapperMediaItem *item); CLAPPER_API void clapper_queue_insert_item (ClapperQueue *queue, ClapperMediaItem *item, gint index); CLAPPER_API void clapper_queue_reposition_item (ClapperQueue *queue, ClapperMediaItem *item, gint index); CLAPPER_API void clapper_queue_remove_item (ClapperQueue *queue, ClapperMediaItem *item); CLAPPER_API void clapper_queue_remove_index (ClapperQueue *queue, guint index); CLAPPER_API ClapperMediaItem * clapper_queue_steal_index (ClapperQueue *queue, guint index); CLAPPER_API void clapper_queue_clear (ClapperQueue *queue); CLAPPER_API gboolean clapper_queue_select_item (ClapperQueue *queue, ClapperMediaItem *item); CLAPPER_API gboolean clapper_queue_select_index (ClapperQueue *queue, guint index); CLAPPER_API gboolean clapper_queue_select_next_item (ClapperQueue *queue); CLAPPER_API gboolean clapper_queue_select_previous_item (ClapperQueue *queue); CLAPPER_API ClapperMediaItem * clapper_queue_get_item (ClapperQueue *queue, guint index); CLAPPER_API ClapperMediaItem * clapper_queue_get_current_item (ClapperQueue *queue); CLAPPER_API guint clapper_queue_get_current_index (ClapperQueue *queue); CLAPPER_API gboolean clapper_queue_item_is_current (ClapperQueue *queue, ClapperMediaItem *item); CLAPPER_API gboolean clapper_queue_find_item (ClapperQueue *queue, ClapperMediaItem *item, guint *index); CLAPPER_API guint clapper_queue_get_n_items (ClapperQueue *queue); CLAPPER_API void clapper_queue_set_progression_mode (ClapperQueue *queue, ClapperQueueProgressionMode mode); CLAPPER_API ClapperQueueProgressionMode clapper_queue_get_progression_mode (ClapperQueue *queue); CLAPPER_API void clapper_queue_set_gapless (ClapperQueue *queue, gboolean gapless); CLAPPER_API gboolean clapper_queue_get_gapless (ClapperQueue *queue); CLAPPER_API void clapper_queue_set_instant (ClapperQueue *queue, gboolean instant); CLAPPER_API gboolean clapper_queue_get_instant (ClapperQueue *queue); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-stream-list-private.h000066400000000000000000000023231474353110700236460ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-stream-list.h" G_BEGIN_DECLS G_GNUC_INTERNAL ClapperStreamList * clapper_stream_list_new (void); G_GNUC_INTERNAL void clapper_stream_list_replace_streams (ClapperStreamList *list, GList *streams); G_GNUC_INTERNAL ClapperStream * clapper_stream_list_get_stream_for_gst_stream (ClapperStreamList *list, GstStream *gst_stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-stream-list.c000066400000000000000000000355471474353110700222070ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperStreamList: * * A list of media streams. */ #include #include "clapper-stream-list-private.h" #include "clapper-stream-private.h" #include "clapper-player-private.h" #include "clapper-playbin-bus-private.h" #define GST_CAT_DEFAULT clapper_stream_list_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperStreamList { GstObject parent; GPtrArray *streams; ClapperStream *current_stream; guint current_index; gboolean in_refresh; }; enum { PROP_0, PROP_CURRENT_STREAM, PROP_CURRENT_INDEX, PROP_N_STREAMS, PROP_LAST }; static void clapper_stream_list_model_iface_init (GListModelInterface *iface); #define parent_class clapper_stream_list_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperStreamList, clapper_stream_list, GST_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_stream_list_model_iface_init)); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static GType clapper_stream_list_model_get_item_type (GListModel *model) { return CLAPPER_TYPE_STREAM; } static guint clapper_stream_list_model_get_n_items (GListModel *model) { ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (model); guint n_streams; GST_OBJECT_LOCK (self); n_streams = self->streams->len; GST_OBJECT_UNLOCK (self); return n_streams; } static gpointer clapper_stream_list_model_get_item (GListModel *model, guint index) { ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (model); ClapperStream *stream = NULL; GST_OBJECT_LOCK (self); if (G_LIKELY (index < self->streams->len)) stream = gst_object_ref (g_ptr_array_index (self->streams, index)); GST_OBJECT_UNLOCK (self); return stream; } static void clapper_stream_list_model_iface_init (GListModelInterface *iface) { iface->get_item_type = clapper_stream_list_model_get_item_type; iface->get_n_items = clapper_stream_list_model_get_n_items; iface->get_item = clapper_stream_list_model_get_item; } static void _post_stream_change (ClapperStreamList *self) { ClapperPlayer *player; GST_OBJECT_LOCK (self); /* We will do a single initial selection ourselves * after all lists are refreshed, so do nothing here yet */ if (G_UNLIKELY (self->in_refresh)) { GST_WARNING_OBJECT (self, "Trying to select/autoselect stream before" " initial selection. This is not supported, please fix your app."); GST_OBJECT_UNLOCK (self); return; } GST_OBJECT_UNLOCK (self); player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); if (G_LIKELY (player != NULL)) { clapper_playbin_bus_post_stream_change (player->bus); gst_object_unref (player); } } static void _announce_current_stream_and_index_change (ClapperStreamList *self) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); gboolean is_main_thread; if (G_UNLIKELY (player == NULL)) return; is_main_thread = g_main_context_is_owner (g_main_context_default ()); GST_DEBUG_OBJECT (self, "Announcing current stream change from %smain thread," " now: %" GST_PTR_FORMAT " (index: %u)", (is_main_thread) ? "" : "non-", self->current_stream, self->current_index); if (is_main_thread) { g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_STREAM]); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_INDEX]); } else { clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_STREAM]); clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), param_specs[PROP_CURRENT_INDEX]); } gst_object_unref (player); } static gboolean clapper_stream_list_select_index_unlocked (ClapperStreamList *self, guint index) { ClapperStream *stream = NULL; if (index != CLAPPER_STREAM_LIST_INVALID_POSITION) stream = g_ptr_array_index (self->streams, index); if (gst_object_replace ((GstObject **) &self->current_stream, GST_OBJECT_CAST (stream))) { self->current_index = index; return TRUE; } return FALSE; } /* * clapper_stream_list_new: * * Returns: (transfer full): a new #ClapperStreamList instance */ ClapperStreamList * clapper_stream_list_new (void) { ClapperStreamList *list; list = g_object_new (CLAPPER_TYPE_STREAM_LIST, NULL); gst_object_ref_sink (list); return list; } /** * clapper_stream_list_select_stream: * @list: a #ClapperStreamList * @stream: a #ClapperStream * * Selects #ClapperStream from @list to be activated. * * Returns: %TRUE if stream was in the @list, %FALSE otherwise. */ gboolean clapper_stream_list_select_stream (ClapperStreamList *self, ClapperStream *stream) { gboolean found, changed = FALSE; guint index = 0; g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), FALSE); g_return_val_if_fail (CLAPPER_IS_STREAM (stream), FALSE); GST_OBJECT_LOCK (self); if ((found = g_ptr_array_find (self->streams, stream, &index))) changed = clapper_stream_list_select_index_unlocked (self, index); GST_OBJECT_UNLOCK (self); if (changed) { _post_stream_change (self); _announce_current_stream_and_index_change (self); } return found; } /** * clapper_stream_list_select_index: * @list: a #ClapperStreamList * @index: a stream index * * Selects #ClapperStream at @index from @list as current one. * * Returns: %TRUE if stream could be selected, %FALSE otherwise. */ gboolean clapper_stream_list_select_index (ClapperStreamList *self, guint index) { gboolean found, changed = FALSE; g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), FALSE); g_return_val_if_fail (index != CLAPPER_STREAM_LIST_INVALID_POSITION, FALSE); GST_OBJECT_LOCK (self); if ((found = index < self->streams->len)) changed = clapper_stream_list_select_index_unlocked (self, index); GST_OBJECT_UNLOCK (self); if (changed) { _post_stream_change (self); _announce_current_stream_and_index_change (self); } return found; } /** * clapper_stream_list_get_stream: * @list: a #ClapperStreamList * @index: a stream index * * Get the #ClapperStream 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 #ClapperStream at @index. */ ClapperStream * clapper_stream_list_get_stream (ClapperStreamList *self, guint index) { g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), NULL); return g_list_model_get_item (G_LIST_MODEL (self), index); } /** * clapper_stream_list_get_current_stream: * @list: a #ClapperStreamList * * Get the currently selected #ClapperStream. * * Returns: (transfer full) (nullable): The current #ClapperStream. */ ClapperStream * clapper_stream_list_get_current_stream (ClapperStreamList *self) { ClapperStream *stream = NULL; g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), NULL); GST_OBJECT_LOCK (self); if (self->current_stream) stream = gst_object_ref (self->current_stream); GST_OBJECT_UNLOCK (self); return stream; } /** * clapper_stream_list_get_current_index: * @list: a #ClapperStreamList * * Get index of the currently selected #ClapperStream. * * Returns: Current stream index or [const@Clapper.STREAM_LIST_INVALID_POSITION] * when nothing is selected. */ guint clapper_stream_list_get_current_index (ClapperStreamList *self) { guint index; g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), CLAPPER_STREAM_LIST_INVALID_POSITION); GST_OBJECT_LOCK (self); index = self->current_index; GST_OBJECT_UNLOCK (self); return index; } /** * clapper_stream_list_get_n_streams: * @list: a #ClapperStreamList * * Get the number of streams in #ClapperStreamList. * * 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 streams in #ClapperStreamList. */ guint clapper_stream_list_get_n_streams (ClapperStreamList *self) { g_return_val_if_fail (CLAPPER_IS_STREAM_LIST (self), 0); return g_list_model_get_n_items (G_LIST_MODEL (self)); } void clapper_stream_list_replace_streams (ClapperStreamList *self, GList *streams) { GList *st; guint prev_n_streams, n_streams; guint index = 0, selected_index = 0; gboolean changed, selected = FALSE; GST_OBJECT_LOCK (self); self->in_refresh = TRUE; prev_n_streams = self->streams->len; if (prev_n_streams > 0) g_ptr_array_remove_range (self->streams, 0, prev_n_streams); for (st = streams; st != NULL; st = st->next) { ClapperStream *stream = CLAPPER_STREAM_CAST (st->data); /* Try to select first "default" stream, while avoiding * streams that should not be selected by default. * NOTE: This works only with playbin3 */ if (!selected) { GstStream *gst_stream = clapper_stream_get_gst_stream (stream); GstStreamFlags flags = gst_stream_get_stream_flags (gst_stream); GST_LOG_OBJECT (self, "Stream flags: %i", flags); if ((flags & GST_STREAM_FLAG_SELECT) == GST_STREAM_FLAG_SELECT) { GST_DEBUG_OBJECT (self, "Stream has \"select\" stream flag"); selected = TRUE; selected_index = index; } else if ((flags & GST_STREAM_FLAG_UNSELECT) == GST_STREAM_FLAG_UNSELECT) { GST_DEBUG_OBJECT (self, "Stream has \"unselect\" stream flag"); if (selected_index == index) selected_index++; } } g_ptr_array_add (self->streams, stream); gst_object_set_parent (GST_OBJECT_CAST (stream), GST_OBJECT_CAST (self)); index++; } n_streams = self->streams->len; GST_OBJECT_UNLOCK (self); if (prev_n_streams > 0 || n_streams > 0) { g_list_model_items_changed (G_LIST_MODEL (self), 0, prev_n_streams, n_streams); if (prev_n_streams != n_streams) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_STREAMS]); } /* This can happen when ALL streams had "unselect" flag. * In this case just select the first one. */ if (n_streams > 0) { if (G_UNLIKELY (selected_index > n_streams - 1)) selected_index = 0; } else { selected_index = CLAPPER_STREAM_LIST_INVALID_POSITION; } /* TODO: Consider adding an API (or signal that returns index) * to select preferred initial stream (by e.g. language) */ GST_OBJECT_LOCK (self); changed = clapper_stream_list_select_index_unlocked (self, selected_index); GST_OBJECT_UNLOCK (self); if (changed) { GST_INFO_OBJECT (self, "Initially selecting stream index: %u", selected_index); _announce_current_stream_and_index_change (self); } GST_OBJECT_LOCK (self); self->in_refresh = FALSE; GST_OBJECT_UNLOCK (self); } ClapperStream * clapper_stream_list_get_stream_for_gst_stream (ClapperStreamList *self, GstStream *gst_stream) { ClapperStream *found_stream = NULL; guint i; GST_OBJECT_LOCK (self); for (i = 0; i < self->streams->len; ++i) { ClapperStream *stream = g_ptr_array_index (self->streams, i); GstStream *list_gst_stream = clapper_stream_get_gst_stream (stream); if (gst_stream == list_gst_stream) { found_stream = gst_object_ref (stream); break; } } GST_OBJECT_UNLOCK (self); return found_stream; } static void _stream_remove_func (ClapperStream *stream) { gst_object_unparent (GST_OBJECT_CAST (stream)); gst_object_unref (stream); } static void clapper_stream_list_init (ClapperStreamList *self) { self->streams = g_ptr_array_new_with_free_func ((GDestroyNotify) _stream_remove_func); self->current_index = CLAPPER_STREAM_LIST_INVALID_POSITION; } static void clapper_stream_list_finalize (GObject *object) { ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); gst_clear_object (&self->current_stream); g_ptr_array_unref (self->streams); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_stream_list_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (object); switch (prop_id) { case PROP_CURRENT_STREAM: g_value_take_object (value, clapper_stream_list_get_current_stream (self)); break; case PROP_CURRENT_INDEX: g_value_set_uint (value, clapper_stream_list_get_current_index (self)); break; case PROP_N_STREAMS: g_value_set_uint (value, clapper_stream_list_get_n_streams (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_stream_list_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperStreamList *self = CLAPPER_STREAM_LIST_CAST (object); switch (prop_id) { case PROP_CURRENT_INDEX: clapper_stream_list_select_index (self, g_value_get_uint (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_stream_list_class_init (ClapperStreamListClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperstreamlist", 0, "Clapper Stream List"); gobject_class->get_property = clapper_stream_list_get_property; gobject_class->set_property = clapper_stream_list_set_property; gobject_class->finalize = clapper_stream_list_finalize; /** * ClapperStreamList:current-stream: * * Currently selected stream. */ param_specs[PROP_CURRENT_STREAM] = g_param_spec_object ("current-stream", NULL, NULL, CLAPPER_TYPE_STREAM, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperStreamList:current-index: * * Index of currently selected stream. */ param_specs[PROP_CURRENT_INDEX] = g_param_spec_uint ("current-index", NULL, NULL, 0, G_MAXUINT, CLAPPER_STREAM_LIST_INVALID_POSITION, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperStreamList:n-streams: * * Number of streams in the list. */ param_specs[PROP_N_STREAMS] = g_param_spec_uint ("n-streams", 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); } clapper-0.8.0/src/lib/clapper/clapper-stream-list.h000066400000000000000000000041521474353110700222000ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_STREAM_LIST (clapper_stream_list_get_type()) #define CLAPPER_STREAM_LIST_CAST(obj) ((ClapperStreamList *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperStreamList, clapper_stream_list, CLAPPER, STREAM_LIST, GstObject) /** * CLAPPER_STREAM_LIST_INVALID_POSITION: * * The value used to refer to an invalid position in a #ClapperStreamList */ #define CLAPPER_STREAM_LIST_INVALID_POSITION ((guint) 0xffffffff) CLAPPER_API gboolean clapper_stream_list_select_stream (ClapperStreamList *list, ClapperStream *stream); CLAPPER_API gboolean clapper_stream_list_select_index (ClapperStreamList *list, guint index); CLAPPER_API ClapperStream * clapper_stream_list_get_stream (ClapperStreamList *list, guint index); CLAPPER_API ClapperStream * clapper_stream_list_get_current_stream (ClapperStreamList *list); CLAPPER_API guint clapper_stream_list_get_current_index (ClapperStreamList *list); CLAPPER_API guint clapper_stream_list_get_n_streams (ClapperStreamList *list); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-stream-private.h000066400000000000000000000034471474353110700227050ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-stream.h" G_BEGIN_DECLS G_GNUC_INTERNAL ClapperStream * clapper_stream_new (GstStream *gst_stream); G_GNUC_INTERNAL void clapper_stream_set_gst_stream (ClapperStream *stream, GstStream *gst_stream); G_GNUC_INTERNAL GstStream * clapper_stream_get_gst_stream (ClapperStream *stream); G_GNUC_INTERNAL void clapper_stream_take_string_prop (ClapperStream *stream, GParamSpec *pspec, gchar **ptr, gchar *value); G_GNUC_INTERNAL void clapper_stream_set_string_prop (ClapperStream *stream, GParamSpec *pspec, gchar **ptr, const gchar *value); G_GNUC_INTERNAL void clapper_stream_set_int_prop (ClapperStream *stream, GParamSpec *pspec, gint *ptr, gint value); G_GNUC_INTERNAL void clapper_stream_set_uint_prop (ClapperStream *stream, GParamSpec *pspec, guint *ptr, guint value); G_GNUC_INTERNAL void clapper_stream_set_double_prop (ClapperStream *stream, GParamSpec *pspec, gdouble *ptr, gdouble value); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-stream.c000066400000000000000000000224131474353110700212220ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperStream: * * Represents a stream within media. */ #include #include "clapper-stream-private.h" #include "clapper-player-private.h" #define GST_CAT_DEFAULT clapper_stream_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); typedef struct _ClapperStreamPrivate ClapperStreamPrivate; struct _ClapperStreamPrivate { GstStream *gst_stream; ClapperStreamType stream_type; gchar *title; }; enum { PROP_0, PROP_STREAM_TYPE, PROP_TITLE, PROP_LAST }; #define parent_class clapper_stream_parent_class G_DEFINE_TYPE_WITH_PRIVATE (ClapperStream, clapper_stream, GST_TYPE_OBJECT); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void _update_using_tags (ClapperStream *self, GstTagList *tags) { ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self); gchar *title = NULL; gst_tag_list_get_string_index (tags, GST_TAG_TITLE, 0, &title); clapper_stream_take_string_prop (self, param_specs[PROP_TITLE], &priv->title, title); } ClapperStream * clapper_stream_new (GstStream *gst_stream) { ClapperStream *stream; ClapperStreamPrivate *priv; stream = g_object_new (CLAPPER_TYPE_STREAM, NULL); gst_object_ref_sink (stream); priv = clapper_stream_get_instance_private (stream); priv->gst_stream = gst_object_ref (gst_stream); return stream; } /* * This should be called only during stream construction */ void clapper_stream_set_gst_stream (ClapperStream *stream, GstStream *gst_stream) { ClapperStreamPrivate *priv = clapper_stream_get_instance_private (stream); if (G_LIKELY (gst_object_replace ((GstObject **) &priv->gst_stream, GST_OBJECT_CAST (gst_stream)))) { GstCaps *caps = gst_stream_get_caps (gst_stream); GstTagList *tags = gst_stream_get_tags (gst_stream); if (caps || tags) { ClapperStreamClass *stream_class = CLAPPER_STREAM_GET_CLASS (stream); stream_class->internal_stream_updated (stream, caps, tags); gst_clear_caps (&caps); gst_clear_tag_list (&tags); } } } /** * clapper_stream_get_stream_type: * @stream: a #ClapperStream * * Get the #ClapperStreamType of @stream. * * Returns: type of stream. */ ClapperStreamType clapper_stream_get_stream_type (ClapperStream *self) { ClapperStreamPrivate *priv; g_return_val_if_fail (CLAPPER_IS_STREAM (self), CLAPPER_STREAM_TYPE_UNKNOWN); priv = clapper_stream_get_instance_private (self); return priv->stream_type; } /** * clapper_stream_get_title: * @stream: a #ClapperStream * * Get the title of @stream, if any. * * Returns: (transfer full) (nullable): title of stream. */ gchar * clapper_stream_get_title (ClapperStream *self) { ClapperStreamPrivate *priv; gchar *title; g_return_val_if_fail (CLAPPER_IS_STREAM (self), NULL); priv = clapper_stream_get_instance_private (self); GST_OBJECT_LOCK (self); title = g_strdup (priv->title); GST_OBJECT_UNLOCK (self); return title; } GstStream * clapper_stream_get_gst_stream (ClapperStream *self) { ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self); return priv->gst_stream; } static void clapper_stream_prop_notify (ClapperStream *self, GParamSpec *pspec) { ClapperPlayer *player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)); /* NOTE: This happens when props are initially set during construction * (before parented) which is fine, since we do not have to notify * when user does not have access to object yet. */ if (!player) return; clapper_app_bus_post_prop_notify (player->app_bus, GST_OBJECT_CAST (self), pspec); gst_object_unref (player); } void clapper_stream_take_string_prop (ClapperStream *self, GParamSpec *pspec, gchar **ptr, gchar *value) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = g_strcmp0 (*ptr, value) != 0)) { g_free (*ptr); *ptr = value; GST_DEBUG_OBJECT (self, "Set %s: %s", g_param_spec_get_name (pspec), value); } GST_OBJECT_UNLOCK (self); if (changed) clapper_stream_prop_notify (self, pspec); else g_free (value); } void clapper_stream_set_string_prop (ClapperStream *self, GParamSpec *pspec, gchar **ptr, const gchar *value) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = g_set_str (ptr, value))) { GST_DEBUG_OBJECT (self, "Set %s: %s", g_param_spec_get_name (pspec), value); } GST_OBJECT_UNLOCK (self); if (changed) clapper_stream_prop_notify (self, pspec); } void clapper_stream_set_int_prop (ClapperStream *self, GParamSpec *pspec, gint *ptr, gint value) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = *ptr != value)) { *ptr = value; GST_DEBUG_OBJECT (self, "Set %s: %i", g_param_spec_get_name (pspec), value); } GST_OBJECT_UNLOCK (self); if (changed) clapper_stream_prop_notify (self, pspec); } void clapper_stream_set_uint_prop (ClapperStream *self, GParamSpec *pspec, guint *ptr, guint value) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = *ptr != value)) { *ptr = value; GST_DEBUG_OBJECT (self, "Set %s: %u", g_param_spec_get_name (pspec), value); } GST_OBJECT_UNLOCK (self); if (changed) clapper_stream_prop_notify (self, pspec); } void clapper_stream_set_double_prop (ClapperStream *self, GParamSpec *pspec, gdouble *ptr, gdouble value) { gboolean changed; GST_OBJECT_LOCK (self); if ((changed = !G_APPROX_VALUE (*ptr, value, FLT_EPSILON))) { *ptr = value; GST_DEBUG_OBJECT (self, "Set %s: %lf", g_param_spec_get_name (pspec), value); } GST_OBJECT_UNLOCK (self); if (changed) clapper_stream_prop_notify (self, pspec); } static void clapper_stream_init (ClapperStream *self) { ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self); priv->stream_type = CLAPPER_STREAM_TYPE_UNKNOWN; } static void clapper_stream_finalize (GObject *object) { ClapperStream *self = CLAPPER_STREAM_CAST (object); ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self); GST_TRACE_OBJECT (self, "Finalize"); gst_clear_object (&priv->gst_stream); g_free (priv->title); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_stream_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperStream *self = CLAPPER_STREAM_CAST (object); switch (prop_id) { case PROP_STREAM_TYPE: g_value_set_enum (value, clapper_stream_get_stream_type (self)); break; case PROP_TITLE: g_value_take_string (value, clapper_stream_get_title (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_stream_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperStream *self = CLAPPER_STREAM_CAST (object); ClapperStreamPrivate *priv = clapper_stream_get_instance_private (self); switch (prop_id) { case PROP_STREAM_TYPE: priv->stream_type = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_stream_internal_stream_updated (ClapperStream *self, GstCaps *caps, GstTagList *tags) { if (caps) GST_LOG_OBJECT (self, "Caps: %" GST_PTR_FORMAT, caps); if (tags) { GST_LOG_OBJECT (self, "Tags: %" GST_PTR_FORMAT, tags); _update_using_tags (self, tags); } } static void clapper_stream_class_init (ClapperStreamClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperStreamClass *stream_class = (ClapperStreamClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperstream", 0, "Clapper Stream"); gobject_class->get_property = clapper_stream_get_property; gobject_class->set_property = clapper_stream_set_property; gobject_class->finalize = clapper_stream_finalize; stream_class->internal_stream_updated = clapper_stream_internal_stream_updated; /** * ClapperStream:stream-type: * * Type of stream. */ param_specs[PROP_STREAM_TYPE] = g_param_spec_enum ("stream-type", NULL, NULL, CLAPPER_TYPE_STREAM_TYPE, CLAPPER_STREAM_TYPE_UNKNOWN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperStream:title: * * Title of stream. */ param_specs[PROP_TITLE] = g_param_spec_string ("title", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/clapper-stream.h000066400000000000000000000041761474353110700212350ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_STREAM (clapper_stream_get_type()) #define CLAPPER_STREAM_CAST(obj) ((ClapperStream *)(obj)) CLAPPER_API G_DECLARE_DERIVABLE_TYPE (ClapperStream, clapper_stream, CLAPPER, STREAM, GstObject) struct _ClapperStreamClass { GstObjectClass parent_class; /** * ClapperStreamClass::internal_stream_updated: * @stream: a #ClapperStream * @caps: (nullable): an updated #GstCaps if changed * @tags: (nullable): an updated #GstTagList if changed * * This function is called when internal #GstStream gets updated. * Meant for internal usage only. Used for subclasses to update * their properties accordingly. * * Note that this vfunc is called from different threads. */ void (* internal_stream_updated) (ClapperStream *stream, GstCaps *caps, GstTagList *tags); /*< private >*/ gpointer padding[4]; }; CLAPPER_API ClapperStreamType clapper_stream_get_stream_type (ClapperStream *stream); CLAPPER_API gchar * clapper_stream_get_title (ClapperStream *stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-subtitle-stream-private.h000066400000000000000000000020271474353110700245270ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-subtitle-stream.h" G_BEGIN_DECLS G_GNUC_INTERNAL ClapperStream * clapper_subtitle_stream_new (GstStream *gst_stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-subtitle-stream.c000066400000000000000000000141671474353110700230620ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperSubtitleStream: * * Represents a subtitle stream within media. */ #include #include "clapper-subtitle-stream-private.h" #include "clapper-stream-private.h" #define GST_CAT_DEFAULT clapper_subtitle_stream_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperSubtitleStream { ClapperStream parent; gchar *lang_code; gchar *lang_name; }; #define parent_class clapper_subtitle_stream_parent_class G_DEFINE_TYPE (ClapperSubtitleStream, clapper_subtitle_stream, CLAPPER_TYPE_STREAM); enum { PROP_0, PROP_LANG_CODE, PROP_LANG_NAME, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void _update_using_tags (ClapperSubtitleStream *self, GstTagList *tags) { ClapperStream *stream = CLAPPER_STREAM_CAST (self); gchar *lang_code = NULL, *lang_name = NULL; /* Prefer code (and name from it), fallback to extracted name */ if (!gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_CODE, 0, &lang_code)) gst_tag_list_get_string_index (tags, GST_TAG_LANGUAGE_NAME, 0, &lang_name); clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_CODE], &self->lang_code, lang_code); clapper_stream_take_string_prop (stream, param_specs[PROP_LANG_NAME], &self->lang_name, lang_name); } ClapperStream * clapper_subtitle_stream_new (GstStream *gst_stream) { ClapperSubtitleStream *subtitle_stream; subtitle_stream = g_object_new (CLAPPER_TYPE_SUBTITLE_STREAM, "stream-type", CLAPPER_STREAM_TYPE_SUBTITLE, NULL); gst_object_ref_sink (subtitle_stream); clapper_stream_set_gst_stream (CLAPPER_STREAM_CAST (subtitle_stream), gst_stream); return CLAPPER_STREAM_CAST (subtitle_stream); } /** * clapper_subtitle_stream_get_lang_code: * @stream: a #ClapperSubtitleStream * * Get an ISO-639 language code of the @stream. * * Returns: (transfer full) (nullable): the language code of subtitle stream. */ gchar * clapper_subtitle_stream_get_lang_code (ClapperSubtitleStream *self) { gchar *lang_code; g_return_val_if_fail (CLAPPER_IS_SUBTITLE_STREAM (self), NULL); GST_OBJECT_LOCK (self); lang_code = g_strdup (self->lang_code); GST_OBJECT_UNLOCK (self); return lang_code; } /** * clapper_subtitle_stream_get_lang_name: * @stream: a #ClapperSubtitleStream * * Get language name of the @stream. * * This function will try to return a translated string into current * locale if possible, with a fallback to a name extracted from tags. * * Returns: (transfer full) (nullable): the language name of subtitle stream. */ gchar * clapper_subtitle_stream_get_lang_name (ClapperSubtitleStream *self) { gchar *lang_name = NULL; g_return_val_if_fail (CLAPPER_IS_SUBTITLE_STREAM (self), NULL); GST_OBJECT_LOCK (self); /* Prefer from code as its translated to user locale, * otherwise try to fallback to the one sent in tags */ if (self->lang_code) lang_name = g_strdup (gst_tag_get_language_name (self->lang_code)); if (!lang_name) lang_name = g_strdup (self->lang_name); GST_OBJECT_UNLOCK (self); return lang_name; } static void clapper_subtitle_stream_init (ClapperSubtitleStream *self) { } static void clapper_subtitle_stream_finalize (GObject *object) { ClapperSubtitleStream *self = CLAPPER_SUBTITLE_STREAM_CAST (object); g_free (self->lang_code); g_free (self->lang_name); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_subtitle_stream_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperSubtitleStream *self = CLAPPER_SUBTITLE_STREAM_CAST (object); switch (prop_id) { case PROP_LANG_CODE: g_value_take_string (value, clapper_subtitle_stream_get_lang_code (self)); break; case PROP_LANG_NAME: g_value_take_string (value, clapper_subtitle_stream_get_lang_name (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_subtitle_stream_internal_stream_updated (ClapperStream *stream, GstCaps *caps, GstTagList *tags) { ClapperSubtitleStream *self = CLAPPER_SUBTITLE_STREAM_CAST (stream); CLAPPER_STREAM_CLASS (parent_class)->internal_stream_updated (stream, caps, tags); if (tags) _update_using_tags (self, tags); } static void clapper_subtitle_stream_class_init (ClapperSubtitleStreamClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperStreamClass *stream_class = (ClapperStreamClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappersubtitlestream", 0, "Clapper Subtitle Stream"); gobject_class->get_property = clapper_subtitle_stream_get_property; gobject_class->finalize = clapper_subtitle_stream_finalize; stream_class->internal_stream_updated = clapper_subtitle_stream_internal_stream_updated; /** * ClapperSubtitleStream:lang-code: * * Stream language code in ISO-639 format. */ param_specs[PROP_LANG_CODE] = g_param_spec_string ("lang-code", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperSubtitleStream:lang-name: * * Stream language name. */ param_specs[PROP_LANG_NAME] = g_param_spec_string ("lang-name", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/clapper-subtitle-stream.h000066400000000000000000000030741474353110700230620ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_SUBTITLE_STREAM (clapper_subtitle_stream_get_type()) #define CLAPPER_SUBTITLE_STREAM_CAST(obj) ((ClapperSubtitleStream *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperSubtitleStream, clapper_subtitle_stream, CLAPPER, SUBTITLE_STREAM, ClapperStream) CLAPPER_API gchar * clapper_subtitle_stream_get_lang_code (ClapperSubtitleStream *stream); CLAPPER_API gchar * clapper_subtitle_stream_get_lang_name (ClapperSubtitleStream *stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-threaded-object.c000066400000000000000000000134221474353110700227530ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperThreadedObject: * * A base class for creating objects that work within a separate thread. */ #include "clapper-threaded-object.h" #define GST_CAT_DEFAULT clapper_threaded_object_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); typedef struct _ClapperThreadedObjectPrivate ClapperThreadedObjectPrivate; struct _ClapperThreadedObjectPrivate { GMutex lock; // Separate mutex to not deadlock with subclass on wakeups GCond cond; GThread *thread; GMainContext *context; GMainLoop *loop; gboolean started; }; #define parent_class clapper_threaded_object_parent_class G_DEFINE_TYPE_WITH_PRIVATE (ClapperThreadedObject, clapper_threaded_object, GST_TYPE_OBJECT) /** * clapper_threaded_object_get_context: * @threaded_object: a #ClapperThreadedObject * * Get the #GMainContext of the thread used by this object. * * Useful when you want to invoke object thread to do some * action in it from a different thread. * * Returns: a #GMainContext of the object used thread. */ GMainContext * clapper_threaded_object_get_context (ClapperThreadedObject *self) { ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self); return priv->context; } static gboolean main_loop_running_cb (ClapperThreadedObject *self) { ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self); GST_TRACE_OBJECT (self, "Main loop running now"); g_mutex_lock (&priv->lock); priv->started = TRUE; g_cond_signal (&priv->cond); g_mutex_unlock (&priv->lock); return G_SOURCE_REMOVE; } static gpointer clapper_threaded_object_main (ClapperThreadedObject *self) { ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self); ClapperThreadedObjectClass *threaded_object_class = CLAPPER_THREADED_OBJECT_GET_CLASS (self); const gchar *obj_cls_name = G_OBJECT_CLASS_NAME (threaded_object_class); GSource *idle_source; GST_TRACE_OBJECT (self, "%s thread: %p", obj_cls_name, g_thread_self ()); priv->context = g_main_context_new (); priv->loop = g_main_loop_new (priv->context, FALSE); g_main_context_push_thread_default (priv->context); if (threaded_object_class->thread_start) threaded_object_class->thread_start (self); idle_source = g_idle_source_new (); g_source_set_callback (idle_source, (GSourceFunc) main_loop_running_cb, self, NULL); g_source_attach (idle_source, priv->context); g_source_unref (idle_source); GST_DEBUG_OBJECT (self, "%s main loop running", obj_cls_name); g_main_loop_run (priv->loop); GST_DEBUG_OBJECT (self, "%s main loop stopped", obj_cls_name); if (threaded_object_class->thread_stop) threaded_object_class->thread_stop (self); g_main_context_pop_thread_default (priv->context); g_main_context_unref (priv->context); return NULL; } static void clapper_threaded_object_init (ClapperThreadedObject *self) { ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self); g_mutex_init (&priv->lock); g_cond_init (&priv->cond); } static void clapper_threaded_object_constructed (GObject *object) { ClapperThreadedObject *self = CLAPPER_THREADED_OBJECT_CAST (object); ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self); GST_TRACE_OBJECT (self, "Constructed from thread: %p", g_thread_self ()); g_mutex_lock (&priv->lock); priv->thread = g_thread_new (GST_OBJECT_NAME (object), (GThreadFunc) clapper_threaded_object_main, self); while (!priv->started) g_cond_wait (&priv->cond, &priv->lock); g_mutex_unlock (&priv->lock); G_OBJECT_CLASS (parent_class)->constructed (object); } static void clapper_threaded_object_dispose (GObject *object) { ClapperThreadedObject *self = CLAPPER_THREADED_OBJECT_CAST (object); ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self); g_mutex_lock (&priv->lock); if (priv->loop) { g_main_loop_quit (priv->loop); if (G_LIKELY (priv->thread != g_thread_self ())) g_thread_join (priv->thread); else g_thread_unref (priv->thread); g_clear_pointer (&priv->loop, g_main_loop_unref); } g_mutex_unlock (&priv->lock); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_threaded_object_finalize (GObject *object) { ClapperThreadedObject *self = CLAPPER_THREADED_OBJECT_CAST (object); ClapperThreadedObjectPrivate *priv = clapper_threaded_object_get_instance_private (self); g_mutex_clear (&priv->lock); g_cond_clear (&priv->cond); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_threaded_object_class_init (ClapperThreadedObjectClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperthreadedobject", 0, "Clapper Threaded Object"); gobject_class->constructed = clapper_threaded_object_constructed; gobject_class->dispose = clapper_threaded_object_dispose; gobject_class->finalize = clapper_threaded_object_finalize; } clapper-0.8.0/src/lib/clapper/clapper-threaded-object.h000066400000000000000000000045231474353110700227620ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_THREADED_OBJECT (clapper_threaded_object_get_type()) #define CLAPPER_THREADED_OBJECT_CAST(obj) ((ClapperThreadedObject *)(obj)) CLAPPER_API G_DECLARE_DERIVABLE_TYPE (ClapperThreadedObject, clapper_threaded_object, CLAPPER, THREADED_OBJECT, GstObject) /** * ClapperThreadedObjectClass: * @parent_class: The object class structure. * @thread_start: Called right after thread started. * @thread_stop: Called when thread is going to stop. */ struct _ClapperThreadedObjectClass { GstObjectClass parent_class; /** * ClapperThreadedObjectClass::thread_start: * @threaded_object: a #ClapperThreadedObject * * Called right after thread started. * * Useful for initializing objects that work within this new thread. */ void (* thread_start) (ClapperThreadedObject *threaded_object); /** * ClapperThreadedObjectClass::thread_stop: * @threaded_object: a #ClapperThreadedObject * * Called when thread is going to stop. * * Useful for cleanup of things created on thread start. */ void (* thread_stop) (ClapperThreadedObject *threaded_object); /*< private >*/ gpointer padding[4]; }; CLAPPER_API GMainContext * clapper_threaded_object_get_context (ClapperThreadedObject *threaded_object); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-timeline-private.h000066400000000000000000000022651474353110700232150ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-timeline.h" G_BEGIN_DECLS G_GNUC_INTERNAL ClapperTimeline * clapper_timeline_new (void); G_GNUC_INTERNAL gboolean clapper_timeline_set_toc (ClapperTimeline *timeline, GstToc *toc, gboolean updated); G_GNUC_INTERNAL void clapper_timeline_refresh (ClapperTimeline *timeline); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-timeline.c000066400000000000000000000363151474353110700215430ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperTimeline: * * A media timeline filled with point markers. */ #include #include "clapper-enums.h" #include "clapper-timeline-private.h" #include "clapper-marker-private.h" #include "clapper-player-private.h" #define GST_CAT_DEFAULT clapper_timeline_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperTimeline { GstObject parent; GSequence *markers_seq; GstToc *toc; GPtrArray *pending_markers; gboolean needs_refresh; }; enum { PROP_0, PROP_N_MARKERS, PROP_LAST }; static GType clapper_timeline_list_model_get_item_type (GListModel *model) { return CLAPPER_TYPE_MARKER; } static guint clapper_timeline_list_model_get_n_items (GListModel *model) { ClapperTimeline *self = CLAPPER_TIMELINE_CAST (model); guint n_markers; GST_OBJECT_LOCK (self); n_markers = g_sequence_get_length (self->markers_seq); GST_OBJECT_UNLOCK (self); return n_markers; } static gpointer clapper_timeline_list_model_get_item (GListModel *model, guint index) { ClapperTimeline *self = CLAPPER_TIMELINE_CAST (model); GSequenceIter *iter; ClapperMarker *marker = NULL; GST_OBJECT_LOCK (self); iter = g_sequence_get_iter_at_pos (self->markers_seq, index); if (!g_sequence_iter_is_end (iter)) marker = gst_object_ref (g_sequence_get (iter)); GST_OBJECT_UNLOCK (self); return marker; } static void clapper_timeline_list_model_iface_init (GListModelInterface *iface) { iface->get_item_type = clapper_timeline_list_model_get_item_type; iface->get_n_items = clapper_timeline_list_model_get_n_items; iface->get_item = clapper_timeline_list_model_get_item; } #define parent_class clapper_timeline_parent_class G_DEFINE_TYPE_WITH_CODE (ClapperTimeline, clapper_timeline, GST_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, clapper_timeline_list_model_iface_init)); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void clapper_timeline_post_item_updated (ClapperTimeline *self) { ClapperPlayer *player; if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self)))) { ClapperFeaturesManager *features_manager; if ((features_manager = clapper_player_get_features_manager (player))) { ClapperMediaItem *item; if ((item = CLAPPER_MEDIA_ITEM (gst_object_get_parent (GST_OBJECT_CAST (self))))) { clapper_features_manager_trigger_item_updated (features_manager, item); gst_object_unref (item); } } gst_object_unref (player); } } static gint _markers_compare_func (gconstpointer marker_a, gconstpointer marker_b, gpointer user_data G_GNUC_UNUSED) { gint64 val_a, val_b, result; /* Can happen if someone tries to insert already * inserted marker pointer */ if (marker_a == marker_b) return 0; /* 1 millisecond accuracy should be enough */ val_a = clapper_marker_get_start (CLAPPER_MARKER_CAST (marker_a)) * 1000; val_b = clapper_marker_get_start (CLAPPER_MARKER_CAST (marker_b)) * 1000; /* If start time is the same, sort by earliest end time */ if (val_a == val_b) { val_a = clapper_marker_get_end (CLAPPER_MARKER_CAST (marker_a)) * 1000; val_b = clapper_marker_get_end (CLAPPER_MARKER_CAST (marker_b)) * 1000; /* If both times are the same, check type and if they also are * the same, we will assume that this is the same marker overall */ if (val_a == val_b) { val_a = clapper_marker_get_marker_type (CLAPPER_MARKER_CAST (marker_a)); val_b = clapper_marker_get_marker_type (CLAPPER_MARKER_CAST (marker_b)); } } result = val_a - val_b; return (result > 0) ? 1 : (result < 0) ? -1 : 0; } /* * clapper_timeline_new: * * Returns: (transfer full): a new #ClapperTimeline instance */ ClapperTimeline * clapper_timeline_new (void) { ClapperTimeline *timeline; timeline = g_object_new (CLAPPER_TYPE_TIMELINE, NULL); gst_object_ref_sink (timeline); return timeline; } static inline gint _take_marker_unlocked (ClapperTimeline *self, ClapperMarker *marker) { GSequenceIter *iter; iter = g_sequence_insert_sorted (self->markers_seq, marker, (GCompareDataFunc) _markers_compare_func, NULL); gst_object_set_parent (GST_OBJECT_CAST (marker), GST_OBJECT_CAST (self)); return g_sequence_iter_get_position (iter); } /** * clapper_timeline_insert_marker: * @timeline: a #ClapperTimeline * @marker: a #ClapperMarker * * Insert the #ClapperMarker into @timeline. * * Returns: %TRUE if inserted, %FALSE if marker was * already inserted into timeline. */ gboolean clapper_timeline_insert_marker (ClapperTimeline *self, ClapperMarker *marker) { gboolean success; gint position = 0; g_return_val_if_fail (CLAPPER_IS_TIMELINE (self), FALSE); g_return_val_if_fail (CLAPPER_IS_MARKER (marker), FALSE); GST_OBJECT_LOCK (self); if ((success = !g_sequence_lookup (self->markers_seq, marker, (GCompareDataFunc) _markers_compare_func, NULL))) position = _take_marker_unlocked (self, gst_object_ref (marker)); GST_OBJECT_UNLOCK (self); if (success) { g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_MARKERS]); clapper_timeline_post_item_updated (self); } return success; } /** * clapper_timeline_remove_marker: * @timeline: a #ClapperTimeline * @marker: a #ClapperMarker * * Removes #ClapperMarker from the timeline. * * If marker was not in the @timeline, this function will do nothing, * so it is safe to call if unsure. */ void clapper_timeline_remove_marker (ClapperTimeline *self, ClapperMarker *marker) { GSequenceIter *iter; gint position = 0; gboolean success = FALSE; g_return_if_fail (CLAPPER_IS_TIMELINE (self)); g_return_if_fail (CLAPPER_IS_MARKER (marker)); GST_OBJECT_LOCK (self); if ((iter = g_sequence_lookup (self->markers_seq, marker, (GCompareDataFunc) _markers_compare_func, NULL))) { position = g_sequence_iter_get_position (iter); g_sequence_remove (iter); success = TRUE; } GST_OBJECT_UNLOCK (self); if (success) { g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_MARKERS]); clapper_timeline_post_item_updated (self); } } /** * clapper_timeline_get_marker: * @timeline: a #ClapperTimeline * @index: a marker index * * Get the #ClapperMarker 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 #ClapperMarker at @index. */ ClapperMarker * clapper_timeline_get_marker (ClapperTimeline *self, guint index) { g_return_val_if_fail (CLAPPER_IS_TIMELINE (self), NULL); return g_list_model_get_item (G_LIST_MODEL (self), index); } /** * clapper_timeline_get_n_markers: * @timeline: a #ClapperTimeline * * Get the number of markers in #ClapperTimeline. * * 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 markers in #ClapperTimeline. */ guint clapper_timeline_get_n_markers (ClapperTimeline *self) { g_return_val_if_fail (CLAPPER_IS_TIMELINE (self), 0); return g_list_model_get_n_items (G_LIST_MODEL (self)); } static void _append_marker_from_toc_entry (ClapperTimeline *self, GstTocEntry *entry, GList **markers) { ClapperMarker *marker; ClapperMarkerType marker_type; GstTagList *tags; gchar *title = NULL; gint64 start = 0, stop = 0; gdouble marker_start = 0, marker_end = CLAPPER_MARKER_NO_END; switch (gst_toc_entry_get_entry_type (entry)) { case GST_TOC_ENTRY_TYPE_TITLE: marker_type = CLAPPER_MARKER_TYPE_TITLE; break; case GST_TOC_ENTRY_TYPE_TRACK: marker_type = CLAPPER_MARKER_TYPE_TRACK; break; case GST_TOC_ENTRY_TYPE_CHAPTER: marker_type = CLAPPER_MARKER_TYPE_CHAPTER; break; default: return; } /* Start time is required */ if (G_UNLIKELY (!gst_toc_entry_get_start_stop_times (entry, &start, NULL))) return; marker_start = (gdouble) start / GST_SECOND; if (gst_toc_entry_get_start_stop_times (entry, NULL, &stop)) marker_end = (gdouble) stop / GST_SECOND; if ((tags = gst_toc_entry_get_tags (entry))) gst_tag_list_get_string_index (tags, GST_TAG_TITLE, 0, &title); marker = clapper_marker_new_internal (marker_type, title, marker_start, marker_end); *markers = g_list_append (*markers, marker); g_free (title); } static void _iterate_toc_entries (ClapperTimeline *self, GList *entries, GList **markers) { GList *en; for (en = entries; en != NULL; en = en->next) { GstTocEntry *entry = (GstTocEntry *) en->data; if (gst_toc_entry_is_alternative (entry)) _iterate_toc_entries (self, gst_toc_entry_get_sub_entries (entry), markers); else if (gst_toc_entry_is_sequence (entry)) _append_marker_from_toc_entry (self, entry, markers); } } static inline void _prepare_markers (ClapperTimeline *self, GstToc *toc) { GList *entries = gst_toc_get_entries (toc); GList *ma, *markers = NULL; GST_DEBUG_OBJECT (self, "Preparing markers from TOC: %" GST_PTR_FORMAT, toc); _iterate_toc_entries (self, entries, &markers); GST_OBJECT_LOCK (self); g_ptr_array_remove_range (self->pending_markers, 0, self->pending_markers->len); for (ma = markers; ma != NULL; ma = ma->next) g_ptr_array_add (self->pending_markers, CLAPPER_MARKER_CAST (ma->data)); self->needs_refresh = TRUE; GST_OBJECT_UNLOCK (self); if (markers) g_list_free (markers); } gboolean clapper_timeline_set_toc (ClapperTimeline *self, GstToc *toc, gboolean updated) { gboolean changed; if (gst_toc_get_scope (toc) != GST_TOC_SCOPE_GLOBAL) return FALSE; GST_OBJECT_LOCK (self); if (self->toc == toc) { changed = updated; } else { /* FIXME: Iterate and compare entries and their amount * one by one, so we can avoid update between discovery and playback * (and also when playing the same media item again) */ changed = TRUE; } if (changed) { if (self->toc) gst_toc_unref (self->toc); self->toc = gst_toc_ref (toc); } GST_OBJECT_UNLOCK (self); if (changed) _prepare_markers (self, toc); return changed; } /* Must be called from main thread */ void clapper_timeline_refresh (ClapperTimeline *self) { GSequenceIter *iter; GList *rec, *rec_markers = NULL; gpointer *stolen_markers; gsize n_pending = 0; guint i, n_before, n_after; GST_OBJECT_LOCK (self); /* This prevents us from incorrect behaviour when there were multiple * TOC objects set in a row before we reached main thread handling * for them here and refresh will be now invoked in a row, possibly * erasing markers on its second run */ if (!self->needs_refresh) { GST_OBJECT_UNLOCK (self); return; } GST_DEBUG_OBJECT (self, "Timeline refresh"); n_before = g_sequence_get_length (self->markers_seq); /* Recover markers that should remain */ iter = g_sequence_get_begin_iter (self->markers_seq); while (!g_sequence_iter_is_end (iter)) { ClapperMarker *marker = CLAPPER_MARKER_CAST (g_sequence_get (iter)); if (!clapper_marker_is_internal (marker)) rec_markers = g_list_append (rec_markers, gst_object_ref (marker)); iter = g_sequence_iter_next (iter); } /* Clear sequence */ g_sequence_remove_range ( g_sequence_get_begin_iter (self->markers_seq), g_sequence_get_end_iter (self->markers_seq)); /* Transfer pending markers into sequence */ stolen_markers = g_ptr_array_steal (self->pending_markers, &n_pending); for (i = 0; i < n_pending; ++i) { g_sequence_append (self->markers_seq, CLAPPER_MARKER_CAST (stolen_markers[i])); gst_object_set_parent (GST_OBJECT_CAST (stolen_markers[i]), GST_OBJECT_CAST (self)); } g_free (stolen_markers); /* Transfer recovered markers back into sequence */ for (rec = rec_markers; rec != NULL; rec = rec->next) { ClapperMarker *marker = CLAPPER_MARKER_CAST (rec->data); g_sequence_append (self->markers_seq, marker); gst_object_set_parent (GST_OBJECT_CAST (marker), GST_OBJECT_CAST (self)); } if (rec_markers) g_list_free (rec_markers); /* Sort once after all appends (this way is faster according to documentation) */ g_sequence_sort (self->markers_seq, _markers_compare_func, NULL); n_after = g_sequence_get_length (self->markers_seq); self->needs_refresh = FALSE; GST_OBJECT_UNLOCK (self); GST_DEBUG_OBJECT (self, "Timeline refreshed, n_before: %u, n_after: %u", n_before, n_after); g_list_model_items_changed (G_LIST_MODEL (self), 0, n_before, n_after); if (n_before != n_after) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_N_MARKERS]); clapper_timeline_post_item_updated (self); } static void _marker_remove_func (ClapperMarker *marker) { gst_object_unparent (GST_OBJECT_CAST (marker)); gst_object_unref (marker); } static void clapper_timeline_init (ClapperTimeline *self) { self->markers_seq = g_sequence_new ((GDestroyNotify) _marker_remove_func); self->pending_markers = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); } static void clapper_timeline_finalize (GObject *object) { ClapperTimeline *self = CLAPPER_TIMELINE_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_sequence_free (self->markers_seq); if (self->toc) gst_toc_unref (self->toc); g_ptr_array_unref (self->pending_markers); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_timeline_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperTimeline *self = CLAPPER_TIMELINE_CAST (object); switch (prop_id) { case PROP_N_MARKERS: g_value_set_uint (value, clapper_timeline_get_n_markers (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_timeline_class_init (ClapperTimelineClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappertimeline", 0, "Clapper Timeline"); gobject_class->get_property = clapper_timeline_get_property; gobject_class->finalize = clapper_timeline_finalize; /** * ClapperTimeline:n-markers: * * Number of markers in the timeline. */ param_specs[PROP_N_MARKERS] = g_param_spec_uint ("n-markers", 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); } clapper-0.8.0/src/lib/clapper/clapper-timeline.h000066400000000000000000000033461474353110700215460ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_TIMELINE (clapper_timeline_get_type()) #define CLAPPER_TIMELINE_CAST(obj) ((ClapperTimeline *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperTimeline, clapper_timeline, CLAPPER, TIMELINE, GstObject) CLAPPER_API gboolean clapper_timeline_insert_marker (ClapperTimeline *timeline, ClapperMarker *marker); CLAPPER_API void clapper_timeline_remove_marker (ClapperTimeline *timeline, ClapperMarker *marker); CLAPPER_API ClapperMarker * clapper_timeline_get_marker (ClapperTimeline *timeline, guint index); CLAPPER_API guint clapper_timeline_get_n_markers (ClapperTimeline *timeline); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-utils-private.h000066400000000000000000000032361474353110700225460ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include #include #include "clapper-utils.h" #include "clapper-queue.h" #include "clapper-media-item.h" G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_utils_initialize (void); G_GNUC_INTERNAL void clapper_utils_queue_append_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item); G_GNUC_INTERNAL void clapper_utils_queue_insert_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item, ClapperMediaItem *after_item); G_GNUC_INTERNAL void clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item); G_GNUC_INTERNAL void clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue); G_GNUC_INTERNAL gchar * clapper_utils_uri_from_file (GFile *file); G_GNUC_INTERNAL gchar * clapper_utils_title_from_uri (const gchar *uri); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-utils.c000066400000000000000000000135551474353110700210760ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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-utils-private.h" #include "../shared/clapper-shared-utils-private.h" #define GST_CAT_DEFAULT clapper_utils_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); typedef enum { CLAPPER_UTILS_QUEUE_ALTER_APPEND = 1, CLAPPER_UTILS_QUEUE_ALTER_INSERT, CLAPPER_UTILS_QUEUE_ALTER_REMOVE, CLAPPER_UTILS_QUEUE_ALTER_CLEAR } ClapperUtilsQueueAlterMethod; typedef struct { ClapperQueue *queue; ClapperMediaItem *item; ClapperMediaItem *after_item; ClapperUtilsQueueAlterMethod method; } ClapperUtilsQueueAlterData; void clapper_utils_initialize (void) { GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperutils", 0, "Clapper Utilities"); } static ClapperUtilsQueueAlterData * clapper_utils_queue_alter_data_new (ClapperQueue *queue, ClapperMediaItem *item, ClapperMediaItem *after_item, ClapperUtilsQueueAlterMethod method) { ClapperUtilsQueueAlterData *data = g_new (ClapperUtilsQueueAlterData, 1); data->queue = queue; data->item = item; data->after_item = after_item; data->method = method; GST_TRACE ("Created queue alter data: %p", data); return data; } static void clapper_utils_queue_alter_data_free (ClapperUtilsQueueAlterData *data) { GST_TRACE ("Freeing queue alter data: %p", data); g_free (data); } static gpointer clapper_utils_queue_alter_on_main (ClapperUtilsQueueAlterData *data) { GST_DEBUG ("Queue alter invoked"); switch (data->method) { case CLAPPER_UTILS_QUEUE_ALTER_APPEND: clapper_queue_add_item (data->queue, data->item); break; case CLAPPER_UTILS_QUEUE_ALTER_INSERT:{ guint index; /* If we have "after_item" then we need to insert after it, otherwise prepend */ if (data->after_item) { if (clapper_queue_find_item (data->queue, data->after_item, &index)) index++; else // If not found, just append at the end index = -1; } else { index = 0; } clapper_queue_insert_item (data->queue, data->item, index); break; } case CLAPPER_UTILS_QUEUE_ALTER_REMOVE: clapper_queue_remove_item (data->queue, data->item); break; case CLAPPER_UTILS_QUEUE_ALTER_CLEAR: clapper_queue_clear (data->queue); break; default: g_assert_not_reached (); break; } return NULL; } static inline void clapper_utils_queue_alter_invoke_on_main_sync_take (ClapperUtilsQueueAlterData *data) { GST_DEBUG ("Invoking queue alter on main..."); clapper_shared_utils_context_invoke_sync_full (g_main_context_default (), (GThreadFunc) clapper_utils_queue_alter_on_main, data, (GDestroyNotify) clapper_utils_queue_alter_data_free); GST_DEBUG ("Queue alter invoke finished"); } void clapper_utils_queue_append_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item) { ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, item, NULL, CLAPPER_UTILS_QUEUE_ALTER_APPEND); clapper_utils_queue_alter_invoke_on_main_sync_take (data); } void clapper_utils_queue_insert_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item, ClapperMediaItem *after_item) { ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, item, after_item, CLAPPER_UTILS_QUEUE_ALTER_INSERT); clapper_utils_queue_alter_invoke_on_main_sync_take (data); } void clapper_utils_queue_remove_on_main_sync (ClapperQueue *queue, ClapperMediaItem *item) { ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, item, NULL, CLAPPER_UTILS_QUEUE_ALTER_REMOVE); clapper_utils_queue_alter_invoke_on_main_sync_take (data); } void clapper_utils_queue_clear_on_main_sync (ClapperQueue *queue) { ClapperUtilsQueueAlterData *data = clapper_utils_queue_alter_data_new (queue, NULL, NULL, CLAPPER_UTILS_QUEUE_ALTER_CLEAR); clapper_utils_queue_alter_invoke_on_main_sync_take (data); } gchar * clapper_utils_uri_from_file (GFile *file) { gchar *uri = g_file_get_uri (file); gsize length = strlen (uri); /* GFile might incorrectly append "/" at the end of an URI, * remove it to make it work with GStreamer URI handling */ if (uri[length - 1] == '/') { gchar *fixed_uri; /* NULL terminated copy without last character */ fixed_uri = g_new0 (gchar, length); memcpy (fixed_uri, uri, length - 1); g_free (uri); uri = fixed_uri; } return uri; } gchar * clapper_utils_title_from_uri (const gchar *uri) { gchar *proto, *title = NULL; proto = gst_uri_get_protocol (uri); if (G_UNLIKELY (proto == NULL)) return NULL; if (strcmp (proto, "file") == 0) { gchar *filename = g_filename_from_uri (uri, NULL, NULL); if (filename) { const gchar *ext; title = g_path_get_basename (filename); ext = strrchr (title, '.'); g_free (filename); if (ext && strlen (ext) <= 4) { gchar *tmp = g_strndup (title, strlen (title) - strlen (ext)); g_free (title); title = tmp; } } } else if (strcmp (proto, "dvb") == 0) { const gchar *channel = strrchr (uri, '/') + 1; title = g_strdup (channel); } g_free (proto); return title; } clapper-0.8.0/src/lib/clapper/clapper-utils.h000066400000000000000000000044641474353110700211020ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include G_BEGIN_DECLS /** * CLAPPER_TIME_FORMAT: (skip): * * A string that can be used in printf-like format to display * e.g. position or duration in `hh:mm:ss` format. Meant to be * used together with [func@Clapper.TIME_ARGS]. * * Example: * * ```c * gchar *str = g_strdup_printf ("%" CLAPPER_TIME_FORMAT, CLAPPER_TIME_ARGS (time)); * ``` */ #define CLAPPER_TIME_FORMAT "02u:%02u:%02u" /** * CLAPPER_TIME_ARGS: (skip): * @t: time value in seconds * * Formats @t for the [const@Clapper.TIME_FORMAT] format string. */ #define CLAPPER_TIME_ARGS(t) \ (guint) (((GstClockTime)(t)) / 3600), \ (guint) ((((GstClockTime)(t)) / 60) % 60), \ (guint) (((GstClockTime)(t)) % 60) /** * CLAPPER_TIME_MS_FORMAT: (skip): * * Same as [const@Clapper.TIME_FORMAT], but also displays milliseconds. * Meant to be used together with [func@Clapper.TIME_MS_ARGS]. * * Example: * * ```c * gchar *str = g_strdup_printf ("%" CLAPPER_TIME_MS_FORMAT, CLAPPER_TIME_MS_ARGS (time)); * ``` */ #define CLAPPER_TIME_MS_FORMAT "02u:%02u:%02u.%03u" /** * CLAPPER_TIME_MS_ARGS: (skip): * @t: time value in seconds * * Formats @t for the [const@Clapper.TIME_MS_FORMAT] format string. */ #define CLAPPER_TIME_MS_ARGS(t) \ CLAPPER_TIME_ARGS(t), \ (guint) (((GstClockTime)(t * 1000)) % 1000) G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-version.h.in000066400000000000000000000044771474353110700220400ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif /** * CLAPPER_MAJOR_VERSION: * * Clapper major version component */ #define CLAPPER_MAJOR_VERSION (@CLAPPER_MAJOR_VERSION@) /** * CLAPPER_MINOR_VERSION: * * Clapper minor version component */ #define CLAPPER_MINOR_VERSION (@CLAPPER_MINOR_VERSION@) /** * CLAPPER_MICRO_VERSION: * * Clapper micro version component */ #define CLAPPER_MICRO_VERSION (@CLAPPER_MICRO_VERSION@) /** * CLAPPER_VERSION: * * Clapper version */ #define CLAPPER_VERSION (@CLAPPER_VERSION@) /** * CLAPPER_VERSION_S: * * Clapper version, encoded as a string */ #define CLAPPER_VERSION_S "@CLAPPER_VERSION@" #define CLAPPER_ENCODE_VERSION(major,minor,micro) \ ((major) << 24 | (minor) << 16 | (micro) << 8) /** * CLAPPER_VERSION_HEX: * * Clapper version, encoded as an hexadecimal number, useful for integer comparisons. */ #define CLAPPER_VERSION_HEX \ (CLAPPER_ENCODE_VERSION (CLAPPER_MAJOR_VERSION, CLAPPER_MINOR_VERSION, CLAPPER_MICRO_VERSION)) #define CLAPPER_CHECK_VERSION(major, minor, micro) \ (CLAPPER_MAJOR_VERSION > (major) || \ (CLAPPER_MAJOR_VERSION == (major) && CLAPPER_MINOR_VERSION > (minor)) || \ (CLAPPER_MAJOR_VERSION == (major) && CLAPPER_MINOR_VERSION == (minor) && \ CLAPPER_MICRO_VERSION >= (micro))) clapper-0.8.0/src/lib/clapper/clapper-video-stream-private.h000066400000000000000000000020211474353110700237740ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper-video-stream.h" G_BEGIN_DECLS G_GNUC_INTERNAL ClapperStream * clapper_video_stream_new (GstStream *gst_stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper-video-stream.c000066400000000000000000000231031474353110700223230ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperVideoStream: * * Represents a video stream within media. */ #include "clapper-video-stream-private.h" #include "clapper-stream-private.h" #define GST_CAT_DEFAULT clapper_video_stream_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperVideoStream { ClapperStream parent; gchar *codec; gint width; gint height; gdouble fps; guint bitrate; gchar *pixel_format; }; #define parent_class clapper_video_stream_parent_class G_DEFINE_TYPE (ClapperVideoStream, clapper_video_stream, CLAPPER_TYPE_STREAM); enum { PROP_0, PROP_CODEC, PROP_WIDTH, PROP_HEIGHT, PROP_FPS, PROP_BITRATE, PROP_PIXEL_FORMAT, PROP_LAST }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static void _update_using_caps (ClapperVideoStream *self, GstCaps *caps) { ClapperStream *stream = CLAPPER_STREAM_CAST (self); GstStructure *structure; gint width = 0, height = 0, fps_n = 0, fps_d = 0; if (gst_caps_get_size (caps) == 0) return; structure = gst_caps_get_structure (caps, 0); /* NOTE: We cannot use gst_structure_get() here, * as it stops iterating on first not found key */ gst_structure_get_int (structure, "width", &width); clapper_stream_set_int_prop (stream, param_specs[PROP_WIDTH], &self->width, width); gst_structure_get_int (structure, "height", &height); clapper_stream_set_int_prop (stream, param_specs[PROP_HEIGHT], &self->height, height); gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d); if (G_UNLIKELY (fps_d == 0)) fps_d = 1; clapper_stream_set_double_prop (stream, param_specs[PROP_FPS], &self->fps, (gdouble) fps_n / fps_d); clapper_stream_set_string_prop (stream, param_specs[PROP_PIXEL_FORMAT], &self->pixel_format, gst_structure_get_string (structure, "format")); } static void _update_using_tags (ClapperVideoStream *self, GstTagList *tags) { ClapperStream *stream = CLAPPER_STREAM_CAST (self); gchar *codec = NULL; guint bitrate = 0; gst_tag_list_get_string_index (tags, GST_TAG_VIDEO_CODEC, 0, &codec); clapper_stream_take_string_prop (stream, param_specs[PROP_CODEC], &self->codec, codec); gst_tag_list_get_uint_index (tags, GST_TAG_BITRATE, 0, &bitrate); clapper_stream_set_uint_prop (stream, param_specs[PROP_BITRATE], &self->bitrate, bitrate); } ClapperStream * clapper_video_stream_new (GstStream *gst_stream) { ClapperVideoStream *video_stream; video_stream = g_object_new (CLAPPER_TYPE_VIDEO_STREAM, "stream-type", CLAPPER_STREAM_TYPE_VIDEO, NULL); gst_object_ref_sink (video_stream); clapper_stream_set_gst_stream (CLAPPER_STREAM_CAST (video_stream), gst_stream); return CLAPPER_STREAM_CAST (video_stream); } /** * clapper_video_stream_get_codec: * @stream: a #ClapperVideoStream * * Get codec used to encode @stream. * * Returns: (transfer full) (nullable): the video codec of stream * or %NULL if undetermined. */ gchar * clapper_video_stream_get_codec (ClapperVideoStream *self) { gchar *codec; g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), NULL); GST_OBJECT_LOCK (self); codec = g_strdup (self->codec); GST_OBJECT_UNLOCK (self); return codec; } /** * clapper_video_stream_get_width: * @stream: a #ClapperVideoStream * * Get width of video @stream. * * Returns: the width of video stream. */ gint clapper_video_stream_get_width (ClapperVideoStream *self) { gint width; g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0); GST_OBJECT_LOCK (self); width = self->width; GST_OBJECT_UNLOCK (self); return width; } /** * clapper_video_stream_get_height: * @stream: a #ClapperVideoStream * * Get height of video @stream. * * Returns: the height of video stream. */ gint clapper_video_stream_get_height (ClapperVideoStream *self) { gint height; g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0); GST_OBJECT_LOCK (self); height = self->height; GST_OBJECT_UNLOCK (self); return height; } /** * clapper_video_stream_get_fps: * @stream: a #ClapperVideoStream * * Get number of frames per second in video @stream. * * Returns: the FPS of video stream. */ gdouble clapper_video_stream_get_fps (ClapperVideoStream *self) { gdouble fps; g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0); GST_OBJECT_LOCK (self); fps = self->fps; GST_OBJECT_UNLOCK (self); return fps; } /** * clapper_video_stream_get_bitrate: * @stream: a #ClapperVideoStream * * Get bitrate of video @stream. * * Returns: the bitrate of video stream. */ guint clapper_video_stream_get_bitrate (ClapperVideoStream *self) { guint bitrate; g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), 0); GST_OBJECT_LOCK (self); bitrate = self->bitrate; GST_OBJECT_UNLOCK (self); return bitrate; } /** * clapper_video_stream_get_pixel_format: * @stream: a #ClapperVideoStream * * Get pixel format of video @stream. * * Returns: (transfer full) (nullable): the pixel format of stream * or %NULL if undetermined. */ gchar * clapper_video_stream_get_pixel_format (ClapperVideoStream *self) { gchar *pixel_format; g_return_val_if_fail (CLAPPER_IS_VIDEO_STREAM (self), NULL); GST_OBJECT_LOCK (self); pixel_format = g_strdup (self->pixel_format); GST_OBJECT_UNLOCK (self); return pixel_format; } static void clapper_video_stream_init (ClapperVideoStream *self) { } static void clapper_video_stream_finalize (GObject *object) { ClapperVideoStream *self = CLAPPER_VIDEO_STREAM_CAST (object); g_free (self->codec); g_free (self->pixel_format); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_video_stream_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperVideoStream *self = CLAPPER_VIDEO_STREAM_CAST (object); switch (prop_id) { case PROP_CODEC: g_value_take_string (value, clapper_video_stream_get_codec (self)); break; case PROP_WIDTH: g_value_set_int (value, clapper_video_stream_get_width (self)); break; case PROP_HEIGHT: g_value_set_int (value, clapper_video_stream_get_height (self)); break; case PROP_FPS: g_value_set_double (value, clapper_video_stream_get_fps (self)); break; case PROP_BITRATE: g_value_set_uint (value, clapper_video_stream_get_bitrate (self)); break; case PROP_PIXEL_FORMAT: g_value_take_string (value, clapper_video_stream_get_pixel_format (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_video_stream_internal_stream_updated (ClapperStream *stream, GstCaps *caps, GstTagList *tags) { ClapperVideoStream *self = CLAPPER_VIDEO_STREAM_CAST (stream); CLAPPER_STREAM_CLASS (parent_class)->internal_stream_updated (stream, caps, tags); if (caps) _update_using_caps (self, caps); if (tags) _update_using_tags (self, tags); } static void clapper_video_stream_class_init (ClapperVideoStreamClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperStreamClass *stream_class = (ClapperStreamClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappervideostream", 0, "Clapper Video Stream"); gobject_class->get_property = clapper_video_stream_get_property; gobject_class->finalize = clapper_video_stream_finalize; stream_class->internal_stream_updated = clapper_video_stream_internal_stream_updated; /** * ClapperVideoStream:codec: * * Stream codec. */ param_specs[PROP_CODEC] = g_param_spec_string ("codec", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperVideoStream:width: * * Stream width. */ param_specs[PROP_WIDTH] = g_param_spec_int ("width", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperVideoStream:height: * * Stream height. */ param_specs[PROP_HEIGHT] = g_param_spec_int ("height", NULL, NULL, 0, G_MAXINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperVideoStream:fps: * * Stream FPS. */ param_specs[PROP_FPS] = g_param_spec_double ("fps", NULL, NULL, 0, G_MAXDOUBLE, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperVideoStream:bitrate: * * Stream bitrate. */ param_specs[PROP_BITRATE] = g_param_spec_uint ("bitrate", NULL, NULL, 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperVideoStream:pixel-format: * * Stream pixel format. */ param_specs[PROP_PIXEL_FORMAT] = g_param_spec_string ("pixel-format", NULL, NULL, NULL, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/clapper-video-stream.h000066400000000000000000000035331474353110700223350ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_VIDEO_STREAM (clapper_video_stream_get_type()) #define CLAPPER_VIDEO_STREAM_CAST(obj) ((ClapperVideoStream *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperVideoStream, clapper_video_stream, CLAPPER, VIDEO_STREAM, ClapperStream) CLAPPER_API gchar * clapper_video_stream_get_codec (ClapperVideoStream *stream); CLAPPER_API gint clapper_video_stream_get_width (ClapperVideoStream *stream); CLAPPER_API gint clapper_video_stream_get_height (ClapperVideoStream *stream); CLAPPER_API gdouble clapper_video_stream_get_fps (ClapperVideoStream *stream); CLAPPER_API guint clapper_video_stream_get_bitrate (ClapperVideoStream *stream); CLAPPER_API gchar * clapper_video_stream_get_pixel_format (ClapperVideoStream *stream); G_END_DECLS clapper-0.8.0/src/lib/clapper/clapper.c000066400000000000000000000120241474353110700177260ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include "clapper.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 "gst/clapper-plugin-private.h" #if CLAPPER_WITH_ENHANCERS_LOADER #include "clapper-enhancers-loader-private.h" #endif static gboolean is_initialized = FALSE; static GMutex init_lock; static gboolean clapper_init_check_internal (int *argc, char **argv[]) { g_mutex_lock (&init_lock); if (is_initialized || !gst_init_check (argc, argv, NULL)) goto finish; gst_pb_utils_init (); clapper_utils_initialize (); clapper_playbin_bus_initialize (); clapper_app_bus_initialize (); clapper_features_bus_initialize (); #if CLAPPER_WITH_ENHANCERS_LOADER clapper_enhancers_loader_initialize (); #endif gst_plugin_register_static ( GST_VERSION_MAJOR, GST_VERSION_MINOR, PACKAGE "internal", PLUGIN_DESC, (GstPluginInitFunc) clapper_gst_plugin_init, PACKAGE_VERSION, PLUGIN_LICENSE, PACKAGE, PACKAGE, PACKAGE_ORIGIN); is_initialized = TRUE; finish: g_mutex_unlock (&init_lock); return is_initialized; } /** * clapper_init: * @argc: (inout) (nullable) (optional): pointer to application's argc * @argv: (inout) (array length=argc) (nullable) (optional): pointer to application's argv * * Initializes the Clapper library. Implementations must always call this * before using Clapper API. * * Because Clapper uses GStreamer internally, this function will also initialize * GStreamer before initializing Clapper itself for user convienience, so * application does not have to do so anymore. * * WARNING: This function will terminate your program if it was unable to * initialize for some reason. If you want to do some fallback logic, * use [func@Clapper.init_check] instead. */ void clapper_init (int *argc, char **argv[]) { if (!clapper_init_check_internal (argc, argv)) { g_printerr ("Could not initialize Clapper library\n"); exit (1); } } /** * clapper_init_check: * @argc: (inout) (nullable) (optional): pointer to application's argc * @argv: (inout) (array length=argc) (nullable) (optional): pointer to application's argv * * This function does the same thing as [func@Clapper.init], but instead of * terminating on failure it returns %FALSE. * * Returns: %TRUE if Clapper could be initialized, %FALSE otherwise. */ gboolean clapper_init_check (int *argc, char **argv[]) { return clapper_init_check_internal (argc, argv); } /** * clapper_enhancer_check: * @iface_type: an interface #GType * @scheme: an URI scheme * @host: (nullable): an URI host * @name: (out) (optional) (transfer none): return location for found enhancer name * * Check if an enhancer of @type is available for given @scheme and @host. * * A check that compares requested capabilites of all available Clapper enhancers, * thus it is fast but does not guarantee that the found one will succeed. Please note * that this function will always return %FALSE if Clapper was built without enhancers * loader functionality. To check that, use [const@Clapper.WITH_ENHANCERS_LOADER]. * * This function can be used to quickly determine early if Clapper will at least try to * handle URI and with one of its enhancers and which one. * * Example: * * ```c * gboolean supported = clapper_enhancer_check (CLAPPER_TYPE_EXTRACTABLE, "https", "example.com", NULL); * ``` * * For self hosted services a custom URI @scheme without @host can be used. Enhancers should announce * support for such schemes by defining them in their plugin info files. * * ```c * gboolean supported = clapper_enhancer_check (CLAPPER_TYPE_EXTRACTABLE, "example", NULL, NULL); * ``` * * Returns: whether a plausible enhancer was found. * * Since: 0.8 */ gboolean clapper_enhancer_check (GType iface_type, const gchar *scheme, const gchar *host, const gchar **name) { gboolean success = FALSE; 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 return success; } clapper-0.8.0/src/lib/clapper/clapper.h000066400000000000000000000044131474353110700177360ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #define __CLAPPER_INSIDE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if CLAPPER_HAVE_DISCOVERER #include #endif #if CLAPPER_HAVE_MPRIS #include #endif #if CLAPPER_HAVE_SERVER #include #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__ clapper-0.8.0/src/lib/clapper/features/000077500000000000000000000000001474353110700177535ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper/features/clapper-features-availability.h.in000066400000000000000000000025721474353110700264510ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif /** * CLAPPER_HAVE_DISCOVERER: * * Check if Clapper was compiled with Discoverer feature. */ #define CLAPPER_HAVE_DISCOVERER (@CLAPPER_HAVE_DISCOVERER@) /** * CLAPPER_HAVE_MPRIS: * * Check if Clapper was compiled with MPRIS feature. */ #define CLAPPER_HAVE_MPRIS (@CLAPPER_HAVE_MPRIS@) /** * CLAPPER_HAVE_SERVER: * * Check if Clapper was compiled with Server feature. */ #define CLAPPER_HAVE_SERVER (@CLAPPER_HAVE_SERVER@) clapper-0.8.0/src/lib/clapper/features/discoverer/000077500000000000000000000000001474353110700221205ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper/features/discoverer/clapper-discoverer.c000066400000000000000000000347061474353110700260670ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperDiscoverer: * * An optional Discoverer feature to be added to the player. * * #ClapperDiscoverer is a feature that wraps around #GstDiscoverer API * to automatically discover items within [class@Clapper.Queue]. Once media * is scanned, all extra information of it will be filled within media item, * this includes title, duration, chapters, etc. * * Please note that media items are also discovered during their playback * by the player itself. #ClapperDiscoverer is useful in situations where * one wants to present to the user an updated media item before its * playback, such as an UI that displays playback queue. * * Depending on your application, you can select an optimal * [enum@Clapper.DiscovererDiscoveryMode] that best suits your needs. * * Use [const@Clapper.HAVE_DISCOVERER] macro to check if Clapper API * was compiled with this feature. */ #include #include #include "clapper-discoverer.h" #include "clapper-queue.h" #include "clapper-media-item-private.h" #include "../shared/clapper-shared-utils-private.h" #define DEFAULT_DISCOVERY_MODE CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT #define GST_CAT_DEFAULT clapper_discoverer_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperDiscoverer { ClapperFeature parent; GstDiscoverer *discoverer; GPtrArray *pending_items; ClapperMediaItem *discovered_item; gboolean running; GSource *timeout_source; ClapperDiscovererDiscoveryMode discovery_mode; }; enum { PROP_0, PROP_DISCOVERY_MODE, PROP_LAST }; #define parent_class clapper_discoverer_parent_class G_DEFINE_TYPE (ClapperDiscoverer, clapper_discoverer, CLAPPER_TYPE_FEATURE); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static inline void _clear_timeout_source (ClapperDiscoverer *self) { if (self->timeout_source) { g_source_destroy (self->timeout_source); g_clear_pointer (&self->timeout_source, g_source_unref); } } static inline void _unqueue_discovery (ClapperDiscoverer *self, ClapperMediaItem *item) { guint index = 0; /* Removing item that is being discovered */ if (item == self->discovered_item) { GST_DEBUG_OBJECT (self, "Ignoring discovery of current item %" GST_PTR_FORMAT, item); gst_clear_object (&self->discovered_item); } else if (g_ptr_array_find (self->pending_items, item, &index)) { GST_DEBUG_OBJECT (self, "Removing discovery of pending item %" GST_PTR_FORMAT, item); g_ptr_array_remove_index (self->pending_items, index); } } static inline void _start_discovery (ClapperDiscoverer *self) { if (!self->running) { gst_discoverer_start (self->discoverer); self->running = TRUE; GST_INFO_OBJECT (self, "Discoverer started"); } } static inline void _stop_discovery (ClapperDiscoverer *self) { if (self->running) { gst_discoverer_stop (self->discoverer); self->running = FALSE; GST_INFO_OBJECT (self, "Discoverer stopped"); } } static void _run_discovery (ClapperDiscoverer *self) { ClapperMediaItem *item; ClapperQueue *queue; ClapperDiscovererDiscoveryMode discovery_mode; const gchar *uri; gboolean success = FALSE; if (self->pending_items->len == 0) { GST_DEBUG_OBJECT (self, "No more pending items"); return; } item = g_ptr_array_steal_index (self->pending_items, 0); GST_DEBUG_OBJECT (self, "Investigating discovery of %" GST_PTR_FORMAT, item); queue = CLAPPER_QUEUE_CAST (gst_object_get_parent (GST_OBJECT_CAST (item))); if (G_UNLIKELY (queue == NULL)) { GST_DEBUG_OBJECT (self, "Queued item %" GST_PTR_FORMAT " does not appear to be in queue anymore", item); goto finish; } discovery_mode = clapper_discoverer_get_discovery_mode (self); if (discovery_mode == CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT && clapper_queue_item_is_current (queue, item)) { GST_DEBUG_OBJECT (self, "Queued %" GST_PTR_FORMAT " is current item, ignoring discovery", item); goto finish; } uri = clapper_media_item_get_uri (item); GST_DEBUG_OBJECT (self, "Starting discovery of %" GST_PTR_FORMAT "(%s)", item, uri); /* Need to start first, then append URI */ _start_discovery (self); if ((success = gst_discoverer_discover_uri_async (self->discoverer, uri))) { gst_object_replace ((GstObject **) &self->discovered_item, GST_OBJECT_CAST (item)); GST_DEBUG_OBJECT (self, "Running discovery of %" GST_PTR_FORMAT "(%s)", self->discovered_item, uri); } else { GST_ERROR_OBJECT (self, "Could not run discovery of %" GST_PTR_FORMAT "(%s)", item, uri); } finish: gst_clear_object (&item); gst_clear_object (&queue); /* Continue until we run out of pending items */ if (!success) _run_discovery (self); } static gboolean _run_discovery_delayed_cb (ClapperDiscoverer *self) { GST_DEBUG_OBJECT (self, "Delayed discovery handler reached"); _clear_timeout_source (self); _run_discovery (self); return G_SOURCE_REMOVE; } static void _discovered_cb (GstDiscoverer *discoverer G_GNUC_UNUSED, GstDiscovererInfo *info, GError *error, ClapperDiscoverer *self) { /* Can be NULL if removed while discovery of it was running */ if (self->discovered_item) { const gchar *uri = clapper_media_item_get_uri (self->discovered_item); if (G_LIKELY (error == NULL)) { GST_DEBUG_OBJECT (self, "Finished discovery of %" GST_PTR_FORMAT "(%s)", self->discovered_item, uri); clapper_media_item_update_from_discoverer_info (self->discovered_item, info); } else { GST_ERROR_OBJECT (self, "Discovery of %" GST_PTR_FORMAT "(%s) failed, reason: %s", self->discovered_item, uri, error->message); } /* Clear so its NULL when replaced later */ gst_clear_object (&self->discovered_item); } /* Try to discover next item */ _run_discovery (self); } static void _finished_cb (GstDiscoverer *discoverer G_GNUC_UNUSED, ClapperDiscoverer *self) { if (G_LIKELY (self->pending_items->len == 0)) { GST_DEBUG_OBJECT (self, "Finished discovery of all items"); } else { /* This should never happen, but if it does, then clear * pending items array so we can somewhat recover */ GST_ERROR_OBJECT (self, "Discovery stopped, but still had %u pending items!", self->pending_items->len); g_ptr_array_remove_range (self->pending_items, 0, self->pending_items->len); } _stop_discovery (self); } static void clapper_discoverer_played_item_changed (ClapperFeature *feature, ClapperMediaItem *item) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature); GST_DEBUG_OBJECT (self, "Played item changed to: %" GST_PTR_FORMAT, item); _unqueue_discovery (self, item); } static void clapper_discoverer_queue_item_added (ClapperFeature *feature, ClapperMediaItem *item, guint index) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature); GST_DEBUG_OBJECT (self, "Queue item added %" GST_PTR_FORMAT, item); g_ptr_array_add (self->pending_items, gst_object_ref (item)); /* Already running, nothing more to do */ if (self->running) return; /* Need to always clear timeout here, as mode may * have changed between adding multiple items */ _clear_timeout_source (self); switch (clapper_discoverer_get_discovery_mode (self)) { case CLAPPER_DISCOVERER_DISCOVERY_NONCURRENT: /* We start running after small delay in this mode, so * application can select item after adding it to queue first */ self->timeout_source = clapper_shared_utils_context_timeout_add_full ( g_main_context_get_thread_default (), G_PRIORITY_DEFAULT_IDLE, 50, (GSourceFunc) _run_discovery_delayed_cb, self, NULL); break; case CLAPPER_DISCOVERER_DISCOVERY_ALWAYS: _run_discovery (self); break; default: g_assert_not_reached (); break; } } static void clapper_discoverer_queue_item_removed (ClapperFeature *feature, ClapperMediaItem *item, guint index) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature); GST_DEBUG_OBJECT (self, "Queue item removed %" GST_PTR_FORMAT, item); _unqueue_discovery (self, item); } static void clapper_discoverer_queue_cleared (ClapperFeature *feature) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature); GST_DEBUG_OBJECT (self, "Discarding discovery of all pending items"); if (self->pending_items->len > 0) g_ptr_array_remove_range (self->pending_items, 0, self->pending_items->len); gst_clear_object (&self->discovered_item); _stop_discovery (self); } static gboolean clapper_discoverer_prepare (ClapperFeature *feature) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature); GError *error = NULL; GST_DEBUG_OBJECT (self, "Prepare"); self->discoverer = gst_discoverer_new (15 * GST_SECOND, &error); if (G_UNLIKELY (error != NULL)) { GST_ERROR_OBJECT (self, "Could not prepare, reason: %s", error->message); g_error_free (error); return FALSE; } GST_TRACE_OBJECT (self, "Created new GstDiscoverer: %" GST_PTR_FORMAT, self->discoverer); /* FIXME: Caching in GStreamer is broken. Does not save container tags, such as media title. * Disable it until completely fixed upsteam. Once fixed change to %TRUE. */ g_object_set (self->discoverer, "use-cache", FALSE, NULL); g_signal_connect (self->discoverer, "discovered", G_CALLBACK (_discovered_cb), self); g_signal_connect (self->discoverer, "finished", G_CALLBACK (_finished_cb), self); return TRUE; } static gboolean clapper_discoverer_unprepare (ClapperFeature *feature) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (feature); GST_DEBUG_OBJECT (self, "Unprepare"); _clear_timeout_source (self); /* Do what we also do when queue is cleared */ clapper_discoverer_queue_cleared (feature); gst_clear_object (&self->discoverer); return TRUE; } /** * clapper_discoverer_new: * * Creates a new #ClapperDiscoverer instance. * * Returns: (transfer full): a new #ClapperDiscoverer instance. */ ClapperDiscoverer * clapper_discoverer_new (void) { ClapperDiscoverer *discoverer = g_object_new (CLAPPER_TYPE_DISCOVERER, NULL); gst_object_ref_sink (discoverer); return discoverer; } /** * clapper_discoverer_set_discovery_mode: * @discoverer: a #ClapperDiscoverer * @mode: a #ClapperDiscovererDiscoveryMode * * Set the [enum@Clapper.DiscovererDiscoveryMode] of @discoverer. */ void clapper_discoverer_set_discovery_mode (ClapperDiscoverer *self, ClapperDiscovererDiscoveryMode mode) { gboolean changed; g_return_if_fail (CLAPPER_IS_DISCOVERER (self)); GST_OBJECT_LOCK (self); if ((changed = self->discovery_mode != mode)) self->discovery_mode = mode; GST_OBJECT_UNLOCK (self); if (changed) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_DISCOVERY_MODE]); } /** * clapper_discoverer_get_discovery_mode: * @discoverer: a #ClapperDiscoverer * * Get the [enum@Clapper.DiscovererDiscoveryMode] of @discoverer. * * Returns: a currently set #ClapperDiscovererDiscoveryMode. */ ClapperDiscovererDiscoveryMode clapper_discoverer_get_discovery_mode (ClapperDiscoverer *self) { ClapperDiscovererDiscoveryMode mode; g_return_val_if_fail (CLAPPER_IS_DISCOVERER (self), DEFAULT_DISCOVERY_MODE); GST_OBJECT_LOCK (self); mode = self->discovery_mode; GST_OBJECT_UNLOCK (self); return mode; } static void clapper_discoverer_init (ClapperDiscoverer *self) { self->pending_items = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); self->discovery_mode = DEFAULT_DISCOVERY_MODE; } static void clapper_discoverer_finalize (GObject *object) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (object); g_ptr_array_unref (self->pending_items); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_discoverer_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (object); switch (prop_id) { case PROP_DISCOVERY_MODE: clapper_discoverer_set_discovery_mode (self, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_discoverer_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperDiscoverer *self = CLAPPER_DISCOVERER_CAST (object); switch (prop_id) { case PROP_DISCOVERY_MODE: g_value_set_enum (value, clapper_discoverer_get_discovery_mode (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_discoverer_class_init (ClapperDiscovererClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperFeatureClass *feature_class = (ClapperFeatureClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperdiscoverer", 0, "Clapper Discoverer"); gobject_class->get_property = clapper_discoverer_get_property; gobject_class->set_property = clapper_discoverer_set_property; gobject_class->finalize = clapper_discoverer_finalize; feature_class->prepare = clapper_discoverer_prepare; feature_class->unprepare = clapper_discoverer_unprepare; feature_class->played_item_changed = clapper_discoverer_played_item_changed; feature_class->queue_item_added = clapper_discoverer_queue_item_added; feature_class->queue_item_removed = clapper_discoverer_queue_item_removed; feature_class->queue_cleared = clapper_discoverer_queue_cleared; /** * ClapperDiscoverer:discovery-mode: * * Discoverer discovery mode. */ param_specs[PROP_DISCOVERY_MODE] = g_param_spec_enum ("discovery-mode", NULL, NULL, CLAPPER_TYPE_DISCOVERER_DISCOVERY_MODE, DEFAULT_DISCOVERY_MODE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/features/discoverer/clapper-discoverer.h000066400000000000000000000031751474353110700260700ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_DISCOVERER (clapper_discoverer_get_type()) #define CLAPPER_DISCOVERER_CAST(obj) ((ClapperDiscoverer *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperDiscoverer, clapper_discoverer, CLAPPER, DISCOVERER, ClapperFeature) CLAPPER_API ClapperDiscoverer * clapper_discoverer_new (void); CLAPPER_API void clapper_discoverer_set_discovery_mode (ClapperDiscoverer *discoverer, ClapperDiscovererDiscoveryMode mode); CLAPPER_API ClapperDiscovererDiscoveryMode clapper_discoverer_get_discovery_mode (ClapperDiscoverer *discoverer); G_END_DECLS clapper-0.8.0/src/lib/clapper/features/discoverer/meson.build000066400000000000000000000006331474353110700242640ustar00rootroot00000000000000feature_option = get_option(feature_name) if feature_option.disabled() subdir_done() endif clapper_features_headers += [ 'features/discoverer/clapper-discoverer.h', ] clapper_features_sources += [ 'features/discoverer/clapper-discoverer.c', ] install_headers('clapper-discoverer.h', install_dir: join_paths(clapper_headers_dir, 'features', 'discoverer'), ) clapper_available_features += feature_name clapper-0.8.0/src/lib/clapper/features/meson.build000066400000000000000000000016431474353110700221210ustar00rootroot00000000000000clapper_features_headers = [] clapper_features_sources = [] clapper_features_sources_internal = [] clapper_features_deps = [] clapper_available_features = [] features_availability_conf = configuration_data() clapper_possible_features = [ 'discoverer', 'mpris', 'server', ] foreach feature_name : clapper_possible_features subdir(feature_name) features_availability_conf.set( 'CLAPPER_HAVE_@0@'.format(feature_name.replace('-', '_').to_upper()), clapper_available_features.contains(feature_name) ? 'TRUE' : 'FALSE' ) endforeach clapper_features_availability_header = configure_file( input: 'clapper-features-availability.h.in', output: 'clapper-features-availability.h', configuration: features_availability_conf, ) install_headers(clapper_features_availability_header, install_dir: join_paths(clapper_headers_dir, 'features'), ) clapper_features_headers += [ clapper_features_availability_header ] clapper-0.8.0/src/lib/clapper/features/mpris/000077500000000000000000000000001474353110700211055ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper/features/mpris/clapper-mpris-gdbus.xml000066400000000000000000000065421474353110700255160ustar00rootroot00000000000000 clapper-0.8.0/src/lib/clapper/features/mpris/clapper-mpris.c000066400000000000000000001445431474353110700240420ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperMpris: * * An optional `MPRIS` feature to add to the player. * * Not every OS supports `MPRIS`. Use [const@Clapper.HAVE_MPRIS] macro * to check if Clapper API was compiled with this feature. */ #include "clapper-mpris.h" #include "clapper-mpris-gdbus.h" #include "clapper-player.h" #include "clapper-utils-private.h" #define CLAPPER_MPRIS_DO_WITH_PLAYER(mpris, _player_dst, ...) { \ *_player_dst = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (mpris))); \ if (G_LIKELY (*_player_dst != NULL)) \ __VA_ARGS__ \ gst_clear_object (_player_dst); } #define CLAPPER_MPRIS_SECONDS_TO_USECONDS(seconds) ((gint64) (seconds * G_GINT64_CONSTANT (1000000))) #define CLAPPER_MPRIS_USECONDS_TO_SECONDS(useconds) ((gdouble) useconds / G_GINT64_CONSTANT (1000000)) #define CLAPPER_MPRIS_COMPARE(a,b) (strcmp (a,b) == 0) #define CLAPPER_MPRIS_NO_TRACK "/org/mpris/MediaPlayer2/TrackList/NoTrack" #define CLAPPER_MPRIS_PLAYBACK_STATUS_PLAYING "Playing" #define CLAPPER_MPRIS_PLAYBACK_STATUS_PAUSED "Paused" #define CLAPPER_MPRIS_PLAYBACK_STATUS_STOPPED "Stopped" #define CLAPPER_MPRIS_LOOP_NONE "None" #define CLAPPER_MPRIS_LOOP_TRACK "Track" #define CLAPPER_MPRIS_LOOP_PLAYLIST "Playlist" #define DEFAULT_QUEUE_CONTROLLABLE FALSE #define GST_CAT_DEFAULT clapper_mpris_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); typedef struct { gchar *id; ClapperMediaItem *item; } ClapperMprisTrack; struct _ClapperMpris { ClapperFeature parent; ClapperMprisMediaPlayer2 *base_skeleton; ClapperMprisMediaPlayer2Player *player_skeleton; ClapperMprisMediaPlayer2TrackList *tracks_skeleton; gboolean base_exported; gboolean player_exported; gboolean tracks_exported; guint name_id; gboolean registered; GMainLoop *loop; GPtrArray *tracks; ClapperMprisTrack *current_track; ClapperQueueProgressionMode default_mode; ClapperQueueProgressionMode non_shuffle_mode; gchar *own_name; gchar *identity; gchar *desktop_entry; gint queue_controllable; // atomic gchar *fallback_art_url; }; enum { PROP_0, PROP_OWN_NAME, PROP_IDENTITY, PROP_DESKTOP_ENTRY, PROP_QUEUE_CONTROLLABLE, PROP_FALLBACK_ART_URL, PROP_LAST }; #define parent_class clapper_mpris_parent_class G_DEFINE_TYPE (ClapperMpris, clapper_mpris, CLAPPER_TYPE_FEATURE); static const gchar *const empty_tracklist[] = { NULL, }; static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static ClapperMprisTrack * clapper_mpris_track_new (ClapperMediaItem *item) { ClapperMprisTrack *track = g_new (ClapperMprisTrack, 1); /* MPRIS docs: "Media players may not use any paths starting with /org/mpris * unless explicitly allowed by this specification. Such paths are intended to * have special meaning, such as /org/mpris/MediaPlayer2/TrackList/NoTrack" */ track->id = g_strdup_printf ("/org/clapper/MediaItem%u", clapper_media_item_get_id (item)); track->item = gst_object_ref (item); GST_TRACE ("Created track: %s", track->id); return track; } static void clapper_mpris_track_free (ClapperMprisTrack *track) { GST_TRACE ("Freeing track: %s", track->id); g_free (track->id); gst_object_unref (track->item); g_free (track); } static inline void _mpris_read_initial_tracks (ClapperMpris *self, ClapperQueue *queue) { ClapperMediaItem *item, *current_item; guint i = 0; current_item = clapper_queue_get_current_item (queue); while ((item = clapper_queue_get_item (queue, i))) { ClapperMprisTrack *track = clapper_mpris_track_new (item); if (track->item == current_item) self->current_track = track; g_ptr_array_add (self->tracks, track); gst_object_unref (item); i++; } gst_clear_object (¤t_item); } static gboolean _mpris_find_track_by_item (ClapperMpris *self, ClapperMediaItem *search_item, guint *index) { guint i; for (i = 0; i < self->tracks->len; ++i) { ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, i); if (search_item == track->item) { if (index) *index = i; return TRUE; } } return FALSE; } static gboolean _mpris_find_track_by_id (ClapperMpris *self, const gchar *search_id, guint *index) { guint i; for (i = 0; i < self->tracks->len; ++i) { ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, i); if (CLAPPER_MPRIS_COMPARE (track->id, search_id)) { if (index) *index = i; return TRUE; } } return FALSE; } static GVariant * _mpris_build_track_metadata (ClapperMpris *self, ClapperMprisTrack *track) { GVariantBuilder builder; GVariant *variant; const gchar *uri; gchar *title; gint64 duration; g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); uri = clapper_media_item_get_uri (track->item); title = clapper_media_item_get_title (track->item); duration = CLAPPER_MPRIS_SECONDS_TO_USECONDS ( clapper_media_item_get_duration (track->item)); g_variant_builder_add (&builder, "{sv}", "mpris:trackid", g_variant_new_string (track->id)); g_variant_builder_add (&builder, "{sv}", "mpris:length", g_variant_new_int64 (duration)); g_variant_builder_add (&builder, "{sv}", "xesam:url", g_variant_new_string (uri)); if (title) { g_variant_builder_add (&builder, "{sv}", "xesam:title", g_variant_new_string (title)); } /* TODO: Fill more xesam props from tags within media info */ GST_OBJECT_LOCK (self); /* TODO: Support image sample or per-item custom artwork */ if (self->fallback_art_url) { g_variant_builder_add (&builder, "{sv}", "mpris:artUrl", g_variant_new_string (self->fallback_art_url)); } GST_OBJECT_UNLOCK (self); variant = g_variant_builder_end (&builder); g_free (title); return variant; } static gchar ** _filter_names (const gchar *const *all_names) { GStrvBuilder *builder; gchar **filtered_names; guint i; builder = g_strv_builder_new (); for (i = 0; all_names[i]; ++i) { const gchar *const *remaining_names = all_names + i + 1; if (*remaining_names && g_strv_contains (remaining_names, all_names[i])) continue; GST_LOG ("Found: %s", all_names[i]); g_strv_builder_add (builder, all_names[i]); } filtered_names = g_strv_builder_end (builder); g_strv_builder_unref (builder); return filtered_names; } static gchar ** clapper_mpris_get_supported_uri_schemes (ClapperMpris *self) { GStrvBuilder *builder; gchar **all_schemes, **filtered_schemes; GList *elements, *el; guint i; GST_DEBUG_OBJECT (self, "Checking supported URI schemes"); builder = g_strv_builder_new (); elements = gst_element_factory_list_get_elements ( GST_ELEMENT_FACTORY_TYPE_SRC, GST_RANK_NONE); for (el = elements; el != NULL; el = el->next) { GstElementFactory *factory = GST_ELEMENT_FACTORY (el->data); const gchar *const *protocols; if (gst_element_factory_get_uri_type (factory) != GST_URI_SRC) continue; if (!(protocols = gst_element_factory_get_uri_protocols (factory))) continue; for (i = 0; protocols[i]; ++i) g_strv_builder_add (builder, protocols[i]); } all_schemes = g_strv_builder_end (builder); g_strv_builder_unref (builder); gst_plugin_feature_list_free (elements); filtered_schemes = _filter_names ((const gchar *const *) all_schemes); g_strfreev (all_schemes); return filtered_schemes; } static gchar ** clapper_mpris_get_supported_mime_types (ClapperMpris *self) { GStrvBuilder *builder; gchar **all_types, **filtered_types; GList *elements, *el; GST_DEBUG_OBJECT (self, "Checking supported mime-types"); builder = g_strv_builder_new (); elements = gst_element_factory_list_get_elements ( GST_ELEMENT_FACTORY_TYPE_DEMUXER, GST_RANK_NONE); for (el = elements; el != NULL; el = el->next) { GstElementFactory *factory = GST_ELEMENT_FACTORY (el->data); const GList *pad_templates, *pt; pad_templates = gst_element_factory_get_static_pad_templates (factory); for (pt = pad_templates; pt != NULL; pt = pt->next) { GstStaticPadTemplate *template = (GstStaticPadTemplate *) pt->data; GstCaps *caps; guint i, size; if (template->direction != GST_PAD_SINK) continue; caps = gst_static_pad_template_get_caps (template); size = gst_caps_get_size (caps); for (i = 0; i < size; ++i) { GstStructure *structure = gst_caps_get_structure (caps, i); const gchar *name = gst_structure_get_name (structure); /* Skip GStreamer internal mime types */ if (g_str_has_prefix (name, "application/x-gst-")) continue; /* GStreamer uses "video/quicktime" for MP4. If we can * handle it, then also add more generic ones. */ if (strcmp (name, "video/quicktime") == 0) { g_strv_builder_add (builder, "video/mp4"); g_strv_builder_add (builder, "audio/mp4"); } g_strv_builder_add (builder, name); } gst_caps_unref (caps); } } all_types = g_strv_builder_end (builder); g_strv_builder_unref (builder); gst_plugin_feature_list_free (elements); filtered_types = _filter_names ((const gchar *const *) all_types); g_strfreev (all_types); return filtered_types; } static void clapper_mpris_unregister (ClapperMpris *self) { GST_DEBUG_OBJECT (self, "Unregister"); if (self->base_exported) { g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->base_skeleton)); self->base_exported = FALSE; } if (self->player_exported) { g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->player_skeleton)); self->player_exported = FALSE; } if (self->tracks_exported) { g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->tracks_skeleton)); self->tracks_exported = FALSE; } self->registered = FALSE; } static void clapper_mpris_refresh_current_track (ClapperMpris *self, GVariant *variant) { gboolean is_live = FALSE; GST_LOG_OBJECT (self, "Current track refresh"); /* Set or clear metadata */ clapper_mpris_media_player2_player_set_metadata (self->player_skeleton, variant); /* Properties related to media item availablity, not current state */ clapper_mpris_media_player2_player_set_can_play (self->player_skeleton, self->current_track != NULL); clapper_mpris_media_player2_player_set_can_pause (self->player_skeleton, self->current_track != NULL); /* FIXME: Also disable for LIVE content */ clapper_mpris_media_player2_player_set_can_seek (self->player_skeleton, self->current_track != NULL); clapper_mpris_media_player2_player_set_minimum_rate (self->player_skeleton, (is_live) ? 1.0 : G_MINDOUBLE); clapper_mpris_media_player2_player_set_maximum_rate (self->player_skeleton, (is_live) ? 1.0 : G_MAXDOUBLE); } static void clapper_mpris_refresh_track (ClapperMpris *self, ClapperMprisTrack *track) { GVariant *variant = g_variant_take_ref (_mpris_build_track_metadata (self, track)); if (track == self->current_track) clapper_mpris_refresh_current_track (self, variant); clapper_mpris_media_player2_track_list_emit_track_metadata_changed (self->tracks_skeleton, track->id, variant); g_variant_unref (variant); } static void clapper_mpris_refresh_all_tracks (ClapperMpris *self) { guint i; for (i = 0; i < self->tracks->len; ++i) { ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, i); clapper_mpris_refresh_track (self, track); } } static void clapper_mpris_refresh_track_list (ClapperMpris *self) { GStrvBuilder *builder = NULL; gchar **tracks_ids; guint i; GST_LOG_OBJECT (self, "Track list refresh"); /* Track list is empty */ if (self->tracks->len == 0) { clapper_mpris_media_player2_track_list_set_tracks (self->tracks_skeleton, empty_tracklist); return; } builder = g_strv_builder_new (); for (i = 0; i < self->tracks->len; ++i) { ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, i); g_strv_builder_add (builder, track->id); } tracks_ids = g_strv_builder_end (builder); g_strv_builder_unref (builder); clapper_mpris_media_player2_track_list_set_tracks (self->tracks_skeleton, (const gchar *const *) tracks_ids); g_strfreev (tracks_ids); } static void clapper_mpris_refresh_can_go_next_previous (ClapperMpris *self) { gboolean can_previous = FALSE, can_next = FALSE; GST_LOG_OBJECT (self, "Next/Previous availability refresh"); if (self->current_track && clapper_mpris_get_queue_controllable (self)) { guint index = 0; if (_mpris_find_track_by_item (self, self->current_track->item, &index)) { can_previous = (index > 0); can_next = (index < self->tracks->len - 1); } } clapper_mpris_media_player2_player_set_can_go_previous (self->player_skeleton, can_previous); clapper_mpris_media_player2_player_set_can_go_next (self->player_skeleton, can_next); } static void clapper_mpris_state_changed (ClapperFeature *feature, ClapperPlayerState state) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); const gchar *status_str = CLAPPER_MPRIS_PLAYBACK_STATUS_STOPPED; switch (state) { case CLAPPER_PLAYER_STATE_PLAYING: status_str = CLAPPER_MPRIS_PLAYBACK_STATUS_PLAYING; break; case CLAPPER_PLAYER_STATE_PAUSED: case CLAPPER_PLAYER_STATE_BUFFERING: status_str = CLAPPER_MPRIS_PLAYBACK_STATUS_PAUSED; break; default: break; } GST_DEBUG_OBJECT (self, "Playback status changed to: %s", status_str); clapper_mpris_media_player2_player_set_playback_status (self->player_skeleton, status_str); } static void clapper_mpris_position_changed (ClapperFeature *feature, gdouble position) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); GST_LOG_OBJECT (self, "Position changed to: %lf", position); clapper_mpris_media_player2_player_set_position (self->player_skeleton, CLAPPER_MPRIS_SECONDS_TO_USECONDS (position)); } static void clapper_mpris_speed_changed (ClapperFeature *feature, gdouble speed) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); gdouble mpris_speed; mpris_speed = clapper_mpris_media_player2_player_get_rate (self->player_skeleton); if (!G_APPROX_VALUE (speed, mpris_speed, FLT_EPSILON)) { GST_LOG_OBJECT (self, "Speed changed to: %lf", speed); clapper_mpris_media_player2_player_set_rate (self->player_skeleton, speed); } } static void clapper_mpris_volume_changed (ClapperFeature *feature, gdouble volume) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); gdouble mpris_volume; if (G_UNLIKELY (volume < 0)) volume = 0; mpris_volume = clapper_mpris_media_player2_player_get_volume (self->player_skeleton); if (!G_APPROX_VALUE (volume, mpris_volume, FLT_EPSILON)) { GST_LOG_OBJECT (self, "Volume changed to: %lf", volume); clapper_mpris_media_player2_player_set_volume (self->player_skeleton, volume); } } static void clapper_mpris_played_item_changed (ClapperFeature *feature, ClapperMediaItem *item) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); GVariant *variant = NULL; guint index = 0; GST_DEBUG_OBJECT (self, "Played item changed to: %" GST_PTR_FORMAT, item); if (G_LIKELY (_mpris_find_track_by_item (self, item, &index))) { self->current_track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, index); variant = _mpris_build_track_metadata (self, self->current_track); } else { self->current_track = NULL; } clapper_mpris_refresh_current_track (self, variant); clapper_mpris_refresh_can_go_next_previous (self); } static void clapper_mpris_item_updated (ClapperFeature *feature, ClapperMediaItem *item) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); guint index = 0; GST_LOG_OBJECT (self, "Item updated: %" GST_PTR_FORMAT, item); if (_mpris_find_track_by_item (self, item, &index)) { ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, index); clapper_mpris_refresh_track (self, track); } } static void clapper_mpris_queue_item_added (ClapperFeature *feature, ClapperMediaItem *item, guint index) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); ClapperMprisTrack *track, *prev_track = NULL; GVariant *variant; /* Safety precaution for a case when someone adds MPRIS feature * in middle of altering playlist from another thread, since we * also read initial playlist after name is acquired. */ if (G_UNLIKELY (_mpris_find_track_by_item (self, item, NULL))) return; GST_DEBUG_OBJECT (self, "Queue item added at position: %u", index); track = clapper_mpris_track_new (item); g_ptr_array_insert (self->tracks, index, track); clapper_mpris_refresh_track_list (self); clapper_mpris_refresh_can_go_next_previous (self); variant = _mpris_build_track_metadata (self, track); /* NoTrack when item is added at first position in queue */ clapper_mpris_media_player2_track_list_emit_track_added (self->tracks_skeleton, variant, (prev_track != NULL) ? prev_track->id : CLAPPER_MPRIS_NO_TRACK); } static void clapper_mpris_queue_item_removed (ClapperFeature *feature, ClapperMediaItem *item, guint index) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); ClapperMprisTrack *track; GST_DEBUG_OBJECT (self, "Queue item removed"); track = (ClapperMprisTrack *) g_ptr_array_steal_index (self->tracks, index); if (track == self->current_track) { self->current_track = NULL; clapper_mpris_refresh_current_track (self, NULL); } clapper_mpris_refresh_track_list (self); clapper_mpris_refresh_can_go_next_previous (self); clapper_mpris_media_player2_track_list_emit_track_removed (self->tracks_skeleton, track->id); clapper_mpris_track_free (track); } static void clapper_mpris_queue_item_repositioned (ClapperFeature *feature, guint before, guint after) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); ClapperMprisTrack *track; GST_DEBUG_OBJECT (self, "Queue item repositioned: %u -> %u", before, after); track = (ClapperMprisTrack *) g_ptr_array_steal_index (self->tracks, before); g_ptr_array_insert (self->tracks, after, track); clapper_mpris_refresh_track_list (self); clapper_mpris_refresh_can_go_next_previous (self); } static void clapper_mpris_queue_cleared (ClapperFeature *feature) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); guint n_items = self->tracks->len; if (n_items > 0) g_ptr_array_remove_range (self->tracks, 0, n_items); self->current_track = NULL; clapper_mpris_refresh_current_track (self, NULL); clapper_mpris_refresh_can_go_next_previous (self); clapper_mpris_refresh_track_list (self); clapper_mpris_media_player2_track_list_emit_track_list_replaced (self->tracks_skeleton, empty_tracklist, CLAPPER_MPRIS_NO_TRACK); } static void clapper_mpris_queue_progression_changed (ClapperFeature *feature, ClapperQueueProgressionMode mode) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); const gchar *loop_status = CLAPPER_MPRIS_LOOP_NONE; gboolean shuffle = FALSE; GST_DEBUG_OBJECT (self, "Queue progression changed to: %i", mode); switch (mode) { case CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM: loop_status = CLAPPER_MPRIS_LOOP_TRACK; break; case CLAPPER_QUEUE_PROGRESSION_CAROUSEL: loop_status = CLAPPER_MPRIS_LOOP_PLAYLIST; break; case CLAPPER_QUEUE_PROGRESSION_SHUFFLE: shuffle = TRUE; break; case CLAPPER_QUEUE_PROGRESSION_NONE: case CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE: self->default_mode = mode; break; default: break; } if (mode != CLAPPER_QUEUE_PROGRESSION_SHUFFLE) self->non_shuffle_mode = mode; clapper_mpris_media_player2_player_set_loop_status (self->player_skeleton, loop_status); clapper_mpris_media_player2_player_set_shuffle (self->player_skeleton, shuffle); } static gboolean _handle_open_uri_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, const gchar *uri, ClapperMpris *self) { ClapperPlayer *player; if (!clapper_mpris_get_queue_controllable (self)) return G_DBUS_METHOD_INVOCATION_UNHANDLED; GST_DEBUG_OBJECT (self, "Handle open URI: %s", uri); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); ClapperMediaItem *item = clapper_media_item_new (uri); /* We can only alter ClapperQueue from main thread. * Adding items to it will trigger clapper_mpris_queue_item_added(), * then we will add this new item to our track list */ clapper_utils_queue_append_on_main_sync (queue, item); if (clapper_queue_select_item (queue, item)) clapper_player_play (player); gst_object_unref (item); }); clapper_mpris_media_player2_player_complete_open_uri (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_play_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle play"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { clapper_player_play (player); }); clapper_mpris_media_player2_player_complete_play (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_pause_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle pause"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { clapper_player_pause (player); }); clapper_mpris_media_player2_player_complete_pause (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_play_pause_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle play/pause"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperPlayerState state = clapper_player_get_state (player); switch (state) { case CLAPPER_PLAYER_STATE_PLAYING: clapper_player_pause (player); break; case CLAPPER_PLAYER_STATE_PAUSED: case CLAPPER_PLAYER_STATE_STOPPED: clapper_player_play (player); break; default: break; } }); clapper_mpris_media_player2_player_complete_play_pause (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_stop_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle stop"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { clapper_player_stop (player); }); clapper_mpris_media_player2_player_complete_stop (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_next_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, ClapperMpris *self) { ClapperPlayer *player; if (!clapper_mpris_get_queue_controllable (self)) return G_DBUS_METHOD_INVOCATION_UNHANDLED; GST_DEBUG_OBJECT (self, "Handle next"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); clapper_queue_select_next_item (queue); }); clapper_mpris_media_player2_player_complete_next (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_previous_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, ClapperMpris *self) { ClapperPlayer *player; if (!clapper_mpris_get_queue_controllable (self)) return G_DBUS_METHOD_INVOCATION_UNHANDLED; GST_DEBUG_OBJECT (self, "Handle previous"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); clapper_queue_select_previous_item (queue); }); clapper_mpris_media_player2_player_complete_previous (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_seek_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, gint64 offset, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle seek"); if (!self->current_track) goto finish; CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { gdouble position, seek_position; position = clapper_player_get_position (player); seek_position = position + CLAPPER_MPRIS_USECONDS_TO_SECONDS (offset); if (seek_position <= 0) { clapper_player_seek (player, 0); } else { gdouble duration = clapper_media_item_get_duration (self->current_track->item); if (seek_position > duration) { ClapperQueue *queue = clapper_player_get_queue (player); clapper_queue_select_next_item (queue); } else { clapper_player_seek (player, seek_position); } } }); finish: clapper_mpris_media_player2_player_complete_seek (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_set_position_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GDBusMethodInvocation *invocation, const gchar *track_id, gint64 position, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle set position"); if (G_UNLIKELY (position < 0) || !self->current_track) goto finish; CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { gdouble duration, position_dbl; duration = clapper_media_item_get_duration (self->current_track->item); position_dbl = CLAPPER_MPRIS_USECONDS_TO_SECONDS (position); if (position_dbl <= duration) clapper_player_seek (player, position_dbl); }); finish: clapper_mpris_media_player2_player_complete_set_position (player_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static void _handle_rate_notify_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GParamSpec *pspec G_GNUC_UNUSED, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle rate notify"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { gdouble speed, player_speed; speed = clapper_mpris_media_player2_player_get_rate (player_skeleton); player_speed = clapper_player_get_speed (player); if (!G_APPROX_VALUE (speed, player_speed, FLT_EPSILON)) clapper_player_set_speed (player, speed); }); } static void _handle_volume_notify_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GParamSpec *pspec G_GNUC_UNUSED, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle volume notify"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { gdouble volume, player_volume; volume = clapper_mpris_media_player2_player_get_volume (player_skeleton); player_volume = clapper_player_get_volume (player); if (!G_APPROX_VALUE (volume, player_volume, FLT_EPSILON)) clapper_player_set_volume (player, volume); }); } static void _handle_loop_status_notify_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GParamSpec *pspec G_GNUC_UNUSED, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle loop status notify"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); ClapperQueueProgressionMode mode, player_mode; const gchar *loop_status; loop_status = clapper_mpris_media_player2_player_get_loop_status (player_skeleton); player_mode = clapper_queue_get_progression_mode (queue); /* When in shuffle and no loop, assume default mode (none or consecutive). * This prevents us from getting stuck constantly changing loop and shuffle. */ if (player_mode == CLAPPER_QUEUE_PROGRESSION_SHUFFLE) player_mode = self->default_mode; mode = CLAPPER_MPRIS_COMPARE (loop_status, CLAPPER_MPRIS_LOOP_TRACK) ? CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM : CLAPPER_MPRIS_COMPARE (loop_status, CLAPPER_MPRIS_LOOP_PLAYLIST) ? CLAPPER_QUEUE_PROGRESSION_CAROUSEL : self->default_mode; if (mode != player_mode) clapper_queue_set_progression_mode (queue, mode); }); } static void _handle_shuffle_notify_cb (ClapperMprisMediaPlayer2Player *player_skeleton, GParamSpec *pspec G_GNUC_UNUSED, ClapperMpris *self) { ClapperPlayer *player; GST_DEBUG_OBJECT (self, "Handle shuffle notify"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); ClapperQueueProgressionMode player_mode; gboolean shuffle, player_shuffle; player_mode = clapper_queue_get_progression_mode (queue); shuffle = clapper_mpris_media_player2_player_get_shuffle (player_skeleton); player_shuffle = (player_mode == CLAPPER_QUEUE_PROGRESSION_SHUFFLE); if (shuffle != player_shuffle) { clapper_queue_set_progression_mode (queue, (shuffle) ? CLAPPER_QUEUE_PROGRESSION_SHUFFLE : self->non_shuffle_mode); } }); } static gboolean _handle_get_tracks_metadata_cb (ClapperMprisMediaPlayer2TrackList *tracks_skeleton, GDBusMethodInvocation *invocation, const gchar *const *tracks_ids, ClapperMpris *self) { GVariantBuilder builder; GVariant *tracks_variant = NULL; gboolean initialized = FALSE; guint i; GST_DEBUG_OBJECT (self, "Handle get tracks metadata"); for (i = 0; tracks_ids[i]; ++i) { guint index = 0; if (_mpris_find_track_by_id (self, tracks_ids[i], &index)) { ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, index); GVariant *variant = _mpris_build_track_metadata (self, track); if (!initialized) { g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY); initialized = TRUE; } g_variant_builder_add_value (&builder, variant); } } if (initialized) tracks_variant = g_variant_builder_end (&builder); clapper_mpris_media_player2_track_list_complete_get_tracks_metadata (tracks_skeleton, invocation, tracks_variant); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_add_track_cb (ClapperMprisMediaPlayer2TrackList *tracks_skeleton, GDBusMethodInvocation *invocation, const gchar *uri, const gchar *after_track, gboolean set_current, ClapperMpris *self) { ClapperPlayer *player; if (!clapper_mpris_get_queue_controllable (self)) return G_DBUS_METHOD_INVOCATION_UNHANDLED; GST_DEBUG_OBJECT (self, "Handle add track, URI: %s, after_track: %s," " set_current: %s", uri, after_track, set_current ? "yes" : "no"); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperMediaItem *after_item = NULL; gboolean add; if ((add = CLAPPER_MPRIS_COMPARE (after_track, CLAPPER_MPRIS_NO_TRACK))) { GST_DEBUG_OBJECT (self, "Prepend, since requested after \"NoTrack\""); } else { guint index = 0; if ((add = _mpris_find_track_by_id (self, after_track, &index))) { ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, index); GST_DEBUG_OBJECT (self, "Append after: %s", track->id); after_item = track->item; } } if (add) { ClapperQueue *queue = clapper_player_get_queue (player); ClapperMediaItem *item = clapper_media_item_new (uri); clapper_utils_queue_insert_on_main_sync (queue, item, after_item); if (set_current && clapper_queue_select_item (queue, item)) clapper_player_play (player); gst_object_unref (item); } }); clapper_mpris_media_player2_track_list_complete_add_track (tracks_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_remove_track_cb (ClapperMprisMediaPlayer2TrackList *tracks_skeleton, GDBusMethodInvocation *invocation, const gchar *track_id, ClapperMpris *self) { ClapperPlayer *player; guint index = 0; if (!clapper_mpris_get_queue_controllable (self)) return G_DBUS_METHOD_INVOCATION_UNHANDLED; GST_DEBUG_OBJECT (self, "Handle remove track"); if (!_mpris_find_track_by_id (self, track_id, &index)) goto finish; CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, index); clapper_utils_queue_remove_on_main_sync (queue, track->item); }); finish: clapper_mpris_media_player2_track_list_complete_remove_track (tracks_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static gboolean _handle_go_to_cb (ClapperMprisMediaPlayer2TrackList *tracks_skeleton, GDBusMethodInvocation *invocation, const gchar *track_id, ClapperMpris *self) { ClapperPlayer *player; guint index = 0; if (!clapper_mpris_get_queue_controllable (self)) return G_DBUS_METHOD_INVOCATION_UNHANDLED; if (!_mpris_find_track_by_id (self, track_id, &index)) goto finish; CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); ClapperMprisTrack *track = (ClapperMprisTrack *) g_ptr_array_index (self->tracks, index); if (clapper_queue_select_item (queue, track->item)) clapper_player_play (player); }); finish: clapper_mpris_media_player2_track_list_complete_go_to (tracks_skeleton, invocation); return G_DBUS_METHOD_INVOCATION_HANDLED; } static void _name_acquired_cb (GDBusConnection *connection, const gchar *name, ClapperMpris *self) { ClapperPlayer *player; GError *error = NULL; gchar **uri_schemes, **mime_types; GST_DEBUG_OBJECT (self, "Name acquired: %s", name); if (!(self->base_exported = g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (self->base_skeleton), connection, "/org/mpris/MediaPlayer2", &error))) { goto finish; } if (!(self->player_exported = g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (self->player_skeleton), connection, "/org/mpris/MediaPlayer2", &error))) { goto finish; } if (!(self->tracks_exported = g_dbus_interface_skeleton_export ( G_DBUS_INTERFACE_SKELETON (self->tracks_skeleton), connection, "/org/mpris/MediaPlayer2", &error))) { goto finish; } self->registered = TRUE; clapper_mpris_media_player2_set_identity (self->base_skeleton, self->identity); clapper_mpris_media_player2_set_desktop_entry (self->base_skeleton, self->desktop_entry); uri_schemes = clapper_mpris_get_supported_uri_schemes (self); clapper_mpris_media_player2_set_supported_uri_schemes (self->base_skeleton, (const gchar *const *) uri_schemes); g_strfreev (uri_schemes); mime_types = clapper_mpris_get_supported_mime_types (self); clapper_mpris_media_player2_set_supported_mime_types (self->base_skeleton, (const gchar *const *) mime_types); g_strfreev (mime_types); /* As stated in MPRIS docs: "This property is not expected to change, * as it describes an intrinsic capability of the implementation." */ clapper_mpris_media_player2_player_set_can_control (self->player_skeleton, TRUE); clapper_mpris_media_player2_set_has_track_list (self->base_skeleton, TRUE); clapper_mpris_media_player2_track_list_set_can_edit_tracks (self->tracks_skeleton, clapper_mpris_get_queue_controllable (self)); CLAPPER_MPRIS_DO_WITH_PLAYER (self, &player, { ClapperQueue *queue = clapper_player_get_queue (player); GVariant *variant = NULL; _mpris_read_initial_tracks (self, queue); /* Update tracks IDs after reading initial tracks from queue */ clapper_mpris_refresh_track_list (self); if (self->current_track) variant = _mpris_build_track_metadata (self, self->current_track); clapper_mpris_refresh_current_track (self, variant); clapper_mpris_refresh_can_go_next_previous (self); /* Set some initial default progressions to revert to and * try to update them in progression_changed call below */ self->default_mode = CLAPPER_QUEUE_PROGRESSION_NONE; self->non_shuffle_mode = CLAPPER_QUEUE_PROGRESSION_NONE; /* Trigger update with current values */ clapper_mpris_state_changed (CLAPPER_FEATURE (self), clapper_player_get_state (player)); clapper_mpris_position_changed (CLAPPER_FEATURE (self), clapper_player_get_position (player)); clapper_mpris_speed_changed (CLAPPER_FEATURE (self), clapper_player_get_speed (player)); clapper_mpris_volume_changed (CLAPPER_FEATURE (self), clapper_player_get_volume (player)); clapper_mpris_queue_progression_changed (CLAPPER_FEATURE (self), clapper_queue_get_progression_mode (queue)); }); finish: if (error) { GST_ERROR_OBJECT (self, "Error: %s", (error && error->message) ? error->message : "Unknown DBUS error occured"); g_error_free (error); clapper_mpris_unregister (self); } if (self->loop && g_main_loop_is_running (self->loop)) g_main_loop_quit (self->loop); } static void _name_lost_cb (GDBusConnection *connection, const gchar *name, ClapperMpris *self) { GST_DEBUG_OBJECT (self, "Name lost: %s", name); if (self->loop && g_main_loop_is_running (self->loop)) g_main_loop_quit (self->loop); clapper_mpris_unregister (self); } static gboolean clapper_mpris_prepare (ClapperFeature *feature) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); GDBusConnection *connection; gchar *address; GST_DEBUG_OBJECT (self, "Prepare"); if (!(address = g_dbus_address_get_for_bus_sync (G_BUS_TYPE_SESSION, NULL, NULL))) { GST_WARNING_OBJECT (self, "No MPRIS bus address"); return FALSE; } GST_INFO_OBJECT (self, "Obtained MPRIS DBus address: %s", address); connection = g_dbus_connection_new_for_address_sync (address, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, NULL, NULL, NULL); g_free (address); if (!connection) { GST_WARNING_OBJECT (self, "No MPRIS bus connection"); return FALSE; } GST_INFO_OBJECT (self, "Obtained MPRIS DBus connection"); self->loop = g_main_loop_new (g_main_context_get_thread_default (), FALSE); self->name_id = g_bus_own_name_on_connection (connection, self->own_name, G_BUS_NAME_OWNER_FLAGS_NONE, (GBusNameAcquiredCallback) _name_acquired_cb, (GBusNameLostCallback) _name_lost_cb, self, NULL); g_object_unref (connection); /* Wait until connection is established */ g_main_loop_run (self->loop); g_clear_pointer (&self->loop, g_main_loop_unref); if (self->registered) { GST_DEBUG_OBJECT (self, "Own name ID: %u", self->name_id); } else if (self->name_id > 0) { GST_ERROR_OBJECT (self, "Could not register MPRIS connection"); g_bus_unown_name (self->name_id); self->name_id = 0; } return self->registered; } static gboolean clapper_mpris_unprepare (ClapperFeature *feature) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); GST_DEBUG_OBJECT (self, "Unprepare"); clapper_mpris_unregister (self); if (self->name_id > 0) { g_bus_unown_name (self->name_id); self->name_id = 0; } return TRUE; } static void clapper_mpris_property_changed (ClapperFeature *feature, GParamSpec *pspec) { ClapperMpris *self = CLAPPER_MPRIS_CAST (feature); GST_DEBUG_OBJECT (self, "Property changed: \"%s\"", g_param_spec_get_name (pspec)); if (pspec == param_specs[PROP_FALLBACK_ART_URL]) { clapper_mpris_refresh_all_tracks (self); } else if (pspec == param_specs[PROP_QUEUE_CONTROLLABLE]) { clapper_mpris_media_player2_track_list_set_can_edit_tracks (self->tracks_skeleton, clapper_mpris_get_queue_controllable (self)); clapper_mpris_refresh_can_go_next_previous (self); } } /** * clapper_mpris_new: * @own_name: an unique DBus name with "org.mpris.MediaPlayer2." prefix * @identity: a media player friendly name * @desktop_entry: (nullable): desktop file basename (without ".desktop" extension) * * Creates a new #ClapperMpris instance. * * Returns: (transfer full): a new #ClapperMpris instance. */ ClapperMpris * clapper_mpris_new (const gchar *own_name, const gchar *identity, const gchar *desktop_entry) { ClapperMpris *mpris; mpris = g_object_new (CLAPPER_TYPE_MPRIS, "own-name", own_name, "identity", identity, "desktop-entry", desktop_entry, NULL); gst_object_ref_sink (mpris); return mpris; } /** * clapper_mpris_set_queue_controllable: * @mpris: a #ClapperMpris * @controllable: if #ClapperQueue should be controllable * * Set whether remote MPRIS clients can control #ClapperQueue. * * This includes ability to open new URIs, adding/removing * items from the queue and selecting current item for * playback remotely using MPRIS interface. * * You probably want to keep this disabled if your application * is supposed to manage what is played now and not MPRIS client. */ void clapper_mpris_set_queue_controllable (ClapperMpris *self, gboolean controllable) { gboolean prev_controllable; g_return_if_fail (CLAPPER_IS_MPRIS (self)); prev_controllable = (gboolean) g_atomic_int_exchange (&self->queue_controllable, (gint) controllable); if (prev_controllable != controllable) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_QUEUE_CONTROLLABLE]); } /** * clapper_mpris_get_queue_controllable: * @mpris: a #ClapperMpris * * Get whether remote `MPRIS` clients can control [class@Clapper.Queue]. * * Returns: %TRUE if control over #ClapperQueue is allowed, %FALSE otherwise. */ gboolean clapper_mpris_get_queue_controllable (ClapperMpris *self) { g_return_val_if_fail (CLAPPER_IS_MPRIS (self), FALSE); return (gboolean) g_atomic_int_get (&self->queue_controllable); } /** * clapper_mpris_set_fallback_art_url: * @mpris: a #ClapperMpris * @art_url: (nullable): an art URL * * Set fallback artwork to show when media does not provide one. */ void clapper_mpris_set_fallback_art_url (ClapperMpris *self, const gchar *art_url) { gboolean changed; g_return_if_fail (CLAPPER_IS_MPRIS (self)); GST_OBJECT_LOCK (self); changed = g_set_str (&self->fallback_art_url, art_url); GST_OBJECT_UNLOCK (self); if (changed) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_FALLBACK_ART_URL]); } /** * clapper_mpris_get_fallback_art_url: * @mpris: a #ClapperMpris * * Get fallback art URL earlier set by user. * * Returns: (transfer full) (nullable): fallback art URL. */ gchar * clapper_mpris_get_fallback_art_url (ClapperMpris *self) { gchar *art_url; g_return_val_if_fail (CLAPPER_IS_MPRIS (self), NULL); GST_OBJECT_LOCK (self); art_url = g_strdup (self->fallback_art_url); GST_OBJECT_UNLOCK (self); return art_url; } static void clapper_mpris_init (ClapperMpris *self) { self->base_skeleton = clapper_mpris_media_player2_skeleton_new (); self->player_skeleton = clapper_mpris_media_player2_player_skeleton_new (); self->tracks_skeleton = clapper_mpris_media_player2_track_list_skeleton_new (); self->tracks = g_ptr_array_new_with_free_func ((GDestroyNotify) clapper_mpris_track_free); g_atomic_int_set (&self->queue_controllable, (gint) DEFAULT_QUEUE_CONTROLLABLE); g_signal_connect (self->player_skeleton, "handle-open-uri", G_CALLBACK (_handle_open_uri_cb), self); g_signal_connect (self->player_skeleton, "handle-play", G_CALLBACK (_handle_play_cb), self); g_signal_connect (self->player_skeleton, "handle-pause", G_CALLBACK (_handle_pause_cb), self); g_signal_connect (self->player_skeleton, "handle-play-pause", G_CALLBACK (_handle_play_pause_cb), self); g_signal_connect (self->player_skeleton, "handle-stop", G_CALLBACK (_handle_stop_cb), self); g_signal_connect (self->player_skeleton, "handle-next", G_CALLBACK (_handle_next_cb), self); g_signal_connect (self->player_skeleton, "handle-previous", G_CALLBACK (_handle_previous_cb), self); g_signal_connect (self->player_skeleton, "handle-seek", G_CALLBACK (_handle_seek_cb), self); g_signal_connect (self->player_skeleton, "handle-set-position", G_CALLBACK (_handle_set_position_cb), self); g_signal_connect (self->player_skeleton, "notify::rate", G_CALLBACK (_handle_rate_notify_cb), self); g_signal_connect (self->player_skeleton, "notify::volume", G_CALLBACK (_handle_volume_notify_cb), self); g_signal_connect (self->player_skeleton, "notify::loop-status", G_CALLBACK (_handle_loop_status_notify_cb), self); g_signal_connect (self->player_skeleton, "notify::shuffle", G_CALLBACK (_handle_shuffle_notify_cb), self); g_signal_connect (self->tracks_skeleton, "handle-get-tracks-metadata", G_CALLBACK (_handle_get_tracks_metadata_cb), self); g_signal_connect (self->tracks_skeleton, "handle-add-track", G_CALLBACK (_handle_add_track_cb), self); g_signal_connect (self->tracks_skeleton, "handle-remove-track", G_CALLBACK (_handle_remove_track_cb), self); g_signal_connect (self->tracks_skeleton, "handle-go-to", G_CALLBACK (_handle_go_to_cb), self); } static void clapper_mpris_finalize (GObject *object) { ClapperMpris *self = CLAPPER_MPRIS_CAST (object); g_object_unref (self->base_skeleton); g_object_unref (self->player_skeleton); g_object_unref (self->tracks_skeleton); self->current_track = NULL; g_ptr_array_unref (self->tracks); g_free (self->own_name); g_free (self->identity); g_free (self->desktop_entry); g_free (self->fallback_art_url); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_mpris_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperMpris *self = CLAPPER_MPRIS_CAST (object); switch (prop_id) { case PROP_OWN_NAME: self->own_name = g_value_dup_string (value); break; case PROP_IDENTITY: self->identity = g_value_dup_string (value); break; case PROP_DESKTOP_ENTRY: self->desktop_entry = g_value_dup_string (value); break; case PROP_QUEUE_CONTROLLABLE: clapper_mpris_set_queue_controllable (self, g_value_get_boolean (value)); break; case PROP_FALLBACK_ART_URL: clapper_mpris_set_fallback_art_url (self, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_mpris_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperMpris *self = CLAPPER_MPRIS_CAST (object); switch (prop_id) { case PROP_OWN_NAME: g_value_set_string (value, self->own_name); break; case PROP_IDENTITY: g_value_set_string (value, self->identity); break; case PROP_DESKTOP_ENTRY: g_value_set_string (value, self->desktop_entry); break; case PROP_QUEUE_CONTROLLABLE: g_value_set_boolean (value, clapper_mpris_get_queue_controllable (self)); break; case PROP_FALLBACK_ART_URL: g_value_take_string (value, clapper_mpris_get_fallback_art_url (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_mpris_class_init (ClapperMprisClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperFeatureClass *feature_class = (ClapperFeatureClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappermpris", 0, "Clapper Mpris"); gobject_class->get_property = clapper_mpris_get_property; gobject_class->set_property = clapper_mpris_set_property; gobject_class->finalize = clapper_mpris_finalize; feature_class->prepare = clapper_mpris_prepare; feature_class->unprepare = clapper_mpris_unprepare; feature_class->property_changed = clapper_mpris_property_changed; feature_class->state_changed = clapper_mpris_state_changed; feature_class->position_changed = clapper_mpris_position_changed; feature_class->speed_changed = clapper_mpris_speed_changed; feature_class->volume_changed = clapper_mpris_volume_changed; feature_class->played_item_changed = clapper_mpris_played_item_changed; feature_class->item_updated = clapper_mpris_item_updated; feature_class->queue_item_added = clapper_mpris_queue_item_added; feature_class->queue_item_removed = clapper_mpris_queue_item_removed; feature_class->queue_item_repositioned = clapper_mpris_queue_item_repositioned; feature_class->queue_cleared = clapper_mpris_queue_cleared; feature_class->queue_progression_changed = clapper_mpris_queue_progression_changed; /** * ClapperMpris:own-name: * * DBus name to own on connection. * * Must be written as a reverse DNS format starting with "org.mpris.MediaPlayer2." prefix. * Each #ClapperMpris instance running on the same system must have an unique name. * * Example: "org.mpris.MediaPlayer2.MyPlayer.instance123" */ param_specs[PROP_OWN_NAME] = g_param_spec_string ("own-name", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMpris:identity: * * A friendly name to identify the media player. * * Example: "My Player" */ param_specs[PROP_IDENTITY] = g_param_spec_string ("identity", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMpris:desktop-entry: * * The basename of an installed .desktop file with the ".desktop" extension stripped. */ param_specs[PROP_DESKTOP_ENTRY] = g_param_spec_string ("desktop-entry", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMpris:queue-controllable: * * Whether remote MPRIS clients can control #ClapperQueue. */ param_specs[PROP_QUEUE_CONTROLLABLE] = g_param_spec_boolean ("queue-controllable", NULL, NULL, DEFAULT_QUEUE_CONTROLLABLE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperMpris:fallback-art-url: * * Fallback artwork to show when media does not provide one. */ param_specs[PROP_FALLBACK_ART_URL] = g_param_spec_string ("fallback-art-url", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/features/mpris/clapper-mpris.h000066400000000000000000000034401474353110700240350ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_MPRIS (clapper_mpris_get_type()) #define CLAPPER_MPRIS_CAST(obj) ((ClapperMpris *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperMpris, clapper_mpris, CLAPPER, MPRIS, ClapperFeature) CLAPPER_API ClapperMpris * clapper_mpris_new (const gchar *own_name, const gchar *identity, const gchar *desktop_entry); CLAPPER_API void clapper_mpris_set_queue_controllable (ClapperMpris *mpris, gboolean controllable); CLAPPER_API gboolean clapper_mpris_get_queue_controllable (ClapperMpris *mpris); CLAPPER_API void clapper_mpris_set_fallback_art_url (ClapperMpris *mpris, const gchar *art_url); CLAPPER_API gchar * clapper_mpris_get_fallback_art_url (ClapperMpris *mpris); G_END_DECLS clapper-0.8.0/src/lib/clapper/features/mpris/meson.build000066400000000000000000000027351474353110700232560ustar00rootroot00000000000000feature_option = get_option(feature_name) if feature_option.disabled() subdir_done() endif # Known OSes that can support our MPRIS implementation os_supported = ['linux'].contains(host_machine.system()) if not os_supported if feature_option.enabled() error('@0@ feature was enabled, but OS is not supported by it'.format(feature_name)) endif subdir_done() endif feature_deps = [ dependency('gio-unix-2.0', version: glib_req, required: false), ] foreach dep : feature_deps if not dep.found() if feature_option.enabled() error('@0@ feature was enabled, but required dependencies were not found'.format(feature_name)) endif subdir_done() endif endforeach gdbus_codegen = find_program('gdbus-codegen', required: false) if not gdbus_codegen.found() if feature_option.enabled() error('@0@ feature was enabled, but program gdbus-codegen was not found'.format(feature_name)) endif subdir_done() endif clapper_mpris_gdbus = gnome.gdbus_codegen('clapper-mpris-gdbus', sources: 'clapper-mpris-gdbus.xml', interface_prefix: 'org.mpris.', namespace: 'ClapperMpris', ) clapper_features_headers += [ 'features/mpris/clapper-mpris.h', ] clapper_features_sources += [ 'features/mpris/clapper-mpris.c', ] clapper_features_sources_internal += [ clapper_mpris_gdbus, ] clapper_features_deps += feature_deps install_headers('clapper-mpris.h', install_dir: join_paths(clapper_headers_dir, 'features', 'mpris'), ) clapper_available_features += feature_name clapper-0.8.0/src/lib/clapper/features/server/000077500000000000000000000000001474353110700212615ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper/features/server/clapper-server-actions-private.h000066400000000000000000000046421474353110700275000ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-enums.h" G_BEGIN_DECLS typedef enum { CLAPPER_SERVER_ACTION_INVALID = 0, CLAPPER_SERVER_ACTION_TOGGLE_PLAY, CLAPPER_SERVER_ACTION_PLAY, CLAPPER_SERVER_ACTION_PAUSE, CLAPPER_SERVER_ACTION_STOP, CLAPPER_SERVER_ACTION_SEEK, CLAPPER_SERVER_ACTION_SET_SPEED, CLAPPER_SERVER_ACTION_SET_VOLUME, CLAPPER_SERVER_ACTION_SET_MUTE, CLAPPER_SERVER_ACTION_SET_PROGRESSION, CLAPPER_SERVER_ACTION_ADD, CLAPPER_SERVER_ACTION_INSERT, CLAPPER_SERVER_ACTION_SELECT, CLAPPER_SERVER_ACTION_REMOVE, CLAPPER_SERVER_ACTION_CLEAR } ClapperServerAction; G_GNUC_INTERNAL ClapperServerAction clapper_server_actions_get_action (const gchar *text); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_seek (const gchar *text, gdouble *position); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_set_speed (const gchar *text, gdouble *speed); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_set_volume (const gchar *text, gdouble *volume); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_set_mute (const gchar *text, gboolean *mute); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_set_progression (const gchar *text, ClapperQueueProgressionMode *mode); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_add (const gchar *text, const gchar **uri); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_insert (const gchar *text, gchar **uri, guint *after_id); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_select (const gchar *text, guint *id); G_GNUC_INTERNAL gboolean clapper_server_actions_parse_remove (const gchar *text, guint *id); G_END_DECLS clapper-0.8.0/src/lib/clapper/features/server/clapper-server-actions.c000066400000000000000000000136731474353110700260270ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-server-actions-private.h" #include "clapper-server-names-private.h" #include "clapper-enums.h" inline ClapperServerAction clapper_server_actions_get_action (const gchar *text) { /* Actions without arg(s) */ if (strcmp (text, "toggle_play") == 0) return CLAPPER_SERVER_ACTION_TOGGLE_PLAY; if (strcmp (text, "play") == 0) return CLAPPER_SERVER_ACTION_PLAY; if (strcmp (text, "pause") == 0) return CLAPPER_SERVER_ACTION_PAUSE; if (strcmp (text, "stop") == 0) return CLAPPER_SERVER_ACTION_STOP; if (strcmp (text, "clear") == 0) return CLAPPER_SERVER_ACTION_CLEAR; /* Actions followed by space and arg(s) */ if (g_str_has_prefix (text, "seek ")) return CLAPPER_SERVER_ACTION_SEEK; if (g_str_has_prefix (text, "set_speed ")) return CLAPPER_SERVER_ACTION_SET_SPEED; if (g_str_has_prefix (text, "set_volume ")) return CLAPPER_SERVER_ACTION_SET_VOLUME; if (g_str_has_prefix (text, "set_mute ")) return CLAPPER_SERVER_ACTION_SET_MUTE; if (g_str_has_prefix (text, "set_progression ")) return CLAPPER_SERVER_ACTION_SET_PROGRESSION; if (g_str_has_prefix (text, "add ")) return CLAPPER_SERVER_ACTION_ADD; if (g_str_has_prefix (text, "insert ")) return CLAPPER_SERVER_ACTION_INSERT; if (g_str_has_prefix (text, "select ")) return CLAPPER_SERVER_ACTION_SELECT; if (g_str_has_prefix (text, "remove ")) return CLAPPER_SERVER_ACTION_REMOVE; return CLAPPER_SERVER_ACTION_INVALID; } static inline gboolean _string_is_number (const gchar *string, gboolean decimal) { guint i; for (i = 0; string[i] != '\0'; ++i) { if (!g_ascii_isdigit (string[i])) { if (decimal && string[i] == '.') continue; return FALSE; } } return (i > 0); } static gboolean _parse_uint (const gchar *text, guint *val) { gint64 tmp_val; if (!_string_is_number (text, FALSE)) return FALSE; tmp_val = g_ascii_strtoll (text, NULL, 10); /* guint overflow check */ if (tmp_val < 0 || tmp_val > G_MAXUINT) return FALSE; *val = (guint) tmp_val; return TRUE; } static gboolean _parse_double (const gchar *text, gdouble *val) { if (!_string_is_number (text, TRUE)) return FALSE; *val = g_ascii_strtod (text, NULL); return TRUE; } static gboolean _parse_boolean (const gchar *text, gboolean *val) { gboolean res; if ((res = (strcmp (text, "true") == 0))) *val = TRUE; else if ((res = (strcmp (text, "false") == 0))) *val = FALSE; return res; } inline gboolean clapper_server_actions_parse_seek (const gchar *text, gdouble *position) { /* "seek" + whitespace = 5 */ if (!_parse_double (text + 5, position)) return FALSE; return (*position >= 0); } inline gboolean clapper_server_actions_parse_set_speed (const gchar *text, gdouble *speed) { /* "set_speed" + whitespace = 10 */ return _parse_double (text + 10, speed); } inline gboolean clapper_server_actions_parse_set_volume (const gchar *text, gdouble *volume) { /* "set_volume" + whitespace = 11 */ if (!_parse_double (text + 11, volume)) return FALSE; if (*volume <= 0 || *volume > 2.0) return FALSE; return TRUE; } inline gboolean clapper_server_actions_parse_set_mute (const gchar *text, gboolean *mute) { /* "set_mute" + whitespace = 9 */ return _parse_boolean (text + 9, mute); } inline gboolean clapper_server_actions_parse_set_progression (const gchar *text, ClapperQueueProgressionMode *mode) { gboolean res; /* "set_progression" + whitespace = 16 */ text += 16; if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_NONE) == 0))) *mode = CLAPPER_QUEUE_PROGRESSION_NONE; else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_CONSECUTIVE) == 0))) *mode = CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE; else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_REPEAT_ITEM) == 0))) *mode = CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM; else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_CAROUSEL) == 0))) *mode = CLAPPER_QUEUE_PROGRESSION_CAROUSEL; else if ((res = (strcmp (text, CLAPPER_SERVER_QUEUE_PROGRESSION_SHUFFLE) == 0))) *mode = CLAPPER_QUEUE_PROGRESSION_SHUFFLE; return res; } inline gboolean clapper_server_actions_parse_add (const gchar *text, const gchar **uri) { /* "add" + whitespace = 4 */ text += 4; /* No more spaces allowed */ if (strchr (text, ' ') != NULL) return FALSE; if (!gst_uri_is_valid (text)) return FALSE; *uri = text; return TRUE; } inline gboolean clapper_server_actions_parse_insert (const gchar *text, gchar **uri, guint *after_id) { gchar **data; gboolean res; /* "insert" + whitespace = 7 */ text += 7; data = g_strsplit (text, " ", 2); if ((res = (g_strv_length (data) == 2 && gst_uri_is_valid (data[0]) && _parse_uint (data[1], after_id)))) { *uri = g_strdup (data[0]); } g_strfreev (data); return res; } inline gboolean clapper_server_actions_parse_select (const gchar *text, guint *id) { /* "select" + whitespace = 7 */ return _parse_uint (text + 7, id); } inline gboolean clapper_server_actions_parse_remove (const gchar *text, guint *id) { /* "remove" + whitespace = 7 */ return _parse_uint (text + 7, id); } clapper-0.8.0/src/lib/clapper/features/server/clapper-server-json-private.h000066400000000000000000000021041474353110700270000ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-server.h" #include "clapper-media-item.h" G_BEGIN_DECLS G_GNUC_INTERNAL gchar * clapper_server_json_build_complete (ClapperServer *server, ClapperMediaItem *played_item, guint played_index, GPtrArray *items); G_END_DECLS clapper-0.8.0/src/lib/clapper/features/server/clapper-server-json.c000066400000000000000000000200321474353110700253230ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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-server-json-private.h" #include "clapper-server-names-private.h" #include "clapper-player.h" #include "clapper-queue.h" #define CLAPPER_SERVER_JSON_BUILD(dest, ...) { \ GString *_json = g_string_new ("{"); \ __VA_ARGS__ \ g_string_append (_json, "}"); \ *dest = g_string_free (_json, FALSE); } #define _JSON_AUTO_COMMA \ if (_json->str[_json->len - 1] != '{' \ && _json->str[_json->len - 1] != '[') \ g_string_append (_json, ","); #define _ADD_KEY_VAL_BOOLEAN(key, val) \ _JSON_AUTO_COMMA \ g_string_append_printf (_json, "\"%s\":%s", key, (val) ? "true" : "false"); #define _ADD_KEY_VAL_UINT(key, val) \ _JSON_AUTO_COMMA \ g_string_append_printf (_json, "\"%s\":%" G_GUINT64_FORMAT, key, (guint64) val); #define _ADD_KEY_VAL_DOUBLE(key, val) { \ gchar _buf[G_ASCII_DTOSTR_BUF_SIZE]; \ _JSON_AUTO_COMMA \ g_string_append_printf (_json, "\"%s\":%s", key, \ g_ascii_formatd (_buf, sizeof (_buf), "%.2f", val)); } #define _ADD_KEY_VAL_STRING(key, val) \ _JSON_AUTO_COMMA \ if (G_UNLIKELY (val == NULL)) \ g_string_append_printf (_json, "\"%s\":null", key); \ else \ g_string_append_printf (_json, "\"%s\":\"%s\"", key, val); #define _ADD_KEY_VAL_STRING_TAKE(key, val) \ _ADD_KEY_VAL_STRING(key, val) \ g_free (val); #define _ADD_VAL_STRING(val) \ _JSON_AUTO_COMMA \ if (G_UNLIKELY (val == NULL)) \ g_string_append (_json, "null"); \ else \ g_string_append_printf (_json, "\"%s\"", val); #define _ADD_VAL_STRING_TAKE(val) \ _ADD_VAL_STRING(val) \ g_free (val); #define _ADD_OBJECT(...) \ _JSON_AUTO_COMMA \ g_string_append (_json, "{"); \ __VA_ARGS__ \ g_string_append (_json, "}"); #define _ADD_NAMED_OBJECT(name, ...) \ _JSON_AUTO_COMMA \ g_string_append_printf (_json, "\"%s\":{", name); \ __VA_ARGS__ \ g_string_append (_json, "}"); #define _ADD_NAMED_ARRAY(name, ...) \ _JSON_AUTO_COMMA \ g_string_append_printf (_json, "\"%s\":[", name); \ __VA_ARGS__ \ g_string_append (_json, "]"); static inline void clapper_server_json_escape_string (gchar **string) { gchar *dest, *src = *string; guint i, offset = 0; for (i = 0; src[i] != '\0'; ++i) { switch (src[i]) { case '\"': offset++; default: break; } } /* Nothing to escape, leave string unchaged */ if (offset == 0) return; /* Previous length + n_escapes + term */ dest = g_new (gchar, i + offset + 1); offset = 0; for (i = 0; src[i] != '\0'; ++i) { switch (src[i]) { case '\"': dest[i + offset] = '\\'; dest[i + offset + 1] = '\"'; offset++; break; default: dest[i + offset] = src[i]; break; } } dest[i + offset] = '\0'; g_free (*string); *string = dest; } gchar * clapper_server_json_build_complete (ClapperServer *server, ClapperMediaItem *played_item, guint played_index, GPtrArray *items) { ClapperPlayer *player; gchar *data = NULL; player = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (server))); if (G_UNLIKELY (player == NULL)) return NULL; CLAPPER_SERVER_JSON_BUILD (&data, { switch (clapper_player_get_state (player)) { case CLAPPER_PLAYER_STATE_PLAYING: _ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_PLAYING); break; case CLAPPER_PLAYER_STATE_PAUSED: _ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_PAUSED); break; case CLAPPER_PLAYER_STATE_BUFFERING: _ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_BUFFERING); break; case CLAPPER_PLAYER_STATE_STOPPED: _ADD_KEY_VAL_STRING ("state", CLAPPER_SERVER_PLAYER_STATE_STOPPED); break; default: g_assert_not_reached (); break; } _ADD_KEY_VAL_UINT ("position", clapper_player_get_position (player)); _ADD_KEY_VAL_DOUBLE ("speed", clapper_player_get_speed (player)); _ADD_KEY_VAL_DOUBLE ("volume", clapper_player_get_volume (player)); _ADD_KEY_VAL_BOOLEAN ("mute", clapper_player_get_mute (player)); _ADD_NAMED_OBJECT ("queue", { ClapperQueue *queue = clapper_player_get_queue (player); _ADD_KEY_VAL_BOOLEAN ("controllable", clapper_server_get_queue_controllable (server)); _ADD_KEY_VAL_UINT ("played_index", played_index); _ADD_KEY_VAL_UINT ("n_items", items->len); switch (clapper_queue_get_progression_mode (queue)) { case CLAPPER_QUEUE_PROGRESSION_NONE: _ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_NONE); break; case CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE: _ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_CONSECUTIVE); break; case CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM: _ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_REPEAT_ITEM); break; case CLAPPER_QUEUE_PROGRESSION_CAROUSEL: _ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_CAROUSEL); break; case CLAPPER_QUEUE_PROGRESSION_SHUFFLE: _ADD_KEY_VAL_STRING ("progression", CLAPPER_SERVER_QUEUE_PROGRESSION_SHUFFLE); break; default: g_assert_not_reached (); break; } _ADD_NAMED_ARRAY ("items", { guint i; for (i = 0; i < items->len; ++i) { _ADD_OBJECT ({ ClapperMediaItem *item = (ClapperMediaItem *) g_ptr_array_index (items, i); gchar *title = clapper_media_item_get_title (item); if (title) clapper_server_json_escape_string (&title); _ADD_KEY_VAL_UINT ("id", clapper_media_item_get_id (item)); _ADD_KEY_VAL_STRING_TAKE ("title", title); _ADD_KEY_VAL_UINT ("duration", clapper_media_item_get_duration (item)); /* TODO: Add more info per item (including timeline markers) */ }); } }); }); }); gst_object_unref (player); return data; } clapper-0.8.0/src/lib/clapper/features/server/clapper-server-mdns-private.h000066400000000000000000000021231474353110700267710ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include G_BEGIN_DECLS G_GNUC_INTERNAL void clapper_server_mdns_debug_init (void); G_GNUC_INTERNAL void clapper_server_mdns_serve (gchar *name, guint port); G_GNUC_INTERNAL void clapper_server_mdns_remove (guint port); G_END_DECLS clapper-0.8.0/src/lib/clapper/features/server/clapper-server-mdns.c000066400000000000000000000227231474353110700253240ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-server-mdns-private.h" #define CLAPPER_SERVER_MDNS_SERVICE "_clapper._tcp.local" #define N_RESP 4 #define PTR_INDEX(i) (i * N_RESP) #define TXT_INDEX(i) (PTR_INDEX(i) + 1) #define SRV_INDEX(i) (PTR_INDEX(i) + 2) #define A_AAAA_INDEX(i) (PTR_INDEX(i) + 3) typedef struct { gchar *name; gchar *service_link; guint port; } ClapperServerMdnsEntry; typedef struct { GPtrArray *entries; GPtrArray *pending_entries; } ClapperServerMdns; #define GST_CAT_DEFAULT clapper_server_mdns_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); static ClapperServerMdns *mdns = NULL; static GCond mdns_cond; static GMutex mdns_lock; void clapper_server_mdns_debug_init (void) { GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperservermdns", GST_DEBUG_FG_RED, "Clapper Server MDNS"); } static void clapper_server_mdns_entry_free (ClapperServerMdnsEntry *entry) { GST_TRACE ("Freeing MDNS entry: %p", entry); g_free (entry->name); g_free (entry->service_link); g_free (entry); } static void clapper_server_mdns_remove_port (GPtrArray *entries, guint port) { guint i; for (i = 0; i < entries->len; ++i) { ClapperServerMdnsEntry *entry = g_ptr_array_index (entries, i); if (entry->port == port) { GST_TRACE ("Removing entry with port: %u", port); g_ptr_array_remove_index (entries, i); break; } } } static inline void _send_entries (struct mdns_ctx *ctx, const struct sockaddr *addr, enum mdns_announce_type type, GPtrArray *entries) { const guint n_answers = N_RESP * entries->len; gchar domain_name[32]; guint i; struct rr_entry *answers = g_alloca0 (sizeof (struct rr_entry) * n_answers); struct mdns_hdr hdr = { 0, }; hdr.flags |= FLAG_QR; hdr.flags |= FLAG_AA; hdr.num_ans_rr = n_answers; g_snprintf (domain_name, sizeof (domain_name), "%s.local", g_get_host_name ()); for (i = 0; i < entries->len; ++i) { ClapperServerMdnsEntry *entry = g_ptr_array_index (entries, i); GST_LOG ("Preparing answers for MDNS query, service: \"%s\"" ", domain: \"%s\", link: \"%s\"", CLAPPER_SERVER_MDNS_SERVICE, domain_name, entry->service_link); answers[PTR_INDEX(i)] = (struct rr_entry) { .type = RR_PTR, .name = (char *) CLAPPER_SERVER_MDNS_SERVICE, .data.PTR.domain = entry->service_link, .rr_class = RR_IN, .msbit = 1, .ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120, .next = &answers[TXT_INDEX(i)] }; answers[TXT_INDEX(i)] = (struct rr_entry) { .type = RR_TXT, .name = entry->service_link, .rr_class = RR_IN, .msbit = 1, .ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120, .next = &answers[SRV_INDEX(i)] }; answers[SRV_INDEX(i)] = (struct rr_entry) { .type = RR_SRV, .name = entry->service_link, .data.SRV.port = entry->port, .data.SRV.priority = 0, .data.SRV.weight = 0, .data.SRV.target = domain_name, .rr_class = RR_IN, .msbit = 1, .ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120, .next = &answers[A_AAAA_INDEX(i)] }; answers[A_AAAA_INDEX(i)] = (struct rr_entry) { .name = domain_name, .rr_class = RR_IN, .msbit = 1, .ttl = (type == MDNS_ANNOUNCE_GOODBYE) ? 0 : 120, .next = (i + 1 < entries->len) ? &answers[PTR_INDEX(i + 1)] : NULL }; if (addr->sa_family == AF_INET) { answers[A_AAAA_INDEX(i)].type = RR_A; memcpy (&answers[A_AAAA_INDEX(i)].data.A.addr, &((struct sockaddr_in *) addr)->sin_addr, sizeof (answers[A_AAAA_INDEX(i)].data.A.addr)); } else { answers[A_AAAA_INDEX(i)].type = RR_AAAA; memcpy(&answers[A_AAAA_INDEX(i)].data.AAAA.addr, &((struct sockaddr_in6 *) addr)->sin6_addr, sizeof (answers[A_AAAA_INDEX(i)].data.AAAA.addr)); } GST_LOG ("Prepared %u/%u bunches of answers", i + 1, entries->len); } /* Needs to still have lock here, as pointers * in answers are simply assigned from entries */ GST_LOG ("Sending all answers"); mdns_entries_send (ctx, &hdr, answers); } static void _mdns_cb (struct mdns_ctx *ctx, const struct sockaddr *addr, const char* service, enum mdns_announce_type type) { if (service && strcmp (service, CLAPPER_SERVER_MDNS_SERVICE) != 0) return; g_mutex_lock (&mdns_lock); switch (type) { case MDNS_ANNOUNCE_INITIAL: if (mdns->pending_entries->len > 0) { GST_LOG ("Handling announcement type: INITIAL"); _send_entries (ctx, addr, type, mdns->pending_entries); /* Move to entries after initial announcement */ while (mdns->pending_entries->len > 0) { ClapperServerMdnsEntry *entry; /* MDNS advertises entries in reverse order */ entry = g_ptr_array_steal_index (mdns->pending_entries, 0); g_ptr_array_insert (mdns->entries, 0, entry); } } break; case MDNS_ANNOUNCE_RESPONSE: case MDNS_ANNOUNCE_GOODBYE: if (mdns->entries->len > 0) { GST_LOG ("Handling announcement type: %s", (type == MDNS_ANNOUNCE_RESPONSE) ? "RESPONSE" : "GOODBYE"); _send_entries (ctx, addr, type, mdns->entries); } break; default: break; } g_mutex_unlock (&mdns_lock); } static gboolean mdns_stop_cb (struct mdns_ctx *ctx) { gboolean announce; g_mutex_lock (&mdns_lock); if (mdns->entries->len == 0 && mdns->pending_entries->len == 0) { g_mutex_unlock (&mdns_lock); return TRUE; } announce = (mdns->pending_entries->len > 0); g_mutex_unlock (&mdns_lock); if (announce) mdns_request_initial_announce (ctx, NULL); return FALSE; } static gpointer mdns_thread_func (gpointer user_data) { struct mdns_ctx *ctx = NULL; int resp; char err_str[128]; GST_TRACE ("MDNS init"); if ((resp = mdns_init (&ctx, MDNS_ADDR_IPV4, MDNS_PORT)) < 0) { mdns_strerror(resp, err_str, sizeof (err_str)); GST_ERROR ("Could not initialize MDNS, reason: %s", err_str); return NULL; } mdns_announce (ctx, RR_PTR, (mdns_announce_callback) _mdns_cb, ctx); GST_DEBUG ("MDNS start"); serve: if ((resp = mdns_serve (ctx, (mdns_stop_func) mdns_stop_cb, ctx)) < 0) { mdns_strerror(resp, err_str, sizeof (err_str)); GST_ERROR ("Could start MDNS, reason: %s", err_str); } g_mutex_lock (&mdns_lock); /* Can happen when stopped due to lack of entries, * but entries were added afterwards */ if (resp >= 0 && (mdns->entries->len > 0 || mdns->pending_entries->len > 0)) { g_mutex_unlock (&mdns_lock); goto serve; } /* No more going back now */ GST_DEBUG ("MDNS stop"); /* Destroy with a lock, this ensures unbind * of MDNS_PORT before doing "mdns_init" again */ GST_TRACE ("MDNS destroy"); mdns_destroy (ctx); GST_TRACE ("Freeing MDNS entries storage: %p", mdns); g_ptr_array_unref (mdns->entries); g_ptr_array_unref (mdns->pending_entries); g_clear_pointer (&mdns, g_free); g_cond_broadcast (&mdns_cond); g_mutex_unlock (&mdns_lock); return NULL; } void clapper_server_mdns_serve (gchar *name, guint port) { ClapperServerMdnsEntry *entry; const gchar *prgname = g_get_prgname (); gboolean stopped; entry = g_new (ClapperServerMdnsEntry, 1); entry->name = name; entry->service_link = g_strdup_printf ("%s %s %s.%s", g_get_host_name (), (prgname) ? prgname : "clapperplayer", entry->name, CLAPPER_SERVER_MDNS_SERVICE); entry->port = port; GST_TRACE ("Created MDNS entry: %p", entry); g_mutex_lock (&mdns_lock); if ((stopped = mdns == NULL)) { mdns = g_new (ClapperServerMdns, 1); mdns->entries = g_ptr_array_new_with_free_func ( (GDestroyNotify) clapper_server_mdns_entry_free); mdns->pending_entries = g_ptr_array_new_with_free_func ( (GDestroyNotify) clapper_server_mdns_entry_free); GST_TRACE ("Created MDNS entries storage: %p", mdns); } g_ptr_array_add (mdns->pending_entries, entry); g_mutex_unlock (&mdns_lock); if (stopped) { GThread *thread; GError *error = NULL; GST_DEBUG ("Starting MDNS service"); thread = g_thread_try_new ("clapper-server-mdns", (GThreadFunc) mdns_thread_func, NULL, &error); if (error) { GST_ERROR ("Could not create MDNS thread, reason: %s", error->message); g_error_free (error); } else { g_thread_unref (thread); } } } void clapper_server_mdns_remove (guint port) { g_mutex_lock (&mdns_lock); clapper_server_mdns_remove_port (mdns->entries, port); clapper_server_mdns_remove_port (mdns->pending_entries, port); if (mdns && mdns->entries->len == 0 && mdns->pending_entries->len == 0) { GST_DEBUG ("MDNS is going to stop"); while (mdns != NULL) g_cond_wait (&mdns_cond, &mdns_lock); } g_mutex_unlock (&mdns_lock); } clapper-0.8.0/src/lib/clapper/features/server/clapper-server-names-private.h000066400000000000000000000035121474353110700271360ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 G_BEGIN_DECLS #define CLAPPER_SERVER_WS_EVENT_STATE "state" #define CLAPPER_SERVER_WS_EVENT_POSITION "position" #define CLAPPER_SERVER_WS_EVENT_SPEED "speed" #define CLAPPER_SERVER_WS_EVENT_VOLUME "volume" #define CLAPPER_SERVER_WS_EVENT_MUTED "muted" #define CLAPPER_SERVER_WS_EVENT_UNMUTED "unmuted" #define CLAPPER_SERVER_WS_EVENT_PLAYED_INDEX "played_index" #define CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED "queue_changed" #define CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION "queue_progression" #define CLAPPER_SERVER_PLAYER_STATE_STOPPED "stopped" #define CLAPPER_SERVER_PLAYER_STATE_BUFFERING "buffering" #define CLAPPER_SERVER_PLAYER_STATE_PAUSED "paused" #define CLAPPER_SERVER_PLAYER_STATE_PLAYING "playing" #define CLAPPER_SERVER_QUEUE_PROGRESSION_NONE "none" #define CLAPPER_SERVER_QUEUE_PROGRESSION_CONSECUTIVE "consecutive" #define CLAPPER_SERVER_QUEUE_PROGRESSION_REPEAT_ITEM "repeat_item" #define CLAPPER_SERVER_QUEUE_PROGRESSION_CAROUSEL "carousel" #define CLAPPER_SERVER_QUEUE_PROGRESSION_SHUFFLE "shuffle" G_END_DECLS clapper-0.8.0/src/lib/clapper/features/server/clapper-server.c000066400000000000000000001042011474353110700243550ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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. */ /** * ClapperServer: * * An optional Server feature to add to the player. * * #ClapperServer is a feature that hosts a local server * providing an ability to both monitor and control playback * through WebSocket messages and HTTP requests. * * Use [const@Clapper.HAVE_SERVER] macro to check if Clapper API * was compiled with this feature. */ #include #include #include "clapper-server.h" #include "clapper-server-names-private.h" #include "clapper-server-json-private.h" #include "clapper-server-actions-private.h" #include "clapper-server-mdns-private.h" #include "clapper-enums.h" #include "clapper-player.h" #include "clapper-queue.h" #include "clapper-media-item.h" #include "clapper-utils-private.h" #include "../shared/clapper-shared-utils-private.h" #define CLAPPER_SERVER_WS_EVENT_STATE_MAKE(s) (CLAPPER_SERVER_WS_EVENT_STATE " " s) #define CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION_MAKE(s) (CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION " " s) #define DOUBLE_EVENT_MAX_SIZE 12 #define UINT_EVENT_MAX_SIZE 24 #define PORT_MAX 65535 #define DEFAULT_ENABLED FALSE #define DEFAULT_QUEUE_CONTROLLABLE FALSE #define GST_CAT_DEFAULT clapper_server_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperServer { ClapperFeature parent; SoupServer *server; GPtrArray *ws_connections; GPtrArray *items; ClapperMediaItem *played_item; guint played_index; guint position_uint; guint error_id; guint running_id; GSource *timeout_source; gint enabled; // atomic gboolean running; // only changed from features thread guint port; guint current_port; gint queue_controllable; // atomic }; typedef struct { ClapperServer *server; GError *error; } ClapperServerErrorData; enum { PROP_0, PROP_ENABLED, PROP_RUNNING, PROP_PORT, PROP_CURRENT_PORT, PROP_QUEUE_CONTROLLABLE, PROP_LAST }; enum { SIGNAL_ERROR, SIGNAL_LAST }; #define parent_class clapper_server_parent_class G_DEFINE_TYPE (ClapperServer, clapper_server, CLAPPER_TYPE_FEATURE); static GParamSpec *param_specs[PROP_LAST] = { NULL, }; static guint signals[SIGNAL_LAST] = { 0, }; static ClapperServerErrorData * clapper_server_error_data_new (ClapperServer *self, GError *error) { ClapperServerErrorData *data = g_new (ClapperServerErrorData, 1); GST_TRACE ("Created server error data: %p", data); data->server = gst_object_ref (self); data->error = error; return data; } static void clapper_server_error_data_free (ClapperServerErrorData *data) { GST_TRACE ("Freeing server error data: %p", data); gst_object_unref (data->server); g_clear_error (&data->error); g_free (data); } static void clapper_server_notify_port_and_running_on_main_idle (ClapperServer *self) { GST_OBJECT_LOCK (self); self->running_id = 0; GST_OBJECT_UNLOCK (self); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_CURRENT_PORT]); g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_RUNNING]); } static void clapper_server_send_error_on_main_idle (ClapperServerErrorData *data) { ClapperServer *self = data->server; GST_OBJECT_LOCK (self); self->error_id = 0; GST_OBJECT_UNLOCK (self); g_signal_emit (G_OBJECT (self), signals[SIGNAL_ERROR], 0, data->error); } static inline void _clear_delayed_queue_changed_timeout (ClapperServer *self) { if (self->timeout_source) { g_source_destroy (self->timeout_source); g_clear_pointer (&self->timeout_source, g_source_unref); } } static inline guint _find_current_port (ClapperServer *self) { GSList *uris_list, *list; guint found_port = 0; uris_list = soup_server_get_uris (self->server); for (list = uris_list; list != NULL; list = g_slist_next (list)) { GUri *uri = list->data; gint current_port = g_uri_get_port (uri); if (current_port > 0) { found_port = current_port; break; } } g_slist_free_full (uris_list, (GDestroyNotify) g_uri_unref); if (G_UNLIKELY (found_port == 0)) GST_ERROR_OBJECT (self, "Could not determine server current port"); return found_port; } static inline void _start_server (ClapperServer *self) { GError *error = NULL; guint current_port; /* We only edit this from feature thread, * so no lock needed here */ if (self->running) return; if (!soup_server_listen_all (self->server, clapper_server_get_port (self), SOUP_SERVER_LISTEN_IPV4_ONLY, &error)) { ClapperServerErrorData *data; GST_ERROR_OBJECT (self, "Error starting server: %s", GST_STR_NULL (error->message)); data = clapper_server_error_data_new (self, error); GST_OBJECT_LOCK (self); g_clear_handle_id (&self->error_id, g_source_remove); self->error_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) clapper_server_send_error_on_main_idle, data, (GDestroyNotify) clapper_server_error_data_free); GST_OBJECT_UNLOCK (self); return; } current_port = _find_current_port (self); GST_INFO_OBJECT (self, "Server started on port: %u", current_port); GST_OBJECT_LOCK (self); self->current_port = current_port; self->running = TRUE; g_clear_handle_id (&self->running_id, g_source_remove); self->running_id = g_idle_add_once ( (GSourceOnceFunc) clapper_server_notify_port_and_running_on_main_idle, self); GST_OBJECT_UNLOCK (self); clapper_server_mdns_serve ( gst_object_get_name (GST_OBJECT_CAST (self)), current_port); } static inline void _stop_server (ClapperServer *self) { guint current_port; if (!self->running) return; _clear_delayed_queue_changed_timeout (self); GST_OBJECT_LOCK (self); current_port = self->current_port; self->current_port = 0; self->running = FALSE; g_clear_handle_id (&self->running_id, g_source_remove); self->running_id = g_idle_add_once ( (GSourceOnceFunc) clapper_server_notify_port_and_running_on_main_idle, self); GST_OBJECT_UNLOCK (self); clapper_server_mdns_remove (current_port); /* Remove everyone */ if (self->ws_connections->len > 0) g_ptr_array_remove_range (self->ws_connections, 0, self->ws_connections->len); soup_server_disconnect (self->server); GST_INFO_OBJECT (self, "Server stopped listening"); } static void _clear_stored_queue (ClapperServer *self) { if (self->items->len > 0) g_ptr_array_remove_range (self->items, 0, self->items->len); gst_clear_object (&self->played_item); self->played_index = CLAPPER_QUEUE_INVALID_POSITION; } static void _ws_connection_message_cb (SoupWebsocketConnection *connection, gint type, GBytes *message, ClapperServer *self) { ClapperPlayer *player; ClapperServerAction action; const gchar *text; if (G_UNLIKELY (type != SOUP_WEBSOCKET_DATA_TEXT)) { GST_WARNING_OBJECT (self, "Received WS message with non-text data!"); return; } text = g_bytes_get_data (message, NULL); if (G_UNLIKELY (text == NULL)) { GST_WARNING_OBJECT (self, "Received WS message without any text!"); return; } action = clapper_server_actions_get_action (text); if (action == CLAPPER_SERVER_ACTION_INVALID) { GST_INFO_OBJECT (self, "Ignoring WS message with invalid action text"); return; } player = CLAPPER_PLAYER_CAST (gst_object_get_parent (GST_OBJECT_CAST (self))); if (G_UNLIKELY (player == NULL)) return; switch (action) { case CLAPPER_SERVER_ACTION_TOGGLE_PLAY: switch (clapper_player_get_state (player)) { case CLAPPER_PLAYER_STATE_STOPPED: case CLAPPER_PLAYER_STATE_PAUSED: clapper_player_play (player); break; case CLAPPER_PLAYER_STATE_PLAYING: clapper_player_pause (player); break; default: break; } break; case CLAPPER_SERVER_ACTION_PLAY: clapper_player_play (player); break; case CLAPPER_SERVER_ACTION_PAUSE: clapper_player_pause (player); break; case CLAPPER_SERVER_ACTION_STOP: clapper_player_stop (player); break; case CLAPPER_SERVER_ACTION_SEEK:{ gdouble position; if (clapper_server_actions_parse_seek (text, &position)) clapper_player_seek (player, position); break; } case CLAPPER_SERVER_ACTION_SET_SPEED:{ gdouble speed; if (clapper_server_actions_parse_set_speed (text, &speed)) clapper_player_set_speed (player, speed); break; } case CLAPPER_SERVER_ACTION_SET_VOLUME:{ gdouble volume; if (clapper_server_actions_parse_set_volume (text, &volume)) clapper_player_set_volume (player, volume); break; } case CLAPPER_SERVER_ACTION_SET_MUTE:{ gboolean mute; if (clapper_server_actions_parse_set_mute (text, &mute)) clapper_player_set_mute (player, mute); break; } case CLAPPER_SERVER_ACTION_SET_PROGRESSION:{ ClapperQueueProgressionMode mode; if (clapper_server_actions_parse_set_progression (text, &mode)) clapper_queue_set_progression_mode (clapper_player_get_queue (player), mode); break; } case CLAPPER_SERVER_ACTION_ADD:{ const gchar *uri; if (clapper_server_get_queue_controllable (self) && clapper_server_actions_parse_add (text, &uri)) { ClapperMediaItem *item = clapper_media_item_new (uri); clapper_utils_queue_append_on_main_sync (clapper_player_get_queue (player), item); gst_object_unref (item); } break; } case CLAPPER_SERVER_ACTION_INSERT:{ gchar *uri; guint after_id; if (clapper_server_get_queue_controllable (self) && clapper_server_actions_parse_insert (text, &uri, &after_id)) { ClapperMediaItem *item, *after_item = NULL; guint i; for (i = 0; i < self->items->len; ++i) { ClapperMediaItem *tmp_after_item = (ClapperMediaItem *) g_ptr_array_index (self->items, i); if (after_id == clapper_media_item_get_id (tmp_after_item)) { after_item = tmp_after_item; break; } } item = clapper_media_item_new (uri); clapper_utils_queue_insert_on_main_sync (clapper_player_get_queue (player), item, after_item); gst_object_unref (item); g_free (uri); } break; } case CLAPPER_SERVER_ACTION_SELECT:{ guint id; if (clapper_server_get_queue_controllable (self) && clapper_server_actions_parse_select (text, &id)) { guint i; for (i = 0; i < self->items->len; ++i) { ClapperMediaItem *item = (ClapperMediaItem *) g_ptr_array_index (self->items, i); if (id == clapper_media_item_get_id (item)) { clapper_queue_select_item (clapper_player_get_queue (player), item); break; } } } break; } case CLAPPER_SERVER_ACTION_REMOVE:{ guint id; if (clapper_server_get_queue_controllable (self) && clapper_server_actions_parse_remove (text, &id)) { guint i; for (i = 0; i < self->items->len; ++i) { ClapperMediaItem *item = (ClapperMediaItem *) g_ptr_array_index (self->items, i); if (id == clapper_media_item_get_id (item)) { clapper_utils_queue_remove_on_main_sync (clapper_player_get_queue (player), item); break; } } } break; } case CLAPPER_SERVER_ACTION_CLEAR: if (clapper_server_get_queue_controllable (self)) clapper_utils_queue_clear_on_main_sync (clapper_player_get_queue (player)); break; default: g_assert_not_reached (); break; } gst_object_unref (player); } static void _ws_connection_closed_cb (SoupWebsocketConnection *connection, ClapperServer *self) { GST_INFO_OBJECT (self, "WebSocket connection closed: %p", connection); g_ptr_array_remove (self->ws_connections, connection); } static void _request_cb (SoupServer *server, SoupServerMessage *msg, const gchar *path, GHashTable *query, ClapperServer *self) { gchar *data; if (!(data = clapper_server_json_build_complete (self, self->played_item, self->played_index, self->items))) { soup_server_message_set_status (msg, SOUP_STATUS_SERVICE_UNAVAILABLE, NULL); return; } soup_server_message_set_status (msg, SOUP_STATUS_OK, NULL); soup_server_message_set_response (msg, "application/json", SOUP_MEMORY_TAKE, data, strlen (data)); } static void _websocket_connection_cb (SoupServer *server, SoupServerMessage *msg, const gchar *path, SoupWebsocketConnection *connection, ClapperServer *self) { GST_INFO_OBJECT (self, "New WebSocket connection: %p", connection); g_signal_connect (connection, "message", G_CALLBACK (_ws_connection_message_cb), self); g_signal_connect (connection, "closed", G_CALLBACK (_ws_connection_closed_cb), self); g_ptr_array_add (self->ws_connections, g_object_ref (connection)); } static void clapper_server_send_ws_message (ClapperServer *self, const gchar *text) { guint i; GST_LOG_OBJECT (self, "Sending WS message to clients: \"%s\"", text); for (i = 0; i < self->ws_connections->len; ++i) { SoupWebsocketConnection *connection = g_ptr_array_index (self->ws_connections, i); if (soup_websocket_connection_get_state (connection) != SOUP_WEBSOCKET_STATE_OPEN) continue; soup_websocket_connection_send_text (connection, text); } } static inline void clapper_server_send_ws_double_event (ClapperServer *self, const gchar *event, gdouble val) { gchar text[DOUBLE_EVENT_MAX_SIZE]; g_snprintf (text, sizeof (text), "%s %.2lf", event, val); clapper_server_send_ws_message (self, text); } static inline void clapper_server_send_ws_uint_event (ClapperServer *self, const gchar *event, guint val) { gchar text[UINT_EVENT_MAX_SIZE]; g_snprintf (text, sizeof (text), "%s %u", event, val); clapper_server_send_ws_message (self, text); } static gboolean _send_ws_queue_changed_delayed_cb (ClapperServer *self) { GST_DEBUG_OBJECT (self, "Delayed queue changed handler reached"); _clear_delayed_queue_changed_timeout (self); clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED); return G_SOURCE_REMOVE; } static void clapper_server_state_changed (ClapperFeature *feature, ClapperPlayerState state) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "State changed to: %i", state); if (!self->running || self->ws_connections->len == 0) return; switch (state) { case CLAPPER_PLAYER_STATE_PLAYING: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_STATE_MAKE (CLAPPER_SERVER_PLAYER_STATE_PLAYING)); break; case CLAPPER_PLAYER_STATE_PAUSED: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_STATE_MAKE (CLAPPER_SERVER_PLAYER_STATE_PAUSED)); break; case CLAPPER_PLAYER_STATE_BUFFERING: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_STATE_MAKE (CLAPPER_SERVER_PLAYER_STATE_BUFFERING)); break; case CLAPPER_PLAYER_STATE_STOPPED: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_STATE_MAKE (CLAPPER_SERVER_PLAYER_STATE_STOPPED)); break; default: g_assert_not_reached (); break; } } static void clapper_server_position_changed (ClapperFeature *feature, gdouble position) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); /* Limit to seconds */ if (ABS (self->position_uint - position) < 1) return; self->position_uint = (guint) position; GST_LOG_OBJECT (self, "Position changed to: %u", self->position_uint); if (self->running && self->ws_connections->len > 0) clapper_server_send_ws_uint_event (self, CLAPPER_SERVER_WS_EVENT_POSITION, position); } static void clapper_server_speed_changed (ClapperFeature *feature, gdouble speed) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_LOG_OBJECT (self, "Speed changed to: %lf", speed); if (self->running && self->ws_connections->len > 0) clapper_server_send_ws_double_event (self, CLAPPER_SERVER_WS_EVENT_SPEED, speed); } static void clapper_server_volume_changed (ClapperFeature *feature, gdouble volume) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_LOG_OBJECT (self, "Volume changed to: %lf", volume); if (self->running && self->ws_connections->len > 0) clapper_server_send_ws_double_event (self, CLAPPER_SERVER_WS_EVENT_VOLUME, volume); } static void clapper_server_mute_changed (ClapperFeature *feature, gboolean mute) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); if (self->running && self->ws_connections->len > 0) { clapper_server_send_ws_message (self, (mute) ? CLAPPER_SERVER_WS_EVENT_MUTED : CLAPPER_SERVER_WS_EVENT_UNMUTED); } } static void clapper_server_played_item_changed (ClapperFeature *feature, ClapperMediaItem *item) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Played item changed to: %" GST_PTR_FORMAT, item); gst_object_replace ((GstObject **) &self->played_item, GST_OBJECT_CAST (item)); if (!g_ptr_array_find (self->items, self->played_item, &self->played_index)) self->played_index = CLAPPER_QUEUE_INVALID_POSITION; if (self->running && self->ws_connections->len > 0) clapper_server_send_ws_uint_event (self, CLAPPER_SERVER_WS_EVENT_PLAYED_INDEX, self->played_index); } static void clapper_server_item_updated (ClapperFeature *feature, ClapperMediaItem *item) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_LOG_OBJECT (self, "Item updated: %" GST_PTR_FORMAT, item); if (!self->running || self->ws_connections->len == 0) return; /* Clear timeout, since we will either send * immediately or set same timeout again */ _clear_delayed_queue_changed_timeout (self); if (item != self->played_item) { /* Happens once per item when discovered, so send immediately */ clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED); } else { /* Current item can be updated very often (when bitrate changes) * so reduce amount of work here by adding delay */ self->timeout_source = clapper_shared_utils_context_timeout_add_full ( g_main_context_get_thread_default (), G_PRIORITY_DEFAULT_IDLE, 1000, (GSourceFunc) _send_ws_queue_changed_delayed_cb, self, NULL); } } static void clapper_server_queue_item_added (ClapperFeature *feature, ClapperMediaItem *item, guint index) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Queue item added %" GST_PTR_FORMAT, item); g_ptr_array_insert (self->items, index, gst_object_ref (item)); if (self->running && self->ws_connections->len > 0) { _clear_delayed_queue_changed_timeout (self); clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED); } } static void clapper_server_queue_item_removed (ClapperFeature *feature, ClapperMediaItem *item, guint index) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Queue item removed %" GST_PTR_FORMAT, item); if (item == self->played_item) { gst_clear_object (&self->played_item); self->played_index = CLAPPER_QUEUE_INVALID_POSITION; } g_ptr_array_remove_index (self->items, index); if (self->running && self->ws_connections->len > 0) { _clear_delayed_queue_changed_timeout (self); clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED); } } static void clapper_server_queue_item_repositioned (ClapperFeature *feature, guint before, guint after) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); ClapperMediaItem *item; GST_DEBUG_OBJECT (self, "Queue item repositioned: %u -> %u", before, after); item = (ClapperMediaItem *) g_ptr_array_steal_index (self->items, before); g_ptr_array_insert (self->items, after, item); if (self->running && self->ws_connections->len > 0) { _clear_delayed_queue_changed_timeout (self); clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED); } } static void clapper_server_queue_cleared (ClapperFeature *feature) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Queue cleared"); _clear_stored_queue (self); if (self->running && self->ws_connections->len > 0) { _clear_delayed_queue_changed_timeout (self); clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED); } } static void clapper_server_queue_progression_changed (ClapperFeature *feature, ClapperQueueProgressionMode mode) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Queue progression changed to: %i", mode); if (!self->running || self->ws_connections->len == 0) return; switch (mode) { case CLAPPER_QUEUE_PROGRESSION_NONE: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION_MAKE (CLAPPER_SERVER_QUEUE_PROGRESSION_NONE)); break; case CLAPPER_QUEUE_PROGRESSION_CONSECUTIVE: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION_MAKE (CLAPPER_SERVER_QUEUE_PROGRESSION_CONSECUTIVE)); break; case CLAPPER_QUEUE_PROGRESSION_REPEAT_ITEM: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION_MAKE (CLAPPER_SERVER_QUEUE_PROGRESSION_REPEAT_ITEM)); break; case CLAPPER_QUEUE_PROGRESSION_CAROUSEL: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION_MAKE (CLAPPER_SERVER_QUEUE_PROGRESSION_CAROUSEL)); break; case CLAPPER_QUEUE_PROGRESSION_SHUFFLE: clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_PROGRESSION_MAKE (CLAPPER_SERVER_QUEUE_PROGRESSION_SHUFFLE)); break; default: g_assert_not_reached (); break; } } static gboolean clapper_server_prepare (ClapperFeature *feature) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Prepare"); self->server = soup_server_new ( "server-header", "clapper-server", NULL); soup_server_add_handler (self->server, "/", (SoupServerCallback) _request_cb, self, NULL); soup_server_add_websocket_handler (self->server, "/websocket", NULL, NULL, (SoupServerWebsocketCallback) _websocket_connection_cb, self, NULL); if (clapper_server_get_enabled (self)) _start_server (self); return TRUE; } static gboolean clapper_server_unprepare (ClapperFeature *feature) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Unprepare"); _stop_server (self); _clear_stored_queue (self); g_clear_object (&self->server); return TRUE; } static void clapper_server_property_changed (ClapperFeature *feature, GParamSpec *pspec) { ClapperServer *self = CLAPPER_SERVER_CAST (feature); GST_DEBUG_OBJECT (self, "Property changed: \"%s\"", g_param_spec_get_name (pspec)); if (pspec == param_specs[PROP_ENABLED]) { if (clapper_server_get_enabled (self)) _start_server (self); else _stop_server (self); } else if (pspec == param_specs[PROP_QUEUE_CONTROLLABLE]) { _clear_delayed_queue_changed_timeout (self); clapper_server_send_ws_message (self, CLAPPER_SERVER_WS_EVENT_QUEUE_CHANGED); } } /** * clapper_server_new: * * Creates a new #ClapperServer instance. * * Returns: (transfer full): a new #ClapperServer instance. */ ClapperServer * clapper_server_new (void) { ClapperServer *server = g_object_new (CLAPPER_TYPE_SERVER, NULL); gst_object_ref_sink (server); return server; } /** * clapper_server_set_enabled: * @server: a #ClapperServer * @enabled: if #ClapperServer should run * * Set whether #ClapperServer should be running. * * Note that server feature will run only after being added to the player. * It can be however set to enabled earlier. If server was already added, * changing this property allows to start/stop server at any time. * * To be notified when server is actually running/stopped after being enabled/disabled, * you can listen for changes to [property@Clapper.Server:running] property. */ void clapper_server_set_enabled (ClapperServer *self, gboolean enabled) { gboolean prev_enabled; g_return_if_fail (CLAPPER_IS_SERVER (self)); prev_enabled = (gboolean) g_atomic_int_exchange (&self->enabled, (gint) enabled); if (prev_enabled != enabled) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_ENABLED]); } /** * clapper_server_get_enabled: * @server: a #ClapperServer * * Get whether #ClapperServer is set to be running. * * Returns: %TRUE if enabled, %FALSE otherwise. */ gboolean clapper_server_get_enabled (ClapperServer *self) { g_return_val_if_fail (CLAPPER_IS_SERVER (self), FALSE); return (gboolean) g_atomic_int_get (&self->enabled); } /** * clapper_server_get_running: * @server: a #ClapperServer * * Get whether #ClapperServer is currently running. * * Returns: %TRUE if running, %FALSE otherwise. */ gboolean clapper_server_get_running (ClapperServer *self) { gboolean running; g_return_val_if_fail (CLAPPER_IS_SERVER (self), FALSE); GST_OBJECT_LOCK (self); running = self->running; GST_OBJECT_UNLOCK (self); return running; } /** * clapper_server_set_port: * @server: a #ClapperServer * @port: a port number or 0 for random free port * * Set server listening port. */ void clapper_server_set_port (ClapperServer *self, guint port) { gboolean changed; g_return_if_fail (CLAPPER_IS_SERVER (self)); g_return_if_fail (port <= PORT_MAX); GST_OBJECT_LOCK (self); if ((changed = (port != self->port))) self->port = port; GST_OBJECT_UNLOCK (self); if (changed) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_PORT]); } /** * clapper_server_get_port: * @server: a #ClapperServer * * Get requested server listening port. * * If you want to know the port server is currently listening on, * use [method@Clapper.Server.get_current_port] instead. * * Returns: Requested listening port or 0 when using random port. */ guint clapper_server_get_port (ClapperServer *self) { guint port; g_return_val_if_fail (CLAPPER_IS_SERVER (self), 0); GST_OBJECT_LOCK (self); port = self->port; GST_OBJECT_UNLOCK (self); return port; } /** * clapper_server_get_current_port: * @server: a #ClapperServer * * Get port on which server is currently listening on. * * Returns: Current listening port or 0 if server is not listening. */ guint clapper_server_get_current_port (ClapperServer *self) { guint current_port; g_return_val_if_fail (CLAPPER_IS_SERVER (self), 0); GST_OBJECT_LOCK (self); current_port = self->current_port; GST_OBJECT_UNLOCK (self); return current_port; } /** * clapper_server_set_queue_controllable: * @server: a #ClapperServer * @controllable: if #ClapperQueue should be controllable * * Set whether remote @server clients can control [class@Clapper.Queue]. * * This includes ability to add/remove items from the queue and selecting * current item for playback remotely using WebSocket messages. * * You probably want to keep this disabled if your application * is supposed to manage what is played now and not WebSocket client. */ void clapper_server_set_queue_controllable (ClapperServer *self, gboolean controllable) { gboolean prev_controllable; g_return_if_fail (CLAPPER_IS_SERVER (self)); prev_controllable = (gboolean) g_atomic_int_exchange (&self->queue_controllable, (gint) controllable); if (prev_controllable != controllable) g_object_notify_by_pspec (G_OBJECT (self), param_specs[PROP_QUEUE_CONTROLLABLE]); } /** * clapper_server_get_queue_controllable: * @server: a #ClapperServer * * Get whether remote @server clients can control [class@Clapper.Queue]. * * Returns: %TRUE if control over #ClapperQueue is allowed, %FALSE otherwise. */ gboolean clapper_server_get_queue_controllable (ClapperServer *self) { g_return_val_if_fail (CLAPPER_IS_SERVER (self), FALSE); return (gboolean) g_atomic_int_get (&self->queue_controllable); } static void clapper_server_init (ClapperServer *self) { self->items = g_ptr_array_new_with_free_func ((GDestroyNotify) gst_object_unref); self->ws_connections = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); self->played_index = CLAPPER_QUEUE_INVALID_POSITION; self->position_uint = G_MAXUINT; g_atomic_int_set (&self->enabled, (gint) DEFAULT_ENABLED); g_atomic_int_set (&self->queue_controllable, (gint) DEFAULT_QUEUE_CONTROLLABLE); } static void clapper_server_dispose (GObject *object) { ClapperServer *self = CLAPPER_SERVER_CAST (object); GST_OBJECT_LOCK (self); g_clear_handle_id (&self->error_id, g_source_remove); g_clear_handle_id (&self->running_id, g_source_remove); GST_OBJECT_UNLOCK (self); G_OBJECT_CLASS (parent_class)->dispose (object); } static void clapper_server_finalize (GObject *object) { ClapperServer *self = CLAPPER_SERVER_CAST (object); g_ptr_array_unref (self->ws_connections); g_ptr_array_unref (self->items); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_server_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ClapperServer *self = CLAPPER_SERVER_CAST (object); switch (prop_id) { case PROP_ENABLED: clapper_server_set_enabled (self, g_value_get_boolean (value)); break; case PROP_PORT: clapper_server_set_port (self, g_value_get_uint (value)); break; case PROP_QUEUE_CONTROLLABLE: clapper_server_set_queue_controllable (self, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_server_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ClapperServer *self = CLAPPER_SERVER_CAST (object); switch (prop_id) { case PROP_ENABLED: g_value_set_boolean (value, clapper_server_get_enabled (self)); break; case PROP_RUNNING: g_value_set_boolean (value, clapper_server_get_running (self)); break; case PROP_PORT: g_value_set_uint (value, clapper_server_get_port (self)); break; case PROP_CURRENT_PORT: g_value_set_uint (value, clapper_server_get_current_port (self)); break; case PROP_QUEUE_CONTROLLABLE: g_value_set_boolean (value, clapper_server_get_queue_controllable (self)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void clapper_server_class_init (ClapperServerClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperFeatureClass *feature_class = (ClapperFeatureClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperserver", 0, "Clapper Server"); clapper_server_mdns_debug_init (); gobject_class->get_property = clapper_server_get_property; gobject_class->set_property = clapper_server_set_property; gobject_class->dispose = clapper_server_dispose; gobject_class->finalize = clapper_server_finalize; feature_class->prepare = clapper_server_prepare; feature_class->unprepare = clapper_server_unprepare; feature_class->property_changed = clapper_server_property_changed; feature_class->state_changed = clapper_server_state_changed; feature_class->position_changed = clapper_server_position_changed; feature_class->speed_changed = clapper_server_speed_changed; feature_class->volume_changed = clapper_server_volume_changed; feature_class->mute_changed = clapper_server_mute_changed; feature_class->played_item_changed = clapper_server_played_item_changed; feature_class->item_updated = clapper_server_item_updated; feature_class->queue_item_added = clapper_server_queue_item_added; feature_class->queue_item_removed = clapper_server_queue_item_removed; feature_class->queue_item_repositioned = clapper_server_queue_item_repositioned; feature_class->queue_cleared = clapper_server_queue_cleared; feature_class->queue_progression_changed = clapper_server_queue_progression_changed; /** * ClapperServer:enabled: * * Whether server is enabled. */ param_specs[PROP_ENABLED] = g_param_spec_boolean ("enabled", NULL, NULL, DEFAULT_ENABLED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperServer:running: * * Whether server is currently running. */ param_specs[PROP_RUNNING] = g_param_spec_boolean ("running", NULL, NULL, FALSE, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperServer:port: * * Port to listen on or 0 for using random unused port. */ param_specs[PROP_PORT] = g_param_spec_uint ("port", NULL, NULL, 0, PORT_MAX, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperServer:current-port: * * Port on which server is currently listening on or 0 if not listening. */ param_specs[PROP_CURRENT_PORT] = g_param_spec_uint ("current-port", NULL, NULL, 0, PORT_MAX, 0, G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperServer:queue-controllable: * * Whether remote server clients can control #ClapperQueue. */ param_specs[PROP_QUEUE_CONTROLLABLE] = g_param_spec_boolean ("queue-controllable", NULL, NULL, DEFAULT_QUEUE_CONTROLLABLE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * ClapperServer::error: * @server: a #ClapperServer * @error: a #GError * * Error signal when server could not start. * This will be emitted from application main thread. */ signals[SIGNAL_ERROR] = g_signal_new ("error", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_ERROR); g_object_class_install_properties (gobject_class, PROP_LAST, param_specs); } clapper-0.8.0/src/lib/clapper/features/server/clapper-server.h000066400000000000000000000037601474353110700243720ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 can be included directly." #endif #include #include #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_SERVER (clapper_server_get_type()) #define CLAPPER_SERVER_CAST(obj) ((ClapperServer *)(obj)) CLAPPER_API G_DECLARE_FINAL_TYPE (ClapperServer, clapper_server, CLAPPER, SERVER, ClapperFeature) CLAPPER_API ClapperServer * clapper_server_new (void); CLAPPER_API void clapper_server_set_enabled (ClapperServer *server, gboolean enabled); CLAPPER_API gboolean clapper_server_get_enabled (ClapperServer *server); CLAPPER_API gboolean clapper_server_get_running (ClapperServer *server); CLAPPER_API void clapper_server_set_port (ClapperServer *server, guint port); CLAPPER_API guint clapper_server_get_port (ClapperServer *server); CLAPPER_API guint clapper_server_get_current_port (ClapperServer *server); CLAPPER_API void clapper_server_set_queue_controllable (ClapperServer *server, gboolean controllable); CLAPPER_API gboolean clapper_server_get_queue_controllable (ClapperServer *server); G_END_DECLS clapper-0.8.0/src/lib/clapper/features/server/meson.build000066400000000000000000000016241474353110700234260ustar00rootroot00000000000000feature_option = get_option(feature_name) if feature_option.disabled() subdir_done() endif feature_deps = [ dependency('libsoup-3.0', required: false), dependency('microdns', version: '>= 0.2.0', required: false), ] foreach dep : feature_deps if not dep.found() if feature_option.enabled() error('@0@ feature was enabled, but required dependencies were not found'.format(feature_name)) endif subdir_done() endif endforeach clapper_features_headers += [ 'features/server/clapper-server.h', ] clapper_features_sources += [ 'features/server/clapper-server.c', 'features/server/clapper-server-json.c', 'features/server/clapper-server-actions.c', 'features/server/clapper-server-mdns.c', ] clapper_features_deps += feature_deps install_headers('clapper-server.h', install_dir: join_paths(clapper_headers_dir, 'features', 'server'), ) clapper_available_features += feature_name clapper-0.8.0/src/lib/clapper/gst/000077500000000000000000000000001474353110700167325ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper/gst/clapper-enhancer-director-private.h000066400000000000000000000030311474353110700255700ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include #include "../clapper-threaded-object.h" #include "../clapper-harvest.h" G_BEGIN_DECLS #define CLAPPER_TYPE_ENHANCER_DIRECTOR (clapper_enhancer_director_get_type()) #define CLAPPER_ENHANCER_DIRECTOR_CAST(obj) ((ClapperEnhancerDirector *)(obj)) G_GNUC_INTERNAL G_DECLARE_FINAL_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER, ENHANCER_DIRECTOR, ClapperThreadedObject) 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); G_END_DECLS clapper-0.8.0/src/lib/clapper/gst/clapper-enhancer-director.c000066400000000000000000000117101474353110700241160ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-enhancer-director-private.h" #include "../clapper-enhancers-loader-private.h" #include "../clapper-extractable-private.h" #include "../clapper-harvest-private.h" #include "../../shared/clapper-shared-utils-private.h" #define GST_CAT_DEFAULT clapper_enhancer_director_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperEnhancerDirector { ClapperThreadedObject parent; }; #define parent_class clapper_enhancer_director_parent_class G_DEFINE_TYPE (ClapperEnhancerDirector, clapper_enhancer_director, CLAPPER_TYPE_THREADED_OBJECT); typedef struct { GUri *uri; GCancellable *cancellable; GError **error; } ClapperEnhancerDirectorData; static gpointer clapper_enhancer_director_extract_in_thread (ClapperEnhancerDirectorData *data) { ClapperExtractable *extractable = NULL; ClapperHarvest *harvest = clapper_harvest_new (); gboolean success = FALSE, cached = FALSE; /* Cancelled during thread switching */ if (g_cancellable_is_cancelled (data->cancellable)) goto finish; /* TODO: Cache lookup */ if (cached) { // success = fill harvest from cache goto finish; } extractable = CLAPPER_EXTRACTABLE_CAST (clapper_enhancers_loader_create_enhancer_for_uri ( CLAPPER_TYPE_EXTRACTABLE, data->uri)); /* Check just before extract */ if (g_cancellable_is_cancelled (data->cancellable)) goto finish; 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 */ } } else { gst_clear_object (&harvest); /* Ensure we have some error set on failure */ if (*data->error == NULL) { const gchar *err_msg = (g_cancellable_is_cancelled (data->cancellable)) ? "Extraction was cancelled" : "Extraction failed"; g_set_error (data->error, GST_RESOURCE_ERROR, GST_RESOURCE_ERROR_FAILED, "%s", err_msg); } } gst_clear_object (&extractable); return harvest; } /* * clapper_enhancer_director_new: * * Returns: (transfer full): a new #ClapperEnhancerDirector instance. */ ClapperEnhancerDirector * clapper_enhancer_director_new (void) { ClapperEnhancerDirector *director; director = g_object_new (CLAPPER_TYPE_ENHANCER_DIRECTOR, NULL); gst_object_ref_sink (director); return director; } ClapperHarvest * clapper_enhancer_director_extract (ClapperEnhancerDirector *self, GUri *uri, GCancellable *cancellable, GError **error) { ClapperEnhancerDirectorData *data = g_new (ClapperEnhancerDirectorData, 1); 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)), (GThreadFunc) clapper_enhancer_director_extract_in_thread, data, (GDestroyNotify) g_free)); } static void clapper_enhancer_director_thread_start (ClapperThreadedObject *threaded_object) { GST_TRACE_OBJECT (threaded_object, "Enhancer director thread start"); } static void clapper_enhancer_director_thread_stop (ClapperThreadedObject *threaded_object) { GST_TRACE_OBJECT (threaded_object, "Enhancer director thread stop"); } static void clapper_enhancer_director_init (ClapperEnhancerDirector *self) { } static void clapper_enhancer_director_finalize (GObject *object) { GST_TRACE_OBJECT (object, "Finalize"); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_enhancer_director_class_init (ClapperEnhancerDirectorClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; ClapperThreadedObjectClass *threaded_object = (ClapperThreadedObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperenhancerdirector", 0, "Clapper Enhancer Director"); gobject_class->finalize = clapper_enhancer_director_finalize; threaded_object->thread_start = clapper_enhancer_director_thread_start; threaded_object->thread_stop = clapper_enhancer_director_thread_stop; } clapper-0.8.0/src/lib/clapper/gst/clapper-enhancer-src-private.h000066400000000000000000000023551474353110700245540ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_ENHANCER_SRC (clapper_enhancer_src_get_type()) #define CLAPPER_ENHANCER_SRC_CAST(obj) ((ClapperEnhancerSrc *)(obj)) G_GNUC_INTERNAL G_DECLARE_FINAL_TYPE (ClapperEnhancerSrc, clapper_enhancer_src, CLAPPER, ENHANCER_SRC, GstPushSrc) GST_ELEMENT_REGISTER_DECLARE (clapperenhancersrc) G_END_DECLS clapper-0.8.0/src/lib/clapper/gst/clapper-enhancer-src.c000066400000000000000000000325251474353110700231010ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 "); } clapper-0.8.0/src/lib/clapper/gst/clapper-plugin-private.h000066400000000000000000000017421474353110700235010ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include G_BEGIN_DECLS G_GNUC_INTERNAL gboolean clapper_gst_plugin_init (GstPlugin *plugin); G_END_DECLS clapper-0.8.0/src/lib/clapper/gst/clapper-plugin.c000066400000000000000000000033321474353110700220210ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #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-uri-list-demux-private.h" gboolean clapper_gst_plugin_init (GstPlugin *plugin) { gboolean res = FALSE; #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); /* Avoid registering an URI handler without schemes */ if (clapper_enhancers_loader_has_enhancers (CLAPPER_TYPE_EXTRACTABLE)) res |= GST_ELEMENT_REGISTER (clapperenhancersrc, plugin); #endif res |= GST_ELEMENT_REGISTER (clapperurilistdemux, plugin); return res; } clapper-0.8.0/src/lib/clapper/gst/clapper-uri-list-demux-private.h000066400000000000000000000023551474353110700250740ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include #include G_BEGIN_DECLS #define CLAPPER_TYPE_URI_LIST_DEMUX (clapper_uri_list_demux_get_type()) #define CLAPPER_URI_LIST_DEMUX_CAST(obj) ((ClapperUriListDemux *)(obj)) G_GNUC_INTERNAL G_DECLARE_FINAL_TYPE (ClapperUriListDemux, clapper_uri_list_demux, CLAPPER, URI_LIST_DEMUX, GstBin) GST_ELEMENT_REGISTER_DECLARE (clapperurilistdemux) G_END_DECLS clapper-0.8.0/src/lib/clapper/gst/clapper-uri-list-demux.c000066400000000000000000000324311474353110700234150ustar00rootroot00000000000000/* Clapper Playback Library * Copyright (C) 2024 Rafał Dzięgiel * * 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 #include "clapper-uri-list-demux-private.h" #define GST_CAT_DEFAULT clapper_uri_list_demux_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); struct _ClapperUriListDemux { GstBin parent; GMutex lock; GstAdapter *input_adapter; GstElement *uri_handler; GstElement *typefind; GstPad *typefind_src; GstStructure *http_headers; }; static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("text/uri-list, source=(string)clapper-harvest")); static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_SOMETIMES, GST_STATIC_CAPS_ANY); #define parent_class clapper_uri_list_demux_parent_class G_DEFINE_TYPE (ClapperUriListDemux, clapper_uri_list_demux, GST_TYPE_BIN); GST_ELEMENT_REGISTER_DEFINE (clapperurilistdemux, "clapperurilistdemux", 512, CLAPPER_TYPE_URI_LIST_DEMUX); static void _set_property (GstObject *obj, const gchar *prop_name, gpointer value) { g_object_set (G_OBJECT (obj), prop_name, value, NULL); if (gst_debug_category_get_threshold (GST_CAT_DEFAULT) >= GST_LEVEL_DEBUG) { gchar *el_name; el_name = gst_object_get_name (obj); GST_DEBUG ("Set %s %s", el_name, prop_name); g_free (el_name); } } static gboolean configure_deep_element (GQuark field_id, const GValue *value, GstElement *child) { GObjectClass *gobject_class; const GstStructure *substructure; if (!GST_VALUE_HOLDS_STRUCTURE (value)) return TRUE; substructure = gst_value_get_structure (value); if (!gst_structure_has_name (substructure, "request-headers")) return TRUE; gobject_class = G_OBJECT_GET_CLASS (child); if (g_object_class_find_property (gobject_class, "user-agent")) { const gchar *ua; if ((ua = gst_structure_get_string (substructure, "User-Agent"))) _set_property (GST_OBJECT_CAST (child), "user-agent", (gchar *) ua); } if (g_object_class_find_property (gobject_class, "extra-headers")) { GstStructure *extra_headers; extra_headers = gst_structure_copy (substructure); gst_structure_set_name (extra_headers, "extra-headers"); gst_structure_remove_field (extra_headers, "User-Agent"); _set_property (GST_OBJECT_CAST (child), "extra-headers", extra_headers); gst_structure_free (extra_headers); } return TRUE; } static void clapper_uri_list_demux_deep_element_added (GstBin *bin, GstBin *sub_bin, GstElement *child) { if (GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_SOURCE)) { ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (bin); g_mutex_lock (&self->lock); if (self->http_headers) { gst_structure_foreach (self->http_headers, (GstStructureForeachFunc) configure_deep_element, child); } g_mutex_unlock (&self->lock); } } static gboolean remove_sometimes_pad_cb (GstElement *element, GstPad *pad, ClapperUriListDemux *self) { GstPadTemplate *template = gst_pad_get_pad_template (pad); GstPadPresence presence = GST_PAD_TEMPLATE_PRESENCE (template); gst_object_unref (template); if (presence == GST_PAD_SOMETIMES) { GST_DEBUG_OBJECT (self, "Removing src pad"); gst_pad_set_active (pad, FALSE); if (G_UNLIKELY (!gst_element_remove_pad (element, pad))) g_critical ("Failed to remove pad from bin"); } return TRUE; } static void clapper_uri_list_demux_reset (ClapperUriListDemux *self) { GstElement *element = GST_ELEMENT_CAST (self); gst_element_foreach_pad (element, (GstElementForeachPadFunc) remove_sometimes_pad_cb, NULL); } static GstStateChangeReturn clapper_uri_list_demux_change_state (GstElement *element, GstStateChange transition) { ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (element); GstStateChangeReturn ret; ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); if (ret == GST_STATE_CHANGE_FAILURE) return ret; switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: clapper_uri_list_demux_reset (self); break; default: break; } return ret; } static gboolean _feature_filter (GstPluginFeature *feature, const gchar *search_proto) { GstElementFactory *factory; const gchar *const *protocols; const gchar *feature_name; guint i; if (!GST_IS_ELEMENT_FACTORY (feature)) return FALSE; factory = GST_ELEMENT_FACTORY_CAST (feature); if (gst_element_factory_get_uri_type (factory) != GST_URI_SRC) return FALSE; feature_name = gst_plugin_feature_get_name (feature); /* Do not loop endlessly creating our own sources and demuxers */ if (!feature_name || strcmp (feature_name, "clapperenhancersrc") == 0) return FALSE; protocols = gst_element_factory_get_uri_protocols (factory); if (protocols) { for (i = 0; protocols[i]; ++i) { if (g_ascii_strcasecmp (protocols[i], search_proto) == 0) return TRUE; } } return FALSE; } static GstElement * _make_handler_for_uri (ClapperUriListDemux *self, const gchar *uri) { GstElement *element = NULL; GList *factories, *f; gchar *protocol; if (!gst_uri_is_valid (uri)) { GST_ERROR_OBJECT (self, "Cannot create handler for invalid URI: \"%s\"", uri); return NULL; } protocol = gst_uri_get_protocol (uri); factories = gst_registry_feature_filter (gst_registry_get (), (GstPluginFeatureFilter) _feature_filter, FALSE, protocol); g_free (protocol); factories = g_list_sort (factories, (GCompareFunc) gst_plugin_feature_rank_compare_func); for (f = factories; f; f = g_list_next (f)) { GstElementFactory *factory = f->data; if ((element = gst_element_factory_create (factory, NULL)) && gst_uri_handler_set_uri (GST_URI_HANDLER (element), uri, NULL)) break; gst_clear_object (&element); } gst_plugin_feature_list_free (factories); GST_DEBUG_OBJECT (self, "Created URI handler: %s", GST_OBJECT_NAME (element)); return element; } static gboolean clapper_uri_list_demux_process_buffer (ClapperUriListDemux *self, GstBuffer *buffer) { GstMemory *mem; GstMapInfo info; mem = gst_buffer_peek_memory (buffer, 0); if (mem && gst_memory_map (mem, &info, GST_MAP_READ)) { GstPad *uri_handler_src, *typefind_sink, *src_ghostpad; GstPadLinkReturn pad_link_ret; GST_DEBUG_OBJECT (self, "Stream URI: %s", (const gchar *) info.data); if (self->uri_handler) { GST_DEBUG_OBJECT (self, "Trying to reuse existing URI handler"); if (gst_uri_handler_set_uri (GST_URI_HANDLER (self->uri_handler), (const gchar *) info.data, NULL)) { GST_DEBUG_OBJECT (self, "Reused existing URI handler"); } else { GST_DEBUG_OBJECT (self, "Could not reuse existing URI handler"); if (self->typefind_src) { gst_element_remove_pad (GST_ELEMENT_CAST (self), self->typefind_src); gst_clear_object (&self->typefind_src); } gst_bin_remove (GST_BIN_CAST (self), self->uri_handler); gst_bin_remove (GST_BIN_CAST (self), self->typefind); self->uri_handler = NULL; self->typefind = NULL; } } if (!self->uri_handler) { GST_DEBUG_OBJECT (self, "Creating new URI handler element"); self->uri_handler = _make_handler_for_uri (self, (const gchar *) info.data); if (G_UNLIKELY (!self->uri_handler)) { GST_ERROR_OBJECT (self, "Could not create URI handler element"); GST_ELEMENT_ERROR (self, CORE, MISSING_PLUGIN, ("Missing plugin to handle URI: %s", info.data), (NULL)); gst_memory_unmap (mem, &info); return FALSE; } gst_bin_add (GST_BIN_CAST (self), self->uri_handler); self->typefind = gst_element_factory_make ("typefind", NULL); gst_bin_add (GST_BIN_CAST (self), self->typefind); uri_handler_src = gst_element_get_static_pad (self->uri_handler, "src"); typefind_sink = gst_element_get_static_pad (self->typefind, "sink"); pad_link_ret = gst_pad_link_full (uri_handler_src, typefind_sink, GST_PAD_LINK_CHECK_NOTHING); if (pad_link_ret != GST_PAD_LINK_OK) g_critical ("Failed to link bin elements"); g_object_unref (uri_handler_src); g_object_unref (typefind_sink); self->typefind_src = gst_element_get_static_pad (self->typefind, "src"); src_ghostpad = gst_ghost_pad_new_from_template ("src", self->typefind_src, gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (self), "src")); gst_pad_set_active (src_ghostpad, TRUE); if (!gst_element_add_pad (GST_ELEMENT_CAST (self), src_ghostpad)) { g_critical ("Failed to add source pad to bin"); } else { GST_DEBUG_OBJECT (self, "Added src pad, signalling \"no-more-pads\""); gst_element_no_more_pads (GST_ELEMENT_CAST (self)); } } gst_memory_unmap (mem, &info); gst_element_sync_state_with_parent (self->typefind); gst_element_sync_state_with_parent (self->uri_handler); } return TRUE; } static gboolean clapper_uri_list_demux_sink_event (GstPad *pad, GstObject *parent, GstEvent *event) { ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (parent); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS:{ GstBuffer *buffer; gsize size; gboolean success; size = gst_adapter_available (self->input_adapter); if (size == 0) { GST_WARNING_OBJECT (self, "Received EOS without URI data"); break; } buffer = gst_adapter_take_buffer (self->input_adapter, size); success = clapper_uri_list_demux_process_buffer (self, buffer); gst_buffer_unref (buffer); if (success) { gst_event_unref (event); return TRUE; } break; } case GST_EVENT_CUSTOM_DOWNSTREAM_STICKY:{ const GstStructure *structure = gst_event_get_structure (event); if (structure && gst_structure_has_name (structure, "http-headers")) { GST_DEBUG_OBJECT (self, "Received \"http-headers\" custom event"); g_mutex_lock (&self->lock); gst_clear_structure (&self->http_headers); self->http_headers = gst_structure_copy (structure); g_mutex_unlock (&self->lock); } break; } default: break; } return gst_pad_event_default (pad, parent, event); } static GstFlowReturn clapper_uri_list_demux_sink_chain (GstPad *pad, GstObject *parent, GstBuffer *buffer) { ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (parent); gst_adapter_push (self->input_adapter, buffer); GST_DEBUG_OBJECT (self, "Received buffer, total collected: %" G_GSIZE_FORMAT " bytes", gst_adapter_available (self->input_adapter)); return GST_FLOW_OK; } static void clapper_uri_list_demux_init (ClapperUriListDemux *self) { GstPad *sink_pad; g_mutex_init (&self->lock); self->input_adapter = gst_adapter_new (); sink_pad = gst_pad_new_from_template (gst_element_class_get_pad_template ( GST_ELEMENT_GET_CLASS (self), "sink"), "sink"); gst_pad_set_event_function (sink_pad, GST_DEBUG_FUNCPTR (clapper_uri_list_demux_sink_event)); gst_pad_set_chain_function (sink_pad, GST_DEBUG_FUNCPTR (clapper_uri_list_demux_sink_chain)); gst_pad_set_active (sink_pad, TRUE); if (!gst_element_add_pad (GST_ELEMENT_CAST (self), sink_pad)) g_critical ("Failed to add sink pad to bin"); } static void clapper_uri_list_demux_finalize (GObject *object) { ClapperUriListDemux *self = CLAPPER_URI_LIST_DEMUX_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); g_object_unref (self->input_adapter); gst_clear_object (&self->typefind_src); gst_clear_structure (&self->http_headers); g_mutex_clear (&self->lock); G_OBJECT_CLASS (parent_class)->finalize (object); } static void clapper_uri_list_demux_class_init (ClapperUriListDemuxClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstElementClass *gstelement_class = (GstElementClass *) klass; GstBinClass *gstbin_class = (GstBinClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperurilistdemux", 0, "Clapper URI List Demux"); gobject_class->finalize = clapper_uri_list_demux_finalize; gstbin_class->deep_element_added = clapper_uri_list_demux_deep_element_added; gstelement_class->change_state = clapper_uri_list_demux_change_state; gst_element_class_add_static_pad_template (gstelement_class, &sink_template); gst_element_class_add_static_pad_template (gstelement_class, &src_template); gst_element_class_set_static_metadata (gstelement_class, "Clapper URI List Demux", "Demuxer", "A custom demuxer for URI lists", "Rafał Dzięgiel "); } clapper-0.8.0/src/lib/clapper/meson.build000066400000000000000000000164571474353110700203140ustar00rootroot00000000000000clapper_dep = dependency('', required: false) clapper_option = get_option('clapper') clapper_headers_dir = join_paths(includedir, clapper_api_name, 'clapper') clapper_enhancers_dir = join_paths(clapper_libdir, 'enhancers') clapper_with_enhancers_loader = false build_clapper = false clapper_pkg_reqs = [ 'gstreamer-1.0', 'gstreamer-base-1.0', 'gstreamer-audio-1.0', 'gstreamer-tag-1.0', 'gstreamer-pbutils-1.0', 'glib-2.0', 'gobject-2.0', 'gio-2.0', ] if clapper_option.disabled() subdir_done() endif clapper_deps = [ gst_dep, gst_base_dep, gst_audio_dep, gst_tag_dep, gst_pbutils_dep, glib_dep, gobject_dep, gio_dep, ] foreach dep : clapper_deps if not dep.found() if clapper_option.enabled() error('clapper option was enabled, but required dependencies were not found') endif subdir_done() endif endforeach # libpeas is an optional dependency enhancers_option = get_option('enhancers-loader') clapper_with_enhancers_loader = (not enhancers_option.disabled() and peas_dep.found()) if not clapper_with_enhancers_loader and enhancers_option.enabled() error('enhancers-loader option was enabled, but required dependencies were not found') endif config_h = configuration_data() config_h.set_quoted('PACKAGE', meson.project_name()) config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) config_h.set_quoted('PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') config_h.set_quoted('PLUGIN_DESC', 'Clapper elements') config_h.set_quoted('PLUGIN_LICENSE', 'LGPL') config_h.set_quoted('CLAPPER_ENHANCERS_PATH', clapper_enhancers_dir) configure_file( output: 'config.h', configuration: config_h, ) visibility_conf = configuration_data() visibility_conf.set( 'CLAPPER_API', 'CLAPPER', ) clapper_visibility_header = configure_file( input: '../shared/clapper-api-visibility.h.in', output: 'clapper-visibility.h', configuration: visibility_conf, ) version_conf = configuration_data() version_conf.set( 'CLAPPER_VERSION', meson.project_version(), ) version_conf.set( 'CLAPPER_MAJOR_VERSION', version_array[0].to_int(), ) version_conf.set( 'CLAPPER_MINOR_VERSION', version_array[1].to_int(), ) version_conf.set( 'CLAPPER_MICRO_VERSION', version_array[2].to_int(), ) clapper_version_header = configure_file( input: 'clapper-version.h.in', output: 'clapper-version.h', configuration: version_conf, ) # Include the generated headers clapper_conf_inc = [ include_directories('.'), include_directories('..'), ] clapper_headers = [ 'clapper.h', 'clapper-enums.h', 'clapper-audio-stream.h', 'clapper-extractable.h', 'clapper-feature.h', 'clapper-harvest.h', 'clapper-marker.h', 'clapper-media-item.h', 'clapper-player.h', 'clapper-queue.h', 'clapper-stream.h', 'clapper-stream-list.h', 'clapper-subtitle-stream.h', 'clapper-threaded-object.h', 'clapper-timeline.h', 'clapper-utils.h', 'clapper-video-stream.h', clapper_version_header, clapper_visibility_header, ] clapper_sources = [ 'clapper.c', 'clapper-app-bus.c', 'clapper-audio-stream.c', 'clapper-extractable.c', 'clapper-feature.c', 'clapper-features-bus.c', 'clapper-features-manager.c', 'clapper-harvest.c', 'clapper-marker.c', 'clapper-media-item.c', 'clapper-playbin-bus.c', 'clapper-player.c', 'clapper-queue.c', 'clapper-stream.c', 'clapper-stream-list.c', 'clapper-subtitle-stream.c', 'clapper-threaded-object.c', 'clapper-timeline.c', 'clapper-utils.c', 'clapper-video-stream.c', 'gst/clapper-plugin.c', 'gst/clapper-uri-list-demux.c', '../shared/clapper-shared-utils.c', ] clapper_c_args = [ '-DG_LOG_DOMAIN="Clapper"', '-DCLAPPER_COMPILATION', '-DGST_USE_UNSTABLE_API', ] if get_option('default_library') == 'static' clapper_c_args += ['-DCLAPPER_STATIC_COMPILATION'] endif clapper_possible_functionalities = [ 'enhancers-loader', ] clapper_available_functionalities = [] if clapper_with_enhancers_loader clapper_deps += peas_dep clapper_sources += [ 'clapper-enhancers-loader.c', 'gst/clapper-enhancer-src.c', 'gst/clapper-enhancer-director.c', ] clapper_available_functionalities += 'enhancers-loader' endif functionalities_availability_conf = configuration_data() foreach functionality_name : clapper_possible_functionalities functionalities_availability_conf.set( 'CLAPPER_WITH_@0@'.format(functionality_name.replace('-', '_').to_upper()), clapper_available_functionalities.contains(functionality_name) ? 'TRUE' : 'FALSE' ) endforeach clapper_headers += configure_file( input: 'clapper-functionalities-availability.h.in', output: 'clapper-functionalities-availability.h', configuration: functionalities_availability_conf, ) subdir('features') clapper_enums = gnome.mkenums_simple( 'clapper-enum-types', sources: clapper_headers, decorator: 'CLAPPER_API', header_prefix: '#include ', identifier_prefix: 'Clapper', symbol_prefix: 'clapper', install_header: true, install_dir: clapper_headers_dir, ) clapper_lib = library( clapper_api_name, clapper_sources + clapper_features_sources + clapper_features_sources_internal + clapper_enums, dependencies: clapper_deps + clapper_features_deps, include_directories: clapper_conf_inc, c_args: clapper_c_args, version: clapper_version, install: true, ) install_headers(clapper_headers, install_dir: clapper_headers_dir, ) build_clapper = true if build_gir clapper_gir = gnome.generate_gir(clapper_lib, sources: [ clapper_sources, clapper_features_sources, clapper_headers, clapper_features_headers, clapper_enums, ], extra_args: [ gir_init_section, '--quiet', '--warn-all', '-DCLAPPER_COMPILATION', '-DGST_USE_UNSTABLE_API', ], nsversion: version_array[0] + '.0', namespace: 'Clapper', identifier_prefix: 'Clapper', symbol_prefix: 'clapper', export_packages: clapper_api_name, install: true, includes: [ 'Gst-1.0', 'GstBase-1.0', 'GstAudio-1.0', 'GstTag-1.0', 'GstPbutils-1.0', 'GLib-2.0', 'GObject-2.0', 'Gio-2.0', ], header: join_paths(meson.project_name(), 'clapper.h'), ) endif if build_vapi if not build_gir if get_option('vapi').enabled() error('Cannot build "vapi" without "introspection"') endif else clapper_vapi = gnome.generate_vapi(clapper_api_name, sources: clapper_gir[0], packages: clapper_pkg_reqs, metadata_dirs: [ join_paths(meson.current_source_dir(), 'metadata') ], install: true, ) endif endif clapper_pkgconfig_variables = [ 'enhancersdir=${libdir}/' + clapper_api_name + '/enhancers', 'features=' + ' '.join(clapper_available_features), 'functionalities=' + ' '.join(clapper_available_functionalities), ] pkgconfig.generate(clapper_lib, unescaped_variables: clapper_pkgconfig_variables, subdirs: [clapper_api_name], filebase: clapper_api_name, name: meson.project_name(), version: meson.project_version(), description: 'Clapper playback library', requires: clapper_pkg_reqs, ) clapper_dep = declare_dependency( link_with: clapper_lib, include_directories: clapper_conf_inc, dependencies: clapper_deps + clapper_features_deps, sources: [ clapper_visibility_header, clapper_version_header, clapper_features_availability_header, clapper_enums[1], ], ) clapper-0.8.0/src/lib/clapper/metadata/000077500000000000000000000000001474353110700177155ustar00rootroot00000000000000clapper-0.8.0/src/lib/clapper/metadata/Clapper-0.0.metadata000066400000000000000000000002531474353110700233000ustar00rootroot00000000000000// Skipped by GI, but Vala can handle it fine //init_get_option_group skip=false *_FORMAT skip=false // Init func compatibility init.argv unowned init_check.argv unowned clapper-0.8.0/src/lib/gst/000077500000000000000000000000001474353110700153045ustar00rootroot00000000000000clapper-0.8.0/src/lib/gst/meson.build000066400000000000000000000000211474353110700174370ustar00rootroot00000000000000subdir('plugin') clapper-0.8.0/src/lib/gst/plugin/000077500000000000000000000000001474353110700166025ustar00rootroot00000000000000clapper-0.8.0/src/lib/gst/plugin/gstclappercontexthandler.c000066400000000000000000000051171474353110700240610ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclappercontexthandler.h" #define parent_class gst_clapper_context_handler_parent_class G_DEFINE_TYPE (GstClapperContextHandler, gst_clapper_context_handler, GST_TYPE_OBJECT); static gboolean _default_handle_context_query (GstClapperContextHandler *self, GstBaseSink *bsink, GstQuery *query) { GST_FIXME_OBJECT (self, "Need to handle context query"); return FALSE; } static void gst_clapper_context_handler_init (GstClapperContextHandler *self) { } static void gst_clapper_context_handler_finalize (GObject *object) { GstClapperContextHandler *self = GST_CLAPPER_CONTEXT_HANDLER_CAST (object); GST_TRACE_OBJECT (self, "Finalize"); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static void gst_clapper_context_handler_class_init (GstClapperContextHandlerClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstClapperContextHandlerClass *handler_class = (GstClapperContextHandlerClass *) klass; gobject_class->finalize = gst_clapper_context_handler_finalize; handler_class->handle_context_query = _default_handle_context_query; } gboolean gst_clapper_context_handler_handle_context_query (GstClapperContextHandler *self, GstBaseSink *bsink, GstQuery *query) { GstClapperContextHandlerClass *handler_class = GST_CLAPPER_CONTEXT_HANDLER_GET_CLASS (self); return handler_class->handle_context_query (self, bsink, query); } GstClapperContextHandler * gst_clapper_context_handler_obtain_with_type (GPtrArray *context_handlers, GType type) { guint i; for (i = 0; i < context_handlers->len; i++) { GstClapperContextHandler *handler = g_ptr_array_index (context_handlers, i); if (G_TYPE_CHECK_INSTANCE_TYPE (handler, type)) return gst_object_ref (handler); } return NULL; } clapper-0.8.0/src/lib/gst/plugin/gstclappercontexthandler.h000066400000000000000000000055061474353110700240700ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include #include #include "gst/plugin/clapper-gst-visibility.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_CONTEXT_HANDLER (gst_clapper_context_handler_get_type()) #define GST_IS_CLAPPER_CONTEXT_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_CONTEXT_HANDLER)) #define GST_IS_CLAPPER_CONTEXT_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_CONTEXT_HANDLER)) #define GST_CLAPPER_CONTEXT_HANDLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_CONTEXT_HANDLER, GstClapperContextHandlerClass)) #define GST_CLAPPER_CONTEXT_HANDLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_CONTEXT_HANDLER, GstClapperContextHandler)) #define GST_CLAPPER_CONTEXT_HANDLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_CONTEXT_HANDLER, GstClapperContextHandlerClass)) #define GST_CLAPPER_CONTEXT_HANDLER_CAST(obj) ((GstClapperContextHandler *)(obj)) typedef struct _GstClapperContextHandler GstClapperContextHandler; typedef struct _GstClapperContextHandlerClass GstClapperContextHandlerClass; #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperContextHandler, gst_object_unref) #endif struct _GstClapperContextHandler { GstObject parent; }; struct _GstClapperContextHandlerClass { GstObjectClass parent_class; gboolean (* handle_context_query) (GstClapperContextHandler *handler, GstBaseSink *bsink, GstQuery *query); }; CLAPPER_GST_API GType gst_clapper_context_handler_get_type (void); CLAPPER_GST_API gboolean gst_clapper_context_handler_handle_context_query (GstClapperContextHandler *handler, GstBaseSink *bsink, GstQuery *query); CLAPPER_GST_API GstClapperContextHandler * gst_clapper_context_handler_obtain_with_type (GPtrArray *context_handlers, GType type); G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/gstclapperimporter.c000066400000000000000000000327461474353110700227100ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclapperimporter.h" #include "gstgtkutils.h" #define GST_CAT_DEFAULT gst_clapper_importer_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define parent_class gst_clapper_importer_parent_class G_DEFINE_TYPE (GstClapperImporter, gst_clapper_importer, GST_TYPE_OBJECT); typedef struct { GdkTexture *texture; GstVideoOverlayRectangle *rectangle; gint x, y; guint width, height; gint index; gatomicrefcount ref_count; } GstClapperGdkOverlay; static GstClapperGdkOverlay * gst_clapper_gdk_overlay_new (GdkTexture *texture, GstVideoOverlayRectangle *rectangle, gint x, gint y, guint width, guint height, guint index) { GstClapperGdkOverlay *overlay = g_slice_new (GstClapperGdkOverlay); overlay->texture = g_object_ref (texture); overlay->rectangle = gst_video_overlay_rectangle_ref (rectangle); overlay->x = x; overlay->y = y; overlay->width = width; overlay->height = height; overlay->index = index; g_atomic_ref_count_init (&overlay->ref_count); return overlay; } static GstClapperGdkOverlay * gst_clapper_gdk_overlay_ref (GstClapperGdkOverlay *overlay) { g_atomic_ref_count_inc (&overlay->ref_count); return overlay; } static void gst_clapper_gdk_overlay_unref (GstClapperGdkOverlay *overlay) { if (g_atomic_ref_count_dec (&overlay->ref_count)) { GST_TRACE ("Freeing overlay: %" GST_PTR_FORMAT, overlay); g_object_unref (overlay->texture); gst_video_overlay_rectangle_unref (overlay->rectangle); g_slice_free (GstClapperGdkOverlay, overlay); } } static GstBufferPool * _default_create_pool (GstClapperImporter *self, GstStructure **config) { GST_FIXME_OBJECT (self, "Need to create buffer pool"); return NULL; } static void _default_add_allocation_metas (GstClapperImporter *importer, GstQuery *query) { /* Importer base class handles GstVideoOverlayCompositionMeta */ gst_query_add_allocation_meta (query, GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL); gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); } static GdkTexture * _default_generate_texture (GstClapperImporter *self, GstBuffer *buffer, GstVideoInfo *v_info) { GST_FIXME_OBJECT (self, "GdkTexture generation not implemented"); return NULL; } static void gst_clapper_importer_init (GstClapperImporter *self) { gst_video_info_init (&self->pending_v_info); gst_video_info_init (&self->v_info); self->pending_overlays = g_ptr_array_new_with_free_func ( (GDestroyNotify) gst_clapper_gdk_overlay_unref); self->overlays = g_ptr_array_new_with_free_func ( (GDestroyNotify) gst_clapper_gdk_overlay_unref); gdk_rgba_parse (&self->bg, "black"); } static void gst_clapper_importer_finalize (GObject *object) { GstClapperImporter *self = GST_CLAPPER_IMPORTER_CAST (object); GST_TRACE ("Finalize"); gst_clear_caps (&self->pending_caps); gst_clear_buffer (&self->pending_buffer); gst_clear_buffer (&self->buffer); g_ptr_array_unref (self->pending_overlays); g_ptr_array_unref (self->overlays); g_clear_object (&self->texture); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static void gst_clapper_importer_class_init (GstClapperImporterClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimporter", 0, "Clapper Importer"); gobject_class->finalize = gst_clapper_importer_finalize; importer_class->create_pool = _default_create_pool; importer_class->add_allocation_metas = _default_add_allocation_metas; importer_class->generate_texture = _default_generate_texture; } static GstClapperGdkOverlay * _get_cached_overlay (GPtrArray *overlays, GstVideoOverlayRectangle *rectangle) { guint i; for (i = 0; i < overlays->len; i++) { GstClapperGdkOverlay *overlay = g_ptr_array_index (overlays, i); if (overlay->rectangle == rectangle) return overlay; } return NULL; } static gint _sort_overlays_cb (gconstpointer a, gconstpointer b) { GstClapperGdkOverlay *overlay_a, *overlay_b; overlay_a = *((GstClapperGdkOverlay **) a); overlay_b = *((GstClapperGdkOverlay **) b); return (overlay_a->index - overlay_b->index); } /* * Prepares overlays to show with the next rendered buffer. * * In order for overlays caching to work correctly, this should be called for * every received buffer (even if its going to be disgarded), also must be * called together with pending buffer replacement within a single importer * locking, to make sure prepared overlays always match the pending buffer. */ static void gst_clapper_importer_prepare_overlays_locked (GstClapperImporter *self) { GstVideoOverlayCompositionMeta *comp_meta; guint num_overlays, i; if (G_UNLIKELY (!self->pending_buffer) || !(comp_meta = gst_buffer_get_video_overlay_composition_meta (self->pending_buffer))) { guint n_pending = self->pending_overlays->len; /* Remove all cached overlays if new buffer does not have any */ if (n_pending > 0) { GST_TRACE ("No overlays in buffer, removing all cached ones"); g_ptr_array_remove_range (self->pending_overlays, 0, n_pending); } return; } GST_LOG_OBJECT (self, "Preparing overlays..."); /* Mark all old overlays as unused by giving them negative index */ for (i = 0; i < self->pending_overlays->len; i++) { GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i); overlay->index = -1; } num_overlays = gst_video_overlay_composition_n_rectangles (comp_meta->overlay); for (i = 0; i < num_overlays; i++) { GdkTexture *texture; GstBuffer *comp_buffer; GstVideoFrame comp_frame; GstVideoMeta *v_meta; GstVideoInfo v_info; GstVideoOverlayRectangle *rectangle; GstClapperGdkOverlay *overlay; GstVideoOverlayFormatFlags flags, alpha_flags = 0; gint comp_x, comp_y; guint comp_width, comp_height; rectangle = gst_video_overlay_composition_get_rectangle (comp_meta->overlay, i); if ((overlay = _get_cached_overlay (self->pending_overlays, rectangle))) { overlay->index = i; GST_TRACE ("Reusing cached overlay: %" GST_PTR_FORMAT, overlay); continue; } if (G_UNLIKELY (!gst_video_overlay_rectangle_get_render_rectangle (rectangle, &comp_x, &comp_y, &comp_width, &comp_height))) { GST_WARNING ("Invalid overlay rectangle dimensions: %" GST_PTR_FORMAT, rectangle); continue; } flags = gst_video_overlay_rectangle_get_flags (rectangle); if (flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) alpha_flags |= GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA; comp_buffer = gst_video_overlay_rectangle_get_pixels_unscaled_argb (rectangle, alpha_flags); /* Update overlay video info from video meta */ if ((v_meta = gst_buffer_get_video_meta (comp_buffer))) { gst_video_info_set_format (&v_info, v_meta->format, v_meta->width, v_meta->height); v_info.stride[0] = v_meta->stride[0]; if (alpha_flags & GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA) v_info.flags |= GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA; } if (G_UNLIKELY (!gst_video_frame_map (&comp_frame, &v_info, comp_buffer, GST_MAP_READ))) return; if ((texture = gst_video_frame_into_gdk_texture (&comp_frame))) { overlay = gst_clapper_gdk_overlay_new (texture, rectangle, comp_x, comp_y, comp_width, comp_height, i); g_object_unref (texture); GST_TRACE_OBJECT (self, "Created overlay: %" GST_PTR_FORMAT ", x: %i, y: %i, width: %u, height: %u", overlay, overlay->x, overlay->y, overlay->width, overlay->height); g_ptr_array_insert (self->pending_overlays, i, overlay); } gst_video_frame_unmap (&comp_frame); } /* Remove all overlays that are not going to be used */ for (i = self->pending_overlays->len; i > 0; i--) { GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i - 1); if (overlay->index < 0) { GST_TRACE ("Removing unused cached overlay: %" GST_PTR_FORMAT, overlay); g_ptr_array_remove (self->pending_overlays, overlay); } } /* Sort remaining overlays */ if (self->pending_overlays->len > 1) { GST_LOG_OBJECT (self, "Sorting overlays"); g_ptr_array_sort (self->pending_overlays, (GCompareFunc) _sort_overlays_cb); } if (G_UNLIKELY (num_overlays != self->pending_overlays->len)) { GST_WARNING_OBJECT (self, "Some overlays could not be prepared, %u != %u", num_overlays, self->pending_overlays->len); } GST_LOG_OBJECT (self, "Prepared overlays: %u", self->pending_overlays->len); } void gst_clapper_importer_set_caps (GstClapperImporter *self, GstCaps *caps) { GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self); GST_OBJECT_LOCK (self); gst_caps_replace (&self->pending_caps, caps); GST_OBJECT_UNLOCK (self); if (importer_class->set_caps) importer_class->set_caps (self, caps); } void gst_clapper_importer_set_buffer (GstClapperImporter *self, GstBuffer *buffer) { GST_OBJECT_LOCK (self); /* Pending v_info, buffer and overlays must be * set within a single importer locking */ if (self->pending_caps) { self->has_pending_v_info = gst_video_info_from_caps (&self->pending_v_info, self->pending_caps); gst_clear_caps (&self->pending_caps); } gst_buffer_replace (&self->pending_buffer, buffer); gst_clapper_importer_prepare_overlays_locked (self); GST_OBJECT_UNLOCK (self); } GstBufferPool * gst_clapper_importer_create_pool (GstClapperImporter *self, GstStructure **config) { GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self); return importer_class->create_pool (self, config); } void gst_clapper_importer_add_allocation_metas (GstClapperImporter *self, GstQuery *query) { GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self); importer_class->add_allocation_metas (self, query); } void gst_clapper_importer_snapshot (GstClapperImporter *self, GdkSnapshot *snapshot, gdouble width, gdouble height) { guint i; gboolean buffer_changed; /* Collect all data that we need to snapshot pending buffer, * lock ourselves to make sure everything matches */ GST_OBJECT_LOCK (self); if (self->has_pending_v_info) { self->v_info = self->pending_v_info; self->has_pending_v_info = FALSE; } buffer_changed = gst_buffer_replace (&self->buffer, self->pending_buffer); /* Ref overlays associated with current buffer */ for (i = 0; i < self->pending_overlays->len; i++) { GstClapperGdkOverlay *overlay = g_ptr_array_index (self->pending_overlays, i); g_ptr_array_insert (self->overlays, i, gst_clapper_gdk_overlay_ref (overlay)); } GST_OBJECT_UNLOCK (self); /* Draw black BG when no buffer or imported format has alpha */ if (!self->buffer || GST_VIDEO_INFO_HAS_ALPHA (&self->v_info)) gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height)); if (self->buffer) { if (buffer_changed || !self->texture) { GstClapperImporterClass *importer_class = GST_CLAPPER_IMPORTER_GET_CLASS (self); GST_TRACE_OBJECT (self, "Importing %" GST_PTR_FORMAT, self->buffer); g_clear_object (&self->texture); self->texture = importer_class->generate_texture (self, self->buffer, &self->v_info); } else { GST_TRACE_OBJECT (self, "Reusing texture from %" GST_PTR_FORMAT, self->buffer); } if (G_LIKELY (self->texture)) { gtk_snapshot_append_texture (snapshot, self->texture, &GRAPHENE_RECT_INIT (0, 0, width, height)); if (self->overlays->len > 0) { gfloat scale_x, scale_y; /* FIXME: GStreamer scales subtitles without considering pixel aspect ratio. * See: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/20 */ scale_x = (gfloat) width / GST_VIDEO_INFO_WIDTH (&self->v_info); scale_y = (gfloat) height / GST_VIDEO_INFO_HEIGHT (&self->v_info); for (i = 0; i < self->overlays->len; i++) { GstClapperGdkOverlay *overlay = g_ptr_array_index (self->overlays, i); gtk_snapshot_append_texture (snapshot, overlay->texture, &GRAPHENE_RECT_INIT (overlay->x * scale_x, overlay->y * scale_y, overlay->width * scale_x, overlay->height * scale_y)); } } } else { GST_ERROR_OBJECT (self, "Failed import of %" GST_PTR_FORMAT, self->buffer); /* Draw black instead of texture on failure if not drawn already */ if (!GST_VIDEO_INFO_HAS_ALPHA (&self->v_info)) gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height)); } } /* Unref all used overlays */ if (self->overlays->len > 0) g_ptr_array_remove_range (self->overlays, 0, self->overlays->len); } clapper-0.8.0/src/lib/gst/plugin/gstclapperimporter.h000066400000000000000000000075651474353110700227160ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include #include #include #include "gst/plugin/clapper-gst-visibility.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_IMPORTER (gst_clapper_importer_get_type()) #define GST_IS_CLAPPER_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CLAPPER_IMPORTER)) #define GST_IS_CLAPPER_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CLAPPER_IMPORTER)) #define GST_CLAPPER_IMPORTER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporterClass)) #define GST_CLAPPER_IMPORTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporter)) #define GST_CLAPPER_IMPORTER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CLAPPER_IMPORTER, GstClapperImporterClass)) #define GST_CLAPPER_IMPORTER_CAST(obj) ((GstClapperImporter *)(obj)) #define GST_CLAPPER_IMPORTER_DEFINE(camel,lower,type) \ G_DEFINE_TYPE (camel, lower, type) \ G_MODULE_EXPORT GstClapperImporter *make_importer (GPtrArray *context_handlers); \ G_MODULE_EXPORT GstCaps *make_caps (gboolean is_template, \ GstRank *rank, GPtrArray *context_handlers); typedef struct _GstClapperImporter GstClapperImporter; typedef struct _GstClapperImporterClass GstClapperImporterClass; #ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC G_DEFINE_AUTOPTR_CLEANUP_FUNC (GstClapperImporter, gst_object_unref) #endif struct _GstClapperImporter { GstObject parent; GstCaps *pending_caps; GstBuffer *pending_buffer, *buffer; GPtrArray *pending_overlays, *overlays; GstVideoInfo pending_v_info, v_info; gboolean has_pending_v_info; GdkTexture *texture; GdkRGBA bg; }; struct _GstClapperImporterClass { GstObjectClass parent_class; void (* set_caps) (GstClapperImporter *importer, GstCaps *caps); GstBufferPool * (* create_pool) (GstClapperImporter *importer, GstStructure **config); void (* add_allocation_metas) (GstClapperImporter *importer, GstQuery *query); GdkTexture * (* generate_texture) (GstClapperImporter *importer, GstBuffer *buffer, GstVideoInfo *v_info); }; CLAPPER_GST_API GType gst_clapper_importer_get_type (void); GstBufferPool * gst_clapper_importer_create_pool (GstClapperImporter *importer, GstStructure **config); void gst_clapper_importer_add_allocation_metas (GstClapperImporter *importer, GstQuery *query); void gst_clapper_importer_set_caps (GstClapperImporter *importer, GstCaps *caps); void gst_clapper_importer_set_buffer (GstClapperImporter *importer, GstBuffer *buffer); void gst_clapper_importer_snapshot (GstClapperImporter *importer, GdkSnapshot *snapshot, gdouble width, gdouble height); G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/gstclapperimporterloader.c000066400000000000000000000255141474353110700240720ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "gstclapperimporterloader.h" #include "gstclapperimporter.h" #include "gstclappercontexthandler.h" #ifdef G_OS_WIN32 #include static HMODULE _importer_dll_handle = NULL; #endif #define GST_CAT_DEFAULT gst_clapper_importer_loader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define parent_class gst_clapper_importer_loader_parent_class G_DEFINE_TYPE (GstClapperImporterLoader, gst_clapper_importer_loader, GST_TYPE_OBJECT); typedef GstClapperImporter* (* MakeImporter) (GPtrArray *context_handlers); typedef GstCaps* (* MakeCaps) (gboolean is_template, GstRank *rank, GPtrArray *context_handlers); typedef struct { GModule *module; GstCaps *caps; GstRank rank; } GstClapperImporterData; static void gst_clapper_importer_data_free (GstClapperImporterData *data) { GST_TRACE ("Freeing importer data: %" GST_PTR_FORMAT, data); gst_clear_caps (&data->caps); g_free (data); } static GstClapperImporterData * _obtain_importer_data (GModule *module, gboolean is_template, GPtrArray *context_handlers) { MakeCaps make_caps; GstClapperImporterData *data; GST_DEBUG ("Found importer: %s", g_module_name (module)); if (!g_module_symbol (module, "make_caps", (gpointer *) &make_caps) || make_caps == NULL) { GST_WARNING ("Make caps function missing in importer"); return NULL; } data = g_new0 (GstClapperImporterData, 1); data->module = module; data->caps = make_caps (is_template, &data->rank, context_handlers); GST_TRACE ("Created importer data: %" GST_PTR_FORMAT, data); if (G_UNLIKELY (!data->caps)) { if (!is_template) { GST_ERROR ("Invalid importer without caps: %s", g_module_name (data->module)); } else { /* When importer cannot be actually used, due to e.g. unsupported HW */ GST_DEBUG ("No actual caps returned from importer"); } gst_clapper_importer_data_free (data); return NULL; } GST_DEBUG ("Importer caps: %" GST_PTR_FORMAT, data->caps); return data; } static GstClapperImporter * _obtain_importer_internal (GModule *module, GPtrArray *context_handlers) { MakeImporter make_importer; GstClapperImporter *importer; if (!g_module_symbol (module, "make_importer", (gpointer *) &make_importer) || make_importer == NULL) { GST_WARNING ("Make function missing in importer"); return NULL; } importer = make_importer (context_handlers); GST_TRACE ("Created importer: %" GST_PTR_FORMAT, importer); return importer; } static gpointer _obtain_available_modules_once (G_GNUC_UNUSED gpointer data) { GPtrArray *modules; GFile *dir = NULL; GFileEnumerator *dir_enum; GError *error = NULL; const gchar *env_path = g_getenv ("CLAPPER_SINK_IMPORTER_PATH"); GST_INFO ("Preparing modules"); modules = g_ptr_array_new (); #ifdef G_OS_WIN32 if (!env_path || env_path[0] == '\0') { gchar *win_base_dir, *dir_path; win_base_dir = g_win32_get_package_installation_directory_of_module ( _importer_dll_handle); dir_path = g_build_filename (win_base_dir, "lib", "clapper-0.0", "gst", "plugin", "importers", NULL); GST_INFO ("Win32 importers path: %s", dir_path); dir = g_file_new_for_path (dir_path); g_free (win_base_dir); g_free (dir_path); } #endif if (!dir) { const gchar *imp_path = (env_path && env_path[0] != '\0') ? env_path : CLAPPER_SINK_IMPORTER_PATH; dir = g_file_new_for_path (imp_path); } if ((dir_enum = g_file_enumerate_children (dir, G_FILE_ATTRIBUTE_STANDARD_NAME, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, &error))) { gchar *dir_path = g_file_get_path (dir); while (TRUE) { GFileInfo *info = NULL; GModule *module; gchar *module_path; const gchar *module_name; if (!g_file_enumerator_iterate (dir_enum, &info, NULL, NULL, &error) || !info) break; module_name = g_file_info_get_name (info); if (!g_str_has_suffix (module_name, G_MODULE_SUFFIX)) continue; module_path = g_module_build_path (dir_path, module_name); module = g_module_open (module_path, G_MODULE_BIND_LAZY); g_free (module_path); if (!module) { GST_WARNING ("Could not read module: %s, reason: %s", module_name, g_module_error ()); continue; } GST_INFO ("Found module: %s", module_name); g_ptr_array_add (modules, module); } g_object_unref (dir_enum); g_free (dir_path); } g_object_unref (dir); if (error) { GST_ERROR ("Could not load module, reason: %s", (error->message) ? error->message : "unknown"); g_error_free (error); } return modules; } static const GPtrArray * gst_clapper_importer_loader_get_available_modules (void) { static GOnce once = G_ONCE_INIT; g_once (&once, _obtain_available_modules_once, NULL); return (const GPtrArray *) once.retval; } static gint _sort_importers_cb (gconstpointer a, gconstpointer b) { GstClapperImporterData *data_a, *data_b; data_a = *((GstClapperImporterData **) a); data_b = *((GstClapperImporterData **) b); return (data_b->rank - data_a->rank); } static GPtrArray * _obtain_importers (gboolean is_template, GPtrArray *context_handlers) { const GPtrArray *modules; GPtrArray *importers; guint i; GST_DEBUG ("Checking %s importers", (is_template) ? "available" : "usable"); modules = gst_clapper_importer_loader_get_available_modules (); importers = g_ptr_array_new_with_free_func ( (GDestroyNotify) gst_clapper_importer_data_free); for (i = 0; i < modules->len; i++) { GModule *module = g_ptr_array_index (modules, i); GstClapperImporterData *data; if ((data = _obtain_importer_data (module, is_template, context_handlers))) g_ptr_array_add (importers, data); } g_ptr_array_sort (importers, (GCompareFunc) _sort_importers_cb); GST_DEBUG ("Found %i %s importers", importers->len, (is_template) ? "available" : "usable"); return importers; } GstClapperImporterLoader * gst_clapper_importer_loader_new (void) { return g_object_new (GST_TYPE_CLAPPER_IMPORTER_LOADER, NULL); } static GstCaps * _make_caps_for_importers (const GPtrArray *importers) { GstCaps *caps = gst_caps_new_empty (); guint i; for (i = 0; i < importers->len; i++) { GstClapperImporterData *data = g_ptr_array_index (importers, i); gst_caps_append (caps, gst_caps_ref (data->caps)); } return caps; } GstPadTemplate * gst_clapper_importer_loader_make_sink_pad_template (void) { GPtrArray *importers; GstCaps *caps; GstPadTemplate *templ; /* This is only called once from sink class init function */ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperimporterloader", 0, "Clapper Importer Loader"); GST_DEBUG ("Making sink pad template"); importers = _obtain_importers (TRUE, NULL); caps = _make_caps_for_importers (importers); g_ptr_array_unref (importers); if (G_UNLIKELY (gst_caps_is_empty (caps))) gst_caps_append (caps, gst_caps_new_any ()); templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, caps); gst_caps_unref (caps); GST_TRACE ("Created sink pad template"); return templ; } GstCaps * gst_clapper_importer_loader_make_actual_caps (GstClapperImporterLoader *self) { return _make_caps_for_importers (self->importers); } gboolean gst_clapper_importer_loader_handle_context_query (GstClapperImporterLoader *self, GstBaseSink *bsink, GstQuery *query) { guint i; for (i = 0; i < self->context_handlers->len; i++) { GstClapperContextHandler *handler = g_ptr_array_index (self->context_handlers, i); if (gst_clapper_context_handler_handle_context_query (handler, bsink, query)) return TRUE; } return FALSE; } static const GstClapperImporterData * _get_importer_data_for_caps (const GPtrArray *importers, const GstCaps *caps) { guint i; for (i = 0; i < importers->len; i++) { GstClapperImporterData *data = g_ptr_array_index (importers, i); if (!gst_caps_is_always_compatible (caps, data->caps)) continue; return data; } return NULL; } gboolean gst_clapper_importer_loader_find_importer_for_caps (GstClapperImporterLoader *self, GstCaps *caps, GstClapperImporter **importer) { const GstClapperImporterData *data = NULL; GstClapperImporter *found_importer = NULL; GST_OBJECT_LOCK (self); GST_DEBUG_OBJECT (self, "Requested importer for caps: %" GST_PTR_FORMAT, caps); data = _get_importer_data_for_caps (self->importers, caps); GST_LOG_OBJECT (self, "Old importer path: %s, new path: %s", (self->last_module) ? g_module_name (self->last_module) : NULL, (data) ? g_module_name (data->module) : NULL); if (G_UNLIKELY (!data)) { gst_clear_object (importer); goto finish; } if (*importer && (self->last_module == data->module)) { GST_DEBUG_OBJECT (self, "No importer change"); gst_clapper_importer_set_caps (*importer, caps); goto finish; } found_importer = _obtain_importer_internal (data->module, self->context_handlers); gst_clear_object (importer); if (!found_importer) goto finish; gst_clapper_importer_set_caps (found_importer, caps); *importer = found_importer; finish: self->last_module = (*importer && data) ? data->module : NULL; GST_OBJECT_UNLOCK (self); return (*importer != NULL); } static void gst_clapper_importer_loader_init (GstClapperImporterLoader *self) { self->context_handlers = g_ptr_array_new_with_free_func ( (GDestroyNotify) gst_object_unref); self->importers = _obtain_importers (FALSE, self->context_handlers); } static void gst_clapper_importer_loader_finalize (GObject *object) { GstClapperImporterLoader *self = GST_CLAPPER_IMPORTER_LOADER_CAST (object); GST_TRACE ("Finalize"); if (self->importers) g_ptr_array_unref (self->importers); g_ptr_array_unref (self->context_handlers); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static void gst_clapper_importer_loader_class_init (GstClapperImporterLoaderClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; gobject_class->finalize = gst_clapper_importer_loader_finalize; } clapper-0.8.0/src/lib/gst/plugin/gstclapperimporterloader.h000066400000000000000000000037521474353110700240770ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include #include #include "gstclapperimporter.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_IMPORTER_LOADER (gst_clapper_importer_loader_get_type()) G_DECLARE_FINAL_TYPE (GstClapperImporterLoader, gst_clapper_importer_loader, GST, CLAPPER_IMPORTER_LOADER, GstObject) #define GST_CLAPPER_IMPORTER_LOADER_CAST(obj) ((GstClapperImporterLoader *)(obj)) struct _GstClapperImporterLoader { GstObject parent; GModule *last_module; GPtrArray *importers; GPtrArray *context_handlers; }; GstClapperImporterLoader * gst_clapper_importer_loader_new (void); GstPadTemplate * gst_clapper_importer_loader_make_sink_pad_template (void); GstCaps * gst_clapper_importer_loader_make_actual_caps (GstClapperImporterLoader *loader); gboolean gst_clapper_importer_loader_handle_context_query (GstClapperImporterLoader *loader, GstBaseSink *bsink, GstQuery *query); gboolean gst_clapper_importer_loader_find_importer_for_caps (GstClapperImporterLoader *loader, GstCaps *caps, GstClapperImporter **importer); G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/gstclapperpaintable.c000066400000000000000000000370721474353110700230030ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclapperpaintable.h" #include "gstgtkutils.h" #define DEFAULT_PAR_N 1 #define DEFAULT_PAR_D 1 #define GST_CAT_DEFAULT gst_clapper_paintable_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); static void gst_clapper_paintable_iface_init (GdkPaintableInterface *iface); static void gst_clapper_paintable_dispose (GObject *object); static void gst_clapper_paintable_finalize (GObject *object); #define parent_class gst_clapper_paintable_parent_class G_DEFINE_TYPE_WITH_CODE (GstClapperPaintable, gst_clapper_paintable, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, gst_clapper_paintable_iface_init)); static void gst_clapper_paintable_class_init (GstClapperPaintableClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperpaintable", 0, "Clapper Paintable"); gobject_class->dispose = gst_clapper_paintable_dispose; gobject_class->finalize = gst_clapper_paintable_finalize; } static void gst_clapper_paintable_init (GstClapperPaintable *self) { self->display_width = 1; self->display_height = 1; self->display_aspect_ratio = 1.0; self->rotation = GST_VIDEO_ORIENTATION_IDENTITY; self->par_n = DEFAULT_PAR_N; self->par_d = DEFAULT_PAR_D; g_mutex_init (&self->lock); g_mutex_init (&self->importer_lock); gst_video_info_init (&self->v_info); g_weak_ref_init (&self->widget, NULL); gdk_rgba_parse (&self->bg, "black"); } static void gst_clapper_paintable_dispose (GObject *object) { GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object); GST_CLAPPER_PAINTABLE_LOCK (self); if (self->draw_id > 0) { g_source_remove (self->draw_id); self->draw_id = 0; } GST_CLAPPER_PAINTABLE_UNLOCK (self); GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self); gst_clear_object (&self->importer); GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self); GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); } static void gst_clapper_paintable_finalize (GObject *object) { GstClapperPaintable *self = GST_CLAPPER_PAINTABLE (object); GST_TRACE ("Finalize"); g_weak_ref_clear (&self->widget); g_mutex_clear (&self->lock); g_mutex_clear (&self->importer_lock); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static gboolean calculate_display_par (GstClapperPaintable *self, const GstVideoInfo *info) { gint width, height, par_n, par_d, req_par_n, req_par_d; gboolean success; gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), &width, &height, self->rotation); /* Cannot apply aspect ratio if there is no video */ if (width == 0 || height == 0) return FALSE; par_n = GST_VIDEO_INFO_PAR_N (info); par_d = GST_VIDEO_INFO_PAR_D (info); req_par_n = self->par_n; req_par_d = self->par_d; if (par_n == 0) par_n = 1; /* Use defaults if user set zero */ if (req_par_n == 0 || req_par_d == 0) { req_par_n = DEFAULT_PAR_N; req_par_d = DEFAULT_PAR_D; } GST_LOG_OBJECT (self, "PAR: %u/%u, DAR: %u/%u", par_n, par_d, req_par_n, req_par_d); if (!(success = gst_video_calculate_display_ratio (&self->display_ratio_num, &self->display_ratio_den, width, height, par_n, par_d, req_par_n, req_par_d))) { GST_ERROR_OBJECT (self, "Could not calculate display ratio values"); } return success; } static void invalidate_paintable_size_internal (GstClapperPaintable *self) { gint video_width, video_height; guint display_ratio_num, display_ratio_den; GST_CLAPPER_PAINTABLE_LOCK (self); gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (&self->v_info), GST_VIDEO_INFO_HEIGHT (&self->v_info), &video_height, &video_width, self->rotation); display_ratio_num = self->display_ratio_num; display_ratio_den = self->display_ratio_den; GST_CLAPPER_PAINTABLE_UNLOCK (self); if (video_height % display_ratio_den == 0) { GST_LOG ("Keeping video height"); self->display_width = (guint) gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den); self->display_height = video_height; } else if (video_width % display_ratio_num == 0) { GST_LOG ("Keeping video width"); self->display_width = video_width; self->display_height = (guint) gst_util_uint64_scale_int (video_width, display_ratio_den, display_ratio_num); } else { GST_LOG ("Approximating while keeping video height"); self->display_width = (guint) gst_util_uint64_scale_int (video_height, display_ratio_num, display_ratio_den); self->display_height = video_height; } self->display_aspect_ratio = ((gdouble) self->display_width / (gdouble) self->display_height); GST_DEBUG_OBJECT (self, "Invalidate paintable size, display: %dx%d", self->display_width, self->display_height); gdk_paintable_invalidate_size ((GdkPaintable *) self); } static gboolean invalidate_paintable_size_on_main_cb (GstClapperPaintable *self) { GST_CLAPPER_PAINTABLE_LOCK (self); self->draw_id = 0; GST_CLAPPER_PAINTABLE_UNLOCK (self); invalidate_paintable_size_internal (self); return G_SOURCE_REMOVE; } static gboolean update_paintable_on_main_cb (GstClapperPaintable *self) { gboolean size_changed; GST_CLAPPER_PAINTABLE_LOCK (self); /* Check if we will need to invalidate size */ if ((size_changed = self->pending_resize)) self->pending_resize = FALSE; self->draw_id = 0; GST_CLAPPER_PAINTABLE_UNLOCK (self); if (size_changed) invalidate_paintable_size_internal (self); GST_LOG_OBJECT (self, "Invalidate paintable contents"); gdk_paintable_invalidate_contents ((GdkPaintable *) self); return G_SOURCE_REMOVE; } GstClapperPaintable * gst_clapper_paintable_new (void) { return g_object_new (GST_TYPE_CLAPPER_PAINTABLE, NULL); } void gst_clapper_paintable_set_widget (GstClapperPaintable *self, GtkWidget *widget) { g_weak_ref_set (&self->widget, widget); } void gst_clapper_paintable_set_importer (GstClapperPaintable *self, GstClapperImporter *importer) { GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self); gst_object_replace ((GstObject **) &self->importer, GST_OBJECT_CAST (importer)); GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self); } void gst_clapper_paintable_queue_draw (GstClapperPaintable *self) { GST_CLAPPER_PAINTABLE_LOCK (self); if (self->draw_id > 0) { GST_CLAPPER_PAINTABLE_UNLOCK (self); GST_TRACE ("Already have pending draw"); return; } self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) update_paintable_on_main_cb, self, NULL); GST_CLAPPER_PAINTABLE_UNLOCK (self); } gboolean gst_clapper_paintable_set_video_info (GstClapperPaintable *self, const GstVideoInfo *v_info) { GST_CLAPPER_PAINTABLE_LOCK (self); if (gst_video_info_is_equal (&self->v_info, v_info)) { GST_CLAPPER_PAINTABLE_UNLOCK (self); return TRUE; } /* Reject info if values would cause integer overflow */ if (G_UNLIKELY (!calculate_display_par (self, v_info))) { GST_CLAPPER_PAINTABLE_UNLOCK (self); return FALSE; } self->pending_resize = TRUE; self->v_info = *v_info; GST_CLAPPER_PAINTABLE_UNLOCK (self); return TRUE; } void gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *self, gint par_n, gint par_d) { gboolean success; GST_CLAPPER_PAINTABLE_LOCK (self); /* No change */ if (self->par_n == par_n && self->par_d == par_d) { GST_CLAPPER_PAINTABLE_UNLOCK (self); return; } self->par_n = par_n; self->par_d = par_d; /* Check if we can accept new values. This will update * display `ratio_num` and `ratio_den` only when successful */ success = calculate_display_par (self, &self->v_info); /* If paintable update is queued, wait for it, otherwise invalidate * size only for change to be applied even when paused */ if (!success || self->draw_id > 0) { self->pending_resize = success; GST_CLAPPER_PAINTABLE_UNLOCK (self); return; } self->draw_id = g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) invalidate_paintable_size_on_main_cb, self, NULL); GST_CLAPPER_PAINTABLE_UNLOCK (self); } void gst_clapper_paintable_set_rotation (GstClapperPaintable *self, GstVideoOrientationMethod rotation) { GST_CLAPPER_PAINTABLE_LOCK (self); self->rotation = rotation; if (G_UNLIKELY (!calculate_display_par (self, &self->v_info))) { GST_CLAPPER_PAINTABLE_UNLOCK (self); return; } self->pending_resize = TRUE; GST_CLAPPER_PAINTABLE_UNLOCK (self); } GstVideoOrientationMethod gst_clapper_paintable_get_rotation (GstClapperPaintable *self) { GstVideoOrientationMethod rotation; GST_CLAPPER_PAINTABLE_LOCK (self); rotation = self->rotation; GST_CLAPPER_PAINTABLE_UNLOCK (self); return rotation; } /* * GdkPaintableInterface */ static void gst_clapper_paintable_snapshot_internal (GstClapperPaintable *self, GdkSnapshot *snapshot, gdouble width, gdouble height, gint widget_width, gint widget_height) { gfloat scale_x, scale_y; gdouble snapshot_width, snapshot_height; GskTransform *transform = NULL; GST_LOG_OBJECT (self, "Snapshot"); scale_x = (gfloat) width / self->display_width; scale_y = (gfloat) height / self->display_height; /* Apply black borders when keeping aspect ratio */ if (scale_x == scale_y || abs (scale_x - scale_y) <= FLT_EPSILON) { if (widget_height - height > 0) { /* XXX: Top uses integer to work with GTK rounding (not going offscreen) */ gint top_bar_height = (widget_height - height) / 2; gdouble bottom_bar_height = (widget_height - top_bar_height - height); gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, -top_bar_height)); gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, height, width, bottom_bar_height)); } else if (widget_width - width > 0) { gint left_bar_width = (widget_width - width) / 2; gdouble right_bar_width = (widget_width - left_bar_width - width); gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, -left_bar_width, height)); gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (width, 0, right_bar_width, height)); } } GST_CLAPPER_PAINTABLE_IMPORTER_LOCK (self); if (self->importer) { switch (self->rotation) { case GST_VIDEO_ORIENTATION_IDENTITY: default: snapshot_width = width; snapshot_height = height; break; case GST_VIDEO_ORIENTATION_90R: transform = gsk_transform_rotate (transform, 90); transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, -width)); snapshot_width = height; snapshot_height = width; break; case GST_VIDEO_ORIENTATION_180: transform = gsk_transform_rotate (transform, 180); transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, -height)); snapshot_width = width; snapshot_height = height; break; case GST_VIDEO_ORIENTATION_90L: transform = gsk_transform_rotate (transform, 270); transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-height, 0)); snapshot_width = height; snapshot_height = width; break; case GST_VIDEO_ORIENTATION_HORIZ: transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_y_axis ()); transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, 0)); snapshot_width = width; snapshot_height = height; break; case GST_VIDEO_ORIENTATION_VERT: transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_x_axis ()); transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, -height)); snapshot_width = width; snapshot_height = height; break; case GST_VIDEO_ORIENTATION_UL_LR: transform = gsk_transform_rotate (transform, 90); transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_x_axis ()); snapshot_width = height; snapshot_height = width; break; case GST_VIDEO_ORIENTATION_UR_LL: transform = gsk_transform_rotate (transform, 90); transform = gsk_transform_rotate_3d (transform, 180, graphene_vec3_y_axis ()); transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-height, -width)); snapshot_width = height; snapshot_height = width; break; } if (transform) { gtk_snapshot_transform (snapshot, transform); gsk_transform_unref (transform); } gst_clapper_importer_snapshot (self->importer, snapshot, snapshot_width, snapshot_height); } else { GST_LOG_OBJECT (self, "No texture importer, drawing black"); gtk_snapshot_append_color (snapshot, &self->bg, &GRAPHENE_RECT_INIT (0, 0, width, height)); } GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK (self); } static void gst_clapper_paintable_snapshot (GdkPaintable *paintable, GdkSnapshot *snapshot, gdouble width, gdouble height) { GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); GtkWidget *widget; gint widget_width = 0, widget_height = 0; if ((widget = g_weak_ref_get (&self->widget))) { widget_width = gtk_widget_get_width (widget); widget_height = gtk_widget_get_height (widget); g_object_unref (widget); } gst_clapper_paintable_snapshot_internal (self, snapshot, width, height, widget_width, widget_height); } static GdkPaintable * gst_clapper_paintable_get_current_image (GdkPaintable *paintable) { GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); GtkSnapshot *snapshot = gtk_snapshot_new (); /* Snapshot without widget size in order to get * paintable without black borders */ gst_clapper_paintable_snapshot_internal (self, snapshot, self->display_width, self->display_height, 0, 0); return gtk_snapshot_free_to_paintable (snapshot, NULL); } static gint gst_clapper_paintable_get_intrinsic_width (GdkPaintable *paintable) { GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); return self->display_width; } static gint gst_clapper_paintable_get_intrinsic_height (GdkPaintable *paintable) { GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); return self->display_height; } static gdouble gst_clapper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) { GstClapperPaintable *self = GST_CLAPPER_PAINTABLE_CAST (paintable); return self->display_aspect_ratio; } static void gst_clapper_paintable_iface_init (GdkPaintableInterface *iface) { iface->snapshot = gst_clapper_paintable_snapshot; iface->get_current_image = gst_clapper_paintable_get_current_image; iface->get_intrinsic_width = gst_clapper_paintable_get_intrinsic_width; iface->get_intrinsic_height = gst_clapper_paintable_get_intrinsic_height; iface->get_intrinsic_aspect_ratio = gst_clapper_paintable_get_intrinsic_aspect_ratio; } clapper-0.8.0/src/lib/gst/plugin/gstclapperpaintable.h000066400000000000000000000064101474353110700230000ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include #include #include #include "gstclapperimporter.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_PAINTABLE (gst_clapper_paintable_get_type()) G_DECLARE_FINAL_TYPE (GstClapperPaintable, gst_clapper_paintable, GST, CLAPPER_PAINTABLE, GObject) #define GST_CLAPPER_PAINTABLE_CAST(obj) ((GstClapperPaintable *)(obj)) #define GST_CLAPPER_PAINTABLE_GET_LOCK(obj) (&GST_CLAPPER_PAINTABLE_CAST(obj)->lock) #define GST_CLAPPER_PAINTABLE_LOCK(obj) g_mutex_lock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj)) #define GST_CLAPPER_PAINTABLE_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_PAINTABLE_GET_LOCK(obj)) #define GST_CLAPPER_PAINTABLE_IMPORTER_GET_LOCK(obj) (&GST_CLAPPER_PAINTABLE_CAST(obj)->importer_lock) #define GST_CLAPPER_PAINTABLE_IMPORTER_LOCK(obj) g_mutex_lock (GST_CLAPPER_PAINTABLE_IMPORTER_GET_LOCK(obj)) #define GST_CLAPPER_PAINTABLE_IMPORTER_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_PAINTABLE_IMPORTER_GET_LOCK(obj)) struct _GstClapperPaintable { GObject parent; GMutex lock; GMutex importer_lock; GstVideoInfo v_info; GdkRGBA bg; GWeakRef widget; GstClapperImporter *importer; /* Sink properties */ gint par_n, par_d; GstVideoOrientationMethod rotation; /* Resize */ gboolean pending_resize; guint display_ratio_num; guint display_ratio_den; /* GdkPaintableInterface */ gint display_width; gint display_height; gdouble display_aspect_ratio; /* Pending draw signal id */ guint draw_id; }; GstClapperPaintable * gst_clapper_paintable_new (void); void gst_clapper_paintable_queue_draw (GstClapperPaintable *paintable); void gst_clapper_paintable_set_widget (GstClapperPaintable *paintable, GtkWidget *widget); void gst_clapper_paintable_set_importer (GstClapperPaintable *paintable, GstClapperImporter *importer); gboolean gst_clapper_paintable_set_video_info (GstClapperPaintable *paintable, const GstVideoInfo *v_info); void gst_clapper_paintable_set_pixel_aspect_ratio (GstClapperPaintable *paintable, gint par_n, gint par_d); void gst_clapper_paintable_set_rotation (GstClapperPaintable *paintable, GstVideoOrientationMethod rotation); GstVideoOrientationMethod gst_clapper_paintable_get_rotation (GstClapperPaintable *paintable); G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/gstclappersink.c000066400000000000000000000674741474353110700220210ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclappersink.h" #include "gstgtkutils.h" #define DEFAULT_FORCE_ASPECT_RATIO TRUE #define DEFAULT_PAR_N 1 #define DEFAULT_PAR_D 1 #define DEFAULT_KEEP_LAST_FRAME FALSE #define DEFAULT_ROTATION GST_VIDEO_ORIENTATION_AUTO #define WINDOW_CSS_CLASS_NAME "clappersinkwindow" enum { PROP_0, PROP_WIDGET, PROP_FORCE_ASPECT_RATIO, PROP_PIXEL_ASPECT_RATIO, PROP_KEEP_LAST_FRAME, PROP_ROTATE_METHOD, PROP_LAST }; #define GST_CAT_DEFAULT gst_clapper_sink_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); static void gst_clapper_sink_navigation_interface_init ( GstNavigationInterface *iface); #define parent_class gst_clapper_sink_parent_class G_DEFINE_TYPE_WITH_CODE (GstClapperSink, gst_clapper_sink, GST_TYPE_VIDEO_SINK, G_IMPLEMENT_INTERFACE (GST_TYPE_NAVIGATION, gst_clapper_sink_navigation_interface_init)); GST_ELEMENT_REGISTER_DEFINE (clappersink, "clappersink", GST_RANK_NONE, GST_TYPE_CLAPPER_SINK); static void window_clear_no_lock (GstClapperSink *self) { if (!self->window) return; GST_TRACE_OBJECT (self, "Window clear"); if (self->window_destroy_id) { g_signal_handler_disconnect (self->window, self->window_destroy_id); self->window_destroy_id = 0; } self->window = NULL; } static void widget_clear_no_lock (GstClapperSink *self) { if (!self->widget) return; GST_TRACE_OBJECT (self, "Widget clear"); if (self->widget_destroy_id) { g_signal_handler_disconnect (self->widget, self->widget_destroy_id); self->widget_destroy_id = 0; } g_clear_object (&self->widget); } static void widget_destroy_cb (GtkWidget *widget, GstClapperSink *self) { GST_CLAPPER_SINK_LOCK (self); widget_clear_no_lock (self); GST_CLAPPER_SINK_UNLOCK (self); } static void window_destroy_cb (GtkWidget *window, GstClapperSink *self) { GST_DEBUG_OBJECT (self, "Window destroy"); GST_CLAPPER_SINK_LOCK (self); widget_clear_no_lock (self); window_clear_no_lock (self); GST_CLAPPER_SINK_UNLOCK (self); } static void calculate_stream_coords (GstClapperSink *self, GtkWidget *widget, gdouble x, gdouble y, gdouble *stream_x, gdouble *stream_y) { GstVideoRectangle result; gint scaled_width, scaled_height, scale_factor; gint video_width, video_height; gboolean force_aspect_ratio; GST_CLAPPER_SINK_LOCK (self); gst_gtk_get_width_height_for_rotation (GST_VIDEO_INFO_WIDTH (&self->v_info), GST_VIDEO_INFO_HEIGHT (&self->v_info), &video_height, &video_width, gst_clapper_paintable_get_rotation (self->paintable)); force_aspect_ratio = self->force_aspect_ratio; GST_CLAPPER_SINK_UNLOCK (self); scale_factor = gtk_widget_get_scale_factor (widget); scaled_width = gtk_widget_get_width (widget) * scale_factor; scaled_height = gtk_widget_get_height (widget) * scale_factor; if (force_aspect_ratio) { GstVideoRectangle src, dst; src.x = 0; src.y = 0; src.w = gdk_paintable_get_intrinsic_width ((GdkPaintable *) self->paintable); src.h = gdk_paintable_get_intrinsic_height ((GdkPaintable *) self->paintable); dst.x = 0; dst.y = 0; dst.w = scaled_width; dst.h = scaled_height; gst_video_center_rect (&src, &dst, &result, TRUE); } else { result.x = 0; result.y = 0; result.w = scaled_width; result.h = scaled_height; } /* Display coordinates to stream coordinates */ *stream_x = (result.w > 0) ? (x - result.x) / result.w * video_width : 0; *stream_y = (result.h > 0) ? (y - result.y) / result.h * video_height : 0; /* Clip to stream size */ *stream_x = CLAMP (*stream_x, 0, video_width); *stream_y = CLAMP (*stream_y, 0, video_height); GST_LOG ("Transform coords %fx%f => %fx%f", x, y, *stream_x, *stream_y); } static void gst_clapper_sink_widget_motion_event (GtkEventControllerMotion *motion, gdouble x, gdouble y, GstClapperSink *self) { GtkWidget *widget; gdouble stream_x, stream_y; gboolean is_inactive; if (x == self->last_pos_x && y == self->last_pos_y) return; GST_OBJECT_LOCK (self); is_inactive = (GST_STATE (self) < GST_STATE_PLAYING); GST_OBJECT_UNLOCK (self); if (is_inactive) return; self->last_pos_x = x; self->last_pos_y = y; widget = gtk_event_controller_get_widget ((GtkEventController *) motion); calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y); GST_LOG ("Event \"mouse-move\", x: %f, y: %f", stream_x, stream_y); gst_navigation_send_mouse_event ((GstNavigation *) self, "mouse-move", 0, stream_x, stream_y); } static void gst_clapper_sink_widget_button_event (GtkGestureClick *click, gint n_press, gdouble x, gdouble y, GstClapperSink *self) { GtkWidget *widget; GdkEvent *event; GdkEventType event_type; const gchar *event_name; gdouble stream_x, stream_y; gboolean is_inactive; GST_OBJECT_LOCK (self); is_inactive = (GST_STATE (self) < GST_STATE_PLAYING); GST_OBJECT_UNLOCK (self); if (is_inactive) return; event = gtk_event_controller_get_current_event ((GtkEventController *) click); event_type = gdk_event_get_event_type (event); /* FIXME: Touchscreen handling should probably use new touch events from GStreamer 1.22 */ event_name = (event_type == GDK_BUTTON_PRESS || event_type == GDK_TOUCH_BEGIN) ? "mouse-button-press" : (event_type == GDK_BUTTON_RELEASE || event_type == GDK_TOUCH_END) ? "mouse-button-release" : NULL; /* Can be NULL on touch */ if (!event_name) return; widget = gtk_event_controller_get_widget ((GtkEventController *) click); calculate_stream_coords (self, widget, x, y, &stream_x, &stream_y); GST_LOG ("Event \"%s\", x: %f, y: %f", event_name, stream_x, stream_y); /* Gesture is set to handle only primary button, so we do not have to check */ gst_navigation_send_mouse_event ((GstNavigation *) self, event_name, 1, stream_x, stream_y); } /* Must call from main thread only with a lock */ static GtkWidget * gst_clapper_sink_get_widget (GstClapperSink *self) { if (G_UNLIKELY (!self->widget)) { GtkEventController *controller; GtkGesture *gesture; /* Make sure GTK is initialized */ if (!gtk_init_check ()) { GST_ERROR_OBJECT (self, "Could not ensure GTK initialization"); return NULL; } self->widget = gtk_picture_new (); /* Otherwise widget in grid will appear as a 1x1px * video which might be misleading for users */ gtk_widget_set_hexpand (self->widget, TRUE); gtk_widget_set_vexpand (self->widget, TRUE); gtk_widget_set_focusable (self->widget, TRUE); gtk_widget_set_can_focus (self->widget, TRUE); controller = gtk_event_controller_motion_new (); g_signal_connect (controller, "motion", G_CALLBACK (gst_clapper_sink_widget_motion_event), self); gtk_widget_add_controller (self->widget, controller); gesture = gtk_gesture_click_new (); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 1); g_signal_connect (gesture, "pressed", G_CALLBACK (gst_clapper_sink_widget_button_event), self); g_signal_connect (gesture, "released", G_CALLBACK (gst_clapper_sink_widget_button_event), self); gtk_widget_add_controller (self->widget, GTK_EVENT_CONTROLLER (gesture)); /* TODO: Implement touch events once we depend on GStreamer 1.22 */ /* Take floating ref */ g_object_ref_sink (self->widget); /* Set widget back pointer */ gst_clapper_paintable_set_widget (self->paintable, self->widget); /* Set earlier remembered property */ #if GTK_CHECK_VERSION(4,8,0) if (self->force_aspect_ratio) gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_CONTAIN); else gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_FILL); #else gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget), self->force_aspect_ratio); #endif gtk_picture_set_paintable (GTK_PICTURE (self->widget), GDK_PAINTABLE (self->paintable)); self->widget_destroy_id = g_signal_connect (self->widget, "destroy", G_CALLBACK (widget_destroy_cb), self); } return self->widget; } static GtkWidget * gst_clapper_sink_obtain_widget (GstClapperSink *self) { GtkWidget *widget; GST_CLAPPER_SINK_LOCK (self); widget = gst_clapper_sink_get_widget (self); if (widget) g_object_ref (widget); GST_CLAPPER_SINK_UNLOCK (self); return widget; } static void gst_clapper_sink_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); GST_CLAPPER_SINK_LOCK (self); switch (prop_id) { case PROP_WIDGET: if (self->widget) { g_value_set_object (value, self->widget); } else { GtkWidget *widget; GST_CLAPPER_SINK_UNLOCK (self); widget = gst_gtk_invoke_on_main ((GThreadFunc) gst_clapper_sink_obtain_widget, self); GST_CLAPPER_SINK_LOCK (self); g_value_set_object (value, widget); g_object_unref (widget); } break; case PROP_FORCE_ASPECT_RATIO: g_value_set_boolean (value, self->force_aspect_ratio); break; case PROP_PIXEL_ASPECT_RATIO: gst_value_set_fraction (value, self->par_n, self->par_d); break; case PROP_KEEP_LAST_FRAME: g_value_set_boolean (value, self->keep_last_frame); break; case PROP_ROTATE_METHOD: g_value_set_enum (value, self->rotation_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_CLAPPER_SINK_UNLOCK (self); } static void gst_clapper_sink_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); GST_CLAPPER_SINK_LOCK (self); switch (prop_id) { case PROP_FORCE_ASPECT_RATIO: self->force_aspect_ratio = g_value_get_boolean (value); if (self->widget) { #if GTK_CHECK_VERSION(4,8,0) if (self->force_aspect_ratio) gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_CONTAIN); else gtk_picture_set_content_fit (GTK_PICTURE (self->widget), GTK_CONTENT_FIT_FILL); #else gtk_picture_set_keep_aspect_ratio (GTK_PICTURE (self->widget), self->force_aspect_ratio); #endif } break; case PROP_PIXEL_ASPECT_RATIO: self->par_n = gst_value_get_fraction_numerator (value); self->par_d = gst_value_get_fraction_denominator (value); gst_clapper_paintable_set_pixel_aspect_ratio (self->paintable, self->par_n, self->par_d); break; case PROP_KEEP_LAST_FRAME: self->keep_last_frame = g_value_get_boolean (value); break; case PROP_ROTATE_METHOD: self->rotation_mode = g_value_get_enum (value); gst_clapper_paintable_set_rotation (self->paintable, (self->rotation_mode == GST_VIDEO_ORIENTATION_AUTO) ? self->stream_orientation : self->rotation_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_CLAPPER_SINK_UNLOCK (self); } static void gst_clapper_sink_navigation_send_event (GstNavigation *navigation, GstStructure *structure) { GstClapperSink *sink = GST_CLAPPER_SINK_CAST (navigation); GstEvent *event; GST_TRACE_OBJECT (sink, "Navigation event: %" GST_PTR_FORMAT, structure); event = gst_event_new_navigation (structure); if (G_LIKELY (event)) { GstPad *pad; pad = gst_pad_get_peer (GST_VIDEO_SINK_PAD (sink)); if (G_LIKELY (pad)) { if (!gst_pad_send_event (pad, gst_event_ref (event))) { /* If upstream didn't handle the event we'll post a message with it * for the application in case it wants to do something with it */ gst_element_post_message (GST_ELEMENT_CAST (sink), gst_navigation_message_new_event (GST_OBJECT_CAST (sink), event)); } gst_object_unref (pad); } gst_event_unref (event); } } static gboolean gst_clapper_sink_propose_allocation (GstBaseSink *bsink, GstQuery *query) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); GstClapperImporter *importer = NULL; GstCaps *caps; GstVideoInfo info; guint size, min_buffers; gboolean need_pool; gst_query_parse_allocation (query, &caps, &need_pool); if (!caps) { GST_DEBUG_OBJECT (self, "No caps specified"); return FALSE; } if (!gst_video_info_from_caps (&info, caps)) { GST_DEBUG_OBJECT (self, "Invalid caps specified"); return FALSE; } GST_CLAPPER_SINK_LOCK (self); if (self->importer) importer = gst_object_ref (self->importer); GST_CLAPPER_SINK_UNLOCK (self); if (!importer) { GST_DEBUG_OBJECT (self, "No importer to propose allocation"); return FALSE; } /* Normal size of a frame */ size = GST_VIDEO_INFO_SIZE (&info); /* We keep around current buffer and a pending one */ min_buffers = 3; if (need_pool) { GstBufferPool *pool; GstStructure *config = NULL; GST_DEBUG_OBJECT (self, "Need to create buffer pool"); pool = gst_clapper_importer_create_pool (importer, &config); if (pool) { /* If we did not get config, use default one */ if (!config) config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_set_params (config, caps, size, min_buffers, 0); if (!gst_buffer_pool_set_config (pool, config)) { gst_object_unref (pool); gst_object_unref (importer); GST_ERROR_OBJECT (self, "Failed to set config"); return FALSE; } gst_query_add_allocation_pool (query, pool, size, min_buffers, 0); gst_object_unref (pool); } else if (config) { GST_WARNING_OBJECT (self, "Got config without a pool to apply it"); gst_structure_free (config); } } gst_clapper_importer_add_allocation_metas (importer, query); gst_object_unref (importer); return TRUE; } static gboolean gst_clapper_sink_query (GstBaseSink *bsink, GstQuery *query) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); gboolean res = FALSE; if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT) { GST_CLAPPER_SINK_LOCK (self); res = gst_clapper_importer_loader_handle_context_query (self->loader, bsink, query); GST_CLAPPER_SINK_UNLOCK (self); } if (res) return TRUE; return GST_BASE_SINK_CLASS (parent_class)->query (bsink, query); } static gboolean gst_clapper_sink_start_on_main (GstClapperSink *self) { GtkWidget *widget; GST_CLAPPER_SINK_LOCK (self); /* Make sure widget is created */ if (!(widget = gst_clapper_sink_get_widget (self))) { GST_CLAPPER_SINK_UNLOCK (self); return FALSE; } /* When no toplevel window, make our own */ if (G_UNLIKELY (!gtk_widget_get_root (widget) && !self->window)) { GtkWidget *toplevel, *parent; GtkCssProvider *provider; gchar *win_title; if ((parent = gtk_widget_get_parent (widget))) { GtkWidget *temp_parent; while ((temp_parent = gtk_widget_get_parent (parent))) parent = temp_parent; } toplevel = (parent) ? parent : widget; self->window = (GtkWindow *) gtk_window_new (); gtk_widget_add_css_class (GTK_WIDGET (self->window), WINDOW_CSS_CLASS_NAME); provider = gtk_css_provider_new (); gtk_css_provider_load_from_data (provider, "." WINDOW_CSS_CLASS_NAME " { background: none; }", -1); gtk_style_context_add_provider_for_display ( gdk_display_get_default (), GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); g_object_unref (provider); win_title = g_strdup_printf ("Clapper Sink - GTK %u.%u.%u Window", gtk_get_major_version (), gtk_get_minor_version (), gtk_get_micro_version ()); /* Set some common default size, adding stock headerbar height * to it in order to display 4:3 aspect video widget */ gtk_window_set_default_size (self->window, 640, 480 + 37); gtk_window_set_title (self->window, win_title); gtk_window_set_child (self->window, toplevel); g_free (win_title); self->window_destroy_id = g_signal_connect (self->window, "destroy", G_CALLBACK (window_destroy_cb), self); GST_INFO_OBJECT (self, "Presenting window"); gtk_window_present (self->window); } GST_CLAPPER_SINK_UNLOCK (self); return TRUE; } static gboolean gst_clapper_sink_start (GstBaseSink *bsink) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); GST_INFO_OBJECT (self, "Start"); if (G_UNLIKELY (!(! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) gst_clapper_sink_start_on_main, self)))) { GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("GtkWidget could not be created"), (NULL)); return FALSE; } return TRUE; } static gboolean gst_clapper_sink_stop_on_main (GstClapperSink *self) { GtkWindow *window = NULL; GST_CLAPPER_SINK_LOCK (self); if (self->window) window = g_object_ref (self->window); GST_CLAPPER_SINK_UNLOCK (self); if (window) { gtk_window_destroy (window); g_object_unref (window); } return TRUE; } static gboolean gst_clapper_sink_stop (GstBaseSink *bsink) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); gboolean has_window; GST_INFO_OBJECT (self, "Stop"); GST_CLAPPER_SINK_LOCK (self); has_window = (self->window != NULL); GST_CLAPPER_SINK_UNLOCK (self); if (G_UNLIKELY (has_window)) { return (! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) gst_clapper_sink_stop_on_main, self)); } return TRUE; } static gboolean gst_clapper_sink_event (GstBaseSink *bsink, GstEvent *event) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); GstTagList *taglist; GstVideoOrientationMethod orientation; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_TAG: gst_event_parse_tag (event, &taglist); if (gst_video_orientation_from_tag (taglist, &orientation)) { GST_CLAPPER_SINK_LOCK (self); self->stream_orientation = orientation; if (self->rotation_mode == GST_VIDEO_ORIENTATION_AUTO) gst_clapper_paintable_set_rotation (self->paintable, orientation); GST_CLAPPER_SINK_UNLOCK (self); } break; default: break; } return GST_BASE_SINK_CLASS (parent_class)->event (bsink, event); } static GstStateChangeReturn gst_clapper_sink_change_state (GstElement *element, GstStateChange transition) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (element); GST_DEBUG_OBJECT (self, "Changing state: %s => %s", gst_element_state_get_name (GST_STATE_TRANSITION_CURRENT (transition)), gst_element_state_get_name (GST_STATE_TRANSITION_NEXT (transition))); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: /* Reset stream_orientation */ GST_CLAPPER_SINK_LOCK (self); self->stream_orientation = GST_VIDEO_ORIENTATION_IDENTITY; if (self->rotation_mode == GST_VIDEO_ORIENTATION_AUTO) gst_clapper_paintable_set_rotation (self->paintable, self->stream_orientation); GST_CLAPPER_SINK_UNLOCK (self); break; case GST_STATE_CHANGE_PAUSED_TO_READY: GST_CLAPPER_SINK_LOCK (self); if (!self->keep_last_frame && self->importer) { gst_clapper_importer_set_buffer (self->importer, NULL); gst_clapper_paintable_queue_draw (self->paintable); } GST_CLAPPER_SINK_UNLOCK (self); break; default: break; } return GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); } static void gst_clapper_sink_get_times (GstBaseSink *bsink, GstBuffer *buffer, GstClockTime *start, GstClockTime *end) { if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) return; *start = GST_BUFFER_TIMESTAMP (buffer); if (GST_BUFFER_DURATION_IS_VALID (buffer)) { *end = *start + GST_BUFFER_DURATION (buffer); } else { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); gint fps_n, fps_d; GST_CLAPPER_SINK_LOCK (self); fps_n = GST_VIDEO_INFO_FPS_N (&self->v_info); fps_d = GST_VIDEO_INFO_FPS_D (&self->v_info); GST_CLAPPER_SINK_UNLOCK (self); if (fps_n > 0) *end = *start + gst_util_uint64_scale_int (GST_SECOND, fps_d, fps_n); } } static GstCaps * gst_clapper_sink_get_caps (GstBaseSink *bsink, GstCaps *filter) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); GstCaps *result, *tmp; tmp = gst_clapper_importer_loader_make_actual_caps (self->loader); if (filter) { GST_DEBUG ("Intersecting with filter caps: %" GST_PTR_FORMAT, filter); result = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); gst_caps_unref (tmp); } else { result = tmp; } GST_DEBUG ("Returning caps: %" GST_PTR_FORMAT, result); return result; } static gboolean gst_clapper_sink_set_caps (GstBaseSink *bsink, GstCaps *caps) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (bsink); GST_INFO_OBJECT (self, "Set caps: %" GST_PTR_FORMAT, caps); GST_CLAPPER_SINK_LOCK (self); if (G_UNLIKELY (!self->widget)) { GST_CLAPPER_SINK_UNLOCK (self); GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Output widget was destroyed"), (NULL)); return FALSE; } if (!gst_clapper_importer_loader_find_importer_for_caps (self->loader, caps, &self->importer)) { GST_CLAPPER_SINK_UNLOCK (self); GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("No importer for given caps found"), (NULL)); return FALSE; } gst_clapper_paintable_set_importer (self->paintable, self->importer); GST_CLAPPER_SINK_UNLOCK (self); return GST_BASE_SINK_CLASS (parent_class)->set_caps (bsink, caps); } static gboolean gst_clapper_sink_set_info (GstVideoSink *vsink, GstCaps *caps, const GstVideoInfo *info) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink); gboolean res; GST_CLAPPER_SINK_LOCK (self); self->v_info = *info; GST_DEBUG_OBJECT (self, "Video info changed"); res = gst_clapper_paintable_set_video_info (self->paintable, info); GST_CLAPPER_SINK_UNLOCK (self); return res; } static GstFlowReturn gst_clapper_sink_show_frame (GstVideoSink *vsink, GstBuffer *buffer) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (vsink); GST_TRACE ("Got %" GST_PTR_FORMAT, buffer); GST_CLAPPER_SINK_LOCK (self); if (G_UNLIKELY (!self->widget)) { GST_CLAPPER_SINK_UNLOCK (self); GST_ELEMENT_ERROR (self, RESOURCE, NOT_FOUND, ("Output widget was destroyed"), (NULL)); return GST_FLOW_ERROR; } gst_clapper_importer_set_buffer (self->importer, buffer); gst_clapper_paintable_queue_draw (self->paintable); GST_CLAPPER_SINK_UNLOCK (self); return GST_FLOW_OK; } static void gst_clapper_sink_init (GstClapperSink *self) { GObjectClass *gobject_class; gobject_class = (GObjectClass *) GST_CLAPPER_SINK_GET_CLASS (self); /* HACK: install here instead of class init to avoid GStreamer * plugin scanner GObject type conflicts with older GTK versions */ if (!g_object_class_find_property (gobject_class, "widget")) { g_object_class_install_property (gobject_class, PROP_WIDGET, g_param_spec_object ("widget", "GTK Widget", "The GtkWidget to place in the widget hierarchy", GTK_TYPE_WIDGET, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); } self->force_aspect_ratio = DEFAULT_FORCE_ASPECT_RATIO; self->par_n = DEFAULT_PAR_N; self->par_d = DEFAULT_PAR_D; self->keep_last_frame = DEFAULT_KEEP_LAST_FRAME; self->rotation_mode = DEFAULT_ROTATION; g_mutex_init (&self->lock); gst_video_info_init (&self->v_info); self->paintable = gst_clapper_paintable_new (); self->loader = gst_clapper_importer_loader_new (); } static void gst_clapper_sink_dispose (GObject *object) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); GST_CLAPPER_SINK_LOCK (self); window_clear_no_lock (self); widget_clear_no_lock (self); g_clear_object (&self->paintable); gst_clear_object (&self->importer); GST_CLAPPER_SINK_UNLOCK (self); GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); } static void gst_clapper_sink_finalize (GObject *object) { GstClapperSink *self = GST_CLAPPER_SINK_CAST (object); GST_TRACE ("Finalize"); gst_clear_object (&self->loader); g_mutex_clear (&self->lock); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static void gst_clapper_sink_class_init (GstClapperSinkClass *klass) { GstPadTemplate *sink_pad_templ; GObjectClass *gobject_class = (GObjectClass *) klass; GstElementClass *gstelement_class = (GstElementClass *) klass; GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass; GstVideoSinkClass *gstvideosink_class = (GstVideoSinkClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappersink", 0, "Clapper Sink"); gobject_class->get_property = gst_clapper_sink_get_property; gobject_class->set_property = gst_clapper_sink_set_property; gobject_class->dispose = gst_clapper_sink_dispose; gobject_class->finalize = gst_clapper_sink_finalize; g_object_class_install_property (gobject_class, PROP_FORCE_ASPECT_RATIO, g_param_spec_boolean ("force-aspect-ratio", "Force aspect ratio", "When enabled, scaling will respect original aspect ratio", DEFAULT_FORCE_ASPECT_RATIO, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PIXEL_ASPECT_RATIO, gst_param_spec_fraction ("pixel-aspect-ratio", "Pixel Aspect Ratio", "The pixel aspect ratio of the device", DEFAULT_PAR_N, DEFAULT_PAR_D, G_MAXINT, 1, 1, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_KEEP_LAST_FRAME, g_param_spec_boolean ("keep-last-frame", "Keep last frame", "Keep showing last video frame after playback instead of black screen", DEFAULT_KEEP_LAST_FRAME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_ROTATE_METHOD, g_param_spec_enum ("rotate-method", "Rotate Method", "Rotate method to use", GST_TYPE_VIDEO_ORIENTATION_METHOD, DEFAULT_ROTATION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gstelement_class->change_state = gst_clapper_sink_change_state; gstbasesink_class->get_caps = gst_clapper_sink_get_caps; gstbasesink_class->set_caps = gst_clapper_sink_set_caps; gstbasesink_class->get_times = gst_clapper_sink_get_times; gstbasesink_class->propose_allocation = gst_clapper_sink_propose_allocation; gstbasesink_class->query = gst_clapper_sink_query; gstbasesink_class->start = gst_clapper_sink_start; gstbasesink_class->stop = gst_clapper_sink_stop; gstbasesink_class->event = gst_clapper_sink_event; gstvideosink_class->set_info = gst_clapper_sink_set_info; gstvideosink_class->show_frame = gst_clapper_sink_show_frame; gst_element_class_set_static_metadata (gstelement_class, "Clapper video sink", "Sink/Video", "A GTK4 video sink used by Clapper media player", "Rafał Dzięgiel "); sink_pad_templ = gst_clapper_importer_loader_make_sink_pad_template (); gst_element_class_add_pad_template (gstelement_class, sink_pad_templ); } /* * GstNavigationInterface */ static void gst_clapper_sink_navigation_interface_init (GstNavigationInterface *iface) { /* TODO: Port to "send_event_simple" once we depend on GStreamer 1.22 */ iface->send_event = gst_clapper_sink_navigation_send_event; } clapper-0.8.0/src/lib/gst/plugin/gstclappersink.h000066400000000000000000000043461474353110700220130ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include #include #include #include #include "gstclapperpaintable.h" #include "gstclapperimporterloader.h" #include "gstclapperimporter.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_SINK (gst_clapper_sink_get_type()) G_DECLARE_FINAL_TYPE (GstClapperSink, gst_clapper_sink, GST, CLAPPER_SINK, GstVideoSink) #define GST_CLAPPER_SINK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CLAPPER_SINK, GstClapperSinkClass)) #define GST_CLAPPER_SINK_CAST(obj) ((GstClapperSink *)(obj)) #define GST_CLAPPER_SINK_GET_LOCK(obj) (&GST_CLAPPER_SINK_CAST(obj)->lock) #define GST_CLAPPER_SINK_LOCK(obj) g_mutex_lock (GST_CLAPPER_SINK_GET_LOCK(obj)) #define GST_CLAPPER_SINK_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_SINK_GET_LOCK(obj)) struct _GstClapperSink { GstVideoSink parent; GMutex lock; GstClapperPaintable *paintable; GstClapperImporterLoader *loader; GstClapperImporter *importer; GstVideoInfo v_info; GstVideoOrientationMethod stream_orientation; GtkWidget *widget; GtkWindow *window; /* Properties */ gboolean force_aspect_ratio; gint par_n, par_d; gboolean keep_last_frame; GstVideoOrientationMethod rotation_mode; /* Position coords */ gdouble last_pos_x; gdouble last_pos_y; gulong widget_destroy_id; gulong window_destroy_id; }; GST_ELEMENT_REGISTER_DECLARE (clappersink); G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/gstgdkformats.h000066400000000000000000000027011474353110700216320ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #if G_BYTE_ORDER == G_LITTLE_ENDIAN #define GST_GDK_MEMORY_ENDIAN_FORMATS "RGBA64_LE" #define GST_GDK_GL_TEXTURE_ENDIAN_FORMATS "RGBA64_LE" #elif G_BYTE_ORDER == G_BIG_ENDIAN #define GST_GDK_MEMORY_ENDIAN_FORMATS "RGBA64_BE" #define GST_GDK_GL_TEXTURE_ENDIAN_FORMATS "RGBA64_BE" #endif #define GST_GDK_MEMORY_FORMATS \ GST_GDK_MEMORY_ENDIAN_FORMATS ", " \ "ABGR, BGRA, ARGB, RGBA, BGRx, RGBx, BGR, RGB" /* Formats that `GdkGLTexture` supports */ #define GST_GDK_GL_TEXTURE_FORMATS \ GST_GDK_GL_TEXTURE_ENDIAN_FORMATS ", " \ "RGBA, RGBx, RGB" clapper-0.8.0/src/lib/gst/plugin/gstgtkutils.c000066400000000000000000000110011474353110700213230ustar00rootroot00000000000000/* * GStreamer * Copyright (C) 2015 Matthew Waters * Copyright (C) 2015 Thibault Saunier * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "gstgtkutils.h" #define _IS_FRAME_PREMULTIPLIED(f) (GST_VIDEO_INFO_FLAG_IS_SET (&(f)->info, GST_VIDEO_FLAG_PREMULTIPLIED_ALPHA)) struct invoke_context { GThreadFunc func; gpointer data; GMutex lock; GCond cond; gboolean fired; gpointer res; }; static gboolean gst_gtk_invoke_func (struct invoke_context *info) { g_mutex_lock (&info->lock); info->res = info->func (info->data); info->fired = TRUE; g_cond_signal (&info->cond); g_mutex_unlock (&info->lock); return G_SOURCE_REMOVE; } gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data) { GMainContext *main_context = g_main_context_default (); struct invoke_context info; g_mutex_init (&info.lock); g_cond_init (&info.cond); info.fired = FALSE; info.func = func; info.data = data; g_main_context_invoke (main_context, (GSourceFunc) gst_gtk_invoke_func, &info); g_mutex_lock (&info.lock); while (!info.fired) g_cond_wait (&info.cond, &info.lock); g_mutex_unlock (&info.lock); g_mutex_clear (&info.lock); g_cond_clear (&info.cond); return info.res; } static GdkMemoryFormat gst_gdk_memory_format_from_frame (GstVideoFrame *frame) { switch (GST_VIDEO_FRAME_FORMAT (frame)) { case GST_VIDEO_FORMAT_RGBA64_LE: case GST_VIDEO_FORMAT_RGBA64_BE: return (_IS_FRAME_PREMULTIPLIED (frame)) ? GDK_MEMORY_R16G16B16A16_PREMULTIPLIED : GDK_MEMORY_R16G16B16A16; case GST_VIDEO_FORMAT_RGBA: return (_IS_FRAME_PREMULTIPLIED (frame)) ? GDK_MEMORY_R8G8B8A8_PREMULTIPLIED : GDK_MEMORY_R8G8B8A8; case GST_VIDEO_FORMAT_BGRA: return (_IS_FRAME_PREMULTIPLIED (frame)) ? GDK_MEMORY_B8G8R8A8_PREMULTIPLIED : GDK_MEMORY_B8G8R8A8; case GST_VIDEO_FORMAT_ARGB: return (_IS_FRAME_PREMULTIPLIED (frame)) ? GDK_MEMORY_A8R8G8B8_PREMULTIPLIED : GDK_MEMORY_A8R8G8B8; case GST_VIDEO_FORMAT_ABGR: /* GTK is missing premultiplied ABGR support */ return GDK_MEMORY_A8B8G8R8; case GST_VIDEO_FORMAT_RGBx: return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; case GST_VIDEO_FORMAT_BGRx: return GDK_MEMORY_B8G8R8A8_PREMULTIPLIED; case GST_VIDEO_FORMAT_RGB: return GDK_MEMORY_R8G8B8; case GST_VIDEO_FORMAT_BGR: return GDK_MEMORY_B8G8R8; default: break; } /* This should never happen as long as above switch statement * is updated when new formats are added to caps */ g_assert_not_reached (); /* Fallback format */ return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; } GdkTexture * gst_video_frame_into_gdk_texture (GstVideoFrame *frame) { GdkTexture *texture; GBytes *bytes; bytes = g_bytes_new_with_free_func ( GST_VIDEO_FRAME_PLANE_DATA (frame, 0), GST_VIDEO_FRAME_HEIGHT (frame) * GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0), (GDestroyNotify) gst_buffer_unref, gst_buffer_ref (frame->buffer)); texture = gdk_memory_texture_new ( GST_VIDEO_FRAME_WIDTH (frame), GST_VIDEO_FRAME_HEIGHT (frame), gst_gdk_memory_format_from_frame (frame), bytes, GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0)); g_bytes_unref (bytes); return texture; } void gst_gtk_get_width_height_for_rotation (gint width, gint height, gint *out_width, gint *out_height, GstVideoOrientationMethod rotation) { switch (rotation) { case GST_VIDEO_ORIENTATION_90R: case GST_VIDEO_ORIENTATION_90L: case GST_VIDEO_ORIENTATION_UL_LR: case GST_VIDEO_ORIENTATION_UR_LL: *out_width = height; *out_height = width; break; default: *out_width = width; *out_height = height; break; } } clapper-0.8.0/src/lib/gst/plugin/gstgtkutils.h000066400000000000000000000030511474353110700213360ustar00rootroot00000000000000/* * GStreamer * Copyright (C) 2015 Matthew Waters * Copyright (C) 2015 Thibault Saunier * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include #include #include #include "gst/plugin/clapper-gst-visibility.h" G_BEGIN_DECLS CLAPPER_GST_API gpointer gst_gtk_invoke_on_main (GThreadFunc func, gpointer data); CLAPPER_GST_API GdkTexture * gst_video_frame_into_gdk_texture (GstVideoFrame *frame); void gst_gtk_get_width_height_for_rotation (gint width, gint height, gint *out_width, gint *out_height, GstVideoOrientationMethod rotation); G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/gstplugin.c000066400000000000000000000025261474353110700207670ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "gstclappersink.h" static gboolean plugin_init (GstPlugin *plugin) { if (!g_module_supported ()) return FALSE; gst_plugin_add_dependency_simple (plugin, NULL, CLAPPER_SINK_IMPORTER_PATH, NULL, GST_PLUGIN_DEPENDENCY_FLAG_NONE); return GST_ELEMENT_REGISTER (clappersink, plugin); } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, clapper, "Clapper elements", plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) clapper-0.8.0/src/lib/gst/plugin/handlers/000077500000000000000000000000001474353110700204025ustar00rootroot00000000000000clapper-0.8.0/src/lib/gst/plugin/handlers/gl/000077500000000000000000000000001474353110700210045ustar00rootroot00000000000000clapper-0.8.0/src/lib/gst/plugin/handlers/gl/gstclapperglcontexthandler.c000066400000000000000000000440251474353110700266070ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclapperglcontexthandler.h" #include "gst/plugin/gstgdkformats.h" #include "gst/plugin/gstgtkutils.h" #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND #include #include #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 #include #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX #include #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL || GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL #include #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 #include #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS #include #endif #define GST_CAT_DEFAULT gst_clapper_gl_context_handler_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define parent_class gst_clapper_gl_context_handler_parent_class G_DEFINE_TYPE (GstClapperGLContextHandler, gst_clapper_gl_context_handler, GST_TYPE_OBJECT); static GstGLContext * _wrap_current_gl (GstGLDisplay *display, GdkGLAPI gdk_gl_api, GstGLPlatform platform) { GstGLAPI gst_gl_api = GST_GL_API_NONE; switch (gdk_gl_api) { case GDK_GL_API_GL: gst_gl_api = GST_GL_API_OPENGL | GST_GL_API_OPENGL3; break; case GDK_GL_API_GLES: gst_gl_api = GST_GL_API_GLES2; break; default: g_assert_not_reached (); break; } if (gst_gl_api != GST_GL_API_NONE) { guintptr gl_handle; gst_gl_display_filter_gl_api (display, gst_gl_api); if ((gl_handle = gst_gl_context_get_current_gl_context (platform))) return gst_gl_context_new_wrapped (display, gl_handle, platform, gst_gl_api); } return NULL; } static gboolean _realize_gdk_context_with_api (GdkGLContext *gdk_context, GdkGLAPI api, gint maj, gint min) { GError *error = NULL; gboolean success; gdk_gl_context_set_allowed_apis (gdk_context, api); gdk_gl_context_set_required_version (gdk_context, maj, min); GST_DEBUG ("Trying to realize %s context, min ver: %i.%i", (api & GDK_GL_API_GL) ? "GL" : "GLES", maj, min); if (!(success = gdk_gl_context_realize (gdk_context, &error))) { GST_DEBUG ("Could not realize Gdk context with %s: %s", (api & GDK_GL_API_GL) ? "GL" : "GLES", error->message); g_clear_error (&error); } return success; } static gboolean _gl_context_handler_context_realize (GstClapperGLContextHandler *self, GdkGLContext *gdk_context) { GdkGLAPI preferred_api = GDK_GL_API_GL; GdkDisplay *gdk_display; const gchar *gl_env; gboolean success; GST_DEBUG_OBJECT (self, "Realizing GdkGLContext with default implementation"); /* Use single "GST_GL_API" env to also influence Gdk GL selection */ if ((gl_env = g_getenv ("GST_GL_API"))) { preferred_api = (g_str_has_prefix (gl_env, "gles")) ? GDK_GL_API_GLES : g_str_has_prefix (gl_env, "opengl") ? GDK_GL_API_GL : GDK_GL_API_GL | GDK_GL_API_GLES; /* With requested by user API, we either use it or give up */ return _realize_gdk_context_with_api (gdk_context, preferred_api, 0, 0); } gdk_display = gdk_gl_context_get_display (gdk_context); GST_DEBUG_OBJECT (self, "Auto selecting GL API for display: %s", gdk_display_get_name (gdk_display)); /* Apple decoder uses rectangle texture-target, which GLES does not support. * For Linux we prefer EGL + GLES in order to get direct HW colorspace conversion. * Windows will try EGL + GLES setup first and auto fallback to WGL. */ #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) preferred_api = GDK_GL_API_GLES; #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL if (GDK_IS_X11_DISPLAY (gdk_display) && gdk_x11_display_get_egl_display (gdk_display)) preferred_api = GDK_GL_API_GLES; #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL if (GDK_IS_WIN32_DISPLAY (gdk_display) && gdk_win32_display_get_egl_display (gdk_display)) preferred_api = GDK_GL_API_GLES; #endif /* FIXME: Remove once GStreamer can handle DRM modifiers. This tries to avoid * "scrambled" image on Linux with Intel GPUs that are mostly used together with * x86 CPUs at the expense of using slightly slower non-direct DMABuf import. * See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1236 */ #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND || GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL #if !defined(HAVE_GST_PATCHES) && (defined(__i386__) || defined(_M_IX86) || defined(__x86_64__) || defined(_M_X64)) { guint major = 0, minor = 0, micro = 0, nano = 0; gst_version (&major, &minor, µ, &nano); if (major == 1 && minor < 24) preferred_api = GDK_GL_API_GL; } #endif #endif /* Continue with GLES only if it should have "GL_EXT_texture_norm16" * extension, as we need it to handle P010_10LE, etc. */ if ((preferred_api == GDK_GL_API_GLES) && _realize_gdk_context_with_api (gdk_context, GDK_GL_API_GLES, 3, 1)) return TRUE; /* If not using GLES 3.1, try with core GL 3.2 that GTK4 defaults to */ if (_realize_gdk_context_with_api (gdk_context, GDK_GL_API_GL, 3, 2)) return TRUE; /* Try with what we normally prefer first, otherwise use fallback */ if (!(success = _realize_gdk_context_with_api (gdk_context, preferred_api, 0, 0))) { GdkGLAPI fallback_api; fallback_api = (GDK_GL_API_GL | GDK_GL_API_GLES); fallback_api &= ~preferred_api; success = _realize_gdk_context_with_api (gdk_context, fallback_api, 0, 0); } return success; } static gboolean _retrieve_gl_context_on_main (GstClapperGLContextHandler *self) { GdkDisplay *gdk_display; GdkGLContext *gdk_context; GError *error = NULL; GdkGLAPI gdk_gl_api; GstGLPlatform platform = GST_GL_PLATFORM_NONE; gint gl_major = 0, gl_minor = 0; if (!gtk_init_check ()) { GST_ERROR_OBJECT (self, "Could not ensure GTK initialization"); return FALSE; } gdk_display = gdk_display_get_default (); if (G_UNLIKELY (!gdk_display)) { GST_ERROR_OBJECT (self, "Could not retrieve Gdk display"); return FALSE; } if (!(gdk_context = gdk_display_create_gl_context (gdk_display, &error))) { GST_ERROR_OBJECT (self, "Error creating Gdk GL context: %s", error ? error->message : "No error set by Gdk"); g_clear_error (&error); return FALSE; } if (!_gl_context_handler_context_realize (self, gdk_context)) { GST_ERROR_OBJECT (self, "Could not realize Gdk context: %" GST_PTR_FORMAT, gdk_context); g_object_unref (gdk_context); return FALSE; } gdk_gl_api = gdk_gl_context_get_api (gdk_context); GST_OBJECT_LOCK (self); self->gdk_context = gdk_context; #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) { struct wl_display *wayland_display = gdk_wayland_display_get_wl_display (gdk_display); self->gst_display = (GstGLDisplay *) gst_gl_display_wayland_new_with_display (wayland_display); } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 if (GDK_IS_X11_DISPLAY (gdk_display)) { gpointer display_ptr; #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL display_ptr = gdk_x11_display_get_egl_display (gdk_display); if (display_ptr) { self->gst_display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (display_ptr); } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX if (!self->gst_display) { display_ptr = gdk_x11_display_get_xdisplay (gdk_display); self->gst_display = (GstGLDisplay *) gst_gl_display_x11_new_with_display (display_ptr); } } #endif #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 if (GDK_IS_WIN32_DISPLAY (gdk_display)) { #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL gpointer display_ptr = gdk_win32_display_get_egl_display (gdk_display); if (display_ptr) { self->gst_display = (GstGLDisplay *) gst_gl_display_egl_new_with_egl_display (display_ptr); } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_WGL if (!self->gst_display) { self->gst_display = gst_gl_display_new_with_type (GST_GL_DISPLAY_TYPE_WIN32); } } #endif #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS if (GDK_IS_MACOS_DISPLAY (gdk_display)) { self->gst_display = gst_gl_display_new_with_type (GST_GL_DISPLAY_TYPE_COCOA); } #endif /* Fallback to generic display */ if (G_UNLIKELY (!self->gst_display)) { GST_WARNING_OBJECT (self, "Unknown Gdk display!"); self->gst_display = gst_gl_display_new (); } #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND if (GST_IS_GL_DISPLAY_WAYLAND (self->gst_display)) { platform = GST_GL_PLATFORM_EGL; GST_INFO_OBJECT (self, "Using EGL on Wayland"); goto have_display; } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL if (GST_IS_GL_DISPLAY_EGL (self->gst_display) && GDK_IS_X11_DISPLAY (gdk_display)) { platform = GST_GL_PLATFORM_EGL; GST_INFO_OBJECT (self, "Using EGL on x11"); goto have_display; } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX if (GST_IS_GL_DISPLAY_X11 (self->gst_display)) { platform = GST_GL_PLATFORM_GLX; GST_INFO_OBJECT (self, "Using GLX on x11"); goto have_display; } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL if (GST_IS_GL_DISPLAY_EGL (self->gst_display) && GDK_IS_WIN32_DISPLAY (gdk_display)) { platform = GST_GL_PLATFORM_EGL; GST_INFO_OBJECT (self, "Using EGL on Win32"); goto have_display; } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_WGL if (gst_gl_display_get_handle_type (self->gst_display) == GST_GL_DISPLAY_TYPE_WIN32) { platform = GST_GL_PLATFORM_WGL; GST_INFO_OBJECT (self, "Using WGL on Win32"); goto have_display; } #endif #if GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS if (gst_gl_display_get_handle_type (self->gst_display) == GST_GL_DISPLAY_TYPE_COCOA) { platform = GST_GL_PLATFORM_CGL; GST_INFO_OBJECT (self, "Using CGL on macOS"); goto have_display; } #endif g_clear_object (&self->gdk_context); gst_clear_object (&self->gst_display); GST_OBJECT_UNLOCK (self); GST_ERROR_OBJECT (self, "Unsupported GL platform"); return FALSE; have_display: gdk_gl_context_make_current (self->gdk_context); self->wrapped_context = _wrap_current_gl (self->gst_display, gdk_gl_api, platform); if (!self->wrapped_context) { GST_ERROR ("Could not retrieve Gdk OpenGL context"); gdk_gl_context_clear_current (); g_clear_object (&self->gdk_context); gst_clear_object (&self->gst_display); GST_OBJECT_UNLOCK (self); return FALSE; } GST_INFO ("Retrieved Gdk OpenGL context %" GST_PTR_FORMAT, self->wrapped_context); gst_gl_context_activate (self->wrapped_context, TRUE); if (!gst_gl_context_fill_info (self->wrapped_context, &error)) { GST_ERROR ("Failed to fill Gdk context info: %s", error->message); g_clear_error (&error); gst_gl_context_activate (self->wrapped_context, FALSE); gst_clear_object (&self->wrapped_context); g_clear_object (&self->gdk_context); gst_clear_object (&self->gst_display); GST_OBJECT_UNLOCK (self); return FALSE; } gst_gl_context_get_gl_version (self->wrapped_context, &gl_major, &gl_minor); GST_INFO ("Using OpenGL%s %i.%i", (gdk_gl_api == GDK_GL_API_GLES) ? " ES" : "", gl_major, gl_minor); /* Deactivate in both places */ gst_gl_context_activate (self->wrapped_context, FALSE); gdk_gl_context_clear_current (); GST_OBJECT_UNLOCK (self); return TRUE; } static gboolean _retrieve_gst_context (GstClapperGLContextHandler *self) { GstGLDisplay *gst_display = NULL; GstGLContext *gst_context = NULL; GError *error = NULL; GST_OBJECT_LOCK (self); gst_display = gst_object_ref (self->gst_display); GST_TRACE_OBJECT (self, "Creating new GstGLContext"); /* GstGLDisplay operations require display object lock to be held */ GST_OBJECT_LOCK (gst_display); if (!gst_gl_display_create_context (gst_display, self->wrapped_context, &self->gst_context, &error)) { GST_WARNING ("Could not create OpenGL context: %s", error ? error->message : "Unknown"); g_clear_error (&error); GST_OBJECT_UNLOCK (gst_display); GST_OBJECT_UNLOCK (self); return FALSE; } gst_context = gst_object_ref (self->gst_context); GST_OBJECT_UNLOCK (self); gst_gl_display_add_context (gst_display, gst_context); GST_OBJECT_UNLOCK (gst_display); gst_object_unref (gst_display); gst_object_unref (gst_context); return TRUE; } static gboolean gst_clapper_gl_context_handler_handle_context_query (GstClapperContextHandler *handler, GstBaseSink *bsink, GstQuery *query) { GstClapperGLContextHandler *self = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (handler); gboolean res; GST_OBJECT_LOCK (self); res = gst_gl_handle_context_query (GST_ELEMENT_CAST (bsink), query, self->gst_display, self->gst_context, self->wrapped_context); GST_OBJECT_UNLOCK (self); return res; } static void gst_clapper_gl_context_handler_init (GstClapperGLContextHandler *self) { } static void gst_clapper_gl_context_handler_constructed (GObject *object) { GstClapperGLContextHandler *self = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (object); if (! !gst_gtk_invoke_on_main ((GThreadFunc) (GCallback) _retrieve_gl_context_on_main, self)) { _retrieve_gst_context (self); } GST_CALL_PARENT (G_OBJECT_CLASS, constructed, (object)); } static void gst_clapper_gl_context_handler_finalize (GObject *object) { GstClapperGLContextHandler *self = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (object); GST_TRACE ("Finalize"); g_clear_object (&self->gdk_context); gst_clear_object (&self->gst_display); gst_clear_object (&self->wrapped_context); /* FIXME: It seems GTK continues using created contexts, so we cannot unref * it here. Looking at GTK sink implementation for GtkVideo, I see that they * do not unref it either, but since we use surfaceless context it would be * probably better to have a static GstDisplay and reuse the same context. */ //gst_clear_object (&self->gst_context); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static void gst_clapper_gl_context_handler_class_init (GstClapperGLContextHandlerClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstClapperContextHandlerClass *handler_class = (GstClapperContextHandlerClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperglcontexthandler", 0, "Clapper GL Context Handler"); gobject_class->constructed = gst_clapper_gl_context_handler_constructed; gobject_class->finalize = gst_clapper_gl_context_handler_finalize; handler_class->handle_context_query = gst_clapper_gl_context_handler_handle_context_query; } void gst_clapper_gl_context_handler_add_handler (GPtrArray *context_handlers) { guint i; gboolean found = FALSE; for (i = 0; i < context_handlers->len; i++) { GstClapperContextHandler *handler = g_ptr_array_index (context_handlers, i); if ((found = GST_IS_CLAPPER_GL_CONTEXT_HANDLER (handler))) break; } if (!found) { GstClapperGLContextHandler *handler = g_object_new ( GST_TYPE_CLAPPER_GL_CONTEXT_HANDLER, NULL); g_ptr_array_add (context_handlers, handler); GST_DEBUG ("Added GL context handler to handlers array"); } } GstCaps * gst_clapper_gl_context_handler_make_gdk_gl_caps (const gchar *features, gboolean only_2d) { GstCaps *caps, *tmp; if (only_2d) { tmp = gst_caps_from_string (GST_VIDEO_CAPS_MAKE ( "{ " GST_GDK_GL_TEXTURE_FORMATS " }") ", " "texture-target = (string) { " GST_GL_TEXTURE_TARGET_2D_STR " }"); } else { tmp = gst_caps_from_string (GST_VIDEO_CAPS_MAKE ( "{ " GST_GDK_GL_TEXTURE_FORMATS " }")); } caps = gst_caps_copy (tmp); gst_caps_set_features_simple (tmp, gst_caps_features_new ( features, NULL)); gst_caps_set_features_simple (caps, gst_caps_features_new ( features, GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, NULL)); gst_caps_append (caps, tmp); return caps; } GdkTexture * gst_clapper_gl_context_handler_make_gl_texture (GstClapperGLContextHandler *self, GstBuffer *buffer, GstVideoInfo *v_info) { GdkTexture *texture; GstGLSyncMeta *sync_meta; GstVideoFrame frame; if (G_UNLIKELY (!gst_video_frame_map (&frame, v_info, buffer, GST_MAP_READ | GST_MAP_GL))) { GST_ERROR_OBJECT (self, "Could not map input buffer for reading"); return NULL; } GST_OBJECT_LOCK (self); /* Must have context active here for both sync meta * and Gdk texture format auto-detection to work */ gdk_gl_context_make_current (self->gdk_context); gst_gl_context_activate (self->wrapped_context, TRUE); sync_meta = gst_buffer_get_gl_sync_meta (buffer); /* Wait for all previous OpenGL commands to complete, * before we start using the input texture */ if (sync_meta) { gst_gl_sync_meta_set_sync_point (sync_meta, self->gst_context); gst_gl_sync_meta_wait (sync_meta, self->wrapped_context); } texture = gdk_gl_texture_new ( self->gdk_context, *(guint *) GST_VIDEO_FRAME_PLANE_DATA (&frame, 0), GST_VIDEO_FRAME_WIDTH (&frame), GST_VIDEO_FRAME_HEIGHT (&frame), (GDestroyNotify) gst_buffer_unref, gst_buffer_ref (buffer)); gst_gl_context_activate (self->wrapped_context, FALSE); gdk_gl_context_clear_current (); GST_OBJECT_UNLOCK (self); gst_video_frame_unmap (&frame); return texture; } clapper-0.8.0/src/lib/gst/plugin/handlers/gl/gstclapperglcontexthandler.h000066400000000000000000000057261474353110700266210ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include #include #include #include #include #include "gst/plugin/gstclappercontexthandler.h" #include "gst/plugin/clapper-gst-visibility.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_GL_CONTEXT_HANDLER (gst_clapper_gl_context_handler_get_type()) CLAPPER_GST_API G_DECLARE_FINAL_TYPE (GstClapperGLContextHandler, gst_clapper_gl_context_handler, GST, CLAPPER_GL_CONTEXT_HANDLER, GstClapperContextHandler) #define GST_CLAPPER_GL_CONTEXT_HANDLER_CAST(obj) ((GstClapperGLContextHandler *)(obj)) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WAYLAND (GST_GL_HAVE_WINDOW_WAYLAND && defined (GDK_WINDOWING_WAYLAND)) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 (GST_GL_HAVE_WINDOW_X11 && defined (GDK_WINDOWING_X11)) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_GLX (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 && GST_GL_HAVE_PLATFORM_GLX) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11_EGL (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_X11 && GST_GL_HAVE_PLATFORM_EGL) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 (GST_GL_HAVE_WINDOW_WIN32 && defined (GDK_WINDOWING_WIN32)) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_WGL (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 && GST_GL_HAVE_PLATFORM_WGL) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32_EGL (GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_WIN32 && GST_GL_HAVE_PLATFORM_EGL) #define GST_CLAPPER_GL_CONTEXT_HANDLER_HAVE_MACOS (GST_GL_HAVE_WINDOW_COCOA && defined (GDK_WINDOWING_MACOS) && GST_GL_HAVE_PLATFORM_CGL) struct _GstClapperGLContextHandler { GstClapperContextHandler parent; GdkGLContext *gdk_context; GstGLDisplay *gst_display; GstGLContext *wrapped_context; GstGLContext *gst_context; }; CLAPPER_GST_API void gst_clapper_gl_context_handler_add_handler (GPtrArray *context_handlers); CLAPPER_GST_API GstCaps * gst_clapper_gl_context_handler_make_gdk_gl_caps (const gchar *features, gboolean only_2d); CLAPPER_GST_API GdkTexture * gst_clapper_gl_context_handler_make_gl_texture (GstClapperGLContextHandler *handler, GstBuffer *buffer, GstVideoInfo *v_info); G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/handlers/gl/meson.build000066400000000000000000000101641474353110700231500ustar00rootroot00000000000000gst_clapper_gl_ch_dep = dependency('', required: false) build_gl_ch = ( not get_option('glimporter').disabled() or not get_option('gluploader').disabled() ) gl_support_required = ( get_option('glimporter').enabled() or get_option('gluploader').enabled() ) # GStreamer OpenGL gst_gl_dep = dependency('gstreamer-gl-1.0', version: gst_req, fallback: ['gst-plugins-base'], required: false, ) gst_gl_x11_dep = dependency('', required: false) gst_gl_wayland_dep = dependency('', required: false) gst_gl_egl_dep = dependency('', required: false) gst_gl_apis = gst_gl_dep.get_variable('gl_apis').split() gst_gl_winsys = gst_gl_dep.get_variable('gl_winsys').split() gst_gl_platforms = gst_gl_dep.get_variable('gl_platforms').split() message('GStreamer OpenGL window systems: @0@'.format(' '.join(gst_gl_winsys))) message('GStreamer OpenGL platforms: @0@'.format(' '.join(gst_gl_platforms))) message('GStreamer OpenGL apis: @0@'.format(' '.join(gst_gl_apis))) foreach ws : ['x11', 'wayland', 'android', 'cocoa', 'eagl', 'win32', 'dispmanx', 'viv_fb'] set_variable('gst_gl_have_window_@0@'.format(ws), gst_gl_winsys.contains(ws)) endforeach foreach p : ['glx', 'egl', 'cgl', 'eagl', 'wgl'] set_variable('gst_gl_have_platform_@0@'.format(p), gst_gl_platforms.contains(p)) endforeach foreach api : ['gl', 'gles2'] set_variable('gst_gl_have_api_@0@'.format(api), gst_gl_apis.contains(api)) endforeach gst_gl_proto_dep = dependency('gstreamer-gl-prototypes-1.0', version: gst_req, fallback: ['gst-plugins-base'], required: true ) if gst_gl_have_window_x11 gst_gl_x11_dep = dependency('gstreamer-gl-x11-1.0', version: gst_req, fallback: ['gst-plugins-base'], required: true, ) endif if gst_gl_have_window_wayland gst_gl_wayland_dep = dependency('gstreamer-gl-wayland-1.0', version: gst_req, fallback: ['gst-plugins-base'], required: true, ) endif if gst_gl_have_platform_egl gst_gl_egl_dep = dependency('gstreamer-gl-egl-1.0', version: gst_req, fallback: ['gst-plugins-base'], required: true, ) endif gst_plugin_gl_ch_deps = [gst_clapper_sink_dep, gst_gl_dep, gst_gl_proto_dep] have_gtk_gl_windowing = false if gst_gl_have_window_x11 and (gst_gl_have_platform_egl or gst_gl_have_platform_glx) gtk_x11_dep = dependency('gtk4-x11', required: false) if gtk_x11_dep.found() gst_plugin_gl_ch_deps += gtk_x11_dep if gst_gl_have_platform_glx gst_plugin_gl_ch_deps += gst_gl_x11_dep endif have_gtk_gl_windowing = true endif endif if gst_gl_have_window_wayland and gst_gl_have_platform_egl gtk_wayland_dep = dependency('gtk4-wayland', required: false) if gtk_wayland_dep.found() gst_plugin_gl_ch_deps += [gtk_wayland_dep, gst_gl_wayland_dep] have_gtk_gl_windowing = true endif endif if gst_gl_have_window_win32 and (gst_gl_have_platform_egl or gst_gl_have_platform_wgl) gtk_win32_dep = dependency('gtk4-win32', required: false) if gtk_win32_dep.found() gst_plugin_gl_ch_deps += gtk_win32_dep have_gtk_gl_windowing = true endif endif if gst_gl_have_window_cocoa and gst_gl_have_platform_cgl gtk_macos_dep = dependency('gtk4-macos', required: false) if gtk_macos_dep.found() gst_plugin_gl_ch_deps += gtk_macos_dep have_gtk_gl_windowing = true endif endif if not have_gtk_gl_windowing if gl_support_required error('GL-based importer was enabled, but support for current GL windowing is missing') endif build_gl_ch = false endif if gst_gl_have_platform_egl gst_plugin_gl_ch_deps += gst_gl_egl_dep endif foreach dep : gst_plugin_gl_ch_deps if not dep.found() if gl_support_required error('GL-based importer was enabled, but required dependencies were not found') endif build_gl_ch = false endif endforeach if build_gl_ch gst_clapper_gl_ch_dep = declare_dependency( link_with: library('gstclapperglcontexthandler', 'gstclapperglcontexthandler.c', c_args: gst_clapper_plugin_args, include_directories: gst_plugin_conf_inc, dependencies: gst_plugin_gl_ch_deps, version: meson.project_version(), install: true, ), include_directories: gst_plugin_conf_inc, dependencies: gst_plugin_gl_ch_deps, ) endif clapper-0.8.0/src/lib/gst/plugin/handlers/meson.build000066400000000000000000000000151474353110700225400ustar00rootroot00000000000000subdir('gl') clapper-0.8.0/src/lib/gst/plugin/importers/000077500000000000000000000000001474353110700206265ustar00rootroot00000000000000clapper-0.8.0/src/lib/gst/plugin/importers/gstclapperglimporter.c000066400000000000000000000102661474353110700252500ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclapperglimporter.h" #define GST_CAT_DEFAULT gst_clapper_gl_importer_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define parent_class gst_clapper_gl_importer_parent_class GST_CLAPPER_IMPORTER_DEFINE (GstClapperGLImporter, gst_clapper_gl_importer, GST_TYPE_CLAPPER_IMPORTER); static GstBufferPool * gst_clapper_gl_importer_create_pool (GstClapperImporter *importer, GstStructure **config) { GstClapperGLImporter *self = GST_CLAPPER_GL_IMPORTER_CAST (importer); GstBufferPool *pool; GST_DEBUG_OBJECT (self, "Creating new GL buffer pool"); pool = gst_gl_buffer_pool_new (self->gl_handler->gst_context); *config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_add_option (*config, GST_BUFFER_POOL_OPTION_VIDEO_META); gst_buffer_pool_config_add_option (*config, GST_BUFFER_POOL_OPTION_GL_SYNC_META); return pool; } static void gst_clapper_gl_importer_add_allocation_metas (GstClapperImporter *importer, GstQuery *query) { GstClapperGLImporter *self = GST_CLAPPER_GL_IMPORTER_CAST (importer); /* We can support GL sync meta */ if (self->gl_handler->gst_context->gl_vtable->FenceSync) gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, NULL); /* Also add base importer class supported meta */ GST_CLAPPER_IMPORTER_CLASS (parent_class)->add_allocation_metas (importer, query); } static GdkTexture * gst_clapper_gl_importer_generate_texture (GstClapperImporter *importer, GstBuffer *buffer, GstVideoInfo *v_info) { GstClapperGLImporter *self = GST_CLAPPER_GL_IMPORTER_CAST (importer); return gst_clapper_gl_context_handler_make_gl_texture (self->gl_handler, buffer, v_info); } static void gst_clapper_gl_importer_init (GstClapperGLImporter *self) { } static void gst_clapper_gl_importer_finalize (GObject *object) { GstClapperGLImporter *self = GST_CLAPPER_GL_IMPORTER_CAST (object); gst_clear_object (&self->gl_handler); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static void gst_clapper_gl_importer_class_init (GstClapperGLImporterClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperglimporter", 0, "Clapper GL Importer"); gobject_class->finalize = gst_clapper_gl_importer_finalize; importer_class->create_pool = gst_clapper_gl_importer_create_pool; importer_class->add_allocation_metas = gst_clapper_gl_importer_add_allocation_metas; importer_class->generate_texture = gst_clapper_gl_importer_generate_texture; } GstClapperImporter * make_importer (GPtrArray *context_handlers) { GstClapperGLImporter *self; GstClapperContextHandler *handler; handler = gst_clapper_context_handler_obtain_with_type (context_handlers, GST_TYPE_CLAPPER_GL_CONTEXT_HANDLER); if (G_UNLIKELY (!handler)) return NULL; self = g_object_new (GST_TYPE_CLAPPER_GL_IMPORTER, NULL); self->gl_handler = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (handler); return GST_CLAPPER_IMPORTER_CAST (self); } GstCaps * make_caps (gboolean is_template, GstRank *rank, GPtrArray *context_handlers) { *rank = GST_RANK_SECONDARY; if (!is_template && context_handlers) gst_clapper_gl_context_handler_add_handler (context_handlers); return gst_clapper_gl_context_handler_make_gdk_gl_caps ( GST_CAPS_FEATURE_MEMORY_GL_MEMORY, TRUE); } clapper-0.8.0/src/lib/gst/plugin/importers/gstclapperglimporter.h000066400000000000000000000025011474353110700252460ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include "gst/plugin/gstclapperimporter.h" #include "gst/plugin/handlers/gl/gstclapperglcontexthandler.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_GL_IMPORTER (gst_clapper_gl_importer_get_type()) G_DECLARE_FINAL_TYPE (GstClapperGLImporter, gst_clapper_gl_importer, GST, CLAPPER_GL_IMPORTER, GstClapperImporter) #define GST_CLAPPER_GL_IMPORTER_CAST(obj) ((GstClapperGLImporter *)(obj)) struct _GstClapperGLImporter { GstClapperImporter parent; GstClapperGLContextHandler *gl_handler; }; G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/importers/gstclappergluploader.c000066400000000000000000000244671474353110700252320ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclappergluploader.h" #define GST_CAT_DEFAULT gst_clapper_gl_uploader_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define parent_class gst_clapper_gl_uploader_parent_class GST_CLAPPER_IMPORTER_DEFINE (GstClapperGLUploader, gst_clapper_gl_uploader, GST_TYPE_CLAPPER_IMPORTER); static void _update_elements_caps_locked (GstClapperGLUploader *self, GstCaps *upload_sink_caps) { GstGLContext *gst_context; GstCaps *upload_src_caps, *color_sink_caps, *color_src_caps, *gdk_sink_caps; gst_context = self->gl_handler->gst_context; GST_INFO_OBJECT (self, "Input caps: %" GST_PTR_FORMAT, upload_sink_caps); upload_src_caps = gst_gl_upload_transform_caps (self->upload, gst_context, GST_PAD_SINK, upload_sink_caps, NULL); upload_src_caps = gst_caps_fixate (upload_src_caps); GST_INFO_OBJECT (self, "GLUpload caps: %" GST_PTR_FORMAT, upload_src_caps); gst_gl_upload_set_caps (self->upload, upload_sink_caps, upload_src_caps); gdk_sink_caps = gst_clapper_gl_context_handler_make_gdk_gl_caps ( GST_CAPS_FEATURE_MEMORY_GL_MEMORY, TRUE); color_sink_caps = gst_gl_color_convert_transform_caps (gst_context, GST_PAD_SRC, upload_src_caps, gdk_sink_caps); gst_caps_unref (gdk_sink_caps); /* Second caps arg is transfer-full */ color_src_caps = gst_gl_color_convert_fixate_caps (gst_context, GST_PAD_SINK, upload_src_caps, color_sink_caps); GST_INFO_OBJECT (self, "GLColorConvert caps: %" GST_PTR_FORMAT, color_src_caps); gst_gl_color_convert_set_caps (self->color_convert, upload_src_caps, color_src_caps); self->has_pending_v_info = gst_video_info_from_caps (&self->pending_v_info, color_src_caps); gst_caps_unref (upload_src_caps); gst_caps_unref (color_src_caps); } static void gst_clapper_gl_uploader_set_caps (GstClapperImporter *importer, GstCaps *caps) { GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (importer); GST_CLAPPER_GL_UPLOADER_LOCK (self); _update_elements_caps_locked (self, caps); GST_CLAPPER_GL_UPLOADER_UNLOCK (self); } static void _uploader_reconfigure_locked (GstClapperGLUploader *self) { GstCaps *in_caps = NULL; GST_DEBUG_OBJECT (self, "Reconfiguring upload"); gst_gl_upload_get_caps (self->upload, &in_caps, NULL); if (G_LIKELY (in_caps)) { _update_elements_caps_locked (self, in_caps); gst_caps_unref (in_caps); } } static GstBuffer * _upload_perform_locked (GstClapperGLUploader *self, GstBuffer *buffer) { GstBuffer *upload_buf = NULL; GstGLUploadReturn ret; ret = gst_gl_upload_perform_with_buffer (self->upload, buffer, &upload_buf); if (G_UNLIKELY (ret != GST_GL_UPLOAD_DONE)) { switch (ret) { case GST_GL_UPLOAD_RECONFIGURE: _uploader_reconfigure_locked (self); /* Retry with the same buffer after reconfiguring */ return _upload_perform_locked (self, buffer); default: GST_ERROR_OBJECT (self, "Could not upload input buffer, returned: %i", ret); break; } } return upload_buf; } static GstBufferPool * gst_clapper_gl_uploader_create_pool (GstClapperImporter *importer, GstStructure **config) { /* Since GLUpload API provides a ready to use propose_allocation method, * we will use it with our query in add_allocation_metas instead of * making pool here ourselves */ return NULL; } static void gst_clapper_gl_uploader_add_allocation_metas (GstClapperImporter *importer, GstQuery *query) { GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (importer); GstGLUpload *upload; GST_CLAPPER_GL_UPLOADER_LOCK (self); upload = gst_object_ref (self->upload); GST_CLAPPER_GL_UPLOADER_UNLOCK (self); /* Add glupload supported meta */ gst_gl_upload_propose_allocation (upload, NULL, query); gst_object_unref (upload); /* We can support GL sync meta */ if (self->gl_handler->gst_context->gl_vtable->FenceSync) gst_query_add_allocation_meta (query, GST_GL_SYNC_META_API_TYPE, NULL); /* Also add base importer class supported meta */ GST_CLAPPER_IMPORTER_CLASS (parent_class)->add_allocation_metas (importer, query); } static GdkTexture * gst_clapper_gl_uploader_generate_texture (GstClapperImporter *importer, GstBuffer *buffer, GstVideoInfo *v_info) { GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (importer); GstBuffer *upload_buf, *color_buf; GdkTexture *texture; /* XXX: We both upload and perform color conversion here, thus we skip * upload for buffers that are not going to be shown and gain more free * CPU time to prepare the next one. Improves performance on weak HW. */ GST_LOG_OBJECT (self, "Uploading %" GST_PTR_FORMAT, buffer); GST_CLAPPER_GL_UPLOADER_LOCK (self); upload_buf = _upload_perform_locked (self, buffer); if (G_UNLIKELY (!upload_buf)) { GST_ERROR_OBJECT (self, "Could not perform upload on input buffer"); GST_CLAPPER_GL_UPLOADER_UNLOCK (self); return NULL; } GST_LOG_OBJECT (self, "Uploaded into %" GST_PTR_FORMAT, upload_buf); color_buf = gst_gl_color_convert_perform (self->color_convert, upload_buf); gst_buffer_unref (upload_buf); /* Use video info associated with converted buffer */ if (self->has_pending_v_info) { self->v_info = self->pending_v_info; self->has_pending_v_info = FALSE; } GST_CLAPPER_GL_UPLOADER_UNLOCK (self); if (G_UNLIKELY (!color_buf)) { GST_ERROR_OBJECT (self, "Could not perform color conversion on input buffer"); return NULL; } GST_LOG_OBJECT (self, "Color converted into %" GST_PTR_FORMAT, color_buf); texture = gst_clapper_gl_context_handler_make_gl_texture (self->gl_handler, color_buf, &self->v_info); gst_buffer_unref (color_buf); return texture; } static void gst_clapper_gl_uploader_init (GstClapperGLUploader *self) { g_mutex_init (&self->lock); gst_video_info_init (&self->pending_v_info); gst_video_info_init (&self->v_info); } static void gst_clapper_gl_uploader_finalize (GObject *object) { GstClapperGLUploader *self = GST_CLAPPER_GL_UPLOADER_CAST (object); gst_clear_object (&self->upload); gst_clear_object (&self->color_convert); gst_clear_object (&self->gl_handler); g_mutex_clear (&self->lock); GST_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } static void gst_clapper_gl_uploader_class_init (GstClapperGLUploaderClass *klass) { GObjectClass *gobject_class = (GObjectClass *) klass; GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clappergluploader", 0, "Clapper GL Uploader"); gobject_class->finalize = gst_clapper_gl_uploader_finalize; importer_class->set_caps = gst_clapper_gl_uploader_set_caps; importer_class->create_pool = gst_clapper_gl_uploader_create_pool; importer_class->add_allocation_metas = gst_clapper_gl_uploader_add_allocation_metas; importer_class->generate_texture = gst_clapper_gl_uploader_generate_texture; } GstClapperImporter * make_importer (GPtrArray *context_handlers) { GstClapperGLUploader *self; GstClapperContextHandler *handler; handler = gst_clapper_context_handler_obtain_with_type (context_handlers, GST_TYPE_CLAPPER_GL_CONTEXT_HANDLER); if (G_UNLIKELY (!handler)) return NULL; self = g_object_new (GST_TYPE_CLAPPER_GL_UPLOADER, NULL); self->gl_handler = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST (handler); self->upload = gst_gl_upload_new (self->gl_handler->gst_context); self->color_convert = gst_gl_color_convert_new (self->gl_handler->gst_context); return GST_CLAPPER_IMPORTER_CAST (self); } static GstCaps * _make_actual_caps (GstClapperGLContextHandler *gl_handler) { GstGLUpload *upload; GstCaps *gdk_sink_caps, *color_sink_caps, *upload_sink_caps, *actual; guint i; /* Having "gst_context" means we also have all other contexts and * display as they are used to create it, so no need to check */ if (!gl_handler->gst_context) return NULL; gdk_sink_caps = gst_clapper_gl_context_handler_make_gdk_gl_caps ( GST_CAPS_FEATURE_MEMORY_GL_MEMORY, TRUE); color_sink_caps = gst_gl_color_convert_transform_caps (gl_handler->gst_context, GST_PAD_SRC, gdk_sink_caps, NULL); gst_caps_unref (gdk_sink_caps); upload = gst_gl_upload_new (NULL); upload_sink_caps = gst_gl_upload_transform_caps (upload, gl_handler->gst_context, GST_PAD_SRC, color_sink_caps, NULL); gst_caps_unref (color_sink_caps); gst_object_unref (upload); /* Check for existence and remove duplicated structures, * they may contain unsupported by our GL context formats */ actual = gst_caps_new_empty (); for (i = 0; i < gst_caps_get_size (upload_sink_caps); i++) { GstCaps *tmp = gst_caps_copy_nth (upload_sink_caps, i); if (!gst_caps_can_intersect (actual, tmp)) gst_caps_append (actual, tmp); else gst_caps_unref (tmp); } gst_caps_unref (upload_sink_caps); if (G_UNLIKELY (gst_caps_is_empty (actual))) gst_clear_caps (&actual); return actual; } GstCaps * make_caps (gboolean is_template, GstRank *rank, GPtrArray *context_handlers) { GstCaps *caps = NULL; if (is_template) { caps = gst_gl_upload_get_input_template_caps (); } else if (context_handlers) { GstClapperGLContextHandler *gl_handler; /* Add GL context handler if not already present */ gst_clapper_gl_context_handler_add_handler (context_handlers); if ((gl_handler = GST_CLAPPER_GL_CONTEXT_HANDLER_CAST ( gst_clapper_context_handler_obtain_with_type (context_handlers, GST_TYPE_CLAPPER_GL_CONTEXT_HANDLER)))) { caps = _make_actual_caps (gl_handler); gst_object_unref (gl_handler); } } if (caps) *rank = GST_RANK_MARGINAL + 1; return caps; } clapper-0.8.0/src/lib/gst/plugin/importers/gstclappergluploader.h000066400000000000000000000033771474353110700252340ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include "gst/plugin/gstclapperimporter.h" #include "gst/plugin/handlers/gl/gstclapperglcontexthandler.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_GL_UPLOADER (gst_clapper_gl_uploader_get_type()) G_DECLARE_FINAL_TYPE (GstClapperGLUploader, gst_clapper_gl_uploader, GST, CLAPPER_GL_UPLOADER, GstClapperImporter) #define GST_CLAPPER_GL_UPLOADER_CAST(obj) ((GstClapperGLUploader *)(obj)) #define GST_CLAPPER_GL_UPLOADER_GET_LOCK(obj) (&GST_CLAPPER_GL_UPLOADER_CAST(obj)->lock) #define GST_CLAPPER_GL_UPLOADER_LOCK(obj) g_mutex_lock (GST_CLAPPER_GL_UPLOADER_GET_LOCK(obj)) #define GST_CLAPPER_GL_UPLOADER_UNLOCK(obj) g_mutex_unlock (GST_CLAPPER_GL_UPLOADER_GET_LOCK(obj)) struct _GstClapperGLUploader { GstClapperImporter parent; GMutex lock; GstClapperGLContextHandler *gl_handler; GstGLUpload *upload; GstGLColorConvert *color_convert; GstVideoInfo pending_v_info, v_info; gboolean has_pending_v_info; }; G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/importers/gstclapperrawimporter.c000066400000000000000000000062441474353110700254400ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstclapperrawimporter.h" #include "gst/plugin/gstgtkutils.h" #include "gst/plugin/gstgdkformats.h" #define GST_CAT_DEFAULT gst_clapper_raw_importer_debug GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT); #define parent_class gst_clapper_raw_importer_parent_class GST_CLAPPER_IMPORTER_DEFINE (GstClapperRawImporter, gst_clapper_raw_importer, GST_TYPE_CLAPPER_IMPORTER); static GstBufferPool * gst_clapper_raw_importer_create_pool (GstClapperImporter *importer, GstStructure **config) { GstClapperRawImporter *self = GST_CLAPPER_RAW_IMPORTER_CAST (importer); GstBufferPool *pool; GST_DEBUG_OBJECT (self, "Creating new buffer pool"); pool = gst_video_buffer_pool_new (); *config = gst_buffer_pool_get_config (pool); gst_buffer_pool_config_add_option (*config, GST_BUFFER_POOL_OPTION_VIDEO_META); return pool; } static GdkTexture * gst_clapper_raw_importer_generate_texture (GstClapperImporter *importer, GstBuffer *buffer, GstVideoInfo *v_info) { GdkTexture *texture; GstVideoFrame frame; if (G_UNLIKELY (!gst_video_frame_map (&frame, v_info, buffer, GST_MAP_READ))) { GST_ERROR_OBJECT (importer, "Could not map input buffer for reading"); return NULL; } texture = gst_video_frame_into_gdk_texture (&frame); gst_video_frame_unmap (&frame); return texture; } static void gst_clapper_raw_importer_init (GstClapperRawImporter *self) { } static void gst_clapper_raw_importer_class_init (GstClapperRawImporterClass *klass) { GstClapperImporterClass *importer_class = (GstClapperImporterClass *) klass; GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "clapperrawimporter", 0, "Clapper RAW Importer"); importer_class->create_pool = gst_clapper_raw_importer_create_pool; importer_class->generate_texture = gst_clapper_raw_importer_generate_texture; } GstClapperImporter * make_importer (GPtrArray *context_handlers) { return g_object_new (GST_TYPE_CLAPPER_RAW_IMPORTER, NULL); } GstCaps * make_caps (gboolean is_template, GstRank *rank, GPtrArray *context_handlers) { *rank = GST_RANK_MARGINAL; return gst_caps_from_string ( GST_VIDEO_CAPS_MAKE_WITH_FEATURES (GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY ", " GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, "{ " GST_GDK_MEMORY_FORMATS " }") "; " GST_VIDEO_CAPS_MAKE ( "{ " GST_GDK_MEMORY_FORMATS " }")); } clapper-0.8.0/src/lib/gst/plugin/importers/gstclapperrawimporter.h000066400000000000000000000023371474353110700254440ustar00rootroot00000000000000/* * Copyright (C) 2022 Rafał Dzięgiel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #pragma once #include "gst/plugin/gstclapperimporter.h" G_BEGIN_DECLS #define GST_TYPE_CLAPPER_RAW_IMPORTER (gst_clapper_raw_importer_get_type()) G_DECLARE_FINAL_TYPE (GstClapperRawImporter, gst_clapper_raw_importer, GST, CLAPPER_RAW_IMPORTER, GstClapperImporter) #define GST_CLAPPER_RAW_IMPORTER_CAST(obj) ((GstClapperRawImporter *)(obj)) struct _GstClapperRawImporter { GstClapperImporter parent; }; G_END_DECLS clapper-0.8.0/src/lib/gst/plugin/importers/meson.build000066400000000000000000000032071474353110700227720ustar00rootroot00000000000000all_importers = [ 'glimporter', 'gluploader', 'rawimporter', ] # We cannot build any importers without sink that they depend on if not gst_clapper_sink_dep.found() foreach imp : all_importers if get_option(imp).enabled() error('"@0@" option was enabled, but it requires building gstreamer plugin'.format(imp)) endif endforeach endif build_glimporter = ( not get_option('glimporter').disabled() and gst_clapper_gl_ch_dep.found() ) if build_glimporter library( 'gstclapperglimporter', 'gstclapperglimporter.c', dependencies: gst_clapper_gl_ch_dep, include_directories: gst_plugin_conf_inc, c_args: gst_clapper_plugin_args, install: true, install_dir: gst_clapper_importers_libdir, ) endif build_gluploader = ( not get_option('gluploader').disabled() and gst_clapper_gl_ch_dep.found() ) if build_gluploader library( 'gstclappergluploader', 'gstclappergluploader.c', dependencies: gst_clapper_gl_ch_dep, include_directories: gst_plugin_conf_inc, c_args: gst_clapper_plugin_args, install: true, install_dir: gst_clapper_importers_libdir, ) endif # No need to auto build rawimporter if we are building gluploader build_rawimporter = ( not get_option('rawimporter').disabled() and (not build_gluploader or get_option('rawimporter').enabled()) and gst_clapper_sink_dep.found() ) if build_rawimporter library( 'gstclapperrawimporter', 'gstclapperrawimporter.c', dependencies: gst_clapper_sink_dep, include_directories: gst_plugin_conf_inc, c_args: gst_clapper_plugin_args, install: true, install_dir: gst_clapper_importers_libdir, ) endif clapper-0.8.0/src/lib/gst/plugin/meson.build000066400000000000000000000046531474353110700207540ustar00rootroot00000000000000gst_clapper_sink_dep = dependency('', required: false) gst_plugins_libdir = join_paths(prefix, libdir, 'gstreamer-1.0') gst_clapper_importers_libdir = join_paths(clapper_libdir, 'gst', 'plugin', 'importers') gst_clapper_plugin_deps = [ gtk4_dep, gst_dep, gst_base_dep, gst_video_dep, gmodule_dep, ] build_gst_plugin = not get_option('gst-plugin').disabled() foreach dep : gst_clapper_plugin_deps if not dep.found() if get_option('gst-plugin').enabled() error('GStreamer plugin was enabled, but required dependencies were not found') endif build_gst_plugin = false endif endforeach gst_clapper_plugin_args = [ '-DHAVE_CONFIG_H', '-DCLAPPER_GST_COMPILATION', '-DGST_USE_UNSTABLE_API', ] if get_option('default_library') == 'static' gst_clapper_plugin_args += [ '-DGST_STATIC_COMPILATION', '-DCLAPPER_GST_STATIC_COMPILATION', ] endif cdata = configuration_data() cdata.set_quoted('PACKAGE', meson.project_name()) cdata.set_quoted('VERSION', meson.project_version()) cdata.set_quoted('PACKAGE_VERSION', meson.project_version()) cdata.set_quoted('GST_PACKAGE_NAME', 'gst-plugin-clapper') cdata.set_quoted('GST_PACKAGE_ORIGIN', 'https://github.com/Rafostar/clapper') cdata.set_quoted('GST_LICENSE', 'LGPL') cdata.set_quoted('CLAPPER_SINK_IMPORTER_PATH', gst_clapper_importers_libdir) configure_file( output: 'config.h', configuration: cdata, ) visibility_conf = configuration_data() visibility_conf.set( 'CLAPPER_API', 'CLAPPER_GST', ) clappergst_visibility_header = configure_file( input: '../../shared/clapper-api-visibility.h.in', output: 'clapper-gst-visibility.h', configuration: visibility_conf, ) gst_plugin_conf_inc = [ include_directories('.'), include_directories('..'), include_directories('../..'), ] gst_clapper_plugin_sources = [ 'gstclappersink.c', 'gstclapperpaintable.c', 'gstgtkutils.c', 'gstplugin.c', 'gstclappercontexthandler.c', 'gstclapperimporter.c', 'gstclapperimporterloader.c', ] if build_gst_plugin gst_clapper_sink_dep = declare_dependency( link_with: library('gstclapper', gst_clapper_plugin_sources, c_args: gst_clapper_plugin_args, include_directories: gst_plugin_conf_inc, dependencies: gst_clapper_plugin_deps, install: true, install_dir: gst_plugins_libdir, ), include_directories: gst_plugin_conf_inc, dependencies: gst_clapper_plugin_deps, ) endif subdir('handlers') subdir('importers') clapper-0.8.0/src/lib/meson.build000066400000000000000000000012311474353110700166460ustar00rootroot00000000000000gir = find_program('g-ir-scanner', required: get_option('introspection')) build_gir = (gir.found() and not get_option('introspection').disabled()) vapigen = find_program('vapigen', required: get_option('vapi')) build_vapi = (vapigen.found() and not get_option('vapi').disabled()) gir_init_section = '--add-init-section=extern void gst_init(gint*,gchar**);' + \ 'g_setenv("GST_REGISTRY_1.0", "@0@", TRUE);'.format(meson.current_build_dir() + '/gir_empty_registry.reg') + \ 'g_setenv("GST_PLUGIN_PATH_1_0", "", TRUE);' + \ 'g_setenv("GST_PLUGIN_SYSTEM_PATH_1_0", "", TRUE);' + \ 'gst_init(NULL,NULL);' subdir('gst') subdir('clapper') subdir('clapper-gtk') clapper-0.8.0/src/lib/shared/000077500000000000000000000000001474353110700157555ustar00rootroot00000000000000clapper-0.8.0/src/lib/shared/clapper-api-visibility.h.in000066400000000000000000000034411474353110700231170ustar00rootroot00000000000000/* Clapper API Visibility * Copyright (C) 2024 Rafał Dzięgiel * * 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_API@_INSIDE__) && !defined(@CLAPPER_API@_COMPILATION) #error "This header file can not be included directly." #endif #if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(@CLAPPER_API@_STATIC_COMPILATION) #define _@CLAPPER_API@_EXPORT __declspec(dllexport) extern #define _@CLAPPER_API@_IMPORT __declspec(dllimport) extern #elif __GNUC__ >= 4 #define _@CLAPPER_API@_EXPORT __attribute__((visibility("default"))) extern #define _@CLAPPER_API@_IMPORT extern #else #define _@CLAPPER_API@_EXPORT extern #define _@CLAPPER_API@_IMPORT extern #endif #if defined(@CLAPPER_API@_COMPILATION) #define _@CLAPPER_API@_VISIBILITY _@CLAPPER_API@_EXPORT #else #define _@CLAPPER_API@_VISIBILITY _@CLAPPER_API@_IMPORT #endif #define @CLAPPER_API@_API _@CLAPPER_API@_VISIBILITY #define @CLAPPER_API@_DEPRECATED G_DEPRECATED _@CLAPPER_API@_VISIBILITY #define @CLAPPER_API@_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _@CLAPPER_API@_VISIBILITY clapper-0.8.0/src/lib/shared/clapper-shared-utils-private.h000066400000000000000000000024301474353110700236250ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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 G_BEGIN_DECLS gpointer clapper_shared_utils_context_invoke_sync (GMainContext *context, GThreadFunc func, gpointer data); gpointer clapper_shared_utils_context_invoke_sync_full (GMainContext *context, GThreadFunc func, gpointer data, GDestroyNotify destroy_func); GSource * clapper_shared_utils_context_timeout_add_full (GMainContext *context, gint priority, guint interval, GSourceFunc func, gpointer data, GDestroyNotify destroy_func); G_END_DECLS clapper-0.8.0/src/lib/shared/clapper-shared-utils.c000066400000000000000000000047751474353110700221660ustar00rootroot00000000000000/* * Copyright (C) 2024 Rafał Dzięgiel * * 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-shared-utils-private.h" struct ClapperSharedUtilsInvoke { GMutex lock; GCond cond; gboolean fired; GThreadFunc func; gpointer data; gpointer res; }; static gboolean _invoke_func (struct ClapperSharedUtilsInvoke *invoke) { g_mutex_lock (&invoke->lock); invoke->res = invoke->func (invoke->data); invoke->fired = TRUE; g_cond_signal (&invoke->cond); g_mutex_unlock (&invoke->lock); return G_SOURCE_REMOVE; } inline gpointer clapper_shared_utils_context_invoke_sync (GMainContext *context, GThreadFunc func, gpointer data) { struct ClapperSharedUtilsInvoke invoke; g_mutex_init (&invoke.lock); g_cond_init (&invoke.cond); invoke.fired = FALSE; invoke.func = func; invoke.data = data; g_main_context_invoke (context, (GSourceFunc) _invoke_func, &invoke); g_mutex_lock (&invoke.lock); while (!invoke.fired) g_cond_wait (&invoke.cond, &invoke.lock); g_mutex_unlock (&invoke.lock); g_mutex_clear (&invoke.lock); g_cond_clear (&invoke.cond); return invoke.res; } inline gpointer clapper_shared_utils_context_invoke_sync_full (GMainContext *context, GThreadFunc func, gpointer data, GDestroyNotify destroy_func) { gpointer res = clapper_shared_utils_context_invoke_sync (context, func, data); if (destroy_func) destroy_func (data); return res; } inline GSource * clapper_shared_utils_context_timeout_add_full (GMainContext *context, gint priority, guint interval, GSourceFunc func, gpointer data, GDestroyNotify destroy_func) { GSource *source = g_timeout_source_new (interval); g_source_set_priority (source, priority); g_source_set_callback (source, func, data, destroy_func); g_source_attach (source, context); return source; } clapper-0.8.0/src/meson.build000066400000000000000000000000341474353110700161000ustar00rootroot00000000000000subdir('lib') subdir('bin')