podcasts-25.2/000077500000000000000000000000001500126606300132615ustar00rootroot00000000000000podcasts-25.2/.gitignore000066400000000000000000000005001500126606300152440ustar00rootroot00000000000000target/ **/*.rs.bk .vscode resources.gresource _build/ build/ vendor/ .criterion/ org.gnome.*.json~ podcasts-gtk/po/gnome-podcasts.pot # scripts/test.sh target_*/ # flatpak-builder stuff .flatpak/ .flatpak-builder/ app/ repo/ # Files configured by meson podcasts-gtk/src/config.rs podcasts-gtk/src/static_resource.rs podcasts-25.2/.gitlab-ci.yml000066400000000000000000000022411500126606300157140ustar00rootroot00000000000000include: - project: 'gnome/citemplates' file: 'flatpak/flatpak-ci-initiative-sdk-extensions.yml' # ref: '' - component: "gitlab.gnome.org/GNOME/citemplates/basic-release-with-dist@25.4" inputs: dist-job-name: "flatpak@x86_64" - project: 'gnome/citemplates' file: 'templates/default-rules.yml' .flatpak-vars: variables: MANIFEST_PATH: "org.gnome.Podcasts.Devel.json" FLATPAK_MODULE: "gnome-podcasts" MESON_ARGS: "-Dprofile=development" APP_ID: "org.gnome.Podcasts.Devel" RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo" BUNDLE: "org.gnome.Podcasts.Devel.flatpak" flatpak@x86_64: extends: [".flatpak@x86_64", ".flatpak-vars"] flatpak@aarch64: extends: [".flatpak@aarch64", ".flatpak-vars"] nightly@x86_64: extends: ".publish_nightly" needs: ["flatpak@x86_64"] nightly@aarch64: extends: ".publish_nightly" needs: ["flatpak@aarch64"] # Configure and run rustfmt # Exits and builds fails if on bad format rustfmt: image: "rust:slim" stage: ".pre" script: - rustup component add rustfmt - rustc -Vv && cargo -Vv - cargo fmt --version - cargo fmt --all -- --color=always --check podcasts-25.2/.gitlab/000077500000000000000000000000001500126606300146015ustar00rootroot00000000000000podcasts-25.2/.gitlab/issue_templates/000077500000000000000000000000001500126606300200075ustar00rootroot00000000000000podcasts-25.2/.gitlab/issue_templates/BrokenFeed.md000066400000000000000000000007271500126606300223430ustar00rootroot00000000000000## Invalid RSS Feed Template. Please provide the source of the xml rss feed. **Feed URL** example.com/podcast **Detailed description of the issue** Would be helpfull if error messages where included from stderr. If you are not sure how to do it, feel free to ask and we will walk you through it! Some common cases might be: * Feed cannot be added * Broken Feed Image * Episode(s) do not download Steps to reproduce: 1. Open GNOME Podcasts 2. Do an action 3. ... podcasts-25.2/.gitlab/issue_templates/Bug.md000066400000000000000000000020511500126606300210440ustar00rootroot00000000000000# Steps to reproduce 1. 2. 3. Reproducible in: - Flatpak unstable: (yes or no) - Other: # Current behavior # Expected behavior # Additional information /label ~"Bug" podcasts-25.2/.gitlab/issue_templates/Epic.md000066400000000000000000000017131500126606300212130ustar00rootroot00000000000000# Current problems # Goals & use cases # Requirements # Relevant art # Proposal & plan /label ~"Epic"podcasts-25.2/.gitlab/issue_templates/Feature.md000066400000000000000000000011101500126606300217150ustar00rootroot00000000000000### Use cases ### Desired behavior ### Benefits of the solution ### Possible drawbacks /label ~"Feature" podcasts-25.2/.gitlab/merge_requests_templates/000077500000000000000000000000001500126606300217115ustar00rootroot00000000000000podcasts-25.2/.gitlab/merge_requests_templates/mr.md000066400000000000000000000001301500126606300226430ustar00rootroot00000000000000### Please attach a relevant issue to this MR, if this doesn't exist please create one. podcasts-25.2/CHANGELOG.md000066400000000000000000000446611500126606300151050ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added: ### Changed: ### Fixed: ### Removed: ## [25.2] - 2025-04-20 ### Added: - Add a Description button to the Episode Sheet World/podcasts!407 - Set current position and track length in mpris World/podcasts!417 ### Changed: - Update rust edition to 2024 World/podcasts!412 - Translation updates ### Fixed: - Miscellaneous fixes for AdwButtonContent World/podcasts!401 - Cosmetic fixes for the Episode Menu World/podcasts!408 - Timestamp links work again with Streamed episodes World/podcasts#372 - Improve rendering and sizing of episode widgets World/podcasts!414 ## [0.7.2] - 2025-01-12 ### Added: - You can now mark individual episodes as played - The Shows will now scale based on the window size - You can close the window with the Control + W shortcut ### Changed: - Rework the download machinery to be faster and more efficient - Rework the way thumbnails are saved on disk - Generate thumbnails in a dedicated thread - Use AdwAboutDialog instead of AdwAboutWindow - Only run cleanup at shutdown to improve application startup - Translation updates ### Fixed: - Automatically detect the image format for thumbnails - Dates are now displayed and calculated using localtime instead of sometimes using UTC - Fix accessibility warnings in the Episode Description - Correctly trigger a download when thumbnail cover for mpris is missing - Handle hidpi values bigger than 2 for thumbnails - Correctly calculate the episode download size if its missing from the xml metadata - Fix a bug where thumbnails would not get downscaled properly ### Removed: - Links in the Episode Description are no longer underlined ## [0.7.1] - 2024-04-19 ### Fixed: - Screenshot link ## [0.7.0] - 2024-04-19 ### Added: - Replace add button popover with dedicated page - discovery: add a spinner on the search result subscribe button. - feed_manager: add a locked feed refresh manager - app: add additional keyboard shortcuts. - episode_description: add stream/download/play/delete buttons. ### Changed: - podcasts-gtk: player: replace "mpris_player" with "mpris_server" - gtk: Create the widgets in the background before inserting them - lazy_load: Improve the loading of widgets - Use newer libadwaita 1.4 widgets ### Fixed: - data: Don't identify episodes by their title when they have a guid - player_toolbar: request a minimum width. ## [0.6.1] - 2023-09-18 ### Added: - Add translation and source code links - Add escape as a universal go-back keybinding - Add missing accessibility labels to lists and menus - Add accessibility labels to the add feed popover - Make the description and cover image accessible - Episode description: make selectable, jump link titles, plaintext fallback ### Changed: - Port episode_description to gtk composite template - Port home_view to gtk composite template - Delete the cover directory when removing a podcast - Downloader: raise the max-redirect-policy to 20 (from 5) - Move to reqwest async and stop using hyper directly. - Port episode_widget to gtk composite template - Upgrade to gtk4 crate to 0.7 - Port home_episode to gtk composite template - Skip hash links and empty links ### Fixed: - MPRIS: connect seek instead of prev/next - Simplify the whole view and make covers accessible - Keep focus on the play button when toggling play/pause - Use correct colons (U+003A instead of U+2236) - Use correct IDs in the MPRIS player - Set images role to presentation - Don't handle 302 as a permanent redirect ## [0.6.0] - 2023-07-04 ### Added: - Support for Dark Mode World/podcasts!199 - Introduce ReadMoreLabel widget World/podcasts!199 ### Changed: - Ported to GTK 4 World/podcasts!199 - Port notifications to AdwToast World/podcasts!199 - Round cover images World/podcasts!199 - Make FileChooser dialogs modal World/podcasts!199 - Port to AdwAboutWindow World/podcasts!219 - Move the update ProgressBar to an Overlay World/podcasts!224 - Switch to Rust 2021 edition World/podcasts!233 - Require GNOME 44 features World/podcasts!233 - Port from deprecated GTK apis World/podcasts!233 - Translation updates ### Fixed: - Correctly import non-utf8 encoded feeds World/podcasts!214 - Escape markup from show descriptions World/podcasts!199 - Fix action bar style World/podcasts!224 - Use a dedicated tokio runtime instance World/podcasts!234 ## [0.5.1] - 2022-01-03 ### Added: - Render lists in episode descriptions World/podcasts!210 ### Changed: - Translation updates ### Fixed: - Pass a file uri to mpris for the cover art instead of http url World/podcasts!209 - Fix itunes tests World/podcasts!207 ## [0.5.0] - 2021-12-04 ### Changed: - Description metadata for app stores World/podcasts!202 - Translation updates ### Fixed: - Fix the wrong User-Agent header being sent World/podcasts!207 - Make newlines display in episode descritpions display correctly World/podcasts!206 ## [0.5.0-beta] - 2021-08-20 ### Added: - View episode descriptions and show notes World/podcasts!178 - Pick up the pace of an episode from where you left off World/podcasts!184 - We now inhibit suspend during playback World/podcasts!188 - Detect Soundcloud playlists and correctly add them as feeds World/podcasts!190 - 0.75 and 0.9 playback rate options World/podcasts!187 - Device form factor and input metadata World/podcasts!195 ### Changed: - Translation updates ### Fixed: - HTTP authentication when the username might be an email World/podcasts!183 - Correctly set a user agent while downloading in more places World/podcasts!194 ### Removed: ## [0.4.9] - 2021-03-11 ### Added: - Automatically refresh Show artwork/covers World/podcasts!176 - Suggest a filename on the export opml dialog World/podcasts!155 - Extract RSS feeds from soundcloud links World/podcasts!177 ### Changed: - Upgrade dependencies World/podcasts!148 World/podcasts!149 - Improve indexing pipeline World/podcasts!150 - Upgrade to `libhandy-1.0` World/podcasts!160 World/podcasts!153 - Use glib channels and refactor actions handling World/podcasts!154 - Use libhandy styling for lists World/podcasts!169 - GTK 3 cleanups in preparation for the GTk 4 port World/podcasts!175 - Translation updates ### Fixed: - Fix date comparison with episodes from previous year World/podcasts!161 - Fix copying downloads across filesystems World/podcasts!180 ### Removed: - Remove static resources World/podcasts!170 ## [0.4.8] - 2020-07-09 ### Added: - Handy Header/Switcher World/podcasts!130 - Revealer for long show descriptions World/podcasts!129 - Adaptive player World/podcasts!131 - Add 1.75 and 2x playback options World/podcasts!131 ### Changed: - Use standard macros World/podcasts!138 - Use async functions and upgrade to futures 0.3 World/podcasts!145 - Store window size World/podcasts!140 - Use monospace font for numbers World/podcasts!134 - Translation updates ### Fixed: - Fix phantom window World/podcasts!128 - to add feed World/podcasts!130 - Prevent simultanious refreshes World/podcasts!133 - Fix buttons in speed menu World/podcasts!134 - Internationalisation fixes ## [0.4.7] - 2019-10-23 ### Added: - Improved appdata validation and meson tests World/podcasts!89 - The ability to export show subscriptions to opml files World/podcasts!77 - Support for feeds requiring authentication World/podcasts!120 ### Changed: - Episodes now have a checkmark to show whether or not they've been played World/podcasts!106 - Changed to how errors are shown when adding podcasts World/podcasts!108 World/podcasts!109 World/podcasts!110 - Improved integration of cargo and meson World/podcasts!94 - Refactored some macros for error handling World/podcasts!82 - Refactored the handling of styling changes World/podcasts!119 - Updated the icon to better match the HIG guidlines World/podcasts#102 - Made Podcasts use a GtkApplication subclass World/podcasts!113 - Updated the MPRIS permissions in order to remove a sandbox hole World/podcasts!124 - Bumped gtk and libhandy minimum versions ### Fixed: - Rewind now works regardless if its the start or the end of the episode World/podcasts!83 - Typos in the README and CONTRIBUTING docs World/podcast!97 World/podcast!98 World/podcast!99 World/podcasts!121 - Show cover is reset properly now if there isn't an image World/podcasts#114 - Query pairs are no longer stripped from URLs World/podcasts!111 - Pause MPRIS button now works on KDE Plasma World/podcasts#115 - The playback widget now properly reflects the playback state on episode change World/podcasts!116 ### Removed: - All preferences World/podcast!104 ## [0.4.6] - 2018-10-07 ### Added: - Felix, @haecker-felix, wrote an [mpris crate](https://crates.io/crates/mpris-player) and implemented MPRIS2 client side support! !74 #68 ### Changed: - Download Cancel button was changed to an Icon instead of a label !72 - The applciation will no longer scale below 360p in width 1933c79f7a87d8261d91ca4e14eb51c1ddc66624 - Update to the latest HIG 5050dda4d2f75b706842de8507d115dd5a1bd0a9 - Chris, @brainblasted, upgraded hyper to 0.12, this brings openssl 1.1 support !75 - Pipeline backend is now completly migrated to tokio-runtime 0887789f5e653dd92ad397fb39561df6dffcb45c - Resume playing an episode will attempt to rewind the track only if more than a minute has passed since the last pause !76 ### Fixed: - Fixed a regression where indexing feeds was blocking the `tokio reactor` #88 !70 - Episodeds Listbox no longer resizes when a download starts #89 !72 - The `total_size` label of the `EpisodeWidget` now behaves correctly if the request fails #90 !73 - The Pipeline will no longer log things in stderr for Requests that returned 304 and are expected to be skipped da361d0cb93cd8edd076859b2c607509a96dac8d - A bug where the HomeView wold get into an invalid state if your only shows had no episodes 32bd2a89a34e8e940b3b260c6be76defe11835ed ### Translations: **Added** - Brazilian Portuguese translation 586cf16f - Swedish translation 2e527250 - Italian translation a23297e5 - Friulian translation 60e09c0d - Hungarian translation 2751a828 - Croatian translation 0476b67b - Latvian translation a681b2c9 - Czech translation 3563a964 - Catalan translation 6ea3fc91 **Updated** - German translation - Finnish translation - Polish translation - Turkish translation - Croatian translation - Indonesian translation - Spanish translation ## [0.4.5] - 2018-08-31 ### Added: - [OARS](https://hughsie.github.io/oars/) Tags where added for compatibility with Store clients b0c94dd9 - Daniel added support for Translations !46 - Svitozar Cherepii(@svito) created a [wiki page](https://wiki.gnome.org/Apps/Podcasts) 70e79e50 - Libhandy was added as a dependancy #70 - Development builds can now be installed in parallel with stable builds !64 ### Changed: - The update indication was moved to an In-App notification #72 - The app icon's accent color was changed from orange to red 0dfb4859 - The stack switcher in the Headerbar is now insesitive on Empty Views !63 ### Fixed: - Improved handling of HTTP redirections #64 !61 !62 - Fixed a major performance regression when loading show covers !67 - More refference cycles have been fixed !59 - OPML import dialog now exits properly and no longer keeps the application from shuting down !65 - Update action is disabled if there isn't something to update #71 ### Translations: - Added Finish 93696026 - Added Polish 1bd6efc0 - Added Turkish 73929f2d - Added Spanish !46 - Added German 6b6c390c - Added Galician 0060a634 - Added Indonesian ded0224f - Added Korean 36f16963 ## [0.4.4] - 2018-07-31 ### Changed: - `SendCell` crate was replaced with `Fragile`. (Jorda Petridis) 838320785ebbea94e009698b473495cfec076f54 - Update dependancies (Jorda Petridis) 91bea8551998b16e44e5358fdd43c53422bcc6f3 ### Fixed: - Fix more refference cycles. (Jorda Petridis) 3496df24f8d8bfa8c8a53d8f00262d42ee39b41c - Actually fix cargo-vendor (Jorda Petridis) ## [0.4.3] - 2018-07-27 ### Fixed: - Fix the cargo vendor config for the tarball releash script. (Jorda Petridis) a2440c19e11ca4dcdbcb67cd85259a41fe3754d6 ## [0.4.2] - 2018-07-27 ### Changed: - Minimum size requested by the Views. (Jorda Petridis) 7c96152f3f53f271247230dccf1c9cd5947b685f ### Fixed: - Screenshot metadata in appstream data. (Jorda Petridis) a2440c19e11ca4dcdbcb67cd85259a41fe3754d6 ## [0.4.1] - 2018-07-26 ### Added: - Custom icons for the fast-forward and rewind actions in the Player were added. (Tobias Bernard) e77000076b3d78b8625f4c7ef367376d0130ece6 - Hicolor and symbolic icons for the Application. (Tobias Bernard and Sam Hewitt) edae1b04801dba9d91d5d4145db79b287f0eec2c - Basic prefferences dialog (Zander Brown). [34](https://gitlab.gnome.org/World/podcasts/merge_requests/34) - Dbus service preperation. Not used till the MPRIS2 integration has landed. (Zander Brown) [42](https://gitlab.gnome.org/World/podcasts/merge_requests/42) - Episodes and Images will only get drawn when needed. Big Performance impact. (Jordan Petridis) [43](https://gitlab.gnome.org/World/podcasts/merge_requests/43) ### Changed: - The `ShowWidget` control button were moved to a secondary menu in the Headerbar. (Jordan Petridis) 536805791e336a3e112799be554706bb804d2bef - EmptyView layout improvements. (Jorda Petridis) 3c3d6c1e7f15b88308a9054b15a6ca0d8fa233ce 518ea9c8b57885c44bda9c418b19fef26ae0e55d - Improved the `AddButton` behavior. (Jorda Petridis) 67ab54f8203f19aad198dc49e935127d25432b41 ### Fixed: - A couple reffence cycles where fixed. (Jorda Petridis) ### Removed: - The delay between the application startup and the `update_on_startup` action. (Jorda Petridis) 7569465a612ee5ef84d0e58f4e1010c8d14080d4 ## [0.4.0] - 2018-07-04 ### Added: - Keyboard Shortcuts and a Shortcuts dialog were implemented. (ZanderBrown) [!33](https://gitlab.gnome.org/World/podcasts/merge_requests/33) ### Changed: - The `FileChooser` of the OPML import was changed to use the `FileChooserNative` widget/API. (ZanderBrown) [!33](https://gitlab.gnome.org/World/podcasts/merge_requests/33) - The `EpisdeWidget` was refactored. [!38](https://gitlab.gnome.org/World/podcasts/merge_requests/38) - `EpisdeWidget`'s progressbar was changed to be non-blocking and should feel way more responsive now. 9b0ac5b83dadecdff51cd398293afdf0d5276012 - An embeded audio player was implemented! [!40](https://gitlab.gnome.org/World/podcasts/merge_requests/40) - Various Database changes. [!41](https://gitlab.gnome.org/World/podcasts/merge_requests/41) ### Fixed: - Fixed a bug whre the about dialog would be unclosable. (ZanderBrown) [!37](https://gitlab.gnome.org/World/podcasts/merge_requests/37) ## [0.3.4] - 2018-05-20 ### Fixed: - Flatpak can now access the Home folder. This fixes the OPML import feature from not being able to access any file. ## [0.3.3] - 2018-05-19 ### Added: - Initial functionality for importing shows from an OPML file was implemented. - ShowsView now rembmers the vertical alignment of the scrollbar between refreshes. 4d2b64e79d8518454b3677612664cd32044cf837 ### Changed: - Minimum `rustc` version requirment was bumped to `1.26` - Some animations should be smoother now. 7d598bb1d08b05fd5ab532657acdad967c0afbc3 - InAppNotification now can be used to propagate some erros to the user. 7035fe05c4741b3e7ccce6827f72766226d5fc0a and 118dac5a1ab79c0b4ebe78e88256a4a38b138c04 ### Fixed: - Fixed a of by one bug in the `ShowsView` where the last show was never shown. bd12b09cbc8132fd39a266fd091e24bc6c3c040f ## [0.3.2] - 2018-05-07 ### Added: - Vies now have a new fancy scrolling animation when they are refereshed. ### Changed: - Downlaoding and loading images now is done asynchronously and is not blocking programs execution. [#7](https://gitlab.gnome.org/World/podcasts/issues/7) - Bold, italics links and some other `html` tags can now be rendered in the Show Description. [#25](https://gitlab.gnome.org/World/podcasts/issues/25) - `Rayon` Threadpools are now used instead of unlimited one-off threads. - `EpisdeWidget`s are now loaded asynchronously accross views. - `EpisodeWidget`s no longer trigger a `View` refresh for trivial stuff 03bd95184808ccab3e0ea0e3713a52ee6b7c9ab4 - `ShowWidget` layout was changed 9a5cc1595d982f3232ee7595b83b6512ac8f6c88 - `ShowWidget` Description is inside a scrolled window now ### Fixed: - `EpisodeWidget` Height now is consistent accros views [#57](https://gitlab.gnome.org/World/podcasts/issues/57) - Implemented a tail-recursion loop to follow-up when a feed redirects to another url. c6a24e839a8ba77d09673f299cfc1e64ba7078f3 ### Removed: - Removed the custom configuration file and replaced instructions to just use meson. 1f1d4af8ba7db8f56435d13a1c191ecff3d4a85b ## [0.3.1] - 2018-03-28 ### Added: - Ability to mark all episodes of a Show as watched. [#47](https://gitlab.gnome.org/World/podcasts/issues/47) - Now you are able to subscribe to itunes™ podcasts by using the itunes link of the show. [#49](https://gitlab.gnome.org/World/podcasts/issues/49) - Hammond now remembers the window size and position. (Rowan Lewis) [#50](https://gitlab.gnome.org/World/podcasts/issues/50) - Implemnted the initial work for integrating with GSettings and storing preferences. (Rowan Lewis) [!22](https://gitlab.gnome.org/World/podcasts/merge_requests/22) [!23](https://gitlab.gnome.org/World/podcasts/merge_requests/23) - Shows without episodes now display an empty message similar to EmptyView. [#44](https://gitlab.gnome.org/World/podcasts/issues/44) ### Changed: - EpisdeWidget has been reimplemented as a compile time state machine. [!18](https://gitlab.gnome.org/World/podcasts/merge_requests/18) - Content Views no longer scroll horizontally when shrunk bellow their minimum size. [#35](https://gitlab.gnome.org/World/podcasts/issues/35) - Some requests now use the Tor Browser's user agent. (Rowan Lewis) [#53](https://gitlab.gnome.org/World/podcasts/issues/53) ### Fixed: - Double border aroun the main window was fixed. (Rowan Lewis) [#52](https://gitlab.gnome.org/World/podcasts/issues/52) ## [0.3.0] - 2018-02-11 - Tobias Bernard Redesigned the whole Gtk+ client. - Complete re-write of hammond-data and hammond-gtk modules. - Error handling for all crates was migrated from error-chain to Failure. - Hammond-data now uses futures to parse feeds. - Custom gtk-widgets are now composed structs as opposed to functions returning Gtk widgets. ## [0.2.0] - 2017-11-28 - Database Schema Breaking Changes. - Added url sanitization. #4. - Reworked and refactored of the hammond-data API. - Added some more unit tests - Documented hammond-data public API. ## [0.1.1] - 2017-11-13 - Added appdata.xml file ## [0.1.0] - 2017-11-13 - Initial Release podcasts-25.2/CONTRIBUTING.md000066400000000000000000000100151500126606300155070ustar00rootroot00000000000000## Contributing to GNOME Podcasts Thank you for looking in this file! When contributing to the development of GNOME Podcasts, please first discuss the change you wish to make via issue, email, or any other method with the maintainers before making a change. If you have any questions regarding the use or development of GNOME Podcasts, want to discuss design or simply hang out, please join us in [#podcasts:gnome.org](https://matrix.to/#/#podcasts:gnome.org) or [#hammond on irc.gnome.org.](irc://irc.gnome.org/#hammond) ## Code of Conduct Please note we follow the [GNOME Foundation Code of Conduct](https://conduct.gnome.org/), please adhere to it in all your interactions with the project. ## Source repository GNOME Podcasts's main source repository is at gitlab.gnome.org. You can view the web interface [here](https://gitlab.gnome.org/World/podcasts) Development happens in the main branch. Note that we don't do bug tracking in the GitHub mirror. If you need to publish a branch, feel free to do it at any publically-accessible Git hosting service, although gitlab.gnome.org makes things easier for the maintainers. ## Development Builds Set up the project with the development profile with: ```sh meson setup _build -Dprofile=development ``` You can compile the project with: ```sh meson compile -C _build ``` ## Style We use [rustfmt](https://github.com/rust-lang-nursery/rustfmt) for code formatting and we enforce it on the GitLab CI server. Our continuous integration pipeline assumes the version of rustfmt that is distributed through the stable channel of [rustup](rustup.rs). You can install it with: ```sh rustup component add rustfmt cargo fmt --all ``` It is recommended to add a pre-commit hook to run tests and `cargo fmt`. Don't forget to `git add` again after `cargo fmt`. ``` #!/bin/sh meson test -C _build && cargo fmt --all -- --check ``` ## Running the test suite Running the tests requires an internet connection and will download some files from the [Internet Archive](https://archive.org/). The test suite sets a temporary sqlite database in the `/tmp` folder. Due to that it's not possible to run them in parallel. In order to run the test suite use the following: `meson test -C _build` # Issues, issues and more issues! There are many ways you can contribute to GNOME Podcasts, and all of them involve creating issues in [GNOME Podcasts issue tracker](https://gitlab.gnome.org/World/podcasts/issues). This is the entry point for your contribution. To create an effective and high quality ticket, try to put the following information on your ticket: 1. A detailed description of the issue or feature request - For issues, please add the necessary steps to reproduce the issue. - For feature requests, add a detailed description of your proposal. 2. A checklist of Development tasks 3. A checklist of Design tasks 4. A checklist of QA tasks ## Issue template ``` [Title of the issue or feature request] Detailed description of the issue. Put as much information as you can, potentially with images showing the issue or mockups of the proposed feature. If it's an issue, add the steps to reproduce like this: Steps to reproduce: 1. Open GNOME Podcasts 2. Do an Action 3. ... ## Design Tasks * [ ] design tasks ## Development Tasks * [ ] development tasks ## QA Tasks * [ ] qa (quality assurance) tasks ``` ## Merge Request Process 1. Ensure your code compiles. Run `meson` & `ninja` before creating the merge request. 2. Ensure the test suit passes. Run `meson test -C _build`. 3. Ensure your code is properly formatted. Run `cargo fmt --all`. 4. If you're adding new API, it must be properly documented. 5. The commit message has to be formatted as follows: ``` component: A paragraph explaining the problem and its context. Another one explaining how you solved that. ``` 6. You may merge the merge request once you have the sign-off of the maintainers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. podcasts-25.2/Cargo.lock000066400000000000000000003334101500126606300151720ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aligned-vec" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" [[package]] name = "ammonia" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ab99eae5ee58501ab236beb6f20f6ca39be615267b014899c89b2f0bc18a459" dependencies = [ "html5ever", "maplit", "once_cell", "tendril", "url", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anyhow" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" [[package]] name = "arg_enum_proc_macro" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "async-broadcast" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-channel" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "slab", ] [[package]] name = "async-fs" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock", "blocking", "futures-lite", ] [[package]] name = "async-io" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", ] [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-process" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63255f1dc2381611000436537bbedfe83183faa303a5a0edaf191edef06526bb" dependencies = [ "async-channel", "async-io", "async-lock", "async-signal", "async-task", "blocking", "cfg-if", "event-listener", "futures-lite", "rustix 0.38.44", "tracing", ] [[package]] name = "async-recursion" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "async-signal" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ "async-io", "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "atom_syndication" version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f68d23e2cb4fd958c705b91a6b4c80ceeaf27a9e11651272a8389d5ce1a4a3" dependencies = [ "chrono", "derive_builder", "diligent-date-parser", "never", "quick-xml", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atomic_refcell" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "av1-grain" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" dependencies = [ "anyhow", "arrayvec", "log", "nom", "num-rational", "v_frame", ] [[package]] name = "avif-serialize" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" dependencies = [ "arrayvec", ] [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bit_field" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitstream-io" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2" [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel", "async-task", "futures-io", "futures-lite", "piper", ] [[package]] name = "built" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder-lite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cairo-rs" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" dependencies = [ "bitflags 2.9.0", "cairo-sys-rs", "glib", "libc", ] [[package]] name = "cairo-sys-rs" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f18b6bb8e43c7eb0f2aac7976afe0c61b6f5fc2ab7bc4c139537ea56c92290df" dependencies = [ "glib-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "cc" version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cfg-expr" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-expr" version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" dependencies = [ "smallvec", "target-lexicon", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "pure-rust-locales", "serde", "wasm-bindgen", "windows-link", ] [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "darling" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] [[package]] name = "derive_builder" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "derive_builder_macro" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", "syn", ] [[package]] name = "diesel" version = "2.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34d3950690ba3a6910126162b47e775e203006d4242a15de912bec6c0a695153" dependencies = [ "chrono", "diesel_derives", "libsqlite3-sys", "r2d2", "time", ] [[package]] name = "diesel_derives" version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a93958254b70bea63b4187ff73d10180599d9d8d177071b7f91e6da4e0c0ad55" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", "proc-macro2", "quote", "syn", ] [[package]] name = "diesel_migrations" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" dependencies = [ "diesel", "migrations_internals", "migrations_macros", ] [[package]] name = "diesel_table_macro_syntax" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ "syn", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "diligent-date-parser" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ede7d79366f419921e2e2f67889c12125726692a313bffb474bd5f37a581e9" dependencies = [ "chrono", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "dsl_auto_type" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "139ae9aca7527f85f26dd76483eb38533fd84bd571065da1739656ef71c5ff5b" dependencies = [ "darling", "either", "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "endi" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "enumflags2" version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" dependencies = [ "enumflags2_derive", "serde", ] [[package]] name = "enumflags2_derive" version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "env_logger" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "event-listener" version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ "event-listener", "pin-project-lite", ] [[package]] name = "exr" version = "1.73.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" dependencies = [ "bit_field", "half", "lebe", "miniz_oxide", "rayon-core", "smallvec", "zune-inflate", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] [[package]] name = "field-offset" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ "memoffset", "rustc_version", ] [[package]] name = "flate2" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fragile" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" [[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "gdk-pixbuf" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7563afd6ff0a221edfbb70a78add5075b8d9cb48e637a40a24c3ece3fea414d0" dependencies = [ "gdk-pixbuf-sys", "gio", "glib", "libc", ] [[package]] name = "gdk-pixbuf-sys" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67f2587c9202bf997476bbba6aaed4f78a11538a2567df002a5f57f5331d0b5c" dependencies = [ "gio-sys", "glib-sys", "gobject-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "gdk4" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" dependencies = [ "cairo-rs", "gdk-pixbuf", "gdk4-sys", "gio", "glib", "libc", "pango", ] [[package]] name = "gdk4-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gio-sys", "glib-sys", "gobject-sys", "libc", "pango-sys", "pkg-config", "system-deps 7.0.3", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", ] [[package]] name = "gettext-rs" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44e92f7dc08430aca7ed55de161253a22276dfd69c5526e5c5e95d1f7cf338a" dependencies = [ "gettext-sys", "locale_config", ] [[package]] name = "gettext-sys" version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb45773f5b8945f12aecd04558f545964f943dacda1b1155b3d738f5469ef661" dependencies = [ "cc", "temp-dir", ] [[package]] name = "gif" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" dependencies = [ "color_quant", "weezl", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gio" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4f00c70f8029d84ea7572dd0e1aaa79e5329667b4c17f329d79ffb1e6277487" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-util", "gio-sys", "glib", "libc", "pin-project-lite", "smallvec", ] [[package]] name = "gio-sys" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "160eb5250a26998c3e1b54e6a3d4ea15c6c7762a6062a19a7b63eff6e2b33f9e" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps 7.0.3", "windows-sys 0.59.0", ] [[package]] name = "glib" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" dependencies = [ "bitflags 2.9.0", "futures-channel", "futures-core", "futures-executor", "futures-task", "futures-util", "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", "memchr", "smallvec", ] [[package]] name = "glib-macros" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" dependencies = [ "heck", "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "glib-sys" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8928869a44cfdd1fccb17d6746e4ff82c8f82e41ce705aa026a52ca8dc3aefb" dependencies = [ "libc", "system-deps 7.0.3", ] [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gobject-sys" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c773a3cb38a419ad9c26c81d177d96b4b08980e8bdbbf32dace883e96e96e7e3" dependencies = [ "glib-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "graphene-rs" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbc5911bfb32d68dcfa92c9510c462696c2f715548fcd7f3f1be424c739de19" dependencies = [ "glib", "graphene-sys", "libc", ] [[package]] name = "graphene-sys" version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11a68d39515bf340e879b72cecd4a25c1332557757ada6e8aba8654b4b81d23a" dependencies = [ "glib-sys", "libc", "pkg-config", "system-deps 7.0.3", ] [[package]] name = "gsk4" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" dependencies = [ "cairo-rs", "gdk4", "glib", "graphene-rs", "gsk4-sys", "libc", "pango", ] [[package]] name = "gsk4-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" dependencies = [ "cairo-sys-rs", "gdk4-sys", "glib-sys", "gobject-sys", "graphene-sys", "libc", "pango-sys", "system-deps 7.0.3", ] [[package]] name = "gstreamer" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2188fe829b0ebe12e4cf2bbcf6658470a936269daba7afae92847a2af32c9105" dependencies = [ "cfg-if", "futures-channel", "futures-core", "futures-util", "glib", "gstreamer-sys", "itertools 0.13.0", "libc", "muldiv", "num-integer", "num-rational", "once_cell", "option-operations", "paste", "pin-project-lite", "smallvec", "thiserror 2.0.12", ] [[package]] name = "gstreamer-base" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad33dd444db0d215ac363164f900f800ffb93361ad8a60840e95e14b7de985e8" dependencies = [ "atomic_refcell", "cfg-if", "glib", "gstreamer", "gstreamer-base-sys", "libc", ] [[package]] name = "gstreamer-base-sys" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "114b2a704f19a70f20c54b00e54f5d5376bbf78bd2791e6beb0776c997d8bf24" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "gstreamer-play" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ef455584b832e9fdc76f7952b9432eaee2fd287157b03cf2bc0e83f1b41619c" dependencies = [ "glib", "gstreamer", "gstreamer-play-sys", "gstreamer-video", "libc", ] [[package]] name = "gstreamer-play-sys" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b01c1c4f09cb6709c7da2532b3fcbc14da9006d508baee606328080e46f491f5" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-sys", "gstreamer-video-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "gstreamer-sys" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe159238834058725808cf6604a7c5d9e4a50e1eacd7b0c63bce2fe3a067dbd1" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "gstreamer-video" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad242d388b63c91652c8157de3b0c1f709e49c941a0aae1952455f6ee326ca2d" dependencies = [ "cfg-if", "futures-channel", "glib", "gstreamer", "gstreamer-base", "gstreamer-video-sys", "libc", "once_cell", "thiserror 2.0.12", ] [[package]] name = "gstreamer-video-sys" version = "0.23.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "465ff496889fb38be47f5e821163c2e83414d87c4aa55f5aae62dc7200971d4d" dependencies = [ "glib-sys", "gobject-sys", "gstreamer-base-sys", "gstreamer-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "gtk4" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" dependencies = [ "cairo-rs", "field-offset", "futures-channel", "gdk-pixbuf", "gdk4", "gio", "glib", "graphene-rs", "gsk4", "gtk4-macros", "gtk4-sys", "libc", "pango", ] [[package]] name = "gtk4-macros" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "gtk4-sys" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", "gdk4-sys", "gio-sys", "glib-sys", "gobject-sys", "graphene-sys", "gsk4-sys", "libc", "pango-sys", "system-deps 7.0.3", ] [[package]] name = "h2" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hermit-abi" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "html2text" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "042a9677c258ac2952dd026bb0cd21972f00f644a5a38f5a215cb22cdaf6834e" dependencies = [ "html5ever", "markup5ever", "tendril", "thiserror 1.0.69", "unicode-width", ] [[package]] name = "html5ever" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", "syn", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "humansize" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ "libm", ] [[package]] name = "humantime" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" [[package]] name = "hyper" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "libc", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "image" version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", "image-webp", "num-traits", "png", "qoi", "ravif", "rayon", "rgb", "tiff", "zune-core", "zune-jpeg", ] [[package]] name = "image-webp" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" dependencies = [ "byteorder-lite", "quick-error", ] [[package]] name = "imgref" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "interpolate_name" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-docker" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" dependencies = [ "once_cell", ] [[package]] name = "is-terminal" version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi 0.5.0", "libc", "windows-sys 0.59.0", ] [[package]] name = "is-wsl" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" dependencies = [ "is-docker", "once_cell", ] [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ "getrandom 0.3.2", "libc", ] [[package]] name = "jpeg-decoder" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lebe" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libadwaita" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "500135d29c16aabf67baafd3e7741d48e8b8978ca98bac39e589165c8dc78191" dependencies = [ "gdk4", "gio", "glib", "gtk4", "libadwaita-sys", "libc", "pango", ] [[package]] name = "libadwaita-sys" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6680988058c2558baf3f548a370e4e78da3bf7f08469daa822ac414842c912db" dependencies = [ "gdk4-sys", "gio-sys", "glib-sys", "gobject-sys", "gtk4-sys", "libc", "pango-sys", "system-deps 7.0.3", ] [[package]] name = "libc" version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libfuzzer-sys" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", ] [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libsqlite3-sys" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" dependencies = [ "pkg-config", "vcpkg", ] [[package]] name = "linkify" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" dependencies = [ "memchr", ] [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "locale_config" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d2c35b16f4483f6c26f0e4e9550717a2f6575bcd6f12a53ff0c490a94a6934" dependencies = [ "lazy_static", "objc", "objc-foundation", "regex", "winapi", ] [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" dependencies = [ "imgref", ] [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "malloc_buf" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" dependencies = [ "libc", ] [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", "phf", "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "markup5ever_rcdom" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18" dependencies = [ "html5ever", "markup5ever", "tendril", "xml5ever", ] [[package]] name = "maybe-rayon" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ "cfg-if", "rayon", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "migrations_internals" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" dependencies = [ "serde", "toml", ] [[package]] name = "migrations_macros" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" dependencies = [ "migrations_internals", "proc-macro2", "quote", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", "simd-adler32", ] [[package]] name = "mio" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "mpris-server" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058bc2227727af394f34aa51da3e36aeecf2c808f39315d35f754872660750ae" dependencies = [ "async-channel", "futures-channel", "serde", "trait-variant", "zbus", ] [[package]] name = "muldiv" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" [[package]] name = "native-tls" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "never" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.9.0", "cfg-if", "cfg_aliases", "libc", "memoffset", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "noop_proc_macro" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "objc" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", ] [[package]] name = "objc-foundation" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" dependencies = [ "block", "objc", "objc_id", ] [[package]] name = "objc_id" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" dependencies = [ "objc", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "open" version = "5.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" dependencies = [ "is-wsl", "libc", "pathdiff", ] [[package]] name = "openssl" version = "0.10.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" dependencies = [ "bitflags 2.9.0", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "option-operations" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c26d27bb1aeab65138e4bf7666045169d1717febcc9ff870166be8348b223d0" dependencies = [ "paste", ] [[package]] name = "ordered-stream" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" dependencies = [ "futures-core", "pin-project-lite", ] [[package]] name = "pango" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1f5dc1b8cf9bc08bfc0843a04ee0fa2e78f1e1fa4b126844a383af4f25f0ec" dependencies = [ "gio", "glib", "libc", "pango-sys", ] [[package]] name = "pango-sys" version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dbb9b751673bd8fe49eb78620547973a1e719ed431372122b20abd12445bab5" dependencies = [ "glib-sys", "gobject-sys", "libc", "system-deps 7.0.3", ] [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", "miniz_oxide", ] [[package]] name = "podcasts-data" version = "0.1.0" dependencies = [ "ammonia", "anyhow", "base64", "bytes", "chrono", "derive_builder", "diesel", "diesel_migrations", "futures-util", "glob", "http", "log", "maplit", "mime_guess", "once_cell", "reqwest", "rfc822_sanitizer", "rss", "serde", "serde_json", "tempfile", "thiserror 2.0.12", "tokio", "url", "xdg", "xml-rs", ] [[package]] name = "podcasts-gtk" version = "0.1.0" dependencies = [ "anyhow", "async-channel", "chrono", "fragile", "futures-util", "gettext-rs", "gstreamer", "gstreamer-play", "gtk4", "html2text", "html5ever", "humansize", "image", "libadwaita", "linkify", "locale_config", "log", "markup5ever_rcdom", "mpris-server", "once_cell", "open", "podcasts-data", "pretty_env_logger", "regex", "reqwest", "serde_json", "tempfile", "tokio", "url", ] [[package]] name = "polling" version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_env_logger" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ "env_logger", "log", ] [[package]] name = "proc-macro-crate" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", "syn", ] [[package]] name = "pure-rust-locales" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" [[package]] name = "qoi" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" dependencies = [ "bytemuck", ] [[package]] name = "quick-error" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" version = "0.37.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ce8c88de324ff838700f36fb6ab86c96df0e3c4ab6ef3a9b2044465cce1369" dependencies = [ "encoding_rs", "memchr", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] name = "r2d2" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" dependencies = [ "log", "parking_lot", "scheduled-thread-pool", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.15", ] [[package]] name = "rav1e" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9" dependencies = [ "arbitrary", "arg_enum_proc_macro", "arrayvec", "av1-grain", "bitstream-io", "built", "cfg-if", "interpolate_name", "itertools 0.12.1", "libc", "libfuzzer-sys", "log", "maybe-rayon", "new_debug_unreachable", "noop_proc_macro", "num-derive", "num-traits", "once_cell", "paste", "profiling", "rand", "rand_chacha", "simd_helpers", "system-deps 6.2.2", "thiserror 1.0.69", "v_frame", "wasm-bindgen", ] [[package]] name = "ravif" version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6" dependencies = [ "avif-serialize", "imgref", "loop9", "quick-error", "rav1e", "rayon", "rgb", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags 2.9.0", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tokio-util", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", "windows-registry", ] [[package]] name = "rfc822_sanitizer" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d95e6ac0e635800681025bddc2fa6747cf1159bb897223a74e481ec54b4f5d44" dependencies = [ "chrono", "lazy_static", "regex", ] [[package]] name = "rgb" version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rss" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2107738f003660f0a91f56fd3e3bd3ab5d918b2ddaf1e1ec2136fb1c46f71bf" dependencies = [ "atom_syndication", "derive_builder", "never", "quick-xml", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] name = "rustix" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" dependencies = [ "bitflags 2.9.0", "errno", "libc", "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustls" version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scheduled-thread-pool" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" dependencies = [ "parking_lot", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.9.0", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_repr" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "simd-adler32" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd_helpers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" dependencies = [ "quote", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.9.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "system-deps" version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr 0.15.8", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "system-deps" version = "7.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" dependencies = [ "cfg-expr 0.17.2", "heck", "pkg-config", "toml", "version-compare", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "temp-dir" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc1ee6eef34f12f765cb94725905c6312b6610ab2b0940889cfe58dae7bc3c72" [[package]] name = "tempfile" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", "rustix 1.0.5", "windows-sys 0.59.0", ] [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tiff" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" dependencies = [ "flate2", "jpeg-decoder", "weezl", ] [[package]] name = "time" version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] [[package]] name = "trait-variant" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "uds_windows" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ "memoffset", "tempfile", "winapi", ] [[package]] name = "unicase" version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "v_frame" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" dependencies = [ "aligned-vec", "num-traits", "wasm-bindgen", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version-compare" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-streams" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "weezl" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings 0.4.0", ] [[package]] name = "windows-implement" version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", "windows-strings 0.3.1", "windows-targets 0.53.0", ] [[package]] name = "windows-result" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] [[package]] name = "wit-bindgen-rt" version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ "bitflags 2.9.0", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xdg" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "xdg-home" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "xml-rs" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xml5ever" version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69" dependencies = [ "log", "mac", "markup5ever", ] [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zbus" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ "async-broadcast", "async-executor", "async-fs", "async-io", "async-lock", "async-process", "async-recursion", "async-task", "async-trait", "blocking", "enumflags2", "event-listener", "futures-core", "futures-sink", "futures-util", "hex", "nix", "ordered-stream", "rand", "serde", "serde_repr", "sha1", "static_assertions", "tracing", "uds_windows", "windows-sys 0.52.0", "xdg-home", "zbus_macros", "zbus_names", "zvariant", ] [[package]] name = "zbus_macros" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", "zvariant_utils", ] [[package]] name = "zbus_names" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", "zvariant", ] [[package]] name = "zerocopy" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zune-core" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-inflate" version = "0.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" dependencies = [ "simd-adler32", ] [[package]] name = "zune-jpeg" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] [[package]] name = "zvariant" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" dependencies = [ "endi", "enumflags2", "serde", "static_assertions", "zvariant_derive", ] [[package]] name = "zvariant_derive" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", "zvariant_utils", ] [[package]] name = "zvariant_utils" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", "syn", ] podcasts-25.2/Cargo.toml000066400000000000000000000007351500126606300152160ustar00rootroot00000000000000[workspace] resolver = "2" members = ["podcasts-data", "podcasts-gtk"] [workspace.package] edition = "2024" [workspace.dependencies] anyhow = "1" chrono = {version = "0.4", features = ["serde", "unstable-locales"] } log = "0.4" once_cell = "1" tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync"] } url = "2" reqwest = "0.12" [profile.release] debug = true lto = "thin" [profile.dev] opt-level = 1 lto = "thin" [profile.dev.package."*"] opt-level = 3 podcasts-25.2/LICENSE000066400000000000000000001044211500126606300142700ustar00rootroot00000000000000 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. GNOME Podcasts Copyright (C) 2017 Jordan Petridis 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: GNOME Podcasts Copyright (C) 2017 Jordan Petridis 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 . podcasts-25.2/README.md000066400000000000000000000114621500126606300145440ustar00rootroot00000000000000# GNOME Podcasts ### A Podcast application for GNOME. Listen to your favorite podcasts, right from your desktop. ![Episodes view](./screenshots/home_view.png) ![Shows view](./screenshots/shows_view.png) ![Show widget](./screenshots/show_widget.png) ## Available on Flathub [![Get it from Flathub!](https://flathub.org/api/badge?svg&locale=en)](https://flathub.org/apps/details/org.gnome.Podcasts) ## Quick start GNOME Podcasts can be built and run with [GNOME Builder][builder] >= 41. You can get Builder from [here][get_builder]. You will also need to install the rust-stable extension from flathub. ```sh flatpak install --user flathub org.freedesktop.Sdk.Extension.rust-stable//21.08 ``` Then from Builder, just clone the repo and hit the run button! ## Broken Feeds Found a feed that does not work in GNOME Podcasts? Please [open an issue][new_issue] and choose the `BrokenFeed` template so we will know and fix it! ## Getting in Touch If you have any questions regarding the use or development of GNOME Podcasts, want to discuss design or simply hang out, please join us on our [matrix][matrix] channel. ## Building ### Flatpak Flatpak is the recommended way of building and installing GNOME Podcasts. Here are the dependencies you will need. ```sh # Add flathub and the gnome-nightly repo flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo flatpak remote-add --user --if-not-exists gnome-nightly https://nightly.gnome.org/gnome-nightly.flatpakrepo # Install the gnome-nightly Sdk and Platform runtime flatpak install --user gnome-nightly org.gnome.Sdk org.gnome.Platform # Install the required rust-stable extension from flathub flatpak install --user flathub org.freedesktop.Sdk.Extension.rust-stable//20.08 ``` To install the resulting flatpak you can do: ```bash flatpak-builder --user --install --force-clean --repo=repo podcasts org.gnome.Podcasts.Devel.json ``` ### Building from source ```sh git clone https://gitlab.gnome.org/World/podcasts.git cd podcasts/ meson --prefix=/usr build ninja -C build sudo ninja -C build install ``` #### Dependencies * Rust stable 1.34 or later along with cargo. * Gtk 4.0.0 or later * Gstreamer 1.16 or later * libadwaita 1.0.0 or later * Meson * A network connection Offline build are possible too, but [`cargo-vendor`][vendor] would have to be setup first ## Contributing There are a lot of things yet to be done. If you want to contribute, please check the [Contributions Guidelines][contribution-guidelines]. You can start by taking a look at [Issues][issues] or by opening a [New issue][new_issue]. There are also some minor tasks tagged with `TODO:` and `FIXME:` in the source code. [contribution-guidelines]: https://gitlab.gnome.org/World/podcasts/blob/main/CONTRIBUTING.md ### Translations Helping to translate Podcasts or adding support to a new language is very welcome. You can find everything you need at: [l10n.gnome.org/module/podcasts/](https://l10n.gnome.org/module/podcasts/) ## Overview ```sh $ tree -d ├── screenshots # png's used in the README.md ├── podcasts-data # Storate related stuff, SQLite, XDG setup, RSS Parser. │   ├── migrations # Diesel SQL migrations. │   │   └── ... │   ├── src │   └── tests │   └── feeds # Raw RSS Feeds used for tests. ├── podcasts-gtk # The Gtk+ Client │   ├── resources # GResources folder │   │   └── gtk # Contains the glade.ui files. │   └── src │   ├── stacks # Contains the gtk Stacks that hold all the different views. │   └── widgets # Contains custom widgets such as Show and Episode. ``` ## A note about the project's name The project used to be called Hammond, after Allan Moore's character [Evey Hammond][hammond] from the graphic novel V for Vendetta. It was renamed to GNOME Podcasts on 2018/07/24 shortly before its first public release. ## Acknowledgments GNOME Podcasts's design is heavily inspired by [GNOME Music][music] and [Vocal][vocal]. We also copied some elements from [GNOME News][news]. And almost the entirety of the build system is copied from the [Fractal][fractal] project. [vendor]: https://doc.rust-lang.org/cargo/commands/cargo-vendor.html [matrix]: https://matrix.to/#/#podcasts:gnome.org [flatpak_setup]: https://flatpak.org/setup/ [music]: https://apps.gnome.org/Music/ [vocal]: http://vocalproject.net/ [news]: https://wiki.gnome.org/Design/Apps/Potential/News [fractal]: https://gitlab.gnome.org/World/fractal [hammond]: https://en.wikipedia.org/wiki/Evey_Hammond [issues]: https://gitlab.gnome.org/World/podcasts/issues [new_issue]: https://gitlab.gnome.org/World/podcasts/issues/new [builder]: https://apps.gnome.org/Builder/ [get_builder]: https://flathub.org/apps/org.gnome.Builder podcasts-25.2/changelog.txt000066400000000000000000000146241500126606300157600ustar00rootroot000000000000000eaf5cc2 Update links for new branch and screenshots af5d0c78 Rename screenshot 1d09990b Add new screenshots a14f4ab0 discovery: add a spinner on the search result subscribe button. 27500790 shows: Do not empty the ShowWidget on every shows refresh. 6760b6af feed_manager: add a locked feed refresh manager 909c521e metainfo: add brand colors f9116f9a empty_show: wrap labels, so they fit on a 360px mobile screen. 9fb5c805 discovery_page: set the warning label to wrap. 9d34824a Use gnome feature instead of adding a gtk feature 7fa33896 discovery: Announce the search no provider error to screenreaders. 215cd62c discovery: Show an error when no search provider was selected. 5c8e69cf search_results: fix too wide podcast authors labels. 123ebdb0 app: add keyboard shortcuts. 03c0375f episode_description: move download/stream/play buttons down a row. 4cadf996 player: use a homogeneous stack for each play/pause button pair. 7aef8ef4 manager: send an ErrorNotification when download fails. cded6138 episode_widget: show a text-only icon for episodes without audio. 46ce710d discovery: add margins to the page. b0d4596a gtk: don't call send_blocking! from gio threads. b12fb3de cargo: upgrade gtk, gst, adwaita, mpris-server. abbcc65d app: properly call shutdown_parent() to avoid gtk warning. 8ee123ca episode_description/css: align the play buttons with the bottom of the cover d685e4d7 download_bar: improve code formatting f33fd4a7 window: pop pages before going to the Show widget page. 84e2b5d8 episode_description: add stream/download/play/delete buttons. 9501ffe5 app: finish all pending unsubscribes before quitting. 0067c9e5 utils: use async_channel and await instead of a timeout. cf0b5ca6 player_toolbar: request a minimum width. 09f511e4 Apply 1 suggestion(s) to 1 file(s) 11c9c654 discovery: search itunes and fyyd on a new page. eb860dc0 data: Don't identify episodes by their title when they have a guid e2065300 metainfo: Use reverse DNS developer id 2d315ce2 utils: Log errors on on_export_clicked 2e710105 player: updated mpris-server fb798201 new_episode: always update image_uri 37cb24be episode_description: use action/sender/receiver 7ab5ab4b podcast-data: cleanup old episode-specific-cover files. 28809bea episode_description: add episode_specific cover. 4ed1494a shows: updated shows view never got appended to the ui cc7b6bd6 populated: Remove unused replace_shows function 9e0babcd Reorganize dependencies a74ebe9c Sort dependencies 0cefb8c3 Fix pause_button width 05d7e7fe Use default clippy settings 50033c29 Remove futures crate 0fe7d379 General cleanup fed5e4fd Add error handling 23d210bb Fix clippy warnings and remove dead code 0d0df124 window: Add second AdwToolbarView d08d54db app: Remove go back actions e0b8baee Replace widget from window 6b053c5f show_widget: Use to AdwNavigationPage 08d5b81a show_widget: Remove empty domain 5f7ed740 screenshots: update b07702dd style: improve playback button style 49958842 appdata: Improve appdata for AppStream 1.0 081c8768 appdata: remove categories and keywords 20d734d3 window: Remove setter on header breakpoint e2c5a8e9 app: Simplify match d0e1ac94 app: Replace eprintln with error 0c2de0da app: Port actions to ActionEntry 402a1edd podcasts-gtk: player: replace "mpris_player" with "mpris_server" 40adf6a5 podcasts-data: replace "tempdir" with "tempfile" 87606915 README: fix link to matrix and nuke irc f0e64b79 data: remove AudioVideo and purism tags 221bab58 appdata: Update appdata 02523287 appdata: use appstreamcli for appdata validation 82aa6fa7 content: Remove duplicate function 2327a6ad lazy_load: Tweak the constructor callback priority c181a2ba widgets/episode: defer initialization of the widget 17b5a0a9 widgets/episode: Treat the Widget Template as the default state 64f14f52 views: Do not block when fetching sql models ec8d6c1e lazy_load: Log the total time it takes to construct widgets d80562d6 home_view: Make add_to_boxes async fn and return the handles 6a19f9c5 utils: Pass weak ref of GtkImage in the callback dcff1581 lazy_load: Make it an async fn and return the joined handles 4460b3fc lazy_load: Use the Receiver as a Stream and replace the Vec 44ba4fe5 lazy_load: Replace idle_add with spawn_with_priority b34c295a lazy_load: Replace insert widget callback 9044b413 home_view: Populate the listboxes in parallel df4fa8cf lazy_load: Remove finish_callback 47760c06 gtk: Create the widgets in the background before inserting them 679ec28e window: Define bottom switcher on code 86ec1d82 window: Do not clone player in callback 18cf7f10 window: Hide bottom switcher on show view eb60a260 window: Remove redundant title 5ec7ba7f window: Move AdwBottomSwitcher to window 89cc18f3 window: Use composite templates 1bfc5607 window: Do not set title at runtime bb96b177 show_widget: Add margins to list box 51b79dc3 window: Set player size at construct 37df7a94 window: Fix breakpoint bb066d3b shows_view: Pass weak ref of GtkImage in the callback a92992df lazy_loading: Use a timewindwo instead of hardcoded batch number fa0111d7 lazy_load: Use an idle_add instead of timeout_add 903cec50 lazy_load: Log how much it takes to create widgets af1c9d89 flatpak: fix env vars e98103c4 shows_view: Make the ShowsView struct a gtk widget 762a9c47 lazy_load: Improve the loading of widgets 44fe5ef3 chore: revise contribution guide b5fe3a9f player: Remove margins 41731572 content: Subclass Bin instead of Box c710f958 episode_description: Set selectable after shown 6dc49991 episode_description: Remove redundant a11y prop f15f0d9e Use primary in MenuButtons 4ce0af61 player_dialog: Use AdwToolbarView 909f703a player: Stop using AdwSqueezer 08fa6599 Improve player 10c199da Use AdwNavigationView 35211d76 Stop using AdwSqueezer 61b7d765 appdata: Update GNOME Circle URL 5aadd5b4 Revert "utils: Load images from the path rather than pixbufs" 93152d16 Only optimize dependencies 664a343a pipeline: log the uri of the failed feed 9587803c utils: Don't use async on fns that don't await b40de0eb meson: bump gst requirement to 1.22 from 1.16 d8097b37 player: upgrade gstreamer-player to gstreamer-play affda1dd utils: set useragent for soundcloud/itunes requests 9c64ed75 utils: Load images from the path rather than pixbufs c6548cff Fix clippy lints 0eeacb9e Move to mold linker 2480afdd Update release_process.md 6a9c152b Remove pretty_assertions dep 06795ec4 cargo: Always build optimized debug builds 53989631 Move to gio::spawn_blocking 0ec5b8f4 Switch to "new" resolver 00139666 Bump meson.build post-release 8f72cf85 Update release_process.md podcasts-25.2/meson.build000066400000000000000000000047241500126606300154320ustar00rootroot00000000000000project( 'gnome-podcasts', 'rust', version: '25.2', license: 'GPLv3', meson_version: '>= 1.4', ) dependency('sqlite3', version: '>= 3.20') dependency('openssl', version: '>= 1.0') dependency('dbus-1') dependency('glib-2.0', version: '>= 2.76') dependency('gio-2.0', version: '>= 2.76') dependency('gdk-pixbuf-2.0') dependency('gtk4', version: '>= 4.15.3') dependency('libadwaita-1', version :'>=1.6') dependency('gstreamer-1.0', version: '>= 1.22') dependency('gstreamer-base-1.0', version: '>= 1.22') dependency('gstreamer-audio-1.0', version: '>= 1.22') dependency('gstreamer-play-1.0', version: '>= 1.22') dependency('gstreamer-plugins-base-1.0', version: '>= 1.22') dependency('gstreamer-plugins-bad-1.0', version: '>= 1.22') dependency('gstreamer-bad-audio-1.0', version: '>= 1.22') cargo = find_program('cargo', required: true) gresource = find_program('glib-compile-resources', required: true) gschemas = find_program('glib-compile-schemas', required: true) if get_option('profile') == 'development' profile = '.Devel' vcs_tag = run_command('git', 'rev-parse', '--short', 'HEAD').stdout().strip() if vcs_tag == '' version_suffix = '-devel' else version_suffix = '-@0@'.format (vcs_tag) endif else profile = '' version_suffix = '' endif podcast_toml = files( 'Cargo.toml', 'Cargo.lock', 'podcasts-data/Cargo.toml', 'podcasts-gtk/Cargo.toml', ) application_id = 'org.gnome.Podcasts@0@'.format(profile) i18n = import('i18n') gnome = import('gnome') subdir('podcasts-gtk/po') podir = join_paths (meson.project_source_root (), 'podcasts-gtk', 'po') podcasts_version = meson.project_version() podcasts_prefix = get_option('prefix') podcasts_bindir = join_paths(podcasts_prefix, get_option('bindir')) podcasts_localedir = join_paths(podcasts_prefix, get_option('localedir')) podcasts_conf = configuration_data() podcasts_conf.set('appid', application_id) podcasts_conf.set('bindir', podcasts_bindir) datadir = get_option('datadir') subdir('podcasts-gtk/resources') test_script = find_program('scripts/test.sh') subdir('podcasts-gtk/src') meson.add_dist_script( 'scripts/dist-vendor.sh', meson.project_build_root() / 'meson-dist' / meson.project_name() + '-' + podcasts_version, meson.project_source_root() ) test( 'cargo-test', test_script, args: meson.project_build_root(), workdir: meson.project_source_root(), timeout: 3000 ) gnome.post_install( gtk_update_icon_cache: true, glib_compile_schemas: true, update_desktop_database: true, ) podcasts-25.2/meson_options.txt000066400000000000000000000001571500126606300167210ustar00rootroot00000000000000option ( 'profile', type: 'combo', choices: [ 'default', 'development' ], value: 'default' ) podcasts-25.2/org.gnome.Podcasts.Devel.json000066400000000000000000000041651500126606300206720ustar00rootroot00000000000000{ "id": "org.gnome.Podcasts.Devel", "runtime": "org.gnome.Platform", "runtime-version": "master", "sdk": "org.gnome.Sdk", "sdk-extensions": [ "org.freedesktop.Sdk.Extension.rust-stable", "org.freedesktop.Sdk.Extension.llvm18" ], "command": "gnome-podcasts", "tags": [ "nightly" ], "finish-args": [ "--device=dri", "--share=network", "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--socket=pulseaudio", "--env=RUST_BACKTRACE=1", "--env=RUST_LOG=podcasts_gtk=debug,podcasts_data=debug,glib=debug", "--env=G_ENABLE_DIAGNOSTIC=1" ], "build-options": { "append-path": "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm18/bin", "build-args": [ "--share=network" ], "cflags": "-DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED", "env": { "RUSTFLAGS": "-C force-frame-pointers=yes", "CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "clang", "CARGO_TARGET_AARCH64_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_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold" } }, "modules": [ { "name": "gnome-podcasts", "buildsystem": "meson", "builddir": true, "run-tests": true, "config-opts": [ "-Dprofile=development" ], "build-options": { "test-args": [ "--device=dri", "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--share=network" ] }, "sources": [ { "type": "git", "brach": "main", "url": "https://gitlab.gnome.org/World/podcasts.git" } ] } ] } podcasts-25.2/podcasts-data/000077500000000000000000000000001500126606300160105ustar00rootroot00000000000000podcasts-25.2/podcasts-data/Cargo.toml000066400000000000000000000014751500126606300177470ustar00rootroot00000000000000[package] authors = ["Jordan Petridis "] name = "podcasts-data" version = "0.1.0" edition.workspace = true [dependencies] ammonia = "4" anyhow = { workspace = true } base64 = "0.22" bytes = "1" chrono = { workspace = true } derive_builder = "0.20" diesel = { version = "2", features = ["chrono", "sqlite", "r2d2"] } diesel_migrations = { version = "2", features = ["sqlite"] } futures-util = "0.3" glob = "0.3" http = "1" log = { workspace = true } mime_guess = "2" once_cell = { workspace = true } reqwest = { workspace = true, features = ["json", "stream"] } rfc822_sanitizer = "0.3" rss = "2" serde = { version = "1", features = ["derive"] } tempfile = "3" thiserror = "2" tokio = { workspace = true } url = { workspace = true } xdg = "2" xml-rs = "0.8" [dev-dependencies] serde_json = "1" maplit = "1" podcasts-25.2/podcasts-data/diesel.toml000066400000000000000000000002501500126606300201470ustar00rootroot00000000000000# For documentation on how to configure this file, # see diesel.rs/guides/configuring-diesel-cli [print_schema] file = "src/schema.rs" patch_file = "src/schema.patch" podcasts-25.2/podcasts-data/migrations/000077500000000000000000000000001500126606300201645ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2017-09-15-001128_init_schema/000077500000000000000000000000001500126606300243225ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2017-09-15-001128_init_schema/down.sql000066400000000000000000000000721500126606300260110ustar00rootroot00000000000000Drop Table episode; Drop Table podcast; Drop Table source;podcasts-25.2/podcasts-data/migrations/2017-09-15-001128_init_schema/up.sql000066400000000000000000000017031500126606300254700ustar00rootroot00000000000000CREATE TABLE `source` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `uri` TEXT NOT NULL UNIQUE, `last_modified` TEXT, `http_etag` TEXT ); CREATE TABLE `episode` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT, `uri` TEXT NOT NULL UNIQUE, `local_uri` TEXT, `description` TEXT, `published_date` TEXT, `epoch` INTEGER NOT NULL DEFAULT 0, `length` INTEGER, `guid` TEXT, `played` INTEGER, `favorite` INTEGER NOT NULL DEFAULT 0, `archive` INTEGER NOT NULL DEFAULT 0, `podcast_id` INTEGER NOT NULL ); CREATE TABLE `podcast` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT NOT NULL, `link` TEXT NOT NULL, `description` TEXT NOT NULL, `image_uri` TEXT, `favorite` INTEGER NOT NULL DEFAULT 0, `archive` INTEGER NOT NULL DEFAULT 0, `always_dl` INTEGER NOT NULL DEFAULT 0, `source_id` INTEGER NOT NULL UNIQUE );podcasts-25.2/podcasts-data/migrations/2017-12-09-125835_change_episode_pk/000077500000000000000000000000001500126606300254775ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2017-12-09-125835_change_episode_pk/down.sql000066400000000000000000000013401500126606300271650ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, title TEXT, uri TEXT NOT NULL UNIQUE, local_uri TEXT, description TEXT, published_date TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, guid TEXT, played INTEGER, favorite INTEGER NOT NULL DEFAULT 0, archive INTEGER NOT NULL DEFAULT 0, podcast_id INTEGER NOT NULL ); INSERT INTO episode (title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id) SELECT title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id FROM old_table; Drop table old_table;podcasts-25.2/podcasts-data/migrations/2017-12-09-125835_change_episode_pk/up.sql000066400000000000000000000012111500126606300266370ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, published_date TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, favorite INTEGER DEFAULT 0, archive INTEGER DEFAULT 0, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode (title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id) SELECT title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id FROM old_table; Drop table old_table;podcasts-25.2/podcasts-data/migrations/2017-12-22-145740_add_duration_column/000077500000000000000000000000001500126606300260525ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2017-12-22-145740_add_duration_column/down.sql000066400000000000000000000012121500126606300275360ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, published_date TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, favorite INTEGER DEFAULT 0, archive INTEGER DEFAULT 0, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode (title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id) SELECT title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2017-12-22-145740_add_duration_column/up.sql000066400000000000000000000012341500126606300272170ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, published_date TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, favorite INTEGER DEFAULT 0, archive INTEGER DEFAULT 0, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode (title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id) SELECT title, uri, local_uri, description, published_date, epoch, length, guid, played, favorite, archive, podcast_id FROM old_table; Drop table old_table;podcasts-25.2/podcasts-data/migrations/2017-12-30-201631_remove_published_date/000077500000000000000000000000001500126606300263605ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2017-12-30-201631_remove_published_date/down.sql000066400000000000000000000012211500126606300300440ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, published_date TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, favorite INTEGER DEFAULT 0, archive INTEGER DEFAULT 0, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode (title, uri, local_uri, description, epoch, length, duration, guid, played, favorite, archive, podcast_id) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, favorite, archive, podcast_id FROM old_table; Drop table old_table;podcasts-25.2/podcasts-data/migrations/2017-12-30-201631_remove_published_date/up.sql000066400000000000000000000011731500126606300275270ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, favorite INTEGER DEFAULT 0, archive INTEGER DEFAULT 0, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode (title, uri, local_uri, description, epoch, length, duration, guid, played, favorite, archive, podcast_id) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, favorite, archive, podcast_id FROM old_table; Drop table old_table;podcasts-25.2/podcasts-data/migrations/2018-06-30-141659_remove_dead_fields/000077500000000000000000000000001500126606300256505ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2018-06-30-141659_remove_dead_fields/down.sql000066400000000000000000000023061500126606300273410ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, favorite INTEGER DEFAULT 0, archive INTEGER DEFAULT 0, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode (title, uri, local_uri, description, epoch, length, duration, guid, played, podcast_id, favorite, archive) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, podcast_id, 0, 0 FROM old_table; Drop table old_table; ALTER TABLE podcast RENAME TO old_table; CREATE TABLE `podcast` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT NOT NULL, `link` TEXT NOT NULL, `description` TEXT NOT NULL, `image_uri` TEXT, `source_id` INTEGER NOT NULL UNIQUE, `favorite` INTEGER NOT NULL DEFAULT 0, `archive` INTEGER NOT NULL DEFAULT 0, `always_dl` INTEGER NOT NULL DEFAULT 0 ); INSERT INTO podcast ( id, title, link, description, image_uri, source_id ) SELECT id, title, link, description, image_uri, source_id FROM old_table; Drop table old_table;podcasts-25.2/podcasts-data/migrations/2018-06-30-141659_remove_dead_fields/up.sql000066400000000000000000000020331500126606300270130ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode ( title, uri, local_uri, description, epoch, length, duration, guid, played, podcast_id ) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, podcast_id FROM old_table; Drop table old_table; ALTER TABLE podcast RENAME TO old_table; CREATE TABLE `podcast` ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT NOT NULL, `link` TEXT NOT NULL, `description` TEXT NOT NULL, `image_uri` TEXT, `source_id` INTEGER NOT NULL UNIQUE ); INSERT INTO podcast ( id, title, link, description, image_uri, source_id ) SELECT id, title, link, description, image_uri, source_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2018-06-30-150717_rename_tables/000077500000000000000000000000001500126606300246445ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2018-06-30-150717_rename_tables/down.sql000066400000000000000000000011611500126606300263330ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; ALTER TABLE shows RENAME TO podcast; CREATE TABLE episode ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, PRIMARY KEY (title, podcast_id) ); INSERT INTO episode ( title, uri, local_uri, description, epoch, length, duration, guid, played, podcast_id ) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, show_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2018-06-30-150717_rename_tables/up.sql000066400000000000000000000011541500126606300260120ustar00rootroot00000000000000ALTER TABLE episode RENAME TO old_table; ALTER TABLE podcast RENAME TO shows; CREATE TABLE episodes ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, show_id INTEGER NOT NULL, PRIMARY KEY (title, show_id) ); INSERT INTO episodes ( title, uri, local_uri, description, epoch, length, duration, guid, played, show_id ) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, podcast_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2020-12-12-212018_add_image_cached_column/000077500000000000000000000000001500126606300265605ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2020-12-12-212018_add_image_cached_column/down.sql000066400000000000000000000007371500126606300302570ustar00rootroot00000000000000ALTER TABLE shows RENAME TO old_table; CREATE TABLE shows ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT NOT NULL, `link` TEXT NOT NULL, `description` TEXT NOT NULL, `image_uri` TEXT, `source_id` INTEGER NOT NULL UNIQUE ); INSERT INTO shows (id, title, link, description, image_uri, source_id) SELECT id, title, link, description, image_uri, source_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2020-12-12-212018_add_image_cached_column/up.sql000066400000000000000000000010351500126606300277240ustar00rootroot00000000000000ALTER TABLE shows RENAME TO old_table; CREATE TABLE shows ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT NOT NULL, `link` TEXT NOT NULL, `description` TEXT NOT NULL, `image_uri` TEXT, `image_cached` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `source_id` INTEGER NOT NULL UNIQUE ); INSERT INTO shows (id, title, link, description, image_uri, source_id) SELECT id, title, link, description, image_uri, source_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2020-12-14-214219_add_image_uri_hash_column/000077500000000000000000000000001500126606300271625ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2020-12-14-214219_add_image_uri_hash_column/down.sql000066400000000000000000000010711500126606300306510ustar00rootroot00000000000000ALTER TABLE shows RENAME TO old_table; CREATE TABLE shows ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT NOT NULL, `link` TEXT NOT NULL, `description` TEXT NOT NULL, `image_uri` TEXT, `image_cached` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `source_id` INTEGER NOT NULL UNIQUE ); INSERT INTO shows (id, title, link, description, image_uri, image_cached, source_id) SELECT id, title, link, description, image_uri, image_cached, source_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2020-12-14-214219_add_image_uri_hash_column/up.sql000066400000000000000000000011421500126606300303250ustar00rootroot00000000000000ALTER TABLE shows RENAME TO old_table; CREATE TABLE shows ( `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, `title` TEXT NOT NULL, `link` TEXT NOT NULL, `description` TEXT NOT NULL, `image_uri` TEXT, `image_uri_hash` BLOB, `image_cached` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `source_id` INTEGER NOT NULL UNIQUE ); INSERT INTO shows (id, title, link, description, image_uri, image_cached, source_id) SELECT id, title, link, description, image_uri, image_cached, source_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2021-06-24-185720_add_episode_play_position_column/000077500000000000000000000000001500126606300306305ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2021-06-24-185720_add_episode_play_position_column/down.sql000066400000000000000000000014031500126606300323160ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, podcast_id INTEGER NOT NULL, favorite INTEGER DEFAULT 0, archive INTEGER DEFAULT 0, PRIMARY KEY (title, podcast_id) ); INSERT INTO episodes (title, uri, local_uri, description, epoch, length, duration, guid, played, favorite, archive, podcast_id) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, favorite, archive, podcast_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2021-06-24-185720_add_episode_play_position_column/up.sql000066400000000000000000000013171500126606300317770ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, play_position INTEGER NOT NULL, show_id INTEGER NOT NULL, PRIMARY KEY (title, show_id) ); INSERT INTO episodes (title, uri, local_uri, description, epoch, length, duration, guid, played, show_id, play_position) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, show_id, 0 as play_position FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2023-07-21-001200_add_episode_image_column/000077500000000000000000000000001500126606300267755ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2023-07-21-001200_add_episode_image_column/down.sql000066400000000000000000000013131500126606300304630ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, play_position INTEGER NOT NULL, show_id INTEGER NOT NULL, PRIMARY KEY (title, show_id) ); INSERT INTO episodes (title, uri, local_uri, description, epoch, length, duration, guid, played, show_id, play_position) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, show_id, play_position FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2023-07-21-001200_add_episode_image_column/up.sql000066400000000000000000000015741500126606300301510ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, image_uri TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, play_position INTEGER NOT NULL, show_id INTEGER NOT NULL, PRIMARY KEY (title, show_id) ); INSERT INTO episodes (title, uri, local_uri, description, image_uri, epoch, length, duration, guid, played, show_id, play_position) SELECT title, uri, local_uri, description, NULL as image_uri, epoch, length, duration, guid, played, show_id, play_position FROM old_table; Drop table old_table; -- Force update all feeds, so they can import episode images UPDATE source SET http_etag = NULL, last_modified = NULL; podcasts-25.2/podcasts-data/migrations/2024-02-18-203855_use_rowid_as_episode_pk/000077500000000000000000000000001500126606300267315ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2024-02-18-203855_use_rowid_as_episode_pk/down.sql000066400000000000000000000013121500126606300304160ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, play_position INTEGER NOT NULL, show_id INTEGER NOT NULL, PRIMARY KEY (title, show_id) ); INSERT INTO episodes (title, uri, local_uri, description, epoch, length, duration, guid, played, show_id, play_position) SELECT title, uri, local_uri, description, epoch, length, duration, guid, played, show_id, play_position FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2024-02-18-203855_use_rowid_as_episode_pk/up.sql000066400000000000000000000014441500126606300301010ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, image_uri TEXT, epoch INTEGER NOT NULL DEFAULT 0, length INTEGER, duration INTEGER, guid TEXT, played INTEGER, play_position INTEGER NOT NULL, show_id INTEGER NOT NULL ); INSERT INTO episodes (id, title, uri, local_uri, description, image_uri, epoch, length, duration, guid, played, play_position, show_id) SELECT rowid, title, uri, local_uri, description, image_uri, epoch, length, duration, guid, played, play_position, show_id FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2024-03-06-213550_add_discovery_settings_table/000077500000000000000000000000001500126606300277415ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2024-03-06-213550_add_discovery_settings_table/down.sql000066400000000000000000000000371500126606300314310ustar00rootroot00000000000000DROP TABLE discovery_settings; podcasts-25.2/podcasts-data/migrations/2024-03-06-213550_add_discovery_settings_table/up.sql000066400000000000000000000004221500126606300311040ustar00rootroot00000000000000CREATE TABLE discovery_settings ( platform_id TEXT NOT NULL PRIMARY KEY, enabled BOOLEAN NOT NULL ); INSERT INTO discovery_settings(platform_id, enabled) VALUES('fyyd.de', 0); INSERT INTO discovery_settings(platform_id, enabled) VALUES('itunes.apple.com', 0); podcasts-25.2/podcasts-data/migrations/2024-07-06-140607_use_timestamp_instead_of_integer/000077500000000000000000000000001500126606300306305ustar00rootroot00000000000000podcasts-25.2/podcasts-data/migrations/2024-07-06-140607_use_timestamp_instead_of_integer/down.sql000066400000000000000000000015361500126606300323250ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, image_uri TEXT, epoch INTEGER NOT NULL DEFAULT , length INTEGER, duration INTEGER, guid TEXT, played INTEGER, play_position INTEGER NOT NULL, show_id INTEGER NOT NULL ); INSERT INTO episodes (id, title, uri, local_uri, description, image_uri, (cast(strftime('%s', epoch) as int)), length, duration, guid, (cast(strftime('%s', played) as int)), show_id, play_position) SELECT id, title, uri, local_uri, description, image_uri, epoch, length, duration, guid, played, show_id, play_position FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/migrations/2024-07-06-140607_use_timestamp_instead_of_integer/up.sql000066400000000000000000000015431500126606300320000ustar00rootroot00000000000000ALTER TABLE episodes RENAME TO old_table; CREATE TABLE episodes ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, title TEXT NOT NULL, uri TEXT, local_uri TEXT, description TEXT, image_uri TEXT, epoch TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, length INTEGER, duration INTEGER, guid TEXT, played TIMESTAMP, play_position INTEGER NOT NULL, show_id INTEGER NOT NULL ); INSERT INTO episodes (id, title, uri, local_uri, description, image_uri, epoch, length, duration, guid, played, show_id, play_position) SELECT id, title, uri, local_uri, description, image_uri, datetime(epoch, 'unixepoch'), length, duration, guid, datetime(played, 'unixepoch'), show_id, play_position FROM old_table; Drop table old_table; podcasts-25.2/podcasts-data/src/000077500000000000000000000000001500126606300165775ustar00rootroot00000000000000podcasts-25.2/podcasts-data/src/database.rs000066400000000000000000000055531500126606300207210ustar00rootroot00000000000000// database.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later //! Database Setup. This is only public to help with some unit tests. // Diesel embed_migrations! triggers the lint use diesel::prelude::*; use diesel::r2d2; use diesel::r2d2::ConnectionManager; use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations}; use once_cell::sync::Lazy; use std::path::PathBuf; use crate::errors::DataError; #[cfg(not(test))] use crate::xdg_dirs; type Pool = r2d2::Pool>; const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations/"); static POOL: Lazy = Lazy::new(|| init_pool(DB_PATH.to_str().unwrap())); #[cfg(not(test))] static DB_PATH: Lazy = Lazy::new(|| { xdg_dirs::PODCASTS_XDG .place_data_file("podcasts.db") .unwrap() }); #[cfg(test)] pub(crate) static TEMPDIR: Lazy = Lazy::new(|| tempfile::TempDir::with_prefix("podcasts_unit_test").unwrap()); #[cfg(test)] static DB_PATH: Lazy = Lazy::new(|| TEMPDIR.path().join("podcasts.db")); /// Get an r2d2 `SqliteConnection`. pub(crate) fn connection() -> Pool { POOL.clone() } fn init_pool(db_path: &str) -> Pool { let manager = ConnectionManager::::new(db_path); let pool = r2d2::Pool::builder() .max_size(1) .build(manager) .expect("Failed to create pool."); { let mut db = pool.get().expect("Failed to initialize pool."); run_migration_on(&mut db).expect("Failed to run migrations during init."); } info!("Database pool initialized."); pool } fn run_migration_on( conn: &mut SqliteConnection, ) -> Result>, DataError> { info!("Running DB Migrations..."); conn.run_pending_migrations(MIGRATIONS) .map_err(|_| DataError::DieselMigrationError) } /// Reset the database into a clean state. // Test share a Temp file db. pub fn truncate_db() -> Result<(), DataError> { use diesel::connection::SimpleConnection; let db = connection(); let mut con = db.get()?; con.batch_execute("DELETE FROM episodes; DELETE FROM shows; DELETE FROM source")?; Ok(()) } podcasts-25.2/podcasts-data/src/dbqueries.rs000066400000000000000000000421311500126606300211310ustar00rootroot00000000000000// dbqueries.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later //! Random CRUD helper functions. use chrono::prelude::*; use diesel::prelude::*; use diesel::dsl::exists; use diesel::select; use std::collections::HashMap; use crate::database::connection; use crate::errors::DataError; use crate::models::*; pub fn get_sources() -> Result, DataError> { use crate::schema::source::dsl::*; let db = connection(); let mut con = db.get()?; source .order((http_etag.asc(), last_modified.asc())) .load::(&mut con) .map_err(From::from) } pub fn get_podcasts() -> Result, DataError> { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; shows .order(title.asc()) .load::(&mut con) .map_err(From::from) } pub fn get_podcasts_filter(filter_ids: &[ShowId]) -> Result, DataError> { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; shows .order(title.asc()) .filter(id.ne_all(filter_ids)) .load::(&mut con) .map_err(From::from) } pub fn get_episodes() -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .order(epoch.desc()) .load::(&mut con) .map_err(From::from) } pub(crate) fn get_downloaded_episodes() -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeCleanerModel::as_select()) .filter(local_uri.is_not_null()) .load::(&mut con) .map_err(From::from) } pub(crate) fn get_played_cleaner_episodes() -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeCleanerModel::as_select()) .filter(played.is_not_null()) .load::(&mut con) .map_err(From::from) } pub fn get_episode_from_id(ep_id: EpisodeId) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .filter(id.eq(ep_id)) .get_result::(&mut con) .map_err(From::from) } pub fn get_episode_widget_from_id(ep_id: EpisodeId) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeWidgetModel::as_select()) .filter(id.eq(ep_id)) .get_result::(&mut con) .map_err(From::from) } pub fn get_episode_local_uri_from_id(ep_id: EpisodeId) -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .filter(id.eq(ep_id)) .select(local_uri) .get_result::>(&mut con) .map_err(From::from) } pub fn get_episodes_widgets_filter_limit( filter_ids: &[ShowId], limit: u32, ) -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeWidgetModel::as_select()) .order(epoch.desc()) .filter(show_id.ne_all(filter_ids)) .limit(i64::from(limit)) .load::(&mut con) .map_err(From::from) } pub fn get_podcast_from_id(pid: ShowId) -> Result { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; shows .filter(id.eq(pid)) .get_result::(&mut con) .map_err(From::from) } pub fn get_podcast_cover_from_id(pid: ShowId) -> Result { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; shows .select(ShowCoverModel::as_select()) .filter(id.eq(pid)) .get_result::(&mut con) .map_err(From::from) } pub fn get_pd_episodes(parent: &Show) -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; Episode::belonging_to(parent) .order(epoch.desc()) .load::(&mut con) .map_err(From::from) } pub fn get_pd_episodes_count(parent: &Show) -> Result { let db = connection(); let mut con = db.get()?; Episode::belonging_to(parent) .count() .get_result(&mut con) .map_err(From::from) } pub fn get_pd_episodeswidgets(parent: &Show) -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeWidgetModel::as_select()) .filter(show_id.eq(parent.id())) .order(epoch.desc()) .load::(&mut con) .map_err(From::from) } pub fn get_pd_unplayed_episodes(parent: &Show) -> Result, DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; Episode::belonging_to(parent) .filter(played.is_null()) .order(epoch.desc()) .load::(&mut con) .map_err(From::from) } pub fn get_source_from_uri(uri_: &str) -> Result { use crate::schema::source::dsl::*; let db = connection(); let mut con = db.get()?; source .filter(uri.eq(uri_)) .get_result::(&mut con) .map_err(From::from) } pub fn get_source_from_id(id_: SourceId) -> Result { use crate::schema::source::dsl::*; let db = connection(); let mut con = db.get()?; source .filter(id.eq(id_)) .get_result::(&mut con) .map_err(From::from) } pub fn get_podcast_from_source_id(sid: SourceId) -> Result { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; shows .filter(source_id.eq(sid)) .get_result::(&mut con) .map_err(From::from) } fn get_episode_from_title(title_: &str, pid: ShowId) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .filter(title.eq(title_)) .filter(show_id.eq(pid)) .get_result::(&mut con) .map_err(From::from) } pub fn get_episode(guid: Option<&str>, title: &str, show_id: ShowId) -> Result { if guid.is_some() { get_episode_from_guid(guid, show_id) } else { get_episode_from_title(title, show_id) } } fn get_episode_from_guid(guid_: Option<&str>, pid: ShowId) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .filter(guid.eq(guid_)) .filter(show_id.eq(pid)) .get_result::(&mut con) .map_err(From::from) } fn get_episode_minimal_from_title(title_: &str, pid: ShowId) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeMinimal::as_select()) .filter(title.eq(title_)) .filter(show_id.eq(pid)) .get_result::(&mut con) .map_err(From::from) } pub(crate) fn get_episode_minimal( guid: Option<&str>, title: &str, show_id: ShowId, ) -> Result { if guid.is_some() { get_episode_minimal_from_guid(&guid, show_id) } else { get_episode_minimal_from_title(title, show_id) } } fn get_episode_minimal_from_guid( guid_: &Option<&str>, pid: ShowId, ) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeMinimal::as_select()) .filter(guid.eq(guid_)) .filter(show_id.eq(pid)) .get_result::(&mut con) .map_err(From::from) } #[cfg(test)] pub(crate) fn get_episode_cleaner_from_title( title_: &str, pid: ShowId, ) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; episodes .select(EpisodeCleanerModel::as_select()) .filter(title.eq(title_)) .filter(show_id.eq(pid)) .get_result::(&mut con) .map_err(From::from) } pub(crate) fn remove_feed(pd: &Show) -> Result<(), DataError> { let db = connection(); let mut con = db.get()?; con.transaction(|conn| { delete_source(conn, pd.source_id())?; delete_podcast(conn, pd.id())?; delete_podcast_episodes(conn, pd.id())?; info!("Feed removed from the Database."); Ok(()) }) } /// use utils::delete_show if the podcast was fully imported pub fn remove_source(source: &Source) -> Result<(), DataError> { let db = connection(); let mut con = db.get()?; delete_source(&mut con, source.id()) .map(|_| ()) .map_err(From::from) } fn delete_source(con: &mut SqliteConnection, source_id: SourceId) -> QueryResult { use crate::schema::source::dsl::*; diesel::delete(source.filter(id.eq(source_id))).execute(con) } fn delete_podcast(con: &mut SqliteConnection, show_id: ShowId) -> QueryResult { use crate::schema::shows::dsl::*; diesel::delete(shows.filter(id.eq(show_id))).execute(con) } fn delete_podcast_episodes(con: &mut SqliteConnection, parent_id: ShowId) -> QueryResult { use crate::schema::episodes::dsl::*; diesel::delete(episodes.filter(show_id.eq(parent_id))).execute(con) } pub fn source_exists(url: &str) -> Result { use crate::schema::source::dsl::*; let db = connection(); let mut con = db.get()?; select(exists(source.filter(uri.eq(url)))) .get_result(&mut con) .map_err(From::from) } pub(crate) fn podcast_exists(source_id_: SourceId) -> Result { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; select(exists(shows.filter(source_id.eq(source_id_)))) .get_result(&mut con) .map_err(From::from) } pub(crate) fn episode_exists( guid_: Option<&str>, title_: &str, show_id_: ShowId, ) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; if guid_.is_some() { return select(exists( episodes.filter(show_id.eq(show_id_)).filter(guid.eq(guid_)), )) .get_result(&mut con) .map_err(From::from); } select(exists( episodes .filter(show_id.eq(show_id_)) .filter(title.eq(title_)), )) .get_result(&mut con) .map_err(From::from) } /// Check if the `episodes table contains any rows /// /// Return true if `episodes` table is populated. pub fn is_episodes_populated(filter_show_ids: &[ShowId]) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; select(exists(episodes.filter(show_id.ne_all(filter_show_ids)))) .get_result(&mut con) .map_err(From::from) } /// Check if the `shows` table contains any rows /// /// Return true if `shows` table is populated. pub fn is_podcasts_populated(filter_ids: &[ShowId]) -> Result { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; select(exists(shows.filter(id.ne_all(filter_ids)))) .get_result(&mut con) .map_err(From::from) } /// Check if the `source` table contains any rows /// /// Return true if `source` table is populated. pub fn is_source_populated(filter_ids: &[ShowId]) -> Result { use crate::schema::source::dsl::*; let db = connection(); let mut con = db.get()?; select(exists(source.filter(id.ne_all(filter_ids)))) .get_result(&mut con) .map_err(From::from) } pub(crate) fn index_new_episodes(eps: &[NewEpisode]) -> Result<(), DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; diesel::insert_into(episodes) .values(eps) .execute(&mut con) .map_err(From::from) .map(|_| ()) } pub fn update_none_to_played_now(parent: &Show) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; let epoch_now = Utc::now().naive_utc(); con.transaction(|conn| { diesel::update(Episode::belonging_to(parent).filter(played.is_null())) .set(played.eq(Some(epoch_now))) .execute(conn) .map_err(From::from) }) } fn get_discovery_settings_err() -> Result, DataError> { use crate::schema::discovery_settings::dsl::*; let db = connection(); let mut con = db.get()?; discovery_settings .load::(&mut con) .map(|v| { v.into_iter() .map(|ds| (ds.platform_id, ds.enabled)) .collect() }) .map_err(From::from) } pub fn get_discovery_settings() -> HashMap { get_discovery_settings_err().unwrap_or_default() } pub fn set_discovery_setting(pid: &str, value: bool) -> Result<(), DataError> { use crate::schema::discovery_settings::dsl::*; let db = connection(); let mut con = db.get()?; let item = DiscoverySetting { platform_id: pid.to_string(), enabled: value, }; diesel::insert_into(discovery_settings) .values(&item) .on_conflict(platform_id) .do_update() .set(&item) .execute(&mut con) .map(|_| ()) .map_err(From::from) } #[cfg(test)] mod tests { use super::*; use crate::database::truncate_db; use crate::pipeline::pipeline; use crate::utils::get_feed; use anyhow::Result; #[test] fn test_update_none_to_played_now() -> Result<()> { truncate_db()?; let url = "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\ com/InterceptedWithJeremyScahill"; let source = Source::from_url(url)?; let id = source.id(); let rt = tokio::runtime::Runtime::new()?; rt.block_on(pipeline(vec![source]))?; let pd = get_podcast_from_source_id(id)?; let eps_num = get_pd_unplayed_episodes(&pd)?.len(); assert_ne!(eps_num, 0); update_none_to_played_now(&pd)?; let eps_num2 = get_pd_unplayed_episodes(&pd)?.len(); assert_eq!(eps_num2, 0); Ok(()) } #[test] fn test_episode_exists() -> Result<()> { truncate_db()?; const TEST_SHOW_ID: ShowId = ShowId(1); const TEST_SOURCE_ID: SourceId = SourceId(1); let path = "tests/feeds/2024-03-13-ndr.xml"; let feed = get_feed(path, TEST_SOURCE_ID); feed.index()?; // only title given assert!(episode_exists(None, "Nachrichten", TEST_SHOW_ID)?); assert!(get_episode(None, "Nachrichten", TEST_SHOW_ID).is_ok()); assert!(get_episode_minimal(None, "Nachrichten", TEST_SHOW_ID).is_ok()); // only GUID matches, title is different assert!(episode_exists( Some("AU-20230622-0747-4100-A"), "wrong", TEST_SHOW_ID )?); assert!(get_episode(Some("AU-20230622-0747-4100-A"), "wrong", TEST_SHOW_ID).is_ok()); assert!( get_episode_minimal(Some("AU-20230622-0747-4100-A"), "wrong", TEST_SHOW_ID).is_ok() ); // wrong guid // Should not find, different guid = assume it's a different episode assert!(!episode_exists(Some("wrong"), "Nachrichten", TEST_SHOW_ID)?); assert!(!get_episode(Some("wrong"), "Nachrichten", TEST_SHOW_ID).is_ok()); assert!(!get_episode_minimal(Some("wrong"), "Nachrichten", TEST_SHOW_ID).is_ok()); // no result assert!(!episode_exists(None, "wrong", TEST_SHOW_ID)?); assert!(!get_episode(None, "wrong", TEST_SHOW_ID).is_ok()); assert!(!get_episode_minimal(None, "wrong", TEST_SHOW_ID).is_ok()); Ok(()) } } podcasts-25.2/podcasts-data/src/discovery/000077500000000000000000000000001500126606300206065ustar00rootroot00000000000000podcasts-25.2/podcasts-data/src/discovery/data.rs000066400000000000000000000040341500126606300220660ustar00rootroot00000000000000// data.rs // // Copyright 2022-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use chrono::prelude::*; use url::Url; pub(crate) type UrlString = String; #[derive(Clone, Debug)] pub struct FoundPodcast { pub feed: UrlString, pub title: String, pub author: String, pub description: String, pub art: UrlString, pub episode_count: Option, pub last_publication: Option>, } /// checks for rougly the same feed url impl PartialEq for FoundPodcast { fn eq(&self, other: &Self) -> bool { let a = Url::parse(&self.feed); let b = Url::parse(&other.feed); if let (Ok(a), Ok(b)) = (a, b) { a.path().trim_end_matches('/') == b.path().trim_end_matches('/') && a.host() == b.host() && a.query() == b.query() } else { self.feed == other.feed } } } impl FoundPodcast { /// use the longer description / bigger episode number pub(crate) fn combine(&mut self, other: FoundPodcast) { if other.episode_count.unwrap_or_default() > self.episode_count.unwrap_or_default() { self.episode_count = other.episode_count; } if other.description.len() > self.description.len() { self.description = other.description; } } } pub const ALL_PLATFORM_IDS: [&str; 2] = ["fyyd.de", "itunes.apple.com"]; podcasts-25.2/podcasts-data/src/discovery/fyyd.rs000066400000000000000000000254471500126606300221430ustar00rootroot00000000000000// fyyd.rs // // Copyright 2022-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use crate::discovery::data::*; use anyhow::Result; use chrono::prelude::*; use serde::Deserialize; use url::Url; // curl "https://api.fyyd.de/0.2/search/podcast?term=chapo&count=10" #[derive(Deserialize, Debug)] struct Response { data: Vec, } #[derive(Deserialize, Debug)] struct Podcast { title: String, author: String, description: String, lastpub: Option>, #[serde(rename = "xmlURL")] xml_url: UrlString, // the rss feed #[serde(rename = "smallImageURL")] small_image_url: UrlString, // 150px, next lower is thumbImageURL at 80px episode_count: i32, } impl From for FoundPodcast { fn from(p: Podcast) -> FoundPodcast { FoundPodcast { feed: p.xml_url, title: p.title, author: p.author, description: p.description, art: p.small_image_url, episode_count: Some(p.episode_count), last_publication: p.lastpub, } } } pub async fn search(query: &str, enabled: bool) -> Result> { if !enabled { return Ok(vec![]); } let url = Url::parse_with_params( "https://api.fyyd.de/0.2/search/podcast?count=10", &[("term", query)], )?; let client = crate::downloader::client_builder().build()?; let result: Response = client.get(url).send().await?.json().await?; Ok(result.data.into_iter().map(|p| p.into()).collect()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_1_result() -> Result<()> { let input = std::fs::read_to_string("tests/fyyd/search_1.json")?; let result: Response = serde_json::from_str(&input)?; let found: Vec = result.data.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![ FoundPodcast { feed: "https://feeds.buzzsprout.com/1890340.rss".to_string(), title: "The Deprogram".to_string(), author: "JT, Hakim, and Yugopnik".to_string(), description: " What do an Iraqi, a Balkan Slav and a Texan have in common? A burning hatred for the system. Oh, and a podcast. Say no to eating out of the trash can of ideology. Join us on a journey exploring and critically assessing the perceived “normalcy” of late-stage capitalism. The only truly international, global, and anti-capitalist podcast you’ll find. SUPPORT US on PATREON: https://www.patreon.com/TheDeprogram FOLLOW US on Twitter @TheDeprogramPod ".to_string(), art: "https://img-1.fyyd.de/pd/small/7733270e232241983f50e3e88665b72cfa4f5.jpg".to_string(), episode_count: Some(242), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-08T13:00:00+01:00").unwrap().with_timezone(&chrono::Local)) }]; assert_eq!(1, found.len()); assert_eq!(expected, found); Ok(()) } #[test] fn test_5_results() -> Result<()> { let input = std::fs::read_to_string("tests/fyyd/search_5.json")?; let result: Response = serde_json::from_str(&input)?; let found: Vec = result.data.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![ FoundPodcast { feed: "https://feeds.acast.com/public/shows/7540dfb3-3c5f-43ae-91d3-aad22b2ede46".to_string(), title: "Chapo".to_string(), author: "VICE".to_string(), description: "As Sinaloa cartel leader Joaquín “El Chapo” Guzmán goes on trial, VICE News explores his high-stakes case through the stories of people caught up in the drug war in the U.S. and Mexico. Hosted on Acast. See acast.com/privacy for more information.".to_string(), art: "https://img-1.fyyd.de/pd/small/8374473344d689aada87d46655bb0d1e89028.jpg".to_string(), episode_count: Some(17), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2019-02-22T23:19:05+01:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://feeds.soundcloud.com/users/soundcloud:users:211911700/sounds.rss".to_string(), title: "Chapo Trap House".to_string(), author: "Chapo Trap House".to_string(), description: "Podcast by Chapo Trap House".to_string(), art: "https://img-1.fyyd.de/pd/small/58282c0c842fc16e73e519ff141e856b175f8.jpg".to_string(), episode_count: Some(792), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-08T08:01:37+01:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://www.omnycontent.com/d/playlist/e73c998e-6e60-432f-8610-ae210140c5b1/a6b57093-81fe-4401-a963-af2000fe4e3a/56b7926c-6bfd-4d8c-81f9-af2000ffad49/podcast.rss".to_string(), title: "Surviving El Chapo: The Twins Who Brought Down A Drug Lord".to_string(), author: "iHeartPodcasts".to_string(), description: "Identical twins Jay and Pete Flores, who were once North America’s biggest drug traffickers and El Chapo’s right hand men, turned themselves into the U.S. government with the hopes of starting a new, safer life for their family. But after years of cooperating to get the world's most powerful drug kingpin behind bars, and finally gaining their freedom with a chance to start again, everything for the Flores family began to unravel. In Season 2 of Surviving El Chapo, hosts Curtis \"50 Cent\" Jackson and Charlie Webster hear Jay and Pete reveal for the first time what really happened during their turbulent 14-year prison journey and what it was like to come face-to-face in court with El Chapo. Plus, find out the shocking backstory to the prison sentence that the Flores wives are currently facing.\n\nHosted and executive produced by award-winning artist and producer Curtis \"50 Cent\" Jackson and critically acclaimed broadcast journalist and producer Charlie Webster. Brought to you by Lionsgate Sound as a world exclusive with iHeartPodcasts.".to_string(), art: "https://img-1.fyyd.de/pd/small/80419996d728f3c44a832def2cffbae93e380.jpg".to_string(), episode_count: Some(26), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2023-12-06T09:00:00+01:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://feeds.buzzsprout.com/350771.rss".to_string(), title: "People's History of Ideas Podcast".to_string(), author: "Matthew Rothwell".to_string(), description: "In this podcast, Matthew Rothwell, author of Transpacific Revolutionaries: The Chinese Revolution in Latin America, explores the global history of ideas related to rebellion and revolution. The main focus of this podcast for the near future will be on the history of the Chinese Revolution, going all the way back to its roots in the initial Chinese reactions to British imperialism during the Opium War of 1839-1842, and then following the development of the revolution and many of the ideas that were products of the revolution through to their transnational diffusion in the late 20th century.".to_string(), art: "https://img-1.fyyd.de/pd/small/8442586e8539fdf80c9748cea30f43e638922.jpg".to_string(), episode_count: Some(112), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-02-04T21:00:00+01:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://badfaith.libsyn.com/rss".to_string(), title: "Bad Faith".to_string(), author: "Briahna Joy Gray & Virgil Texas".to_string(), description: "America's only podcast. //\r\n\r\nwith Briahna Joy Gray, former National Press Secretary for Bernie Sanders' Presidential campaign //\r\n\r\nand Virgil Texas //\r\n\r\nSubscribe for exclusive premium episodes at patreon.com/badfaithpodcast /\r\n@badfaithpod / \r\nbadfaithpodcast at gmail dot com".to_string(), art: "https://img-1.fyyd.de/pd/small/6132904ae864f41dbbb60eaf1054cbfcc6292.jpg".to_string(), episode_count: Some(372), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-07T11:44:00+01:00").unwrap().with_timezone(&chrono::Local)) }]; assert_eq!(5, found.len()); assert_eq!(expected, found); Ok(()) } #[test] fn empty_result() -> Result<()> { let input = std::fs::read_to_string("tests/fyyd/search_empty.json")?; let result: Response = serde_json::from_str(&input)?; let found: Vec = result.data.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![]; assert_eq!(expected, found); Ok(()) } #[test] fn unicode_result() -> Result<()> { let input = std::fs::read_to_string("tests/fyyd/search_unicode.json")?; let result: Response = serde_json::from_str(&input)?; let found: Vec = result.data.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![ FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "Weekly discussions of a deteriorating world all from the comfort of your local smoke-filled Spätkauf.\nhttps://www.patreon.com/cornerspaeti\nhttps://www.operationglad.io/start\n".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg".to_string(), episode_count: Some(348), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-07T10:00:00+01:00").unwrap().with_timezone(&chrono::Local)) }]; assert_eq!(expected, found); Ok(()) } } podcasts-25.2/podcasts-data/src/discovery/itunes.rs000066400000000000000000000272021500126606300224660ustar00rootroot00000000000000// itunes.rs // // Copyright 2022-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use crate::discovery::data::*; use anyhow::Result; use chrono::prelude::*; use serde::Deserialize; use url::Url; // curl "https://itunes.apple.com/search?term=chapo&entity=podcast&limit=10" #[derive(Deserialize)] struct SearchResult { results: Vec, } #[derive(Deserialize)] struct Podcast { #[serde(rename = "feedUrl")] feed_url: UrlString, #[serde(rename = "collectionName")] collection_name: String, // Name of the podcast #[serde(rename = "artistName")] artist_name: String, #[serde(rename = "releaseDate")] release_date: Option>, #[serde(rename = "trackCount")] track_count: i32, #[serde(rename = "artworkUrl100")] artwork_url_100: UrlString, } impl From for FoundPodcast { fn from(p: Podcast) -> FoundPodcast { FoundPodcast { feed: p.feed_url, title: p.collection_name, author: p.artist_name, description: "".to_string(), art: p.artwork_url_100, episode_count: Some(p.track_count), last_publication: p.release_date, } } } pub async fn search(query: &str, enabled: bool) -> Result> { if !enabled { return Ok(vec![]); } let url = Url::parse_with_params( "https://itunes.apple.com/search?entity=podcast&limit=10", &[("term", query)], )?; let client = crate::downloader::client_builder().build()?; let result: SearchResult = client.get(url).send().await?.json().await?; Ok(result.results.into_iter().map(|p| p.into()).collect()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_1_result() -> Result<()> { let input = std::fs::read_to_string("tests/itunes/search_1.txt")?; let result: SearchResult = serde_json::from_str(&input)?; let found: Vec = result.results.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![ FoundPodcast { feed: "https://anchor.fm/s/19346b24/podcast/rss".to_string(), title: "CushVlogs Audio - Matt Christman - Chapo Trap House".to_string(), author: "Jackson Jacker".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/100x100bb.jpg".to_string(), episode_count: Some(167), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2021-06-08T06:56:00+02:00").unwrap().with_timezone(&chrono::Local)) } ]; assert_eq!(1, found.len()); assert_eq!(expected, found); Ok(()) } #[test] fn test_10_result() -> Result<()> { let input = std::fs::read_to_string("tests/itunes/search_10.txt")?; let result: SearchResult = serde_json::from_str(&input)?; let found: Vec = result.results.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![ FoundPodcast { feed: "https://feeds.acast.com/public/shows/7540dfb3-3c5f-43ae-91d3-aad22b2ede46".to_string(), title: "Chapo".to_string(), author: "VICE".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/58/9b/8d/589b8d72-13aa-55cb-463f-ad41096ca9ea/mza_560982554884614556.jpg/100x100bb.jpg".to_string(), episode_count: Some(17), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2023-01-25T06:01:00+01:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://feeds.soundcloud.com/users/soundcloud:users:211911700/sounds.rss".to_string(), title: "Chapo Trap House".to_string(), author: "Chapo Trap House".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts18/v4/37/d9/a0/37d9a0b4-64f8-70d4-722e-b3a8772f3424/mza_5335192259855381405.jpg/100x100bb.jpg".to_string(), episode_count: Some(507), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-05T21:55:00+01:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://feeds.megaphone.fm/WMHY2717952910".to_string(), title: "El Chapo: Dos rostros de un capo Podcast".to_string(), author: "CNN en Español".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/31/e9/bb/31e9bb57-31f8-447d-e619-bf94e2ee42af/mza_7685287012963567517.jpg/100x100bb.jpg".to_string(), episode_count: Some(7), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2020-07-15T11:10:00+02:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://anchor.fm/s/19346b24/podcast/rss".to_string(), title: "CushVlogs Audio - Matt Christman - Chapo Trap House".to_string(), author: "Jackson Jacker".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/100x100bb.jpg".to_string(), episode_count: Some(167), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2021-06-08T06:56:00+02:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://audioboom.com/channels/4905580.rss".to_string(), title: "‘El Chapo’: ¿héroe o villano?".to_string(), author: "Univision".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/57/34/8e/57348e38-6c9a-7579-8269-42e61786ae3b/mza_17242328811639650011.jpg/100x100bb.jpg".to_string(), episode_count: Some(4), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2017-05-23T02:59:00+02:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://anchor.fm/s/5b423b40/podcast/rss".to_string(), title: "Chapo".to_string(), author: "Jack Gold".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/43/67/d3/4367d309-707e-6b7e-390a-202565c1d530/mza_9950196886709505935.jpg/100x100bb.jpg".to_string(), episode_count: Some(1), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2021-05-15T07:32:00+02:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://anchor.fm/s/1e56647c/podcast/rss".to_string(), title: "Chapo".to_string(), author: "Mijo 713".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts123/v4/b5/7c/41/b57c410f-8b2f-d9ce-df76-44b5947ea261/mza_17707437198336862.jpg/100x100bb.jpg".to_string(), episode_count: Some(1), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2020-04-25T21:46:00+02:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://www.omnycontent.com/d/playlist/e73c998e-6e60-432f-8610-ae210140c5b1/a6b57093-81fe-4401-a963-af2000fe4e3a/56b7926c-6bfd-4d8c-81f9-af2000ffad49/podcast.rss".to_string(), title: "Surviving El Chapo: The Twins Who Brought Down A Drug Lord".to_string(), author: "iHeartPodcasts".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/aa/90/0f/aa900f15-7a67-e498-7b8c-a8efc9a667b5/mza_18039635476170983620.jpg/100x100bb.jpg".to_string(), episode_count: Some(26), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2023-12-06T09:00:00+01:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://anchor.fm/s/3a15ad8/podcast/rss".to_string(), title: "CHAPOS Corner".to_string(), author: "Chapo".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/a5/08/da/a508da5c-09c7-bc4d-3c2d-938bf406b36b/mza_14501171467539691063.jpg/100x100bb.jpg".to_string(), episode_count: Some(704), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2022-10-19T02:24:00+02:00").unwrap().with_timezone(&chrono::Local)) }, FoundPodcast { feed: "https://anchor.fm/s/5d83aab0/podcast/rss".to_string(), title: "EL CHAPO".to_string(), author: "Bernardo".to_string(), description: "".to_string(), art: "https://is1-ssl.mzstatic.com/image/thumb/Podcasts115/v4/df/3b/2c/df3b2cbc-a8ac-315b-d5db-56d055690dfc/mza_8387652467730745876.jpg/100x100bb.jpg".to_string(), episode_count: Some(10), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2021-06-22T23:50:00+02:00").unwrap().with_timezone(&chrono::Local)) } ]; assert_eq!(10, found.len()); assert_eq!(expected, found); Ok(()) } #[test] fn empty_result() -> Result<()> { let input = std::fs::read_to_string("tests/itunes/search_empty.txt")?; let result: SearchResult = serde_json::from_str(&input)?; let found: Vec = result.results.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![]; assert_eq!(expected, found); Ok(()) } #[test] fn unicode_result() -> Result<()> { let input = std::fs::read_to_string("tests/itunes/search_unicode.txt")?; let result: SearchResult = serde_json::from_str(&input)?; let found: Vec = result.results.into_iter().map(|p| p.into()).collect(); let expected: Vec = vec![FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg" .to_string(), episode_count: Some(348), last_publication: Some( chrono::DateTime::parse_from_rfc3339("2024-03-07T10:00:00+01:00") .unwrap() .with_timezone(&chrono::Local), ), }]; assert_eq!(expected, found); Ok(()) } } podcasts-25.2/podcasts-data/src/discovery/mod.rs000066400000000000000000000001211500126606300217250ustar00rootroot00000000000000mod data; mod fyyd; mod itunes; mod search; pub use data::*; pub use search::*; podcasts-25.2/podcasts-data/src/discovery/search.rs000066400000000000000000000165301500126606300224260ustar00rootroot00000000000000// platform.rs // // Copyright 2022-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use crate::dbqueries; use crate::discovery::data::*; use crate::discovery::fyyd; use crate::discovery::itunes; use anyhow::Result; use thiserror::Error; use tokio::join; #[derive(Error, Debug)] pub enum SearchError { #[error("Network Error: {0}")] ReqwestError(#[from] reqwest::Error), #[error("Other Error: {0}")] AnyhowError(#[from] anyhow::Error), #[error("No Search Platform was selected.")] NoSearchPlatformsSelected, } /// Sends a http search to all platforms that are active in the settings. /// It joins all results into a Vector and tries to filter out duplicates. /// Results are sorted as they are returned by the Search platforms. pub async fn search(query: &str) -> Result, SearchError> { // This looks like it could be abstracted more, // but traits with async fns are impossible to deal with. let settings = dbqueries::get_discovery_settings(); let fyyd_on = *settings.get("fyyd.de").unwrap_or(&false); let itunes_on = *settings.get("itunes.apple.com").unwrap_or(&false); if !fyyd_on && !itunes_on { return Err(SearchError::NoSearchPlatformsSelected); } let fyyd = fyyd::search(query, fyyd_on); let itunes = itunes::search(query, itunes_on); let (fyyd, itunes) = join!(fyyd, itunes); let fyyd: Vec = fyyd.map_err(|e| error!("fyyd {e}")).unwrap_or_default(); let itunes: Vec = itunes.map_err(|e| error!("itunes {e}")).unwrap_or_default(); trace!("combining {fyyd:#?} with {itunes:#?}"); let mut merged = fyyd; merge_results(&mut merged, itunes); Ok(merged) } fn merge_results(merged: &mut Vec, other_results: Vec) { for p in other_results.into_iter() { if let Some(existing) = merged.iter_mut().find(|p2| p.eq(*p2)) { existing.combine(p); } else { merged.push(p); } } } #[cfg(test)] mod tests { use super::*; #[test] fn merge() -> Result<()> { let itunes = vec![FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg" .to_string(), episode_count: Some(347), last_publication: Some( chrono::DateTime::parse_from_rfc3339("2024-03-07T10:00:00+01:00") .unwrap() .with_timezone(&chrono::Local), ), }]; let fyyd = vec![FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "Weekly discussions of a deteriorating world all from the comfort of your local smoke-filled Spätkauf.\nhttps://www.patreon.com/cornerspaeti\nhttps://www.operationglad.io/start\n".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg".to_string(), episode_count: Some(348), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-07T10:00:00+01:00").unwrap().with_timezone(&chrono::Local)) }]; let expected: Vec = vec![ FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "Weekly discussions of a deteriorating world all from the comfort of your local smoke-filled Spätkauf.\nhttps://www.patreon.com/cornerspaeti\nhttps://www.operationglad.io/start\n".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg".to_string(), episode_count: Some(348), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-07T10:00:00+01:00").unwrap().with_timezone(&chrono::Local)) } ]; let itunes2 = itunes.clone(); let mut merged = itunes; merge_results(&mut merged, fyyd); assert_eq!(expected, merged); merge_results(&mut merged, itunes2); assert_eq!(expected, merged); Ok(()) } #[test] fn merge_nones() -> Result<()> { let itunes = vec![FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg" .to_string(), episode_count: None, last_publication: None, }]; let fyyd = vec![FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "Weekly discussions of a deteriorating world all from the comfort of your local smoke-filled Spätkauf.\nhttps://www.patreon.com/cornerspaeti\nhttps://www.operationglad.io/start\n".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg".to_string(), episode_count: Some(348), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-07T10:00:00+01:00").unwrap().with_timezone(&chrono::Local)) }]; let expected: Vec = vec![ FoundPodcast { feed: "https://feeds.fireside.fm/cornerspaeti/rss".to_string(), title: "Corner Späti".to_string(), author: "The Späti Boys".to_string(), description: "Weekly discussions of a deteriorating world all from the comfort of your local smoke-filled Spätkauf.\nhttps://www.patreon.com/cornerspaeti\nhttps://www.operationglad.io/start\n".to_string(), art: "https://img-1.fyyd.de/pd/small/77182e877ac679f9414148fab3dddb74782c2.jpg".to_string(), episode_count: Some(348), last_publication: Some(chrono::DateTime::parse_from_rfc3339("2024-03-07T10:00:00+01:00").unwrap().with_timezone(&chrono::Local)) } ]; let itunes2 = itunes.clone(); let mut merged = itunes; merge_results(&mut merged, fyyd); assert_eq!(expected, merged); merge_results(&mut merged, itunes2); assert_eq!(expected, merged); Ok(()) } } podcasts-25.2/podcasts-data/src/downloader.rs000066400000000000000000000205661500126606300213140ustar00rootroot00000000000000// downloader.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use reqwest::header::*; use reqwest::redirect::Policy; use tempfile::TempDir; use std::fs; use std::fs::{File, copy, remove_file}; use std::io::{BufWriter, Write}; use std::path::Path; use std::sync::{Arc, Mutex}; use crate::errors::DownloadError; use crate::xdg_dirs::PODCASTS_CACHE; use crate::{EpisodeModel, EpisodeWidgetModel, Save}; use crate::ShowCoverModel; use crate::utils; use glob::glob; use std::path::PathBuf; // TODO: Replace path that are of type &str with std::path. // TODO: Have a convention/document absolute/relative paths, if they should end // with / or not. pub trait DownloadProgress { fn get_downloaded(&self) -> u64; fn set_downloaded(&mut self, downloaded: u64); fn get_size(&self) -> u64; fn set_size(&mut self, bytes: u64); fn should_cancel(&self) -> bool; fn cancel(&mut self); } pub fn client_builder() -> reqwest::ClientBuilder { // Haven't included the loop check as // Steal the Stars would trigger it as // it has a loop back before giving correct url let policy = Policy::custom(|attempt| { info!("Redirect Attempt URL: {:?}", attempt.url()); if attempt.previous().len() > 20 { attempt.error("too many redirects") } else if Some(attempt.url()) == attempt.previous().last() { // avoid redirect loops attempt.stop() } else { attempt.follow() } }); reqwest::Client::builder() .redirect(policy) .referer(false) .user_agent(crate::USER_AGENT) } // Adapted from https://github.com/mattgathu/rget . // I never wanted to write a custom downloader. // Sorry to those who will have to work with that code. // Would much rather use a crate, // or bindings for a lib like youtube-dl(python), // But can't seem to find one. // TODO: Write unit-tests. async fn download_into( dir: &str, file_title: &str, url: &str, progress: Option>>, ) -> Result { info!("GET request to: {}", url); let client = client_builder().build()?; let resp = client.get(url).send().await?; info!("Status Resp: {}", resp.status()); if !resp.status().is_success() { if let Some(ref prog) = progress { if let Ok(mut m) = prog.lock() { m.cancel(); } } return Err(DownloadError::UnexpectedResponse(resp.status())); } let headers = resp.headers().clone(); let ct_len = headers .get(CONTENT_LENGTH) .and_then(|h| h.to_str().ok()) .and_then(|len| len.parse().ok()); let ct_type = headers.get(CONTENT_TYPE).and_then(|h| h.to_str().ok()); if let Some(ct_len) = ct_len { info!("File Length: {}", ct_len); } if let Some(ct_type) = ct_type { info!("Content Type: {}", ct_type); } let ext = get_ext(ct_type).unwrap_or_else(|| String::from("unknown")); info!("Extension: {}", ext); // Construct a temp file to save desired content. // It has to be a `new_in` instead of new cause rename can't move cross // filesystems. let tempdir = TempDir::with_prefix_in("temp_download", PODCASTS_CACHE.to_str().unwrap())?; let out_file = format!("{}/temp.part", tempdir.path().to_str().unwrap(),); if let Some(ct_len) = ct_len { if let Some(ref p) = progress { if let Ok(mut m) = p.lock() { m.set_size(ct_len); } } }; // Save requested content into the file. save_io(&out_file, resp, progress).await?; // Construct the desired path. let target = format!("{}/{}.{}", dir, file_title, ext); // Rename/move the tempfile into a permanent place upon success. // Unlike rename(), copy() + remove_file() works even when the // temp dir is on a different mount point than the target dir. copy(&out_file, &target)?; remove_file(out_file)?; info!("Downloading of {} completed successfully.", &target); Ok(target) } /// Determine the file extension from the http content-type header. fn get_ext(content: Option<&str>) -> Option { let mut iter = content?.split('/'); let type_ = iter.next()?; let subtype = iter.next()?; mime_guess::get_extensions(type_, subtype).and_then(|c| { if c.contains(&subtype) { Some(subtype.to_string()) } else { Some(c.first()?.to_string()) } }) } // TODO: Write unit-tests. // TODO: Refactor... Somehow. /// Handles the I/O of fetching a remote file and saving into a Buffer and A /// File. async fn save_io( file: &str, resp: reqwest::Response, progress: Option>>, ) -> Result<(), DownloadError> { use futures_util::StreamExt; use std::ops::Deref; info!("Downloading into: {}", file); let mut writer = BufWriter::new(File::create(file)?); let mut body_stream = resp.bytes_stream(); while let Some(chunk) = body_stream.next().await { if let Ok(chunk) = chunk { writer.write_all(chunk.deref())?; // This sucks. // Actually the whole download module is hack, so w/e. if let Some(prog) = progress.clone() { let len = writer.get_ref().metadata().map(|x| x.len()); if let Ok(l) = len { if let Ok(mut m) = prog.lock() { if m.should_cancel() { return Err(DownloadError::DownloadCancelled); } m.set_downloaded(l); } } } } else { break; } } Ok(()) } // TODO: Refactor pub async fn get_episode( ep: &mut EpisodeWidgetModel, download_dir: &str, progress: Option>>, ) -> Result<(), DownloadError> { // Check if its alrdy downloaded if ep.local_uri().is_some() { if Path::new(ep.local_uri().unwrap()).exists() { return Ok(()); } // If the path is not valid, then set it to None. ep.set_local_uri(None); ep.save()?; }; let path = download_into( download_dir, &ep.id().0.to_string(), ep.uri().unwrap(), progress, ) .await?; // If download succeeds set episode local_uri to dlpath. ep.set_local_uri(Some(&path)); // Over-write episode length let size = fs::metadata(path); if let Ok(s) = size { ep.set_length(Some(s.len() as i32)) }; ep.save()?; Ok(()) } pub fn check_for_cached_image(pd: &ShowCoverModel, uri: &str) -> Option { let cache_path = utils::get_cover_dir(pd.title()).ok()?; let hash = utils::calculate_hash(uri); if let Ok(mut paths) = glob(&format!("{}/{}.*", hash, cache_path)) { // Take the first file matching, disregard extension let path = paths.next().and_then(|x| x.ok()); return path; } None } pub async fn cache_episode_image( pd: &ShowCoverModel, uri: &str, download: bool, ) -> Result { if let Some(path) = check_for_cached_image(pd, uri) { return Ok(path .to_str() .ok_or(DownloadError::InvalidCachedImageLocation)? .to_owned()); } if uri.is_empty() { return Err(DownloadError::NoImageLocation); } let cache_path = utils::get_cover_dir(pd.title())?; let hash = utils::calculate_hash(uri); if download { let path = download_into(&cache_path, &format!("{}", hash), uri, None).await?; info!("Cached img into: {}", &path); Ok(path) } else { Err(DownloadError::DownloadCancelled) } } podcasts-25.2/podcasts-data/src/errors.rs000066400000000000000000000060571500126606300204710ustar00rootroot00000000000000// errors.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use diesel::r2d2; use std::io; use crate::models::{ShowId, Source}; use thiserror::Error; #[derive(Error, Debug)] pub enum DataError { #[error("SQL Query failed: {0}")] DieselResultError(#[from] diesel::result::Error), #[error("Database Migration error")] DieselMigrationError, #[error("R2D2 error: {0}")] R2D2Error(#[from] r2d2::Error), #[error("R2D2 Pool error: {0}")] R2D2PoolError(#[from] r2d2::PoolError), #[error("ToStr Error: {0}")] HttpToStr(#[from] http::header::ToStrError), #[error("Failed to parse a url: {0}")] UrlError(#[from] url::ParseError), #[error("TLS Error: {0}")] ReqwestError(#[from] reqwest::Error), #[error("IO Error: {0}")] IOError(#[from] io::Error), #[error("RSS Error: {0}")] RssError(#[from] rss::Error), #[error("XML Reader Error: {0}")] XmlReaderError(#[from] xml::reader::Error), #[error("Error: {0}")] Bail(String), #[error("Request to {url} returned {status_code}. Context: {context}")] HttpStatusGeneral { url: String, status_code: reqwest::StatusCode, context: String, }, #[error("Source redirects to a new url")] FeedRedirect(Source), #[error("Feed is up to date")] FeedNotModified(Source), #[error("Error occurred while Parsing an Episode. Reason: {}", reason)] ParseEpisodeError { reason: String, parent_id: ShowId }, #[error("Episode was not changed and thus skipped.")] EpisodeNotChanged, #[error("Invalid Uri Error: {0}")] InvalidUri(#[from] http::uri::InvalidUri), #[error("Builder error: {0}")] BuilderError(String), } #[derive(Error, Debug)] pub enum DownloadError { #[error("Request error: {0}")] RequestError(#[from] reqwest::Error), #[error("Data error: {0}")] DataError(#[from] DataError), #[error("Io error: {0}")] IoError(#[from] io::Error), #[error("Unexpected server response: {0}")] UnexpectedResponse(reqwest::StatusCode), #[error("The Download was cancelled.")] DownloadCancelled, #[error("Remote Image location not found.")] NoImageLocation, #[error("Failed to parse CacheLocation.")] InvalidCacheLocation, #[error("Failed to parse Cached Image Location.")] InvalidCachedImageLocation, #[error("Download no longer needed.")] NoLongerNeeded, } podcasts-25.2/podcasts-data/src/feed.rs000066400000000000000000000363511500126606300200600ustar00rootroot00000000000000// feed.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later //! Index Feeds. use crate::dbqueries; use crate::errors::DataError; use crate::models::{EpisodeId, NewEpisode, NewEpisodeMinimal, NewShow, Show, SourceId}; use crate::models::{Index, IndexState, Update}; /// Wrapper struct that hold a `Source` id and the `rss::Channel` /// that corresponds to the `Source.uri` field. #[derive(Debug, Clone, Builder, PartialEq)] #[builder(derive(Debug))] #[builder(setter(into))] pub struct Feed { /// The `rss::Channel` parsed from the `Source` uri. channel: rss::Channel, /// The `Source` id where the xml `rss::Channel` came from. source_id: SourceId, } impl Feed { /// Index the contents of the RSS `Feed` into the database. pub fn index(self) -> Result<(), DataError> { let show = self.parse_podcast().to_podcast()?; self.index_channel_items(show) } fn parse_podcast(&self) -> NewShow { NewShow::new(&self.channel, self.source_id) } fn index_channel_items(self, pd: Show) -> Result<(), DataError> { let stream = self.channel.into_items().into_iter(); // Parse the episodes let episodes = stream.filter_map(move |item| { let ret = NewEpisodeMinimal::new(&item, pd.id()) .and_then(move |ep| determine_ep_state(ep, &item)); if ret.is_ok() { Some(ret) } else { error!("importing ep: {:?}", ret); None } }); // Filter errors, Index updatable episodes, return insertables. let insertable_episodes = filter_episodes(episodes); batch_insert_episodes(&insertable_episodes); Ok(()) } } fn determine_ep_state( ep: NewEpisodeMinimal, item: &rss::Item, ) -> Result, DataError> { // Check if feed exists let exists = dbqueries::episode_exists(ep.guid(), ep.title(), ep.show_id())?; if !exists { Ok(IndexState::Index(ep.into_new_episode(item))) } else { let old = dbqueries::get_episode_minimal(ep.guid(), ep.title(), ep.show_id())?; let id = old.id(); if ep != old { Ok(IndexState::Update((ep.into_new_episode(item), id))) } else { Ok(IndexState::NotChanged) } } } fn filter_episodes(stream: S) -> Vec where S: Iterator, DataError>>, { let result: Vec = stream .filter_map(Result::ok) .filter_map(|state| { match state { IndexState::NotChanged => None, // Update individual rows, and filter them IndexState::Update((ref ep, id)) => { if let Err(err) = ep.update(id) { error!("{}", err); error!("Failed to index episode: {:?}.", ep.title()) } None } IndexState::Index(s) => Some(s), } }) // only Index is left, collect them for batch index .collect(); // filter out duplicates with same guid or title, they are assumed to be the same episode let mut set = std::collections::HashSet::new(); result .into_iter() .filter(|ep| { let id = ep.guid().unwrap_or(ep.title()).to_string(); set.insert(id) }) .collect() } fn batch_insert_episodes(episodes: &[NewEpisode]) { if episodes.is_empty() { return; }; info!("Indexing {} episodes.", episodes.len()); if let Err(err) = dbqueries::index_new_episodes(episodes) { error!("Failed batch indexing: {}", err); info!("Falling back to individual indexing."); } else { for ep in episodes { if let Err(err) = ep.index() { error!("Error: {}.", err); error!("Failed to index episode: {:?}.", ep.title()); } } } } #[cfg(test)] mod tests { use anyhow::Result; use rss::Channel; use crate::EpisodeModel; use crate::Source; use crate::database::truncate_db; use crate::dbqueries; use crate::utils::get_feed; use std::fs; use std::io::BufReader; use super::*; // (path, url) tuples. const URLS: &[(&str, &str)] = { &[ ( "tests/feeds/2018-01-20-Intercepted.xml", "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\ com/InterceptedWithJeremyScahill", ), ( "tests/feeds/2018-01-20-LinuxUnplugged.xml", "https://web.archive.org/web/20180120110314if_/https://feeds.feedburner.\ com/linuxunplugged", ), ( "tests/feeds/2018-01-20-TheTipOff.xml", "https://web.archive.org/web/20180120110727if_/https://rss.acast.com/thetipoff", ), ( "tests/feeds/2018-01-20-StealTheStars.xml", "https://web.archive.org/web/20180120104957if_/https://rss.art19.\ com/steal-the-stars", ), ( "tests/feeds/2018-01-20-GreaterThanCode.xml", "https://web.archive.org/web/20180120104741if_/https://www.greaterthancode.\ com/feed/podcast", ), ( "tests/feeds/2022-series-i-cinema.xml", "https://web.archive.org/web/20220205205130_/https://dinamics.ccma.\ cat/public/podcast/catradio/xml/series-i-cinema.xml", ), ] }; /// randomly chosen const TEST_SOURCE_ID: SourceId = SourceId(42); #[test] fn test_complete_index() -> Result<()> { truncate_db()?; let feeds: Vec<_> = URLS .iter() .map(|&(path, url)| { // Create and insert a Source into db let s = Source::from_url(url).unwrap(); get_feed(path, s.id()) }) .collect(); // Index the channels for feed in feeds { feed.index()? } // Assert the index rows equal the controlled results assert_eq!(dbqueries::get_sources()?.len(), 6); assert_eq!(dbqueries::get_podcasts()?.len(), 6); assert_eq!(dbqueries::get_episodes()?.len(), 404); Ok(()) } #[test] fn test_feed_parse_podcast() -> Result<()> { truncate_db()?; let path = "tests/feeds/2018-01-20-Intercepted.xml"; let feed = get_feed(path, TEST_SOURCE_ID); let file = fs::File::open(path)?; let channel = Channel::read_from(BufReader::new(file))?; let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(feed.parse_podcast(), pd); Ok(()) } #[test] fn test_feed_index_channel_items() -> Result<()> { truncate_db()?; let path = "tests/feeds/2018-01-20-Intercepted.xml"; let feed = get_feed(path, TEST_SOURCE_ID); let pd = feed.parse_podcast().to_podcast()?; feed.index_channel_items(pd)?; assert_eq!(dbqueries::get_podcasts()?.len(), 1); assert_eq!(dbqueries::get_episodes()?.len(), 43); Ok(()) } #[test] fn test_feed_non_utf8() -> Result<()> { truncate_db()?; let path = "tests/feeds/2022-series-i-cinema.xml"; let feed = get_feed(path, TEST_SOURCE_ID); let file = fs::File::open(path)?; let channel = Channel::read_from(BufReader::new(file))?; let description = feed.channel.description(); assert_eq!( description, "Els clàssics, les novetats de la cartellera i les millors sèries, tot en un sol podcast." ); let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(feed.parse_podcast(), pd); Ok(()) } // https://gitlab.gnome.org/World/podcasts/-/issues/239 #[test] fn test_feed_same_title_different_guid() -> Result<()> { truncate_db()?; let path = "tests/feeds/de-grote.xml"; let feed = get_feed(path, TEST_SOURCE_ID); let pd = feed.parse_podcast().to_podcast()?; feed.index_channel_items(pd)?; assert_eq!(dbqueries::get_podcasts()?.len(), 1); assert_eq!(dbqueries::get_episodes()?.len(), 12); Ok(()) } // https://gitlab.gnome.org/World/podcasts/-/issues/239 #[test] fn test_feed_same_title_no_guid() -> Result<()> { truncate_db()?; let path = "tests/feeds/de-grote-no-guid.xml"; let feed = get_feed(path, TEST_SOURCE_ID); let pd = feed.parse_podcast().to_podcast()?; feed.index_channel_items(pd)?; let eps = dbqueries::get_episodes()?; assert_eq!(1, dbqueries::get_podcasts()?.len()); assert_eq!(2, eps.len()); // latest episode (latest item in feed), previous items with same title are ignored let ep1 = eps.get(0).unwrap(); assert_eq!( Some( "https://chtbl.com/track/11G3D/progressive-audio.vrt.be/public/output/aud-7478134e-7c0e-44d4-8d65-32aa87dc6a3a-PODCAST_1/aud-7478134e-7c0e-44d4-8d65-32aa87dc6a3a-PODCAST_1.mp3" ), ep1.uri() ); // teaser (first item in feed) let ep2 = eps.get(1).unwrap(); assert_eq!( Some( "https://chtbl.com/track/11G3D/progressive-audio.vrt.be/public/output/aud-6b925160-4400-4d50-bb54-0085b84643cd-PODCAST_1/aud-6b925160-4400-4d50-bb54-0085b84643cd-PODCAST_1.mp3" ), ep2.uri() ); Ok(()) } // https://gitlab.gnome.org/World/podcasts/-/issues/204 #[test] fn test_reruns() -> Result<()> { truncate_db()?; let path = "tests/feeds/2020-12-29-replyall.xml"; let feed = get_feed(path, TEST_SOURCE_ID); let pd = feed.parse_podcast().to_podcast()?; feed.index_channel_items(pd)?; let show_id = dbqueries::get_podcasts()?.get(0).unwrap().id(); let eps = dbqueries::get_episodes()?; let rerun_eps: Vec<_> = eps .into_iter() .filter(|e| e.title() == "#86 Man of the People") .collect(); assert_eq!(rerun_eps.len(), 2); // rerun let ep1 = rerun_eps.get(0).unwrap(); assert_eq!("#86 Man of the People", ep1.title()); assert_eq!(Some("c16006fa-e2c3-11e9-be80-bf4954f39568"), ep1.guid()); assert_eq!( Some("https://traffic.megaphone.fm/GLT8202680871.mp3?updated=1607019082"), ep1.uri() ); assert_eq!( &dbqueries::get_episode( Some("c16006fa-e2c3-11e9-be80-bf4954f39568"), "#86 Man of the People", show_id )?, ep1 ); // original run let ep2 = rerun_eps.get(1).unwrap(); assert_eq!("#86 Man of the People", ep2.title()); assert_eq!(Some("3e7f1804-affc-11e6-892a-bb965a8b4a3f"), ep2.guid()); assert_eq!( Some("https://traffic.megaphone.fm/GLT1103232835.mp3?updated=1486920888"), ep2.uri() ); assert_eq!( &dbqueries::get_episode( Some("3e7f1804-affc-11e6-892a-bb965a8b4a3f"), "#86 Man of the People", show_id )?, ep2 ); Ok(()) } #[test] // has same title and & sign in title fn test_same_title_streetfight() -> Result<()> { truncate_db()?; let path = "tests/feeds/2024-03-15-streetfightradio.xml"; let feed = get_feed(path, TEST_SOURCE_ID); let pd = feed.parse_podcast().to_podcast()?; feed.index_channel_items(pd)?; let show_id = dbqueries::get_podcasts()?.get(0).unwrap().id(); let eps = dbqueries::get_episodes()?; let same_title_eps: Vec<_> = eps .clone() .into_iter() .filter(|e| e.title() == "Return Of The Macks") .collect(); assert_eq!(2, same_title_eps.len()); let ep1 = same_title_eps.get(0).unwrap(); assert_eq!("Return Of The Macks", ep1.title()); assert_eq!(Some("tag:soundcloud,2010:tracks/501720369"), ep1.guid()); assert_eq!( Some( "https://feeds.soundcloud.com/stream/501720369-streetfightwcrs-return-of-the-macks-1.mp3" ), ep1.uri() ); assert_eq!( &dbqueries::get_episode( Some("tag:soundcloud,2010:tracks/501720369"), "Return Of The Macks", show_id )?, ep1 ); let ep2 = same_title_eps.get(1).unwrap(); assert_eq!("Return Of The Macks", ep2.title()); assert_eq!(Some("tag:soundcloud,2010:tracks/430832790"), ep2.guid()); assert_eq!( Some( "https://feeds.soundcloud.com/stream/430832790-streetfightwcrs-return-of-the-macks.mp3" ), ep2.uri() ); assert_eq!( &dbqueries::get_episode( Some("tag:soundcloud,2010:tracks/430832790"), "Return Of The Macks", show_id )?, ep2 ); // second title let same_title_eps: Vec<_> = eps .into_iter() .filter(|e| e.title() == "Street Fight Q&A") .collect(); assert_eq!(2, same_title_eps.len()); let ep1 = same_title_eps.get(0).unwrap(); assert_eq!("Street Fight Q&A", ep1.title()); assert_eq!(Some("tag:soundcloud,2010:tracks/658646834"), ep1.guid()); assert_eq!( Some( "https://feeds.soundcloud.com/stream/658646834-streetfightwcrs-street-fight-qa-1.mp3" ), ep1.uri() ); assert_eq!( &dbqueries::get_episode( Some("tag:soundcloud,2010:tracks/658646834"), "Street Fight Q&A", show_id )?, ep1 ); let ep2 = same_title_eps.get(1).unwrap(); assert_eq!("Street Fight Q&A", ep2.title()); assert_eq!(Some("tag:soundcloud,2010:tracks/624834786"), ep2.guid()); assert_eq!( Some( "https://feeds.soundcloud.com/stream/624834786-streetfightwcrs-street-fight-qa.mp3" ), ep2.uri() ); assert_eq!( &dbqueries::get_episode( Some("tag:soundcloud,2010:tracks/624834786"), "Street Fight Q&A", show_id )?, ep2 ); Ok(()) } } podcasts-25.2/podcasts-data/src/lib.rs000066400000000000000000000057741500126606300177300ustar00rootroot00000000000000// lib.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later #![recursion_limit = "1024"] #[cfg(test)] #[macro_use] extern crate maplit; #[macro_use] extern crate derive_builder; #[macro_use] extern crate diesel; #[macro_use] extern crate log; pub mod database; #[allow(missing_docs)] pub mod dbqueries; pub mod discovery; #[allow(missing_docs)] pub mod downloader; #[allow(missing_docs)] pub mod errors; mod feed; pub(crate) mod models; pub mod opml; mod parser; pub mod pipeline; mod schema; pub mod utils; pub use crate::feed::{Feed, FeedBuilder}; pub use crate::models::Save; pub use crate::models::{ Episode, EpisodeCleanerModel, EpisodeId, EpisodeModel, EpisodeWidgetModel, Show, ShowCoverModel, ShowId, Source, SourceId, }; // Set the user agent, See #53 for more // Keep this in sync with Tor-browser releases /// The user-agent to be used for all the requests. /// It originates from the Tor-browser UA. pub const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0"; /// [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html) Paths. pub mod xdg_dirs { use once_cell::sync::Lazy; use std::path::PathBuf; pub(crate) static PODCASTS_XDG: Lazy = Lazy::new(|| xdg::BaseDirectories::with_prefix("gnome-podcasts").unwrap()); /// XDG_DATA Directory `Pathbuf`. pub static PODCASTS_DATA: Lazy = Lazy::new(|| { PODCASTS_XDG .create_data_directory(PODCASTS_XDG.get_data_home()) .unwrap() }); /// XDG_CONFIG Directory `Pathbuf`. pub static PODCASTS_CONFIG: Lazy = Lazy::new(|| { PODCASTS_XDG .create_config_directory(PODCASTS_XDG.get_config_home()) .unwrap() }); /// XDG_CACHE Directory `Pathbuf`. pub static PODCASTS_CACHE: Lazy = Lazy::new(|| { PODCASTS_XDG .create_cache_directory(PODCASTS_XDG.get_cache_home()) .unwrap() }); /// GNOME Podcasts Download Directory `PathBuf`. pub static DL_DIR: Lazy = Lazy::new(|| PODCASTS_XDG.create_data_directory("Downloads").unwrap()); /// GNOME Podcasts Tmp Directory `PathBuf`. pub static TMP_DIR: Lazy = Lazy::new(|| PODCASTS_XDG.create_data_directory("tmp").unwrap()); } podcasts-25.2/podcasts-data/src/models/000077500000000000000000000000001500126606300200625ustar00rootroot00000000000000podcasts-25.2/podcasts-data/src/models/discovery_settings.rs000066400000000000000000000021161500126606300243570ustar00rootroot00000000000000// discovery_settings.rs // // Copyright 2022-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use crate::schema::discovery_settings; use diesel::prelude::*; #[derive(Queryable, AsChangeset, PartialEq)] #[diesel(table_name = discovery_settings)] #[derive(Insertable, Debug, Clone)] /// Diesel Model of the discovery_settings table. pub struct DiscoverySetting { pub platform_id: String, pub enabled: bool, } podcasts-25.2/podcasts-data/src/models/episode.rs000066400000000000000000000331451500126606300220660ustar00rootroot00000000000000// episode.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use chrono::prelude::*; use diesel::prelude::*; use crate::database::connection; use crate::errors::DataError; use crate::make_id_wrapper; use crate::models::{Save, Show, ShowId}; use crate::schema::episodes; make_id_wrapper!(EpisodeId); /// A trait to get Episode data regardless. pub trait EpisodeModel { /// Get the model id. `id()` already exists in Diesel. fn id(&self) -> EpisodeId; /// Get the value of the `uri`. /// /// Represents the url(usually) that the media file will be located at. fn uri(&self) -> Option<&str>; /// Epoch representation of the last time the episode was played. /// /// None/Null for unplayed. fn played(&self) -> Option; } #[derive(Queryable, Identifiable, AsChangeset, Associations, PartialEq, Selectable)] #[diesel(table_name = episodes)] #[diesel(treat_none_as_null = true)] #[diesel(belongs_to(Show, foreign_key = show_id))] #[derive(Debug, Clone)] /// Diesel Model of the episode table. pub struct Episode { id: EpisodeId, title: String, uri: Option, local_uri: Option, description: Option, image_uri: Option, epoch: NaiveDateTime, length: Option, duration: Option, guid: Option, played: Option, play_position: i32, show_id: ShowId, } impl EpisodeModel for Episode { /// Get the value of the sqlite's `ROW_ID` fn id(&self) -> EpisodeId { self.id } /// Get the value of the `uri`. /// /// Represents the url(usually) that the media file will be located at. fn uri(&self) -> Option<&str> { self.uri.as_deref() } /// Epoch representation of the last time the episode was played. /// /// None/Null for unplayed. fn played(&self) -> Option { self.played } } impl Save for Episode { type Error = DataError; /// Helper method to easily save/"sync" current state of self to the /// Database. fn save(&self) -> Result { let db = connection(); let mut tempdb = db.get()?; self.save_changes::(&mut tempdb) .map_err(From::from) } } impl Episode { /// Get the value of the sqlite's `ROW_ID` pub fn id(&self) -> EpisodeId { self.id } /// Get the value of the `title` field. pub fn title(&self) -> &str { &self.title } /// Get the value of the `local_uri`. /// /// Represents the local uri,usually filesystem path, /// that the media file will be located at. pub fn local_uri(&self) -> Option<&str> { self.local_uri.as_deref() } /// Get the `description`. pub fn description(&self) -> Option<&str> { self.description.as_deref() } /// Get the Episode's `guid`. pub fn guid(&self) -> Option<&str> { self.guid.as_deref() } /// Get the `epoch` value. /// /// Retrieved from the rss Item publish date. /// Value is set to Utc whenever possible. pub fn epoch(&self) -> NaiveDateTime { self.epoch } /// Get the `length`. /// /// The number represents the size of the file in bytes. pub fn length(&self) -> Option { self.length } /// Get the `duration` value. /// /// The number represents the duration of the item/episode in seconds. pub fn duration(&self) -> Option { self.duration } /// `Show` table foreign key. pub fn show_id(&self) -> ShowId { self.show_id } /// Get play_position /// /// The number represents the number of seconds played in the episode. /// 0 means the episode was either not played or continued to play to the end. pub fn play_position(&self) -> i32 { self.play_position } /// Get image_uri /// /// The uri to the episode specific cover. /// None means no itunes:image was set for this episode. pub fn image_uri(&self) -> Option<&str> { self.image_uri.as_deref() } } #[derive(Queryable, AsChangeset, PartialEq, Selectable)] #[diesel(table_name = episodes)] #[diesel(treat_none_as_null = true)] #[diesel(primary_key(title, show_id))] #[derive(Debug, Clone)] /// Diesel Model to be used for constructing `EpisodeWidgets`. pub struct EpisodeWidgetModel { id: EpisodeId, title: String, uri: Option, local_uri: Option, epoch: NaiveDateTime, length: Option, duration: Option, played: Option, play_position: i32, show_id: ShowId, } impl EpisodeModel for EpisodeWidgetModel { /// Get the value of the sqlite's `ROW_ID` fn id(&self) -> EpisodeId { self.id } /// Get the value of the `uri`. /// /// Represents the url(usually) that the media file will be located at. fn uri(&self) -> Option<&str> { self.uri.as_deref() } /// Epoch representation of the last time the episode was played. /// /// None/Null for unplayed. fn played(&self) -> Option { self.played } } impl From for EpisodeWidgetModel { fn from(e: Episode) -> EpisodeWidgetModel { EpisodeWidgetModel { id: e.id, title: e.title, uri: e.uri, local_uri: e.local_uri, epoch: e.epoch, length: e.length, duration: e.duration, played: e.played, play_position: e.play_position, show_id: e.show_id, } } } impl Save for EpisodeWidgetModel { type Error = DataError; /// Helper method to easily save/"sync" current state of self to the /// Database. fn save(&self) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut tempdb = db.get()?; diesel::update(episodes.filter(id.eq(self.id))) .set(self) .execute(&mut tempdb) .map_err(From::from) } } impl EpisodeWidgetModel { /// Get the value of the `title` field. pub fn title(&self) -> &str { &self.title } /// Get the value of the `local_uri`. /// /// Represents the local uri,usually filesystem path, /// that the media file will be located at. pub fn local_uri(&self) -> Option<&str> { self.local_uri.as_deref() } /// Set the `local_uri`. pub fn set_local_uri(&mut self, value: Option<&str>) { self.local_uri = value.map(|x| x.to_string()); } /// Get the `epoch` value. /// /// Retrieved from the rss Item publish date. /// Value is set to Utc whenever possible. pub fn epoch(&self) -> NaiveDateTime { self.epoch } /// Get the `length`. /// /// The number represents the size of the file in bytes. pub fn length(&self) -> Option { self.length } /// Set the `length`. pub fn set_length(&mut self, value: Option) { self.length = value; } /// Get the `duration` value. /// /// The number represents the duration of the item/episode in seconds. pub fn duration(&self) -> Option { self.duration } /// Set the `played` value. fn set_played(&mut self, value: Option) { self.played = value; } /// `Show` table foreign key. pub fn show_id(&self) -> ShowId { self.show_id } /// Set the `played` value to `None` and save it. pub fn set_unplayed(&mut self) -> Result<(), DataError> { self.set_played(None); self.save().map(|_| ()) } /// Sets the `played` value with the current `epoch` timestap and save it. pub fn set_played_now(&mut self) -> Result<(), DataError> { self.set_played(Some(Utc::now().naive_utc())); self.save().map(|_| ()) } /// Get play_position /// /// The number represents the number of seconds played in the episode. /// `0` means the episode was either not played or continued to play to the end. pub fn play_position(&self) -> i32 { self.play_position } /// Sets `play_position` and saves the record. pub fn set_play_position(&mut self, seconds: i32) -> Result<(), DataError> { self.play_position = seconds; self.save().map(|_| ()) } /// Sets `play_position` if it diverges multiple seconds (10) from the last value. /// If it doesn't diverge Ok(()) is returned, nothing is written. pub fn set_play_position_if_divergent(&mut self, seconds: i32) -> Result<(), DataError> { if seconds != 0 && self.play_position != 0 { if (seconds - self.play_position).abs() > 10 { return self.set_play_position(seconds); } } else { return self.set_play_position(seconds); } Ok(()) } } #[derive(Queryable, AsChangeset, PartialEq, Selectable)] #[diesel(table_name = episodes)] #[diesel(treat_none_as_null = true)] #[diesel(primary_key(title, show_id))] #[derive(Debug, Clone)] /// Diesel Model to be used internal with the `utils::checkup` function. pub struct EpisodeCleanerModel { id: EpisodeId, local_uri: Option, played: Option, } impl Save for EpisodeCleanerModel { type Error = DataError; /// Helper method to easily save/"sync" current state of self to the /// Database. fn save(&self) -> Result { use crate::schema::episodes::dsl::*; let db = connection(); let mut tempdb = db.get()?; diesel::update(episodes.filter(id.eq(self.id))) .set(self) .execute(&mut tempdb) .map_err(From::from) } } impl From for EpisodeCleanerModel { fn from(e: Episode) -> EpisodeCleanerModel { EpisodeCleanerModel { id: e.id(), local_uri: e.local_uri, played: e.played, } } } impl EpisodeCleanerModel { /// Get the value of the sqlite's `ROW_ID` pub fn id(&self) -> EpisodeId { self.id } /// Get the value of the `local_uri`. /// /// Represents the local uri,usually filesystem path, /// that the media file will be located at. pub fn local_uri(&self) -> Option<&str> { self.local_uri.as_deref() } /// Set the `local_uri`. pub fn set_local_uri(&mut self, value: Option<&str>) { self.local_uri = value.map(|x| x.to_string()); } /// Epoch representation of the last time the episode was played. /// /// None/Null for unplayed. pub fn played(&self) -> Option { self.played } /// Set the `played` value. pub fn set_played(&mut self, value: Option) { self.played = value; } } #[derive(Queryable, AsChangeset, PartialEq, Selectable)] #[diesel(table_name = episodes)] #[diesel(treat_none_as_null = true)] #[diesel(primary_key(title, show_id))] #[derive(Debug, Clone)] /// Diesel Model to be used for FIXME. pub struct EpisodeMinimal { id: EpisodeId, title: String, uri: Option, image_uri: Option, epoch: NaiveDateTime, length: Option, duration: Option, play_position: i32, guid: Option, show_id: ShowId, } impl From for EpisodeMinimal { fn from(e: Episode) -> Self { EpisodeMinimal { id: e.id, title: e.title, uri: e.uri, image_uri: e.image_uri, length: e.length, guid: e.guid, epoch: e.epoch, duration: e.duration, play_position: e.play_position, show_id: e.show_id, } } } impl EpisodeMinimal { /// Get the value of the sqlite's `ROW_ID` pub fn id(&self) -> EpisodeId { self.id } /// Get the value of the `title` field. pub fn title(&self) -> &str { &self.title } /// Get the value of the `uri`. /// /// Represents the url(usually) that the media file will be located at. pub fn uri(&self) -> Option<&str> { self.uri.as_deref() } /// A cover image for this specific episode. pub fn image_uri(&self) -> Option<&str> { self.image_uri.as_deref() } /// Get the Episode's `guid`. pub fn guid(&self) -> Option<&str> { self.guid.as_deref() } /// Get the `epoch` value. /// /// Retrieved from the rss Item publish date. /// Value is set to Utc whenever possible. pub fn epoch(&self) -> NaiveDateTime { self.epoch } /// Get the `length`. /// /// The number represents the size of the file in bytes. pub fn length(&self) -> Option { self.length } /// Get the `duration` value. /// /// The number represents the duration of the item/episode in seconds. pub fn duration(&self) -> Option { self.duration } /// `Show` table foreign key. pub fn show_id(&self) -> ShowId { self.show_id } } podcasts-25.2/podcasts-data/src/models/mod.rs000066400000000000000000000063201500126606300212100ustar00rootroot00000000000000// mod.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later mod new_episode; mod new_show; mod new_source; mod discovery_settings; mod episode; mod show; mod source; pub(crate) use self::discovery_settings::DiscoverySetting; pub(crate) use self::new_episode::{NewEpisode, NewEpisodeMinimal}; pub(crate) use self::new_show::NewShow; pub(crate) use self::new_source::NewSource; #[cfg(test)] pub(crate) use self::new_episode::NewEpisodeBuilder; #[cfg(test)] pub(crate) use self::new_show::NewShowBuilder; pub use self::episode::{ Episode, EpisodeCleanerModel, EpisodeId, EpisodeMinimal, EpisodeModel, EpisodeWidgetModel, }; pub use self::show::{Show, ShowCoverModel, ShowId}; pub use self::source::{Source, SourceId}; #[derive(Debug, Clone, PartialEq)] pub enum IndexState { Index(T), Update((T, ID)), NotChanged, } pub(crate) trait Insert { type Error; fn insert(&self) -> Result; } pub trait Update { type Error; fn update(&self, _: ID) -> Result; } // This might need to change in the future pub trait Index: Insert + Update { type Error; fn index(&self) -> Result>::Error>; } /// FIXME: DOCS pub trait Save { /// The Error type to be returned. type Error; /// Helper method to easily save/"sync" current state of a diesel model to /// the Database. fn save(&self) -> Result; } /// Allows to use struct wrappers instead of i32 Id types. #[macro_export] macro_rules! make_id_wrapper { ($type_name:ident) => { use diesel::backend::Backend; use diesel::deserialize::{self, FromSql}; use diesel::serialize::{self, Output, ToSql}; use diesel::sql_types::Integer; use diesel::sqlite::Sqlite; #[derive(AsExpression, FromSqlRow, Debug, PartialEq, Eq, Hash, Clone, Copy, Default)] #[diesel(sql_type = diesel::sql_types::Integer)] pub struct $type_name(pub i32); impl FromSql for $type_name where DB: Backend, i32: FromSql, { fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { i32::from_sql(bytes).map($type_name) } } impl ToSql for $type_name { fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result { >::to_sql(&self.0, out) } } }; } podcasts-25.2/podcasts-data/src/models/new_episode.rs000066400000000000000000000741111500126606300227350ustar00rootroot00000000000000// new_episode.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use chrono::prelude::*; use diesel::prelude::*; use rfc822_sanitizer::parse_from_rfc2822_with_fallback as parse_rfc822; use crate::database::connection; use crate::dbqueries; use crate::errors::DataError; use crate::models::episode::EpisodeId; use crate::models::{Episode, EpisodeMinimal, EpisodeModel, Index, Insert, ShowId, Update}; use crate::parser; use crate::schema::episodes; use crate::utils::url_cleaner; #[derive(Insertable, AsChangeset)] #[diesel(table_name = episodes)] #[derive(Debug, Clone, Default, Builder, PartialEq)] #[builder(default)] #[builder(derive(Debug))] #[builder(setter(into))] pub(crate) struct NewEpisode { title: String, uri: Option, description: Option, image_uri: Option, length: Option, duration: Option, play_position: i32, guid: Option, epoch: NaiveDateTime, show_id: ShowId, } impl From for NewEpisode { fn from(e: NewEpisodeMinimal) -> Self { NewEpisodeBuilder::default() .title(e.title) .uri(e.uri) .image_uri(e.image_uri) .duration(e.duration) .epoch(e.epoch) .show_id(e.show_id) .guid(e.guid) .build() .unwrap() } } impl Insert<()> for NewEpisode { type Error = DataError; /// Should not be called directly, call index() instead. fn insert(&self) -> Result<(), DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; info!("Inserting {:?}", self.title); diesel::insert_into(episodes) .values(self) .execute(&mut con) .map_err(From::from) .map(|_| ()) } } impl Update<(), EpisodeId> for NewEpisode { type Error = DataError; fn update(&self, episode_id: EpisodeId) -> Result<(), DataError> { use crate::schema::episodes::dsl::*; let db = connection(); let mut con = db.get()?; info!("Updating {:?}", self.title); diesel::update(episodes.filter(id.eq(episode_id))) .set(self) .execute(&mut con) .map_err(From::from) .map(|_| ()) } } impl Index<(), EpisodeId> for NewEpisode { type Error = DataError; // Does not update the episode description if it's the only thing that has // changed. fn index(&self) -> Result<(), DataError> { let exists = dbqueries::episode_exists(self.guid(), self.title(), self.show_id())?; if exists { let other = dbqueries::get_episode_minimal(self.guid(), self.title(), self.show_id())?; if self != &other { self.update(other.id()) } else { Ok(()) } } else { self.insert() } } } impl PartialEq for NewEpisode { fn eq(&self, other: &EpisodeMinimal) -> bool { (self.title() == other.title()) && (self.uri() == other.uri()) && (self.image_uri() == other.image_uri()) && (self.duration() == other.duration()) && (self.epoch() == other.epoch()) && (self.guid() == other.guid()) && (self.show_id() == other.show_id()) } } impl PartialEq for NewEpisode { fn eq(&self, other: &Episode) -> bool { (self.title() == other.title()) && (self.uri() == other.uri()) && (self.image_uri() == other.image_uri()) && (self.duration() == other.duration()) && (self.play_position() == other.play_position()) && (self.epoch() == other.epoch()) && (self.guid() == other.guid()) && (self.show_id() == other.show_id()) && (self.description() == other.description()) && (self.length() == other.length()) } } impl NewEpisode { /// Parses an `rss::Item` into a `NewEpisode` Struct. #[allow(dead_code)] pub(crate) fn new(item: &rss::Item, show_id: ShowId) -> Result { NewEpisodeMinimal::new(item, show_id).map(|ep| ep.into_new_episode(item)) } #[allow(dead_code)] pub(crate) fn to_episode(&self) -> Result { self.index()?; dbqueries::get_episode(self.guid(), self.title(), self.show_id) } } // Ignore the following getters. They are used in unit tests mainly. impl NewEpisode { pub(crate) fn title(&self) -> &str { self.title.as_ref() } pub(crate) fn uri(&self) -> Option<&str> { self.uri.as_deref() } pub(crate) fn image_uri(&self) -> Option<&str> { self.image_uri.as_deref() } pub(crate) fn description(&self) -> Option<&str> { self.description.as_deref() } pub(crate) fn guid(&self) -> Option<&str> { self.guid.as_deref() } pub(crate) fn epoch(&self) -> NaiveDateTime { self.epoch } pub(crate) fn duration(&self) -> Option { self.duration } pub(crate) fn play_position(&self) -> i32 { self.play_position } pub(crate) fn length(&self) -> Option { self.length } pub(crate) fn show_id(&self) -> ShowId { self.show_id } } #[derive(Insertable, AsChangeset)] #[diesel(table_name = episodes)] #[derive(Debug, Clone, Builder, PartialEq)] #[builder(derive(Debug))] #[builder(setter(into))] pub(crate) struct NewEpisodeMinimal { title: String, uri: Option, image_uri: Option, length: Option, duration: Option, #[builder(default = "0")] play_position: i32, epoch: NaiveDateTime, guid: Option, show_id: ShowId, } impl PartialEq for NewEpisodeMinimal { fn eq(&self, other: &EpisodeMinimal) -> bool { (self.title() == other.title()) && (self.uri() == other.uri()) && (self.image_uri() == other.image_uri()) && (self.duration() == other.duration()) && (self.epoch() == other.epoch()) && (self.guid() == other.guid()) && (self.show_id() == other.show_id()) } } impl NewEpisodeMinimal { pub(crate) fn new(item: &rss::Item, parent_id: ShowId) -> Result { if item.title().is_none() { let err = DataError::ParseEpisodeError { reason: "No title specified for this Episode.".into(), parent_id, }; return Err(err); } let title = item.title().unwrap().trim().to_owned(); let guid = item.guid().map(|s| s.value().trim().to_owned()); // Get the mime type, the `http` url and the length from the enclosure // http://www.rssboard.org/rss-specification#ltenclosuregtSubelementOfLtitemgt let enc = item.enclosure(); // Get the url let uri = enc.map(|s| url_cleaner(s.url().trim())); let image = item .itunes_ext() .and_then(|i| i.image()) .map(|s| s.to_owned()); // Get the size of the content, it should be in bytes let length = enc.and_then(|x| x.length().parse().ok()); // Default to rfc2822 representation of epoch 0. let date = parse_rfc822(item.pub_date().unwrap_or("Thu, 1 Jan 1970 00:00:00 +0000")); // Should treat information from the rss feeds as invalid by default. // Case: "Thu, 05 Aug 2016 06:00:00 -0400" <-- Actually that was friday. let epoch = date .map(|x| DateTime::::from(x).naive_utc()) .unwrap_or_default(); let duration = parser::parse_itunes_duration(item.itunes_ext()); NewEpisodeMinimalBuilder::default() .title(title) .uri(uri) .image_uri(image) .length(length) .duration(duration) .epoch(epoch) .guid(guid) .show_id(parent_id) .build() .map_err(|err| DataError::BuilderError(format!("{err}"))) } // TODO: TryInto is stabilizing in rustc v1.26! // ^ Jokes on you past self! pub(crate) fn into_new_episode(self, item: &rss::Item) -> NewEpisode { let description = item.description().map(|s| { let sanitized_html = ammonia::Builder::new() // Remove `rel` attributes from `` tags .link_rel(None) .clean(s.trim()) .to_string(); sanitized_html }); NewEpisodeBuilder::default() .title(self.title) .uri(self.uri) .image_uri(self.image_uri) .duration(self.duration) .epoch(self.epoch) .show_id(self.show_id) .guid(self.guid) .length(self.length) .description(description) .build() .unwrap() } } // Ignore the following getters. They are used in unit tests mainly. impl NewEpisodeMinimal { pub(crate) fn title(&self) -> &str { self.title.as_ref() } pub(crate) fn uri(&self) -> Option<&str> { self.uri.as_deref() } pub(crate) fn image_uri(&self) -> Option<&str> { self.image_uri.as_deref() } pub(crate) fn guid(&self) -> Option<&str> { self.guid.as_deref() } pub(crate) fn duration(&self) -> Option { self.duration } pub(crate) fn epoch(&self) -> NaiveDateTime { self.epoch } pub(crate) fn show_id(&self) -> ShowId { self.show_id } } #[cfg(test)] mod tests { use crate::database::truncate_db; use crate::dbqueries; use crate::models::new_episode::{NewEpisodeMinimal, NewEpisodeMinimalBuilder}; use crate::models::*; use anyhow::Result; use chrono::prelude::*; use once_cell::sync::Lazy; use rss::Channel; use std::fs::File; use std::io::BufReader; /// randomly chosen const TEST_SHOW_ID: ShowId = ShowId(42); // TODO: Add tests for other feeds too. // Especially if you find an *interesting* generated feed. // Known prebuilt expected objects. static EXPECTED_MINIMAL_INTERCEPTED_1: Lazy = Lazy::new(|| { NewEpisodeMinimalBuilder::default() .title("The Super Bowl of Racism") .uri(Some(String::from( "http://traffic.megaphone.fm/PPY6458293736.mp3", ))) .image_uri(None) .guid(Some(String::from("7df4070a-9832-11e7-adac-cb37b05d5e24"))) .epoch( DateTime::::from_timestamp(1505296800, 0) .unwrap() .naive_utc(), ) .length(Some(66738886)) .duration(Some(4171)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_MINIMAL_INTERCEPTED_2: Lazy = Lazy::new(|| { NewEpisodeMinimalBuilder::default() .title("Atlas Golfed — U.S.-Backed Think Tanks Target Latin America") .uri(Some(String::from( "http://traffic.megaphone.fm/FL5331443769.mp3", ))) .image_uri(None) .guid(Some(String::from("7c207a24-e33f-11e6-9438-eb45dcf36a1d"))) .epoch( DateTime::::from_timestamp(1502272800, 0) .unwrap() .naive_utc(), ) .length(Some(67527575)) .duration(Some(4415)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_INTERCEPTED_1: Lazy = Lazy::new(|| { let descr = "NSA whistleblower Edward Snowden discusses the massive Equifax data \ breach and allegations of Russian interference in the US election. \ Commentator Shaun King explains his call for a boycott of the NFL and \ talks about his campaign to bring violent neo-Nazis to justice. Rapper \ Open Mike Eagle performs."; NewEpisodeBuilder::default() .title("The Super Bowl of Racism") .uri(Some(String::from( "http://traffic.megaphone.fm/PPY6458293736.mp3", ))) .image_uri(None) .description(Some(String::from(descr))) .guid(Some(String::from("7df4070a-9832-11e7-adac-cb37b05d5e24"))) .length(Some(66738886)) .epoch( DateTime::::from_timestamp(1505296800, 0) .unwrap() .naive_utc(), ) .duration(Some(4171)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_INTERCEPTED_2: Lazy = Lazy::new(|| { let descr = "This week on Intercepted: Jeremy gives an update on the aftermath of \ Blackwater’s 2007 massacre of Iraqi civilians. Intercept reporter Lee \ Fang lays out how a network of libertarian think tanks called the Atlas \ Network is insidiously shaping political infrastructure in Latin \ America. We speak with attorney and former Hugo Chavez adviser Eva \ Golinger about the Venezuela\'s political turmoil.And we hear Claudia \ Lizardo of the Caracas-based band, La Pequeña Revancha, talk about her \ music and hopes for Venezuela."; NewEpisodeBuilder::default() .title("Atlas Golfed — U.S.-Backed Think Tanks Target Latin America") .uri(Some(String::from( "http://traffic.megaphone.fm/FL5331443769.mp3", ))) .image_uri(None) .description(Some(String::from(descr))) .guid(Some(String::from("7c207a24-e33f-11e6-9438-eb45dcf36a1d"))) .length(Some(67527575)) .epoch( DateTime::::from_timestamp(1502272800, 0) .unwrap() .naive_utc(), ) .duration(Some(4415)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static UPDATED_DURATION_INTERCEPTED_1: Lazy = Lazy::new(|| { NewEpisodeBuilder::default() .title("The Super Bowl of Racism") .uri(Some(String::from( "http://traffic.megaphone.fm/PPY6458293736.mp3", ))) .image_uri(None) .description(Some(String::from("New description"))) .guid(Some(String::from("7df4070a-9832-11e7-adac-cb37b05d5e24"))) .length(Some(66738886)) .epoch( DateTime::::from_timestamp(1505296800, 0) .unwrap() .naive_utc(), ) .duration(Some(424242)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_MINIMAL_LUP_1: Lazy = Lazy::new(|| { NewEpisodeMinimalBuilder::default() .title("Hacking Devices with Kali Linux | LUP 214") .uri(Some(String::from( "http://www.podtrac.com/pts/redirect.mp3/traffic.libsyn.com/jnite/lup-0214.mp3", ))) .image_uri(None) .guid(Some(String::from("78A682B4-73E8-47B8-88C0-1BE62DD4EF9D"))) .length(Some(46479789)) .epoch( DateTime::::from_timestamp(1505280282, 0) .unwrap() .naive_utc(), ) .duration(Some(5733)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_MINIMAL_LUP_2: Lazy = Lazy::new(|| { NewEpisodeMinimalBuilder::default() .title("Gnome Does it Again | LUP 213") .uri(Some(String::from( "http://www.podtrac.com/pts/redirect.mp3/traffic.libsyn.com/jnite/lup-0213.mp3", ))) .image_uri(None) .guid(Some(String::from("1CE57548-B36C-4F14-832A-5D5E0A24E35B"))) .epoch( DateTime::::from_timestamp(1504670247, 0) .unwrap() .naive_utc(), ) .length(Some(36544272)) .duration(Some(4491)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_LUP_1: Lazy = Lazy::new(|| { let descr = "Audit your network with a couple of easy commands on Kali Linux. Chris \ decides to blow off a little steam by attacking his IoT devices, Wes has \ the scope on Equifax blaming open source & the Beard just saved the \ show. It’s a really packed episode!"; NewEpisodeBuilder::default() .title("Hacking Devices with Kali Linux | LUP 214") .uri(Some(String::from( "http://www.podtrac.com/pts/redirect.mp3/traffic.libsyn.com/jnite/lup-0214.mp3", ))) .image_uri(None) .description(Some(String::from(descr))) .guid(Some(String::from("78A682B4-73E8-47B8-88C0-1BE62DD4EF9D"))) .length(Some(46479789)) .epoch( DateTime::::from_timestamp(1505280282, 0) .unwrap() .naive_utc(), ) .duration(Some(5733)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_LUP_2: Lazy = Lazy::new(|| { let descr = "

The Gnome project is about to solve one of our audience's biggest Wayland’s \ concerns. But as the project takes on a new level of relevance, decisions for \ the next version of Gnome have us worried about the future.

\n\n

Plus we \ chat with Wimpy about the Ubuntu Rally in NYC, Microsoft’s sneaky move to turn \ Windows 10 into the “ULTIMATE LINUX RUNTIME”, community news & more!

"; NewEpisodeBuilder::default() .title("Gnome Does it Again | LUP 213") .uri(Some(String::from( "http://www.podtrac.com/pts/redirect.mp3/traffic.libsyn.com/jnite/lup-0213.mp3", ))) .image_uri(None) .description(Some(String::from(descr))) .guid(Some(String::from("1CE57548-B36C-4F14-832A-5D5E0A24E35B"))) .length(Some(36544272)) .epoch( DateTime::::from_timestamp(1504670247, 0) .unwrap() .naive_utc(), ) .duration(Some(4491)) .show_id(TEST_SHOW_ID) .build() .unwrap() }); static EXPECTED_NDR_1: Lazy = Lazy::new(|| { let descr = "Die aktuellen Meldungen aus der NDR Info Nachrichtenredaktion."; NewEpisodeBuilder::default() .title("Nachrichten") .uri(Some(String::from( "https://mediandr-a.akamaihd.net/download/podcasts/podcast4450/AU-20240313-2303-4300.mp3", ))) .description(Some(String::from(descr))) .guid(Some(String::from("AU-20240313-2303-4300-A"))) .length(None) .epoch(DateTime::::from_timestamp(1710367140, 0).unwrap().naive_utc()) .duration(Some(202)) .show_id(TEST_SHOW_ID) .image_uri(Some("https://www.ndr.de/nachrichten/info/nachrichten660_v-quadratl.jpg".to_string())) .build() .unwrap() }); static EXPECTED_NDR_2: Lazy = Lazy::new(|| { let descr = "Die aktuellen Meldungen aus der NDR Info Nachrichtenredaktion."; NewEpisodeBuilder::default() .title("Nachrichten") .uri(Some(String::from( "https://mediandr-a.akamaihd.net/download/podcasts/podcast4450/AU-20240314-1705-4100.mp3", ))) .description(Some(String::from(descr))) .guid(Some(String::from("AU-20240314-1705-4100-A"))) .length(None) .epoch(DateTime::::from_timestamp(1710431940, 0).unwrap().naive_utc()) .duration(Some(300)) .show_id(TEST_SHOW_ID) .image_uri(Some("https://www.ndr.de/nachrichten/info/nachrichten660_v-quadratl.jpg".to_string())) .build() .unwrap() }); static EXPECTED_NDR_3: Lazy = Lazy::new(|| { let descr = "Die aktuellen Meldungen aus der NDR Info Nachrichtenredaktion."; NewEpisodeBuilder::default() .title("TITLE_UPDATED") .uri(Some(String::from( "https://mediandr-a.akamaihd.net/download/podcasts/podcast4450/AU-20240314-1705-4100.mp3", ))) .description(Some(String::from(descr))) .guid(Some(String::from("AU-20240314-1705-4100-A"))) .length(None) .epoch(DateTime::::from_timestamp(2000000000, 0).unwrap().naive_utc()) .duration(Some(300)) .show_id(TEST_SHOW_ID) .image_uri(Some("https://www.ndr.de/nachrichten/info/nachrichten660_v-quadratl.jpg".to_string())) .build() .unwrap() }); #[test] fn test_new_episode_minimal_intercepted() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-Intercepted.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let episode = channel.items().iter().nth(14).unwrap(); let ep = NewEpisodeMinimal::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_MINIMAL_INTERCEPTED_1); let episode = channel.items().iter().nth(15).unwrap(); let ep = NewEpisodeMinimal::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_MINIMAL_INTERCEPTED_2); Ok(()) } #[test] fn test_new_episode_intercepted() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-Intercepted.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let episode = channel.items().iter().nth(14).unwrap(); let ep = NewEpisode::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_INTERCEPTED_1); let episode = channel.items().iter().nth(15).unwrap(); let ep = NewEpisode::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_INTERCEPTED_2); Ok(()) } #[test] fn test_new_episode_minimal_lup() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-LinuxUnplugged.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let episode = channel.items().iter().nth(18).unwrap(); let ep = NewEpisodeMinimal::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_MINIMAL_LUP_1); let episode = channel.items().iter().nth(19).unwrap(); let ep = NewEpisodeMinimal::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_MINIMAL_LUP_2); Ok(()) } #[test] fn test_new_episode_lup() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-LinuxUnplugged.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let episode = channel.items().iter().nth(18).unwrap(); let ep = NewEpisode::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_LUP_1); let episode = channel.items().iter().nth(19).unwrap(); let ep = NewEpisode::new(episode, TEST_SHOW_ID)?; assert_eq!(ep, *EXPECTED_LUP_2); Ok(()) } #[test] fn test_minimal_into_new_episode() -> Result<()> { truncate_db()?; let file = File::open("tests/feeds/2018-01-20-Intercepted.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let item = channel.items().iter().nth(14).unwrap(); let ep = EXPECTED_MINIMAL_INTERCEPTED_1 .clone() .into_new_episode(item); assert_eq!(ep, *EXPECTED_INTERCEPTED_1); let item = channel.items().iter().nth(15).unwrap(); let ep = EXPECTED_MINIMAL_INTERCEPTED_2 .clone() .into_new_episode(item); assert_eq!(ep, *EXPECTED_INTERCEPTED_2); Ok(()) } #[test] fn test_new_episode_insert() -> Result<()> { truncate_db()?; let file = File::open("tests/feeds/2018-01-20-Intercepted.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let episode = channel.items().iter().nth(14).unwrap(); let new_ep = NewEpisode::new(episode, TEST_SHOW_ID)?; new_ep.index()?; let ep = dbqueries::get_episode(new_ep.guid(), new_ep.title(), new_ep.show_id())?; assert_eq!(new_ep, ep); assert_eq!(&new_ep, &*EXPECTED_INTERCEPTED_1); assert_eq!(&*EXPECTED_INTERCEPTED_1, &ep); let episode = channel.items().iter().nth(15).unwrap(); let new_ep = NewEpisode::new(episode, TEST_SHOW_ID)?; new_ep.index()?; let ep = dbqueries::get_episode(new_ep.guid(), new_ep.title(), new_ep.show_id())?; assert_eq!(new_ep, ep); assert_eq!(&new_ep, &*EXPECTED_INTERCEPTED_2); assert_eq!(&*EXPECTED_INTERCEPTED_2, &ep); Ok(()) } #[test] fn test_new_episode_update() -> Result<()> { truncate_db()?; let old = EXPECTED_INTERCEPTED_1.clone().to_episode()?; let updated = &*UPDATED_DURATION_INTERCEPTED_1; updated.update(old.id())?; let new = dbqueries::get_episode(old.guid(), old.title(), old.show_id())?; // Assert that updating does not change the id and show_id assert_ne!(old, new); assert_eq!(old.id(), new.id()); assert_eq!(old.show_id(), new.show_id()); assert_eq!(updated, &new); assert_ne!(updated, &old); Ok(()) } #[test] fn test_new_episode_index() -> Result<()> { truncate_db()?; let expected = &*EXPECTED_INTERCEPTED_1; // First insert assert!(expected.index().is_ok()); // Second identical, This should take the early return path assert!(expected.index().is_ok()); // Get the episode let old = dbqueries::get_episode(expected.guid(), expected.title(), expected.show_id())?; // Assert that NewPodcast is equal to the Indexed one assert_eq!(*expected, old); let updated = &*UPDATED_DURATION_INTERCEPTED_1; // Update the podcast assert!(updated.index().is_ok()); // Get the new Podcast let new = dbqueries::get_episode(expected.guid(), expected.title(), expected.show_id())?; // Assert it's diff from the old one. assert_ne!(new, old); assert_eq!(*updated, new); assert_eq!(new.id(), old.id()); assert_eq!(new.show_id(), old.show_id()); Ok(()) } #[test] fn test_new_episode_to_episode() -> Result<()> { let expected = &*EXPECTED_INTERCEPTED_1; // Assert insert() produces the same result that you would get with to_podcast() truncate_db()?; expected.index()?; let old = dbqueries::get_episode(expected.guid(), expected.title(), expected.show_id())?; let ep = expected.to_episode()?; assert_eq!(old, ep); // Same as above, diff order truncate_db()?; let ep = expected.to_episode()?; // did not make a new insert, updated expected.index()?; assert_eq!(dbqueries::get_episodes()?.len(), 1); let old = dbqueries::get_episode(expected.guid(), expected.title(), expected.show_id())?; assert_eq!(old, ep); Ok(()) } // https://gitlab.gnome.org/World/podcasts/-/issues/216 // new episode is imported, always same title, different guid #[test] fn test_feed_ndr() -> Result<()> { truncate_db()?; let file = File::open("tests/feeds/2024-03-13-ndr.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let episode = channel.items().iter().nth(1).unwrap(); let new_ep = NewEpisode::new(episode, TEST_SHOW_ID)?; new_ep.index()?; let ep = dbqueries::get_episode(new_ep.guid(), new_ep.title(), new_ep.show_id())?; assert_eq!(new_ep, ep); assert_eq!(&new_ep, &*EXPECTED_NDR_1); assert_eq!(&*EXPECTED_NDR_1, &ep); let file = File::open("tests/feeds/2024-03-14-ndr.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let episode = channel.items().iter().nth(1).unwrap(); let new_ep = NewEpisode::new(episode, TEST_SHOW_ID)?; new_ep.index()?; let ep = dbqueries::get_episode(new_ep.guid(), new_ep.title(), new_ep.show_id())?; assert_eq!(new_ep, ep); assert_eq!(&new_ep, &*EXPECTED_NDR_2); assert_eq!(&*EXPECTED_NDR_2, &ep); let all_eps = dbqueries::get_episodes()?; assert_eq!(2, all_eps.len()); // update one of the ep's title and epoch let new_ep = &*EXPECTED_NDR_3; new_ep.index()?; // https://gitlab.gnome.org/World/podcasts/-/issues/151 // Title update let ep = dbqueries::get_episode(new_ep.guid(), new_ep.title(), new_ep.show_id())?; assert_eq!(new_ep, &ep); assert_eq!(new_ep, &*EXPECTED_NDR_3); assert_eq!(&*EXPECTED_NDR_3, &ep); assert_eq!( DateTime::::from_timestamp(2000000000, 0) .unwrap() .naive_utc(), ep.epoch() ); assert_eq!("TITLE_UPDATED", ep.title()); let all_eps = dbqueries::get_episodes()?; assert_eq!(2, all_eps.len()); Ok(()) } } podcasts-25.2/podcasts-data/src/models/new_show.rs000066400000000000000000000417461500126606300222750ustar00rootroot00000000000000// new_show.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use diesel::prelude::*; use crate::errors::DataError; use crate::models::{Index, Insert, Update}; use crate::models::{Show, ShowId, SourceId}; use crate::schema::shows; use crate::database::connection; use crate::dbqueries; use crate::utils::{calculate_hash, u64_to_vec_u8, url_cleaner}; #[cfg(test)] use crate::utils::vec_u8_to_u64; use chrono::{NaiveDateTime, Utc}; #[derive(Insertable, AsChangeset)] #[diesel(table_name = shows)] #[derive(Debug, Clone, Default, Builder)] #[builder(default)] #[builder(derive(Debug))] #[builder(setter(into))] pub(crate) struct NewShow { title: String, link: String, description: String, image_uri: Option, image_uri_hash: Option>, image_cached: Option, source_id: SourceId, } impl Insert<()> for NewShow { type Error = DataError; fn insert(&self) -> Result<(), Self::Error> { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; diesel::insert_into(shows) .values(self) .execute(&mut con) .map(|_| ()) .map_err(From::from) } } impl Update<(), ShowId> for NewShow { type Error = DataError; fn update(&self, show_id: ShowId) -> Result<(), Self::Error> { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; info!("Updating {}", self.title); diesel::update(shows.filter(id.eq(show_id))) .set(self) .execute(&mut con) .map(|_| ()) .map_err(From::from) } } // TODO: Maybe return an Enum Instead. // It would make unti testing better too. impl Index<(), ShowId> for NewShow { type Error = DataError; fn index(&self) -> Result<(), DataError> { let exists = dbqueries::podcast_exists(self.source_id)?; if exists { let other = dbqueries::get_podcast_from_source_id(self.source_id)?; if self != &other { self.update(other.id()) } else { Ok(()) } } else { self.insert() } } } impl PartialEq for NewShow { fn eq(&self, other: &NewShow) -> bool { (self.link() == other.link()) && (self.title() == other.title()) && (self.image_uri() == other.image_uri()) && (self.description() == other.description()) && (self.source_id() == other.source_id()) } } impl PartialEq for NewShow { fn eq(&self, other: &Show) -> bool { (self.link() == other.link()) && (self.title() == other.title()) && (self.image_uri() == other.image_uri()) && (self.description() == other.description()) && (self.source_id() == other.source_id()) } } impl NewShow { /// Parses a `rss::Channel` into a `NewShow` Struct. pub(crate) fn new(chan: &rss::Channel, source_id: SourceId) -> NewShow { let title = chan.title().trim(); let link = url_cleaner(chan.link().trim()); let description = ammonia::Builder::new() // Remove `rel` attributes from `
` tags .link_rel(None) .clean(chan.description().trim()) .to_string(); // Try to get the itunes img first let itunes_img = chan .itunes_ext() .and_then(|s| s.image().map(|url| url.trim())) .map(|s| s.to_owned()); // If itunes is None, try to get the channel.image from the rss spec let image_uri = itunes_img.or_else(|| chan.image().map(|s| s.url().trim().to_owned())); let mut hash: Option> = None; if let Some(i) = &image_uri { hash = Some(u64_to_vec_u8(calculate_hash(i))); } NewShowBuilder::default() .title(title) .description(description) .link(link) .image_uri(image_uri) .image_uri_hash(hash) .image_cached(Utc::now().naive_utc()) .source_id(source_id) .build() .unwrap() } // Look out for when tryinto lands into stable. pub(crate) fn to_podcast(&self) -> Result { self.index()?; dbqueries::get_podcast_from_source_id(self.source_id) } } // Ignore the following geters. They are used in unit tests mainly. impl NewShow { pub(crate) fn source_id(&self) -> SourceId { self.source_id } pub(crate) fn title(&self) -> &str { &self.title } pub(crate) fn link(&self) -> &str { &self.link } pub(crate) fn description(&self) -> &str { &self.description } pub(crate) fn image_uri(&self) -> Option<&str> { self.image_uri.as_deref() } #[cfg(test)] pub fn image_uri_hash(&self) -> Option { if let Some(b) = &self.image_uri_hash { return Some(vec_u8_to_u64(b.clone())); } None } #[cfg(test)] pub(crate) fn image_cached(&self) -> Option { self.image_cached } } #[cfg(test)] mod tests { use super::*; use anyhow::Result; use once_cell::sync::Lazy; use rss::Channel; use crate::database::truncate_db; use crate::models::NewShowBuilder; use std::fs::File; use std::io::BufReader; const TEST_SOURCE_ID: SourceId = SourceId(42); // Pre-built expected NewShow structs. static EXPECTED_INTERCEPTED: Lazy = Lazy::new(|| { let descr = "The people behind The Intercept’s fearless reporting and incisive \ commentary—Jeremy Scahill, Glenn Greenwald, Betsy Reed and \ others—discuss the crucial issues of our time: national security, civil \ liberties, foreign policy, and criminal justice. Plus interviews with \ artists, thinkers, and newsmakers who challenge our preconceptions about \ the world we live in."; NewShowBuilder::default() .title("Intercepted with Jeremy Scahill") .link("https://theintercept.com/podcasts") .description(descr) .image_uri(Some(String::from( "http://static.megaphone.fm/podcasts/d5735a50-d904-11e6-8532-73c7de466ea6/image/\ uploads_2F1484252190700-qhn5krasklbce3dh-a797539282700ea0298a3a26f7e49b0b_\ 2FIntercepted_COVER%2B_281_29.png", ))) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); static EXPECTED_LUP: Lazy = Lazy::new(|| { let descr = "An open show powered by community LINUX Unplugged takes the best \ attributes of open collaboration and focuses them into a weekly \ lifestyle show about Linux."; NewShowBuilder::default() .title("LINUX Unplugged Podcast") .link("http://www.jupiterbroadcasting.com/") .description(descr) .image_uri(Some(String::from( "http://www.jupiterbroadcasting.com/images/LASUN-Badge1400.jpg", ))) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); static EXPECTED_TIPOFF: Lazy = Lazy::new(|| { let desc = "

Welcome to The Tip Off- the podcast where we take you behind the \ scenes of some of the best investigative journalism from recent years. \ Each episode we’ll be digging into an investigative scoop- hearing from \ the journalists behind the work as they tell us about the leads, the \ dead-ends and of course, the tip offs. There’ll be car chases, slammed \ doors, terrorist cells, meetings in dimly lit bars and cafes, wrangling \ with despotic regimes and much more. So if you’re curious about the fun, \ complicated detective work that goes into doing great investigative \ journalism- then this is the podcast for you.

"; NewShowBuilder::default() .title("The Tip Off") .link("http://www.acast.com/thetipoff") .description(desc) .image_uri(Some(String::from( "https://imagecdn.acast.com/image?h=1500&w=1500&source=http%3A%2F%2Fi1.sndcdn.\ com%2Favatars-000317856075-a2coqz-original.jpg", ))) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); static EXPECTED_STARS: Lazy = Lazy::new(|| { let descr = "

The first audio drama from Tor Labs and Gideon Media, Steal the Stars \ is a gripping noir science fiction thriller in 14 episodes: Forbidden \ love, a crashed UFO, an alien body, and an impossible heist unlike any \ ever attempted - scripted by Mac Rogers, the award-winning playwright \ and writer of the multi-million download The Message and LifeAfter.

"; let img = "https://dfkfj8j276wwv.cloudfront.net/images/2c/5f/a0/1a/2c5fa01a-ae78-4a8c-\ b183-7311d2e436c3/b3a4aa57a576bb662191f2a6bc2a436c8c4ae256ecffaff5c4c54fd42e\ 923914941c264d01efb1833234b52c9530e67d28a8cebbe3d11a4bc0fbbdf13ecdf1c3.jpeg"; NewShowBuilder::default() .title("Steal the Stars") .link("http://tor-labs.com/") .description(descr) .image_uri(Some(String::from(img))) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); static EXPECTED_CODE: Lazy = Lazy::new(|| { let descr = "A podcast about humans and technology. Panelists: Coraline Ada Ehmke, \ David Brady, Jessica Kerr, Jay Bobo, Astrid Countee and Sam \ Livingston-Gray. Brought to you by @therubyrep."; NewShowBuilder::default() .title("Greater Than Code") .link("https://www.greaterthancode.com/") .description(descr) .image_uri(Some(String::from( "http://www.greaterthancode.com/wp-content/uploads/2016/10/code1400-4.jpg", ))) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); static EXPECTED_ELLINOFRENEIA: Lazy = Lazy::new(|| { NewShowBuilder::default() .title("Ελληνοφρένεια") .link("https://ellinofreneia.sealabs.net/feed.rss") .description("Ανεπίσημο feed της Ελληνοφρένειας") .image_uri(Some("https://ellinofreneia.sealabs.net/logo.png".into())) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); static UPDATED_DESC_INTERCEPTED: Lazy = Lazy::new(|| { NewShowBuilder::default() .title("Intercepted with Jeremy Scahill") .link("https://theintercept.com/podcasts") .description("New Description") .image_uri(Some(String::from( "http://static.megaphone.fm/podcasts/d5735a50-d904-11e6-8532-73c7de466ea6/image/\ uploads_2F1484252190700-qhn5krasklbce3dh-a797539282700ea0298a3a26f7e49b0b_\ 2FIntercepted_COVER%2B_281_29.png", ))) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); #[test] fn test_new_podcast_intercepted() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-Intercepted.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(*EXPECTED_INTERCEPTED, pd); Ok(()) } #[test] fn test_new_podcast_lup() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-LinuxUnplugged.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(*EXPECTED_LUP, pd); Ok(()) } #[test] fn test_new_podcast_thetipoff() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-TheTipOff.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(*EXPECTED_TIPOFF, pd); Ok(()) } #[test] fn test_new_podcast_steal_the_stars() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-StealTheStars.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(*EXPECTED_STARS, pd); Ok(()) } #[test] fn test_new_podcast_greater_than_code() -> Result<()> { let file = File::open("tests/feeds/2018-01-20-GreaterThanCode.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(*EXPECTED_CODE, pd); Ok(()) } #[test] fn test_new_podcast_ellinofreneia() -> Result<()> { let file = File::open("tests/feeds/2018-03-28-Ellinofreneia.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let pd = NewShow::new(&channel, TEST_SOURCE_ID); assert_eq!(*EXPECTED_ELLINOFRENEIA, pd); Ok(()) } #[test] // This maybe could be a doc test on insert. fn test_new_podcast_insert() -> Result<()> { truncate_db()?; let file = File::open("tests/feeds/2018-01-20-Intercepted.xml")?; let channel = Channel::read_from(BufReader::new(file))?; let npd = NewShow::new(&channel, TEST_SOURCE_ID); npd.insert()?; let pd = dbqueries::get_podcast_from_source_id(TEST_SOURCE_ID)?; assert_eq!(npd, pd); assert_eq!(*EXPECTED_INTERCEPTED, npd); assert_eq!(&*EXPECTED_INTERCEPTED, &pd); Ok(()) } #[test] // TODO: Add more test/checks // Currently there's a test that only checks new description or title. // If you have time and want to help, implement the test for the other fields // too. fn test_new_podcast_update() -> Result<()> { truncate_db()?; let old = EXPECTED_INTERCEPTED.to_podcast()?; let updated = &*UPDATED_DESC_INTERCEPTED; updated.update(old.id())?; let new = dbqueries::get_podcast_from_source_id(TEST_SOURCE_ID)?; assert_ne!(old, new); assert_eq!(old.id(), new.id()); assert_eq!(old.source_id(), new.source_id()); assert_eq!(updated, &new); assert_ne!(updated, &old); Ok(()) } #[test] fn test_new_podcast_index() -> Result<()> { truncate_db()?; // First insert assert!(EXPECTED_INTERCEPTED.index().is_ok()); // Second identical, This should take the early return path assert!(EXPECTED_INTERCEPTED.index().is_ok()); // Get the podcast let old = dbqueries::get_podcast_from_source_id(TEST_SOURCE_ID)?; // Assert that NewShow is equal to the Indexed one assert_eq!(&*EXPECTED_INTERCEPTED, &old); let updated = &*UPDATED_DESC_INTERCEPTED; // Update the podcast assert!(updated.index().is_ok()); // Get the new Show let new = dbqueries::get_podcast_from_source_id(TEST_SOURCE_ID)?; // Assert it's diff from the old one. assert_ne!(new, old); assert_eq!(new.id(), old.id()); assert_eq!(new.source_id(), old.source_id()); Ok(()) } #[test] fn test_to_podcast() -> Result<()> { // Assert insert() produces the same result that you would get with to_podcast() truncate_db()?; EXPECTED_INTERCEPTED.insert()?; let old = dbqueries::get_podcast_from_source_id(TEST_SOURCE_ID)?; let pd = EXPECTED_INTERCEPTED.to_podcast()?; assert_eq!(old, pd); // Same as above, diff order truncate_db()?; let pd = EXPECTED_INTERCEPTED.to_podcast()?; // This should error as a unique constrain violation assert!(EXPECTED_INTERCEPTED.insert().is_err()); let old = dbqueries::get_podcast_from_source_id(TEST_SOURCE_ID)?; assert_eq!(old, pd); Ok(()) } } podcasts-25.2/podcasts-data/src/models/new_source.rs000066400000000000000000000037601500126606300226070ustar00rootroot00000000000000// new_source.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use diesel::prelude::*; use url::Url; use crate::database::connection; use crate::dbqueries; // use models::{Insert, Update}; use crate::errors::DataError; use crate::models::Source; use crate::schema::source; #[derive(Insertable)] #[diesel(table_name = source)] #[derive(Debug, Clone, Default, Builder, PartialEq)] #[builder(default)] #[builder(derive(Debug))] #[builder(setter(into))] pub(crate) struct NewSource { uri: String, last_modified: Option, http_etag: Option, } impl NewSource { pub(crate) fn new(uri: &Url) -> NewSource { NewSource { uri: uri.to_string(), last_modified: None, http_etag: None, } } pub(crate) fn insert_or_ignore(&self) -> Result<(), DataError> { use crate::schema::source::dsl::*; let db = connection(); let mut con = db.get()?; diesel::insert_or_ignore_into(source) .values(self) .execute(&mut con) .map(|_| ()) .map_err(From::from) } // Look out for when tryinto lands into stable. pub(crate) fn to_source(&self) -> Result { self.insert_or_ignore()?; dbqueries::get_source_from_uri(&self.uri) } } podcasts-25.2/podcasts-data/src/models/show.rs000066400000000000000000000362011500126606300214120ustar00rootroot00000000000000// show.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use chrono::{Duration, NaiveDateTime, Utc}; use diesel::query_dsl::filter_dsl::FilterDsl; use diesel::{ExpressionMethods, RunQueryDsl}; use crate::database::connection; use crate::errors::DataError; use crate::make_id_wrapper; use crate::models::{Source, SourceId}; use crate::schema::shows; use crate::utils::{calculate_hash, u64_to_vec_u8, vec_u8_to_u64}; make_id_wrapper!(ShowId); #[derive(Queryable, Identifiable, AsChangeset, Associations, PartialEq, Selectable)] #[diesel(belongs_to(Source, foreign_key = source_id))] #[diesel(treat_none_as_null = true)] #[diesel(table_name = shows)] #[derive(Debug, Clone)] /// Diesel Model of the shows table. pub struct Show { id: ShowId, title: String, link: String, description: String, image_uri: Option, image_uri_hash: Option>, image_cached: NaiveDateTime, source_id: SourceId, } impl Show { /// Get the Feed `id`. pub fn id(&self) -> ShowId { self.id } /// Get the Feed `title`. pub fn title(&self) -> &str { &self.title } /// Get the Feed `link`. /// /// Usually the website/homepage of the content creator. pub fn link(&self) -> &str { &self.link } /// Get the `description`. pub fn description(&self) -> &str { &self.description } /// Get the `image_uri`. /// /// Represents the uri(url usually) that the Feed cover image is located at. pub fn image_uri(&self) -> Option<&str> { self.image_uri.as_deref() } /// Get the `image_uri_hash`. pub fn image_uri_hash(&self) -> Option { if let Some(b) = &self.image_uri_hash { return Some(vec_u8_to_u64(b.clone())); } None } /// Get the `image_cached`. pub fn image_cached(&self) -> &NaiveDateTime { &self.image_cached } /// `Source` table foreign key. pub fn source_id(&self) -> SourceId { self.source_id } } #[derive(Queryable, Debug, Clone, Selectable)] #[diesel(treat_none_as_null = true)] #[diesel(table_name = shows)] /// Diesel Model of the Show cover query. /// Used for fetching information about a Show's cover. pub struct ShowCoverModel { id: ShowId, title: String, image_uri: Option, image_uri_hash: Option>, image_cached: NaiveDateTime, } impl From for ShowCoverModel { fn from(p: Show) -> ShowCoverModel { ShowCoverModel { id: p.id, title: p.title, image_uri: p.image_uri, image_uri_hash: p.image_uri_hash, image_cached: p.image_cached, } } } impl ShowCoverModel { /// Get the Feed `id`. pub fn id(&self) -> ShowId { self.id } /// Get the Feed `title`. pub fn title(&self) -> &str { &self.title } /// Get the `image_uri`. /// /// Represents the uri(url usually) that the Feed cover image is located at. pub fn image_uri(&self) -> Option<&str> { self.image_uri.as_deref() } /// Get the `image_uri_hash`. pub fn image_uri_hash(&self) -> Option { if let Some(b) = &self.image_uri_hash { return Some(vec_u8_to_u64(b.clone())); } None } /// Get the `image_cached`. pub fn image_cached(&self) -> &NaiveDateTime { &self.image_cached } /// Determine whether a cached image is valid. /// /// A cached image is valid from the time of its previous download for the given length of time. /// Otherwise, a cached image is invalidated when the hash of its URI has changed. pub fn is_cached_image_valid(&self, valid: &Duration) -> bool { if Utc::now() .naive_utc() .signed_duration_since(*self.image_cached()) > *valid { return false; } if let Some(new) = &self.image_uri() { if let Some(orig) = self.image_uri_hash() { return calculate_hash(new) == orig; } } false } /// Update the timestamp when the image has been cached. pub(crate) fn update_image_cached(&self) -> Result<(), DataError> { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; diesel::update(shows.filter(id.eq(self.id))) .set(image_cached.eq(Utc::now().naive_utc())) .execute(&mut con) .map(|_| ()) .map_err(From::from) } /// Update the hash of the image's URI. fn update_image_uri_hash(&self) -> Result<(), DataError> { use crate::schema::shows::dsl::*; let db = connection(); let mut con = db.get()?; let mut hash: Option> = None; if let Some(i) = &self.image_uri { hash = Some(u64_to_vec_u8(calculate_hash(i))); } diesel::update(shows.filter(id.eq(self.id))) .set(image_uri_hash.eq(&hash)) .execute(&mut con) .map(|_| ()) .map_err(From::from) } /// Update the image's timestamp and URI hash value. pub fn update_image_cache_values(&self) -> Result<(), DataError> { match self.image_uri_hash() { None => self.update_image_uri_hash()?, Some(hash) => match self.image_uri() { None => self.update_image_uri_hash()?, Some(image_uri) => { if calculate_hash(&image_uri) != hash { self.update_image_uri_hash()?; } } }, } self.update_image_cached() } } #[cfg(test)] mod tests { use super::*; use crate::database::truncate_db; use crate::dbqueries; use crate::models::{Insert, NewShow, NewShowBuilder, Update}; use anyhow::Result; use once_cell::sync::Lazy; use std::{thread, time}; const TEST_SOURCE_ID: SourceId = SourceId(42); const TEST_SHOW_ID: ShowId = ShowId(0); static EXPECTED_INTERCEPTED: Lazy = Lazy::new(|| { let descr = "The people behind The Intercept’s fearless reporting and incisive \ commentary—Jeremy Scahill, Glenn Greenwald, Betsy Reed and \ others—discuss the crucial issues of our time: national security, civil \ liberties, foreign policy, and criminal justice. Plus interviews with \ artists, thinkers, and newsmakers who challenge our preconceptions about \ the world we live in."; let image_uri = "http://static.megaphone.fm/podcasts/d5735a50-d904-11e6-8532-73c7de466ea6/image/\ uploads_2F1484252190700-qhn5krasklbce3dh-a797539282700ea0298a3a26f7e49b0b_\ 2FIntercepted_COVER%2B_281_29.png"; NewShowBuilder::default() .title("Intercepted with Jeremy Scahill") .link("https://theintercept.com/podcasts") .description(descr) .image_uri(String::from(image_uri)) .image_uri_hash(Some(vec![164, 62, 7, 221, 215, 202, 38, 41])) .image_cached(Utc::now().naive_utc()) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); static UPDATED_IMAGE_URI_INTERCEPTED: Lazy = Lazy::new(|| { let image_uri = "https://assets.fireside.fm/file/fireside-images/podcasts/images/f/f31a453c-fa15-491f-8618-3f71f1d565e5/cover.jpg?v=3"; NewShowBuilder::default() .title("Intercepted with Jeremy Scahill") .link("https://theintercept.com/podcasts") .description(EXPECTED_INTERCEPTED.description()) .image_uri(String::from(image_uri)) .image_uri_hash(Some(vec![164, 62, 7, 221, 215, 202, 38, 41])) .image_cached(EXPECTED_INTERCEPTED.image_cached().unwrap()) .source_id(TEST_SOURCE_ID) .build() .unwrap() }); #[test] fn should_update_timestamp_when_update_image_cached_is_called_after_the_timestamp_has_expired() -> Result<()> { truncate_db()?; EXPECTED_INTERCEPTED.insert()?; let show = EXPECTED_INTERCEPTED.to_podcast()?; let show: ShowCoverModel = show.into(); let original_timestamp = show.image_cached(); show.update_image_cached().unwrap(); let show = dbqueries::get_podcast_from_id(show.id())?; let updated_timestamp = show.image_cached(); assert!(original_timestamp < updated_timestamp); // The image's URI and its hash should remain unchanged. assert_eq!( show.image_uri().unwrap(), "http://static.megaphone.fm/podcasts/d5735a50-d904-11e6-8532-73c7de466ea6/image/\ uploads_2F1484252190700-qhn5krasklbce3dh-a797539282700ea0298a3a26f7e49b0b_\ 2FIntercepted_COVER%2B_281_29.png" ); assert_eq!(show.image_uri_hash().unwrap(), 2965280433145069220); Ok(()) } #[test] fn should_update_hash_when_update_image_uri_hash_is_called_when_the_hash_is_invalid() -> Result<()> { truncate_db()?; EXPECTED_INTERCEPTED.insert()?; let original = EXPECTED_INTERCEPTED.to_podcast()?; let original_hash: u64 = 2965280433145069220; let updated = &*UPDATED_IMAGE_URI_INTERCEPTED; updated.update(original.id())?; let show = dbqueries::get_podcast_cover_from_id(original.id())?; let not_yet_updated_hash = updated.image_uri_hash().unwrap(); assert_eq!(not_yet_updated_hash, original_hash); show.update_image_uri_hash().unwrap(); let show = dbqueries::get_podcast_from_id(original.id())?; let updated_hash = show.image_uri_hash().unwrap(); let expected_updated_hash: u64 = 1748982167920802687; assert_eq!(updated_hash, expected_updated_hash); assert_eq!( show.image_uri().unwrap(), "https://assets.fireside.fm/file/fireside-images/podcasts/images/f/f31a453c-fa15-491f-8618-3f71f1d565e5/cover.jpg?v=3" ); Ok(()) } #[test] fn should_update_timestamp_only_when_update_image_cached_values_is_called_after_the_timestamp_has_expired() -> Result<()> { truncate_db()?; EXPECTED_INTERCEPTED.insert()?; let show = EXPECTED_INTERCEPTED.to_podcast()?; let show: ShowCoverModel = show.into(); let original_timestamp = show.image_cached(); show.update_image_cache_values().unwrap(); let show = dbqueries::get_podcast_from_id(show.id())?; let updated_timestamp = show.image_cached(); assert!(original_timestamp < updated_timestamp); assert_eq!( show.image_uri().unwrap(), "http://static.megaphone.fm/podcasts/d5735a50-d904-11e6-8532-73c7de466ea6/image/\ uploads_2F1484252190700-qhn5krasklbce3dh-a797539282700ea0298a3a26f7e49b0b_\ 2FIntercepted_COVER%2B_281_29.png" ); assert_eq!(show.image_uri_hash().unwrap(), 2965280433145069220); Ok(()) } #[test] fn should_update_timestamp_and_hash_when_update_image_cached_values_is_called_when_hash_is_invalid() -> Result<()> { truncate_db()?; EXPECTED_INTERCEPTED.insert()?; let original = EXPECTED_INTERCEPTED.to_podcast()?; let original_timestamp = original.image_cached(); let updated = &*UPDATED_IMAGE_URI_INTERCEPTED; updated.update(original.id())?; let show = dbqueries::get_podcast_cover_from_id(original.id())?; let not_yet_updated_hash = show.image_uri_hash().unwrap(); let original_hash: u64 = 2965280433145069220; assert_eq!(not_yet_updated_hash, original_hash); show.update_image_cache_values().unwrap(); let show = dbqueries::get_podcast_from_id(show.id())?; let updated_timestamp = show.image_cached(); assert!(original_timestamp < updated_timestamp); let updated_hash = show.image_uri_hash().unwrap(); let expected_updated_hash: u64 = 1748982167920802687; assert_eq!(updated_hash, expected_updated_hash); assert_eq!( show.image_uri().unwrap(), "https://assets.fireside.fm/file/fireside-images/podcasts/images/f/f31a453c-fa15-491f-8618-3f71f1d565e5/cover.jpg?v=3" ); Ok(()) } #[test] fn cached_image_should_be_valid_when_uri_and_hash_are_unchanged() -> Result<()> { let image_uri = String::from( "http://www.jupiterbroadcasting.com/wp-content/uploads/2018/01/lup-0232-v.jpg", ); let hash = vec![191, 166, 24, 137, 178, 75, 5, 227]; let cover = ShowCoverModel { id: TEST_SHOW_ID, title: String::from("Linux Unplugged"), image_uri: Some(image_uri), image_uri_hash: Some(hash), image_cached: Utc::now().naive_utc(), }; let valid = Duration::weeks(4); assert!(cover.is_cached_image_valid(&valid)); Ok(()) } #[test] fn a_different_uri_should_invalidate_cached_image() -> Result<()> { // The old image URI used for the hash here is: // http://www.jupiterbroadcasting.com/wp-content/uploads/2018/01/lup-0232-v.jpg let new_image_uri = String::from( "https://assets.fireside.fm/file/fireside-images/podcasts/images/f/f31a453c-fa15-491f-8618-3f71f1d565e5/cover.jpg?v=3", ); let hash = vec![191, 166, 24, 137, 178, 75, 5, 227]; let cover = ShowCoverModel { id: TEST_SHOW_ID, title: String::from("Linux Unplugged"), image_uri: Some(new_image_uri), image_uri_hash: Some(hash), image_cached: Utc::now().naive_utc(), }; let valid = Duration::weeks(4); assert!(!cover.is_cached_image_valid(&valid)); Ok(()) } #[test] fn cached_image_should_be_invalidated_after_valid_duration() -> Result<()> { let image_uri = String::from( "http://www.jupiterbroadcasting.com/wp-content/uploads/2018/01/lup-0232-v.jpg", ); let hash = vec![191, 166, 24, 137, 178, 75, 5, 227]; let cover = ShowCoverModel { id: TEST_SHOW_ID, title: String::from("Linux Unplugged"), image_uri: Some(image_uri), image_uri_hash: Some(hash), image_cached: Utc::now().naive_utc(), }; let valid = Duration::nanoseconds(1); thread::sleep(time::Duration::from_nanos(2)); assert!(!cover.is_cached_image_valid(&valid)); Ok(()) } } podcasts-25.2/podcasts-data/src/models/source.rs000066400000000000000000000272021500126606300217330ustar00rootroot00000000000000// source.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use base64::engine::general_purpose; use base64::prelude::*; use diesel::SaveChangesDsl; use http::StatusCode; use http::header::{ AUTHORIZATION, ETAG, HeaderValue, IF_MODIFIED_SINCE, IF_NONE_MATCH, LAST_MODIFIED, LOCATION, USER_AGENT as USER_AGENT_HEADER, }; use rss::Channel; use std::str::FromStr; use url::Url; use crate::USER_AGENT; use crate::database::connection; use crate::errors::*; use crate::feed::{Feed, FeedBuilder}; use crate::make_id_wrapper; use crate::models::{NewSource, Save}; use crate::schema::source; make_id_wrapper!(SourceId); #[derive(Queryable, Identifiable, AsChangeset, PartialEq, Selectable)] #[diesel(table_name = source)] #[diesel(treat_none_as_null = true)] #[derive(Debug, Clone)] /// Diesel Model of the source table. pub struct Source { id: SourceId, uri: String, last_modified: Option, http_etag: Option, } impl Save for Source { type Error = DataError; /// Helper method to easily save/"sync" current state of self to the /// Database. fn save(&self) -> Result { let db = connection(); let mut con = db.get()?; self.save_changes::(&mut con).map_err(From::from) } } impl Source { /// Get the source `id` column. pub fn id(&self) -> SourceId { self.id } /// Represents the location(usually url) of the Feed xml file. pub fn uri(&self) -> &str { &self.uri } /// Set the `uri` field value. pub fn set_uri(&mut self, uri: String) { self.uri = uri; } /// Represents the Http Last-Modified Header field. /// /// See [RFC 7231](https://tools.ietf.org/html/rfc7231#section-7.2) for more. pub fn last_modified(&self) -> Option<&str> { self.last_modified.as_deref() } /// Set `last_modified` value. pub fn set_last_modified(&mut self, value: Option) { // self.last_modified = value.map(|x| x.to_string()); self.last_modified = value; } /// Represents the Http Etag Header field. /// /// See [RFC 7231](https://tools.ietf.org/html/rfc7231#section-7.2) for more. pub fn http_etag(&self) -> Option<&str> { self.http_etag.as_deref() } /// Set `http_etag` value. pub fn set_http_etag(&mut self, value: Option<&str>) { self.http_etag = value.map(|x| x.to_string()); } /// Extract Etag and LastModifier from res, and update self and the /// corresponding db row. fn update_etag(mut self, res: &reqwest::Response) -> Result { let headers = res.headers(); let etag = headers.get(ETAG).and_then(|h| h.to_str().ok()); let lmod = headers .get(LAST_MODIFIED) .and_then(|h| h.to_str().ok()) .map(From::from); if (self.http_etag() != etag) || (self.last_modified != lmod) { self.set_http_etag(etag); self.set_last_modified(lmod); self = self.save()?; } Ok(self) } /// Clear the `HTTP` `Etag` and `Last-modified` headers. /// This method does not sync the state of self in the database, call /// .save() method explicitly fn clear_etags(&mut self) { debug!("Source etags before clear: {:#?}", &self); self.http_etag = None; self.last_modified = None; } fn make_err(self, context: &str, code: StatusCode) -> DataError { DataError::HttpStatusGeneral { url: self.uri, status_code: code, context: context.into(), } } // TODO match on more stuff // 301: Moved Permanently // 304: Up to date Feed, checked with the Etag // 307: Temporary redirect of the url // 308: Permanent redirect of the url // 401: Unathorized // 403: Forbidden // 408: Timeout // 410: Feed deleted // TODO: Rething this api, fn match_status(mut self, res: reqwest::Response) -> Result { let code = res.status(); if code.is_success() { // If request is successful save the etag self = self.update_etag(&res)? } else { match code.as_u16() { // Save etags if it returns NotModified 304 => self = self.update_etag(&res)?, // Clear the Etag/lmod else _ => { self.clear_etags(); self = self.save()?; } }; }; match code.as_u16() { 304 => { info!("304: Source, {} is up to date", self.uri()); return Err(DataError::FeedNotModified(self)); } 301 | 308 => { info!("Feed was moved permanently."); self = self.update_url(&res)?; return Err(DataError::FeedRedirect(self)); } 302 | 307 => { info!("302/307: Temporary Redirect."); return Err(DataError::FeedRedirect(self)); } 401 => return Err(self.make_err("401: Unauthorized.", code)), 403 => return Err(self.make_err("403: Forbidden.", code)), 404 => return Err(self.make_err("404: Not found.", code)), 408 => return Err(self.make_err("408: Request Timeout.", code)), 410 => return Err(self.make_err("410: Feed was deleted..", code)), _ => info!("HTTP StatusCode: {}", code), }; Ok(res) } fn update_url(mut self, res: &reqwest::Response) -> Result { let code = res.status(); let headers = res.headers(); info!("HTTP StatusCode: {}", code); debug!("Headers {:#?}", headers); if let Some(url) = headers.get(LOCATION) { debug!("Previous Source: {:#?}", &self); self.set_uri(url.to_str()?.into()); self.clear_etags(); self = self.save()?; debug!("Updated Source: {:#?}", &self); info!( "Feed url of Source {}, was updated successfully.", self.uri() ); } Ok(self) } /// Construct a new `Source` with the given `uri` and index it. /// /// This only indexes the `Source` struct, not the Podcast Feed. pub fn from_url(uri: &str) -> Result { let url = Url::parse(uri)?; NewSource::new(&url).to_source() } /// `Feed` constructor. /// /// Fetches the latest xml Feed. /// /// Updates the validator Http Headers. /// /// Consumes `self` and Returns the corresponding `Feed` Object. // Refactor into TryInto once it lands on stable. pub async fn into_feed(self, client: &reqwest::Client) -> Result { let id = self.id(); let resp = self.get_response(client).await?; let chan = response_to_channel(resp).await?; FeedBuilder::default() .channel(chan) .source_id(id) .build() .map_err(|err| DataError::BuilderError(format!("{err}"))) } async fn get_response(self, client: &reqwest::Client) -> Result { let mut source = self; loop { match source.request_constructor(client).await { Ok(response) => return Ok(response), Err(err) => match err { DataError::FeedRedirect(s) => { info!("Following redirect..."); source = s; } e => return Err(e), }, } } } async fn request_constructor( self, client: &reqwest::Client, ) -> Result { let uri = Url::from_str(self.uri())?; let mut req = client.get(uri); if let Ok(url) = Url::parse(self.uri()) { if let Some(password) = url.password() { let mut auth = "Basic ".to_owned(); auth.push_str(&general_purpose::URL_SAFE.encode( //url.username() converts @ symbols to %40 automatically. The "replace" undoes that. format!("{}:{}", url.username().replace("%40", "@"), password), )); req = req.header(AUTHORIZATION, HeaderValue::from_str(&auth).unwrap()); } } // Set the UserAgent cause ppl still seem to check it for some reason... req = req.header(USER_AGENT_HEADER, HeaderValue::from_static(USER_AGENT)); if let Some(etag) = self.http_etag() { req = req.header(IF_NONE_MATCH, HeaderValue::from_str(etag).unwrap()); } if let Some(lmod) = self.last_modified() { req = req.header(IF_MODIFIED_SINCE, HeaderValue::from_str(lmod).unwrap()); } let res = req.send().await?; self.match_status(res) } } async fn response_to_channel(res: reqwest::Response) -> Result { use bytes::buf::Buf; let chunk = res.bytes().await?; // Channel will do it's own decoding of strings // based on what is specified in . // So just pass it the raw byets. Channel::read_from(chunk.reader()).map_err(From::from) } #[cfg(test)] mod tests { use super::*; use anyhow::Result; use crate::database::truncate_db; use crate::dbqueries; use crate::downloader::client_builder; use crate::utils::get_feed; #[test] fn test_into_feed() -> Result<()> { truncate_db()?; let rt = tokio::runtime::Runtime::new()?; let client = client_builder().build()?; let url = "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\ com/InterceptedWithJeremyScahill"; let source = Source::from_url(url)?; let id = source.id(); let feed = source.into_feed(&client); let feed = rt.block_on(feed)?; let expected = get_feed("tests/feeds/2018-01-20-Intercepted.xml", id); assert_eq!(expected, feed); Ok(()) } #[test] fn test_into_non_utf8() -> Result<()> { truncate_db()?; let rt = tokio::runtime::Runtime::new()?; let client = client_builder().build()?; let url = "https://web.archive.org/web/20220205205130if_/https://dinamics.ccma.\ cat/public/podcast/catradio/xml/series-i-cinema.xml"; let source = Source::from_url(url)?; let id = source.id(); let feed = source.into_feed(&client); let feed = rt.block_on(feed)?; let expected = get_feed("tests/feeds/2022-series-i-cinema.xml", id); assert_eq!(expected, feed); feed.index()?; assert_eq!(dbqueries::get_podcasts()?.len(), 1); assert_eq!( dbqueries::get_podcasts()?[0].description(), "Els clàssics, les novetats de la cartellera i les millors \ sèries, tot en un sol podcast." ); Ok(()) } } podcasts-25.2/podcasts-data/src/opml.rs000066400000000000000000000275011500126606300201210ustar00rootroot00000000000000// opml.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later //! FIXME: Docs use crate::dbqueries; use crate::errors::DataError; use crate::models::Source; use xml::{ common::XmlVersion, reader, writer::{EmitterConfig, events::XmlEvent}, }; use std::collections::HashSet; use std::fs; use std::io::{Read, Write}; use std::path::Path; use std::fs::File; // use std::io::BufReader; use anyhow::Result; #[derive(Debug, Clone, PartialEq, Eq, Hash)] // FIXME: Make it a Diesel model /// Represents an `outline` xml element as per the `OPML` [specification][spec] /// not `RSS` related sub-elements are omitted. /// /// [spec]: http://dev.opml.org/spec2.html pub struct Opml { title: String, description: String, url: String, } /// Import feed url's from a `R` into the `Source` table. // TODO: Write test pub fn import_to_db(reader: R) -> Result, reader::Error> { let feeds = extract_sources(reader)? .iter() .map(|opml| Source::from_url(&opml.url)) .filter_map(|s| { if let Err(ref err) = s { let txt = "If you think this might be a bug please consider filling a report over \ at https://gitlab.gnome.org/World/podcasts/issues/new"; error!("Failed to import a Show: {}", err); error!("{}", txt); } s.ok() }) .collect(); Ok(feeds) } /// Open a File from `P`, try to parse the OPML then insert the Feeds in the database and /// return the new `Source`s // TODO: Write test pub fn import_from_file>(path: P) -> Result, DataError> { let content = fs::read(path)?; import_to_db(content.as_slice()).map_err(From::from) } /// Export a file to `P`, taking the feeds from the database and outputting /// them in opml format. pub fn export_from_db>(path: P, export_title: &str) -> Result<()> { let file = File::create(path)?; export_to_file(&file, export_title) } /// Export from `Source`s and `Show`s into `F` in OPML format pub fn export_to_file(file: F, export_title: &str) -> Result<()> { let config = EmitterConfig::new().perform_indent(true); let mut writer = config.create_writer(file); let mut events: Vec> = Vec::new(); // Set up headers let doc = XmlEvent::StartDocument { version: XmlVersion::Version10, encoding: Some("UTF-8"), standalone: Some(false), }; events.push(doc); let opml: XmlEvent<'_> = XmlEvent::start_element("opml") .attr("version", "2.0") .into(); events.push(opml); let head: XmlEvent<'_> = XmlEvent::start_element("head").into(); events.push(head); let title_ev: XmlEvent<'_> = XmlEvent::start_element("title").into(); events.push(title_ev); let title_chars: XmlEvent<'_> = XmlEvent::characters(export_title); events.push(title_chars); // Close & <head> events.push(XmlEvent::end_element().into()); events.push(XmlEvent::end_element().into()); let body: XmlEvent<'_> = XmlEvent::start_element("body").into(); events.push(body); for event in events { writer.write(event)?; } // FIXME: Make this a model of a joined query (http://docs.diesel.rs/diesel/macro.joinable.html) let shows = dbqueries::get_podcasts()?.into_iter().map(|show| { let source = dbqueries::get_source_from_id(show.source_id()).unwrap(); (source, show) }); for (ref source, ref show) in shows { let title = show.title(); let link = show.link(); let xml_url = source.uri(); let s_ev: XmlEvent<'_> = XmlEvent::start_element("outline") .attr("text", title) .attr("title", title) .attr("type", "rss") .attr("xmlUrl", xml_url) .attr("htmlUrl", link) .into(); let end_ev: XmlEvent<'_> = XmlEvent::end_element().into(); writer.write(s_ev)?; writer.write(end_ev)?; } // Close <body> and <opml> let end_bod: XmlEvent<'_> = XmlEvent::end_element().into(); writer.write(end_bod)?; let end_opml: XmlEvent<'_> = XmlEvent::end_element().into(); writer.write(end_opml)?; Ok(()) } /// Extracts the `outline` elements from a reader `R` and returns a `HashSet` of `Opml` structs. pub fn extract_sources<R: Read>(reader: R) -> Result<HashSet<Opml>, reader::Error> { let mut list = HashSet::new(); let parser = reader::EventReader::new(reader); parser .into_iter() .map(|e| match e { Ok(reader::XmlEvent::StartElement { name, attributes, .. }) => { if name.local_name == "outline" { let mut title = String::new(); let mut url = String::new(); let mut description = String::new(); attributes.into_iter().for_each(|attribute| { match attribute.name.local_name.as_str() { "title" => title = attribute.value, "xmlUrl" => url = attribute.value, "description" => description = attribute.value, _ => {} } }); let feed = Opml { title, description, url, }; list.insert(feed); } Ok(()) } Err(err) => Err(err), _ => Ok(()), }) .collect::<Result<Vec<_>, reader::Error>>()?; Ok(list) } #[cfg(test)] mod tests { use super::*; use anyhow::Result; use chrono::Local; use crate::database::{TEMPDIR, truncate_db}; use crate::utils::get_feed; const URLS: &[(&str, &str)] = { &[ ( "tests/feeds/2018-01-20-Intercepted.xml", "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\ com/InterceptedWithJeremyScahill", ), ( "tests/feeds/2018-01-20-LinuxUnplugged.xml", "https://web.archive.org/web/20180120110314if_/https://feeds.feedburner.\ com/linuxunplugged", ), ( "tests/feeds/2018-01-20-TheTipOff.xml", "https://web.archive.org/web/20180120110727if_/https://rss.acast.com/thetipoff", ), ( "tests/feeds/2018-01-20-StealTheStars.xml", "https://web.archive.org/web/20180120104957if_/https://rss.art19.\ com/steal-the-stars", ), ( "tests/feeds/2018-01-20-GreaterThanCode.xml", "https://web.archive.org/web/20180120104741if_/https://www.greaterthancode.\ com/feed/podcast", ), ( "tests/feeds/2019-01-27-ACC.xml", "https://web.archive.org/web/20190127005213if_/https://anticapitalistchronicles.libsyn.com/rss", ), ] }; #[test] fn test_extract() -> Result<()> { let int_title = String::from("Intercepted with Jeremy Scahill"); let int_url = String::from("https://feeds.feedburner.com/InterceptedWithJeremyScahill"); let int_desc = String::from( "The people behind The Intercept’s fearless reporting and incisive \ commentary—Jeremy Scahill, Glenn Greenwald, Betsy Reed and others—discuss the \ crucial issues of our time: national security, civil liberties, foreign policy, \ and criminal justice. Plus interviews with artists, thinkers, and newsmakers \ who challenge our preconceptions about the world we live in.", ); let dec_title = String::from("Deconstructed with Mehdi Hasan"); let dec_url = String::from("https://rss.prod.firstlook.media/deconstructed/podcast.rss"); let dec_desc = String::from( "Journalist Mehdi Hasan is known around the world for his televised takedowns of \ presidents and prime ministers. In this new podcast from The Intercept, Mehdi \ unpacks a game-changing news event of the week while challenging the conventional \ wisdom. As a Brit, a Muslim and an immigrant based in Donald Trump's Washington \ D.C., Mehdi gives a refreshingly provocative perspective on the ups and downs of \ American—and global—politics.", ); let sample1 = format!( "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \ <opml version=\"2.0\"> \ <head> \ <title>Test OPML File \ {} \ http://www.opml.org/spec2 \ \ \ \ \ \ ", Local::now().format("%a, %d %b %Y %T %Z"), int_title, int_desc, int_url, dec_title, dec_desc, dec_url, ); let map = hashset![ Opml { title: int_title, description: int_desc, url: int_url }, Opml { title: dec_title, description: dec_desc, url: dec_url }, ]; assert_eq!(extract_sources(sample1.as_bytes())?, map); Ok(()) } #[test] fn text_export() -> Result<()> { truncate_db()?; URLS.iter().for_each(|&(path, url)| { // Create and insert a Source into db let s = Source::from_url(url).unwrap(); let feed = get_feed(path, s.id()); feed.index().unwrap(); }); let mut map: HashSet = HashSet::new(); let shows = dbqueries::get_podcasts()?.into_iter().map(|show| { let source = dbqueries::get_source_from_id(show.source_id()).unwrap(); (source, show) }); for (ref source, ref show) in shows { let title = show.title().to_string(); // description is an optional field that we don't export let description = String::new(); let url = source.uri().to_string(); map.insert(Opml { title, description, url, }); } let opml_path = TEMPDIR.path().join("podcasts.opml"); export_from_db(opml_path.as_path(), "GNOME Podcasts Subscriptions")?; let opml_file = File::open(opml_path.as_path())?; assert_eq!(extract_sources(&opml_file)?, map); // extract_sources drains the reader its passed let mut opml_file = File::open(opml_path.as_path())?; let mut opml_str = String::new(); opml_file.read_to_string(&mut opml_str)?; assert_eq!(opml_str, include_str!("../tests/export_test.opml")); Ok(()) } } podcasts-25.2/podcasts-data/src/parser.rs000066400000000000000000000065261500126606300204520ustar00rootroot00000000000000// parser.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use rss::extension::itunes::ITunesItemExtension; /// Parses an Item Itunes extension and returns it's duration value in seconds. // FIXME: Rafactor #[allow(non_snake_case)] pub(crate) fn parse_itunes_duration(item: Option<&ITunesItemExtension>) -> Option { let duration = item.map(|s| s.duration())??; // FOR SOME FUCKING REASON, IN THE APPLE EXTENSION SPEC // THE DURATION CAN BE EITHER AN INT OF SECONDS OR // A STRING OF THE FOLLOWING FORMATS: // HH:MM:SS, H:MM:SS, MM:SS, M:SS // LIKE WHO THE FUCK THOUGH THAT WOULD BE A GOOD IDEA. if let Ok(NO_FUCKING_LOGIC) = duration.parse::() { return Some(NO_FUCKING_LOGIC); }; let mut seconds = 0; let fk_apple = duration.split(':').collect::>(); if fk_apple.len() == 3 { seconds += fk_apple[0].parse::().unwrap_or(0) * 3600; seconds += fk_apple[1].parse::().unwrap_or(0) * 60; seconds += fk_apple[2].parse::().unwrap_or(0); } else if fk_apple.len() == 2 { seconds += fk_apple[0].parse::().unwrap_or(0) * 60; seconds += fk_apple[1].parse::().unwrap_or(0); } Some(seconds) } #[cfg(test)] mod tests { use rss::extension::itunes::ITunesItemExtensionBuilder; use super::*; #[test] fn test_itunes_duration() { // Input is a String let extension = ITunesItemExtensionBuilder::default() .duration(Some("3370".into())) .build(); let item = Some(&extension); assert_eq!(parse_itunes_duration(item), Some(3370)); // Input is a String let extension = ITunesItemExtensionBuilder::default() .duration(Some("6:10".into())) .build(); let item = Some(&extension); assert_eq!(parse_itunes_duration(item), Some(370)); // Input is a String let extension = ITunesItemExtensionBuilder::default() .duration(Some("56:10".into())) .build(); let item = Some(&extension); assert_eq!(parse_itunes_duration(item), Some(3370)); // Input is a String let extension = ITunesItemExtensionBuilder::default() .duration(Some("1:56:10".into())) .build(); let item = Some(&extension); assert_eq!(parse_itunes_duration(item), Some(6970)); // Input is a String let extension = ITunesItemExtensionBuilder::default() .duration(Some("01:56:10".into())) .build(); let item = Some(&extension); assert_eq!(parse_itunes_duration(item), Some(6970)); } } podcasts-25.2/podcasts-data/src/pipeline.rs000066400000000000000000000075721500126606300207650ustar00rootroot00000000000000// pipeline.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later // FIXME: //! Docs. use crate::Source; use crate::downloader::client_builder; use crate::errors::DataError; /// The pipline to be run for indexing and updating a Podcast feed that originates from /// `Source.uri`. /// /// Messy temp diagram: /// Source -> GET Request -> Update Etags -> Check Status -> Parse `xml/Rss` -> /// Convert `rss::Channel` into `Feed` -> Index Podcast -> Index Episodes. pub async fn pipeline(sources: S) -> Result<(), reqwest::Error> where S: IntoIterator, { let client = client_builder().build()?; let handles: Vec<_> = sources .into_iter() .map(|source| async { let uri = source.uri().to_string(); match source.into_feed(&client).await { Ok(feed) => match feed.index() { Ok(_) => (), Err(err) => error!( "Error while indexing content feed into the database: {} - {}", uri, err ), }, // Avoid spamming the stderr when it's not an actual error Err(DataError::FeedNotModified(_)) => (), Err(err) => error!( "Error while fetching the latest xml feed: {} - {}", uri, err ), } }) .collect(); futures_util::future::join_all(handles).await; Ok(()) } #[cfg(test)] mod tests { use super::*; use crate::Source; use crate::database::truncate_db; use crate::dbqueries; // (path, url) tuples. const URLS: &[&str] = &[ "https://web.archive.org/web/20180120083840if_/https://feeds.feedburner.\ com/InterceptedWithJeremyScahill", "https://web.archive.org/web/20180120110314if_/https://feeds.feedburner.com/linuxunplugged", "https://web.archive.org/web/20180120110727if_/https://rss.acast.com/thetipoff", "https://web.archive.org/web/20180120104957if_/https://rss.art19.com/steal-the-stars", "https://web.archive.org/web/20180120104741if_/https://www.greaterthancode.\ com/feed/podcast", ]; #[test] /// Insert feeds and update/index them. fn test_pipeline() -> Result<(), DataError> { truncate_db()?; let bad_url = "https://gitlab.gnome.org/World/podcasts.atom"; // if a stream returns error/None it stops // bad we want to parse all feeds regardless if one fails Source::from_url(bad_url)?; URLS.iter().for_each(|url| { // Index the urls into the source table. Source::from_url(url).unwrap(); }); let sources = dbqueries::get_sources()?; let rt = tokio::runtime::Runtime::new()?; rt.block_on(pipeline(sources))?; let sources = dbqueries::get_sources()?; // Run again to cover Unique constrains errors. rt.block_on(pipeline(sources))?; // Assert the index rows equal the controlled results assert_eq!(dbqueries::get_sources()?.len(), 6); assert_eq!(dbqueries::get_podcasts()?.len(), 5); assert_eq!(dbqueries::get_episodes()?.len(), 354); Ok(()) } } podcasts-25.2/podcasts-data/src/schema.patch000066400000000000000000000014011500126606300210540ustar00rootroot00000000000000diff --git a/podcasts-data/src/schema.rs b/podcasts-data/src/schema.rs index 03cbed0..88f1622 100644 --- a/podcasts-data/src/schema.rs +++ b/podcasts-data/src/schema.rs @@ -1,8 +1,11 @@ +#![allow(warnings)] + table! { episodes (title, show_id) { + rowid -> Integer, title -> Text, uri -> Nullable, local_uri -> Nullable, description -> Nullable, epoch -> Integer, length -> Nullable, @@ -30,11 +33,7 @@ table! { uri -> Text, last_modified -> Nullable, http_etag -> Nullable, } } -allow_tables_to_appear_in_same_query!( - episodes, - shows, - source, -); +allow_tables_to_appear_in_same_query!(episodes, shows, source); podcasts-25.2/podcasts-data/src/schema.rs000066400000000000000000000021351500126606300204060ustar00rootroot00000000000000#![allow(warnings)] table! { episodes (id) { id -> Integer, title -> Text, uri -> Nullable, local_uri -> Nullable, description -> Nullable, image_uri -> Nullable, epoch -> Timestamp, length -> Nullable, duration -> Nullable, guid -> Nullable, played -> Nullable, play_position -> Integer, show_id -> Integer, } } table! { shows (id) { id -> Integer, title -> Text, link -> Text, description -> Text, image_uri -> Nullable, image_uri_hash -> Nullable, image_cached -> Timestamp, source_id -> Integer, } } table! { source (id) { id -> Integer, uri -> Text, last_modified -> Nullable, http_etag -> Nullable, } } table! { discovery_settings (platform_id) { platform_id -> Text, enabled -> Bool, } } allow_tables_to_appear_in_same_query!(episodes, shows, source, discovery_settings); podcasts-25.2/podcasts-data/src/utils.rs000066400000000000000000000400201500126606300203010ustar00rootroot00000000000000// utils.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later //! Helper utilities for accomplishing various tasks. use chrono::prelude::*; use url::{Position, Url}; use crate::dbqueries; use crate::errors::DownloadError; use crate::models::{EpisodeCleanerModel, Save, Show}; use crate::xdg_dirs::{DL_DIR, PODCASTS_CACHE}; use glob::glob; use std::fs; use std::path::Path; use std::path::PathBuf; /// Convert a `u64` to a `Vec`. /// /// This function is used to convert hash values into a format suitable for the database, i.e. `Vec`. /// The resulting vector will always have exactly 8 values. /// The individual bytes are extracted from the given `u64`, which is parsed as little-endian. pub fn u64_to_vec_u8(u: u64) -> Vec { let bytes: Vec = u.to_le_bytes().to_vec(); debug_assert_eq!(bytes.len(), 8); bytes } /// Convert a `Vec` of bytes to a `u64`. /// /// These values together should represent a `u64` value in little-endian byte order. /// /// # Panics /// /// The given vector must have exactly 8 elements otherwise it will panic. /// pub fn vec_u8_to_u64(v: Vec) -> u64 { assert_eq!(v.len(), 8); u64::from_le_bytes(v[..].try_into().unwrap()) } /// Hash a given value. pub fn calculate_hash(t: &T) -> u64 { let mut s = DefaultHasher::new(); t.hash(&mut s); s.finish() } /// Scan downloaded `episode` entries that might have broken `local_uri`s and /// set them to `None`. fn download_checker() -> Result<(), DownloadError> { let episodes = dbqueries::get_downloaded_episodes()?; episodes .into_iter() .filter_map(|ep| { if !Path::new(ep.local_uri()?).exists() { return Some(ep); } None }) .for_each(update_download_status); Ok(()) } fn update_download_status(mut ep: EpisodeCleanerModel) { ep.set_local_uri(None); if let Err(err) = ep.save() { error!("{}", err); error!("Error while trying to update episode: {:#?}", ep); } } /// Delete watched `episodes` that have exceeded their lifetime after played. fn played_cleaner(cleanup_date: DateTime) -> Result<(), DownloadError> { let episodes = dbqueries::get_played_cleaner_episodes()?; episodes .into_iter() .filter(|ep| ep.local_uri().is_some() && ep.played().is_some()) .for_each(|ep| clean_played(cleanup_date, ep)); Ok(()) } fn clean_played(now_utc: DateTime, mut ep: EpisodeCleanerModel) { let limit = ep.played().unwrap(); if now_utc > limit.and_utc() { delete_local_content(&mut ep) .map(|_| info!("Episode {:?} was deleted successfully.", ep.local_uri())) .map_err(|err| error!("Error: {}", err)) .map_err(|_| error!("Failed to delete file: {:?}", ep.local_uri())) .ok(); } } /// Check `ep.local_uri` field and delete the file it points to. pub fn delete_local_content(ep: &mut EpisodeCleanerModel) -> Result<(), DownloadError> { if ep.local_uri().is_some() { let uri = ep.local_uri().unwrap().to_owned(); if Path::new(&uri).exists() { let res = fs::remove_file(&uri); if res.is_ok() { ep.set_local_uri(None); ep.save()?; } else { error!("Error while trying to delete file: {}", uri); error!("{}", res.unwrap_err()); }; } } else { error!( "Something went wrong evaluating the following path: {:?}", ep.local_uri(), ); } Ok(()) } /// Deletes covers that were last modified before the last `cleanup_date`. fn cover_cleaner(cleanup_date: DateTime) -> Result<(), DownloadError> { let root_cover_dir = PODCASTS_CACHE .to_str() .ok_or(DownloadError::InvalidCachedImageLocation)?; if let Ok(mut paths) = glob(&format!("{}/*/[0-9]*", root_cover_dir)) { while let Some(path) = paths.next().and_then(|x| x.ok()) { if let Err(err) = clean_cover_file(&path, &cleanup_date) { error!("Could not cleanup cover image: {}", err); } } } Ok(()) } fn clean_cover_file(path: &PathBuf, cleanup_date: &DateTime) -> Result<(), DownloadError> { let metadata = std::fs::metadata(path)?; let mdate: DateTime = metadata.modified().map(DateTime::from)?; if &mdate < cleanup_date { if let Err(err) = std::fs::remove_file(path) { error!("Failed to std::remove cover image: {}", err); } } Ok(()) } /// Database cleaning tasks. /// /// * `cleanup_date` is the date when the last cleanup was run. /// /// Runs a download checker which looks for `Episode.local_uri` entries that /// doesn't exist and sets them to None /// /// Runs a cleaner for played Episode's that are past the lifetime limit and /// scheduled for removal. /// /// Runs a cleaner for downloaded Episode covers that have been downloaded /// before the last cleanup and will likely not be viewed again. pub fn checkup(cleanup_date: DateTime) -> Result<(), DownloadError> { info!("Running database checks."); download_checker()?; played_cleaner(cleanup_date)?; cover_cleaner(cleanup_date)?; info!("Checks completed."); Ok(()) } /// Remove fragment identifiers and query pairs from a URL /// If url parsing fails, return's a trimmed version of the original input. pub fn url_cleaner(s: &str) -> String { // Copied from the cookbook. // https://rust-lang-nursery.github.io/rust-cookbook/net.html // #remove-fragment-identifiers-and-query-pairs-from-a-url match Url::parse(s) { Ok(parsed) => parsed[..Position::AfterQuery].to_owned(), _ => s.trim().to_owned(), } } /// Returns the URI of a Show' Download directory given it's title. pub fn get_download_dir(pd_title: &str) -> Result { // It might be better to make it a hash of the title or the Show id let mut dir = DL_DIR.clone(); dir.push(pd_title); // Create the dir fs::DirBuilder::new().recursive(true).create(&dir)?; let dir_str = dir.to_str().ok_or(DownloadError::InvalidCacheLocation)?; Ok(dir_str.to_owned()) } /// Returns the URI of a Show's cover directory given it's title. pub fn get_cover_dir(pd_title: &str) -> Result { let dir = get_cover_dir_path(pd_title); // Create the dir fs::DirBuilder::new().recursive(true).create(&dir)?; let dir_str = dir .to_str() .ok_or(DownloadError::InvalidCachedImageLocation)?; Ok(dir_str.to_owned()) } /// Returns cover dir for a show, does not create it. pub fn get_cover_dir_path(pd_title: &str) -> PathBuf { // It might be better to make it a hash of the title or the Show id let mut dir = PODCASTS_CACHE.clone(); dir.push(pd_title); dir } /// Removes all the entries associated with the given show from the database, /// and deletes all of the downloaded content. // TODO: Write Tests pub fn delete_show(pd: &Show) -> Result<(), DownloadError> { dbqueries::remove_feed(pd)?; info!("{} was removed successfully.", pd.title()); let download_dir = get_download_dir(pd.title())?; fs::remove_dir_all(&download_dir)?; info!( "All the episodes at, {} was removed successfully", &download_dir ); let cover_dir = get_cover_dir(pd.title())?; fs::remove_dir_all(&cover_dir)?; info!("All the Covers at, {} was removed successfully", &cover_dir); Ok(()) } #[cfg(test)] use crate::Feed; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; #[cfg(test)] /// Helper function that open a local file, parse the rss::Channel and gives back a Feed object. /// Alternative Feed constructor to be used for tests. pub fn get_feed(file_path: &str, id: crate::SourceId) -> Feed { use crate::feed::FeedBuilder; use rss::Channel; use std::io::BufReader; // open the xml file let feed = fs::File::open(file_path).unwrap(); // parse it into a channel let chan = Channel::read_from(BufReader::new(feed)).unwrap(); FeedBuilder::default() .channel(chan) .source_id(id) .build() .unwrap() } #[cfg(test)] mod tests { use super::*; use anyhow::Result; use chrono::Duration; use std::fs::File; use std::io::Write; use tempfile::TempDir; use crate::ShowId; use crate::database::truncate_db; use crate::models::NewEpisodeBuilder; fn helper_db() -> Result { // Clean the db truncate_db()?; // Setup tmp file stuff let tmp_dir = TempDir::with_prefix("podcasts_test")?; let valid_path = tmp_dir.path().join("virtual_dl.mp3"); let bad_path = tmp_dir.path().join("invalid_thing.mp3"); let mut tmp_file = File::create(&valid_path)?; writeln!(tmp_file, "Foooo")?; // Setup episodes let n1 = NewEpisodeBuilder::default() .title("foo_bar".to_string()) .show_id(ShowId(0)) .build() .unwrap() .to_episode()?; let n2 = NewEpisodeBuilder::default() .title("bar_baz".to_string()) .show_id(ShowId(1)) .build() .unwrap() .to_episode()?; let mut ep1 = dbqueries::get_episode_cleaner_from_title(n1.title(), n1.show_id())?; let mut ep2 = dbqueries::get_episode_cleaner_from_title(n2.title(), n2.show_id())?; ep1.set_local_uri(Some(valid_path.to_str().unwrap())); ep2.set_local_uri(Some(bad_path.to_str().unwrap())); ep1.save()?; ep2.save()?; Ok(tmp_dir) } #[test] fn test_download_checker() -> Result<()> { let tmp_dir = helper_db()?; download_checker()?; let episodes = dbqueries::get_downloaded_episodes()?; let valid_path = tmp_dir.path().join("virtual_dl.mp3"); assert_eq!(episodes.len(), 1); assert_eq!( Some(valid_path.to_str().unwrap()), episodes.first().unwrap().local_uri() ); let _tmp_dir = helper_db()?; download_checker()?; let episode = dbqueries::get_episode_cleaner_from_title("bar_baz", ShowId(1))?; assert!(episode.local_uri().is_none()); Ok(()) } #[test] fn test_download_cleaner() -> Result<()> { let _tmp_dir = helper_db()?; let mut episode: EpisodeCleanerModel = dbqueries::get_episode_cleaner_from_title("foo_bar", ShowId(0))?; let valid_path = episode.local_uri().unwrap().to_owned(); delete_local_content(&mut episode)?; assert!(!Path::new(&valid_path).exists()); Ok(()) } #[test] fn test_played_cleaner_expired() -> Result<()> { let _tmp_dir = helper_db()?; let mut episode = dbqueries::get_episode_cleaner_from_title("foo_bar", ShowId(0))?; let cleanup_date = Utc::now() - Duration::seconds(1000); let epoch = DateTime::::from_timestamp(cleanup_date.timestamp() - 1, 0); episode.set_played(Some(epoch.unwrap().naive_utc())); episode.save()?; let valid_path = episode.local_uri().unwrap().to_owned(); // This should delete the file played_cleaner(cleanup_date)?; assert!(!Path::new(&valid_path).exists()); Ok(()) } #[test] fn test_played_cleaner_none() -> Result<()> { let _tmp_dir = helper_db()?; let mut episode = dbqueries::get_episode_cleaner_from_title("foo_bar", ShowId(0))?; let cleanup_date = Utc::now() - Duration::seconds(1000); let epoch = DateTime::::from_timestamp(cleanup_date.timestamp() + 1, 0); episode.set_played(Some(epoch.unwrap().naive_utc())); episode.save()?; let valid_path = episode.local_uri().unwrap().to_owned(); // This should not delete the file played_cleaner(cleanup_date)?; assert!(Path::new(&valid_path).exists()); Ok(()) } #[test] fn test_url_cleaner() -> Result<()> { let good_url = "http://traffic.megaphone.fm/FL8608731318.mp3?updated=1484685184"; let bad_url = "http://traffic.megaphone.fm/FL8608731318.mp3?updated=1484685184#foobar"; assert_eq!(url_cleaner(bad_url), good_url); assert_eq!(url_cleaner(good_url), good_url); assert_eq!(url_cleaner(&format!(" {}\t\n", bad_url)), good_url); Ok(()) } #[test] // This test needs access to local system so we ignore it by default. #[ignore] fn test_get_dl_dir() -> Result<()> { let foo_ = format!("{}/{}", DL_DIR.to_str().unwrap(), "foo"); assert_eq!(get_download_dir("foo")?, foo_); let _ = fs::remove_dir_all(foo_); Ok(()) } #[test] fn hash_should_be_the_same_given_the_same_input() -> Result<()> { let image_uri = "http://www.jupiterbroadcasting.com/wp-content/uploads/2018/01/lup-0232-v.jpg"; let first_hash = calculate_hash(&image_uri); let second_hash = calculate_hash(&image_uri); assert_eq!(first_hash, second_hash); Ok(()) } #[test] fn hash_should_be_different_for_different_inputs() -> Result<()> { let old_image_uri = "http://www.jupiterbroadcasting.com/wp-content/uploads/2018/01/lup-0232-v.jpg"; let new_image_uri = "https://assets.fireside.fm/file/fireside-images/podcasts/images/f/f31a453c-fa15-491f-8618-3f71f1d565e5/cover.jpg?v=3"; let old_hash = calculate_hash(&old_image_uri); let new_hash = calculate_hash(&new_image_uri); assert_ne!(old_hash, new_hash); Ok(()) } #[test] fn hash_should_be_different_for_similar_inputs() -> Result<()> { let image_uri_v2 = "https://assets.fireside.fm/file/fireside-images/podcasts/images/f/f31a453c-fa15-491f-8618-3f71f1d565e5/cover.jpg?v=2"; let image_uri_v3 = "https://assets.fireside.fm/file/fireside-images/podcasts/images/f/f31a453c-fa15-491f-8618-3f71f1d565e5/cover.jpg?v=3"; let v2_hash = calculate_hash(&image_uri_v2); let v3_hash = calculate_hash(&image_uri_v3); assert_ne!(v2_hash, v3_hash); Ok(()) } #[test] fn u64_to_vec_u8_should_convert() -> Result<()> { assert_eq!( u64_to_vec_u8(16358564451669550783), vec![191, 166, 24, 137, 178, 75, 5, 227] ); Ok(()) } #[test] fn u64_to_vec_u8_should_produce_a_vector_of_exactly_8_elements() -> Result<()> { assert_eq!(u64_to_vec_u8(u64::MAX).len(), 8); Ok(()) } #[test] fn vec_u8_to_u64_should_convert() -> Result<()> { assert_eq!( vec_u8_to_u64(vec![0, 1, 2, 3, 4, 5, 6, 7]), 506097522914230528 ); Ok(()) } #[test] #[should_panic(expected = "8")] fn vec_u8_to_u64_should_panic_given_an_empty_vector() { vec_u8_to_u64(vec![]); } #[test] #[should_panic(expected = "8")] fn vec_u8_to_u64_should_panic_given_a_vector_with_1_element() { vec_u8_to_u64(vec![1]); } #[test] #[should_panic(expected = "8")] fn vec_u8_to_u64_should_panic_given_a_vector_with_7_elements() { vec_u8_to_u64(vec![10; 7]); } #[test] #[should_panic(expected = "8")] fn vec_u8_to_u64_should_panic_given_a_vector_with_9_elements() { vec_u8_to_u64(vec![12; 9]); } #[test] fn vec_u8_to_u64_should_be_the_inverse_of_u64_to_vec_u8() -> Result<()> { assert_eq!(10, vec_u8_to_u64(u64_to_vec_u8(10))); Ok(()) } } podcasts-25.2/podcasts-data/tests/000077500000000000000000000000001500126606300171525ustar00rootroot00000000000000podcasts-25.2/podcasts-data/tests/export_test.opml000066400000000000000000000030361500126606300224250ustar00rootroot00000000000000 GNOME Podcasts Subscriptions podcasts-25.2/podcasts-data/tests/fyyd/000077500000000000000000000000001500126606300201255ustar00rootroot00000000000000podcasts-25.2/podcasts-data/tests/fyyd/search_1.json000066400000000000000000000147551500126606300225210ustar00rootroot00000000000000{ "status": 1, "msg": "ok", "meta": { "paging": { "count": 10, "page": 0, "first_page": 0, "last_page": 0, "next_page": null, "prev_page": null }, "API_INFO": { "API_VERSION": "0.2" }, "SERVER": "5.75.151.179", "duration": 0 }, "data": [ { "title": "The Deprogram", "id": 77332, "xmlURL": "https:\/\/feeds.buzzsprout.com\/1890340.rss", "htmlURL": "https:\/\/art19.com\/shows\/the-deprogram", "imgURL": "https:\/\/storage.buzzsprout.com\/variants\/97mqytse7beqmzez24paezscgnp3\/6861a7550229613e3387373f20ad829ba4bc5767dd8eb92e70a0abe304d4e657.jpeg", "status": 200, "status_since": "2023-10-20 01:23:30", "slug": "the-deprogram", "layoutImageURL": "https:\/\/img-1.fyyd.de\/pd\/layout\/7733270e232241983f50e3e88665b72cfa4f5.jpg", "thumbImageURL": "https:\/\/img-1.fyyd.de\/pd\/thumbs\/7733270e232241983f50e3e88665b72cfa4f5.png", "smallImageURL": "https:\/\/img-1.fyyd.de\/pd\/small\/7733270e232241983f50e3e88665b72cfa4f5.jpg", "microImageURL": "https:\/\/img-1.fyyd.de\/pd\/micro\/7733270e232241983f50e3e88665b72cfa4f5.png", "language": "en", "generator": "ART19", "categories": [ 119 ], "lastpub": "2024-03-08T13:00:00+01:00", "rank": 54, "url_fyyd": "https:\/\/fyyd.de\/podcast\/the-deprogram\/0", "description": " What do an Iraqi, a Balkan Slav and a Texan have in common? A burning hatred for the system. Oh, and a podcast. Say no to eating out of the trash can of ideology. Join us on a journey exploring and critically assessing the perceived \u201cnormalcy\u201d of late-stage capitalism. The only truly international, global, and anti-capitalist podcast you\u2019ll find. SUPPORT US on PATREON: https:\/\/www.patreon.com\/TheDeprogram FOLLOW US on Twitter @TheDeprogramPod ", "subtitle": " What do an Iraqi, a Balkan Slav and a Texan have in common? A burning hatred for the system. Oh, and a podcast. Say no to eating out of the trash can of ideology. Join us on a journey exploring and critically assessing the perceived \u201cnormalcy\u201d of late-stage capitalism. The only truly international, global, and anti-capitalist podcast you\u2019ll find. SUPPORT US on PATREON: https:\/\/www.patreon.com\/TheDeprogram FOLLOW US on Twitter @TheDeprogramPod ", "tcolor": "#fff", "color": "#0d0e12", "episode_count": 242, "iflags": null, "paymentURL": "https:\/\/www.patreon.com\/TheDeprogram", "author": "JT, Hakim, and Yugopnik", "seasons": { "1": [ { "episode_id": 11413310, "episode_num": 101 }, { "episode_id": 11422939, "episode_num": 102 }, { "episode_id": 11440961, "episode_num": 102 }, { "episode_id": 11451130, "episode_num": 102 }, { "episode_id": 11471208, "episode_num": 103 }, { "episode_id": 11528517, "episode_num": 106 }, { "episode_id": 11537046, "episode_num": 106 }, { "episode_id": 11587387, "episode_num": 107 }, { "episode_id": 11617445, "episode_num": 108 }, { "episode_id": 11644139, "episode_num": 109 }, { "episode_id": 11671171, "episode_num": 110 }, { "episode_id": 11700438, "episode_num": 111 }, { "episode_id": 11719521, "episode_num": 112 }, { "episode_id": 11759510, "episode_num": 113 }, { "episode_id": 11789547, "episode_num": 114 }, { "episode_id": 11808851, "episode_num": 115 }, { "episode_id": 11826393, "episode_num": 115 }, { "episode_id": 11855821, "episode_num": 116 }, { "episode_id": 11897447, "episode_num": 117 }, { "episode_id": 11929464, "episode_num": 118 }, { "episode_id": 11944240, "episode_num": 118 }, { "episode_id": 11957600, "episode_num": 119 }, { "episode_id": 11989992, "episode_num": 120 }, { "episode_id": 12018071, "episode_num": 121 } ] }, "stats": { "medianduration": 1142, "medianduration_string": "19m", "episodecount": 242, "pubinterval_seconds": 280800, "pubinterval": 3, "complete_duration_value": 599630, "pubinterval_string": "alle 3 Tage", "pubinterval_value": 3, "pubinterval_type": 1 } } ] }podcasts-25.2/podcasts-data/tests/fyyd/search_5.json000066400000000000000000002411441500126606300225170ustar00rootroot00000000000000{ "status": 1, "msg": "ok", "meta": { "paging": { "count": 10, "page": 0, "first_page": 0, "last_page": 0, "next_page": null, "prev_page": null }, "API_INFO": { "API_VERSION": "0.2" }, "SERVER": "5.75.151.179", "duration": 0 }, "data": [ { "title": "Chapo", "id": 83744, "xmlURL": "https:\/\/feeds.acast.com\/public\/shows\/7540dfb3-3c5f-43ae-91d3-aad22b2ede46", "htmlURL": "https:\/\/play.acast.com\/s\/chapo", "imgURL": "https:\/\/assets.pippa.io\/shows\/61b79c781695620407e952a5\/show-cover.jpg", "status": 200, "status_since": "2023-10-31 22:12:05", "slug": "chapo", "layoutImageURL": "https:\/\/img-1.fyyd.de\/pd\/layout\/8374473344d689aada87d46655bb0d1e89028.jpg", "thumbImageURL": "https:\/\/img-1.fyyd.de\/pd\/thumbs\/8374473344d689aada87d46655bb0d1e89028.png", "smallImageURL": "https:\/\/img-1.fyyd.de\/pd\/small\/8374473344d689aada87d46655bb0d1e89028.jpg", "microImageURL": "https:\/\/img-1.fyyd.de\/pd\/micro\/8374473344d689aada87d46655bb0d1e89028.png", "language": "en", "generator": "acast.com", "categories": [ 155 ], "lastpub": "2019-02-22T23:19:05+01:00", "rank": 0, "url_fyyd": "https:\/\/fyyd.de\/podcast\/chapo\/0", "description": "As Sinaloa cartel leader Joaqu\u00edn \u201cEl Chapo\u201d Guzm\u00e1n goes on trial, VICE News explores his high-stakes case through the stories of people caught up in the drug war in the U.S. and Mexico. Hosted on Acast. See acast.com\/privacy for more information.", "subtitle": "As Sinaloa cartel leader Joaqu\u00edn \u201cEl Chapo\u201d Guzm\u00e1n goes on trial, VICE News explores his&nbsp;high-stakes case through the stories of people caught up in the drug war in the U.S. and Mexico.", "tcolor": "#fff", "color": "#74736f", "episode_count": 17, "iflags": null, "paymentURL": null, "author": "VICE", "stats": { "medianduration": 1395, "medianduration_string": "23m", "episodecount": 17, "pubinterval_seconds": 520200, "pubinterval": 6, "complete_duration_value": 24735, "pubinterval_string": "w\u00f6chentlich", "pubinterval_value": 7, "pubinterval_type": 2 } }, { "title": "Chapo Trap House", "id": 58282, "xmlURL": "https:\/\/feeds.soundcloud.com\/users\/soundcloud:users:211911700\/sounds.rss", "htmlURL": "https:\/\/www.patreon.com\/chapotraphouse", "imgURL": "https:\/\/i1.sndcdn.com\/avatars-000230770726-ib4tc4-original.jpg", "status": 200, "status_since": "2023-12-05 08:44:35", "slug": "chapo-trap-house", "layoutImageURL": "https:\/\/img-1.fyyd.de\/pd\/layout\/58282c0c842fc16e73e519ff141e856b175f8.jpg", "thumbImageURL": "https:\/\/img-1.fyyd.de\/pd\/thumbs\/58282c0c842fc16e73e519ff141e856b175f8.png", "smallImageURL": "https:\/\/img-1.fyyd.de\/pd\/small\/58282c0c842fc16e73e519ff141e856b175f8.jpg", "microImageURL": "https:\/\/img-1.fyyd.de\/pd\/micro\/58282c0c842fc16e73e519ff141e856b175f8.png", "language": "en", "generator": null, "categories": [ 155 ], "lastpub": "2024-03-08T08:01:37+01:00", "rank": 48, "url_fyyd": "https:\/\/fyyd.de\/podcast\/chapo-trap-house\/0", "description": "Podcast by Chapo Trap House", "subtitle": "Podcast by Chapo Trap House", "tcolor": "#fff", "color": "#568da9", "episode_count": 792, "iflags": null, "paymentURL": null, "author": "Chapo Trap House", "stats": { "medianduration": 3758, "medianduration_string": "1h2m", "episodecount": 792, "pubinterval_seconds": 300584, "pubinterval": 3, "complete_duration_value": 2365588, "pubinterval_string": "alle 3 Tage", "pubinterval_value": 3, "pubinterval_type": 1 } }, { "title": "Surviving El Chapo: The Twins Who Brought Down A Drug Lord", "id": 80419, "xmlURL": "https:\/\/www.omnycontent.com\/d\/playlist\/e73c998e-6e60-432f-8610-ae210140c5b1\/a6b57093-81fe-4401-a963-af2000fe4e3a\/56b7926c-6bfd-4d8c-81f9-af2000ffad49\/podcast.rss", "htmlURL": "https:\/\/www.iheart.com\/podcast\/1119-surviving-el-chapo-the-tw-102800182\/", "imgURL": "https:\/\/www.omnycontent.com\/d\/programs\/e73c998e-6e60-432f-8610-ae210140c5b1\/a6b57093-81fe-4401-a963-af2000fe4e3a\/image.jpg?t=1687879551&size=Large", "status": 200, "status_since": "2024-02-04 19:14:36", "slug": "surviving-el-chapo-the-twins-who-brought-down-a-drug-lord", "layoutImageURL": "https:\/\/img-1.fyyd.de\/pd\/layout\/80419996d728f3c44a832def2cffbae93e380.jpg", "thumbImageURL": "https:\/\/img-1.fyyd.de\/pd\/thumbs\/80419996d728f3c44a832def2cffbae93e380.png", "smallImageURL": "https:\/\/img-1.fyyd.de\/pd\/small\/80419996d728f3c44a832def2cffbae93e380.jpg", "microImageURL": "https:\/\/img-1.fyyd.de\/pd\/micro\/80419996d728f3c44a832def2cffbae93e380.png", "language": "en", "generator": null, "categories": [ 181, 182, 204, 129 ], "lastpub": "2023-12-06T09:00:00+01:00", "rank": 1, "url_fyyd": "https:\/\/fyyd.de\/podcast\/surviving-el-chapo-the-twins-who-brought-down-a-drug-lord\/0", "description": "Identical twins Jay and Pete Flores, who were once North America\u2019s biggest drug traffickers and El Chapo\u2019s right hand men, turned themselves into the U.S. government with the hopes of starting a new, safer life for their family. But after years of cooperating to get the world's most powerful drug kingpin behind bars, and finally gaining their freedom with a chance to start again, everything for the Flores family began to unravel. In Season 2 of Surviving El Chapo, hosts Curtis \"50 Cent\" Jackson and Charlie Webster hear Jay and Pete reveal for the first time what really happened during their turbulent 14-year prison journey and what it was like to come face-to-face in court with El Chapo. Plus, find out the shocking backstory to the prison sentence that the Flores wives are currently facing.\n\nHosted and executive produced by award-winning artist and producer Curtis \"50 Cent\" Jackson and critically acclaimed broadcast journalist and producer Charlie Webster. Brought to you by Lionsgate Sound as a world exclusive with iHeartPodcasts.", "subtitle": "Identical twins Jay and Pete Flores, who were once North America\u2019s biggest drug traffickers and El Chapo\u2019s right hand men, turned themselves into the U.S. government with the hopes of starting a new, safer life for their family. But after years of cooperating to get the world's most powerful drug kingpin behind bars, and finally gaining their freedom with a chance to start again, everything for the Flores family began to unravel. In Season 2 of Surviving El Chapo, hosts Curtis \"50 Cent\" Jackson and Charlie Webster hear Jay and Pete reveal for the first time what really happened during their turbulent 14-year prison journey and what it was like to come face-to-face in court with El Chapo. Plus, find out the shocking backstory to the prison sentence that the Flores wives are currently facing.\n\nHosted and executive produced by award-winning artist and producer Curtis \"50 Cent\" Jackson and critically acclaimed broadcast journalist and producer Charlie Webster. Brought to you by Lionsgate Sound as a world exclusive with iHeartPodcasts.", "tcolor": "#000", "color": "#b5b3aa", "episode_count": 26, "iflags": null, "paymentURL": null, "author": "iHeartPodcasts", "seasons": { "1": [ { "episode_id": 9523586, "episode_num": 1 }, { "episode_id": 9523587, "episode_num": 0 }, { "episode_id": 9523588, "episode_num": 2 }, { "episode_id": 9523602, "episode_num": 3 }, { "episode_id": 9523603, "episode_num": 4 }, { "episode_id": 9523604, "episode_num": 5 }, { "episode_id": 9542170, "episode_num": 6 }, { "episode_id": 9570924, "episode_num": 7 }, { "episode_id": 9597845, "episode_num": 8 }, { "episode_id": 9624260, "episode_num": 9 }, { "episode_id": 9657376, "episode_num": 10 }, { "episode_id": 9690552, "episode_num": 11 }, { "episode_id": 9715837, "episode_num": 12 } ], "2": [ { "episode_id": 10854621, "episode_num": 0 }, { "episode_id": 10854969, "episode_num": 0 }, { "episode_id": 10856531, "episode_num": 0 }, { "episode_id": 10882741, "episode_num": 1 }, { "episode_id": 10981848, "episode_num": 2 }, { "episode_id": 11353908, "episode_num": 3 }, { "episode_id": 11431643, "episode_num": 4 }, { "episode_id": 11459346, "episode_num": 5 }, { "episode_id": 11490396, "episode_num": 6 }, { "episode_id": 11517539, "episode_num": 7 }, { "episode_id": 11544988, "episode_num": 8 }, { "episode_id": 11571919, "episode_num": 9 }, { "episode_id": 11604520, "episode_num": 10 } ] }, "stats": { "medianduration": 2756, "medianduration_string": "45m", "episodecount": 26, "pubinterval_seconds": 604800, "pubinterval": 7, "complete_duration_value": 67145, "pubinterval_string": "w\u00f6chentlich", "pubinterval_value": 7, "pubinterval_type": 2 } }, { "title": "People's History of Ideas Podcast", "id": 84425, "xmlURL": "https:\/\/feeds.buzzsprout.com\/350771.rss", "htmlURL": "https:\/\/peopleshistoryofideas.com\/", "imgURL": "https:\/\/storage.buzzsprout.com\/variants\/L8JbJjTFbZ1QYtVDYbacyBQV\/60854458c4d1acdf4e1c2f79c4137142d85d78e379bdafbd69bd34c85f5819ad?.jpg", "status": 200, "status_since": "2024-01-22 08:20:02", "slug": "people-s-history-of-ideas-podcast", "layoutImageURL": "https:\/\/img-1.fyyd.de\/pd\/layout\/8442586e8539fdf80c9748cea30f43e638922.jpg", "thumbImageURL": "https:\/\/img-1.fyyd.de\/pd\/thumbs\/8442586e8539fdf80c9748cea30f43e638922.png", "smallImageURL": "https:\/\/img-1.fyyd.de\/pd\/small\/8442586e8539fdf80c9748cea30f43e638922.jpg", "microImageURL": "https:\/\/img-1.fyyd.de\/pd\/micro\/8442586e8539fdf80c9748cea30f43e638922.png", "language": "en", "generator": null, "categories": [ 129, 181, 184 ], "lastpub": "2024-02-04T21:00:00+01:00", "rank": 20, "url_fyyd": "https:\/\/fyyd.de\/podcast\/people-s-history-of-ideas-podcast\/0", "description": "In this podcast, Matthew Rothwell, author of Transpacific Revolutionaries: The Chinese Revolution in Latin America, explores the global history of ideas related to rebellion and revolution. The main focus of this podcast for the near future will be on the history of the Chinese Revolution, going all the way back to its roots in the initial Chinese reactions to British imperialism during the Opium War of 1839-1842, and then following the development of the revolution and many of the ideas that were products of the revolution through to their transnational diffusion in the late 20th century.", "subtitle": "In this podcast, Matthew Rothwell, author of Transpacific Revolutionaries: The Chinese Revolution in Latin America, explores the global history of ideas related to rebellion and revolution. The main focus of this podcast for the near future will be on the history of the Chinese Revolution, going all the way back to its roots in the initial Chinese reactions to British imperialism during the Opium War of 1839-1842, and then following the development of the revolution and many of the ideas that were products of the revolution through to their transnational diffusion in the late 20th century.", "tcolor": "#000", "color": "#d1c7b8", "episode_count": 112, "iflags": null, "paymentURL": "https:\/\/www.paypal.com\/donate?hosted_button_id=DACDMMMEASJVJ", "author": "Matthew Rothwell", "seasons": { "1": [ { "episode_id": 11800990, "episode_num": 1 }, { "episode_id": 11800995, "episode_num": 2 }, { "episode_id": 11801001, "episode_num": 3 }, { "episode_id": 11801008, "episode_num": 4 }, { "episode_id": 11801013, "episode_num": 5 }, { "episode_id": 11801021, "episode_num": 6 }, { "episode_id": 11801027, "episode_num": 8 }, { "episode_id": 11801032, "episode_num": 7 }, { "episode_id": 11801038, "episode_num": 9 }, { "episode_id": 11801044, "episode_num": 61 }, { "episode_id": 11801049, "episode_num": 62 }, { "episode_id": 11801054, "episode_num": 63 }, { "episode_id": 11801060, "episode_num": 64 }, { "episode_id": 11801067, "episode_num": 66 }, { "episode_id": 11801073, "episode_num": 65 }, { "episode_id": 11801078, "episode_num": 67 }, { "episode_id": 11801084, "episode_num": 68 }, { "episode_id": 11801089, "episode_num": 69 }, { "episode_id": 11801095, "episode_num": 70 }, { "episode_id": 11801102, "episode_num": 71 }, { "episode_id": 11801107, "episode_num": 72 }, { "episode_id": 11801112, "episode_num": 73 }, { "episode_id": 11801117, "episode_num": 74 }, { "episode_id": 11801122, "episode_num": 75 }, { "episode_id": 11801128, "episode_num": 78 }, { "episode_id": 11801133, "episode_num": 77 }, { "episode_id": 11801139, "episode_num": 76 }, { "episode_id": 11801143, "episode_num": 47 }, { "episode_id": 11801148, "episode_num": 49 }, { "episode_id": 11801152, "episode_num": 50 }, { "episode_id": 11801156, "episode_num": 48 }, { "episode_id": 11801159, "episode_num": 51 }, { "episode_id": 11801162, "episode_num": 56 }, { "episode_id": 11801166, "episode_num": 55 }, { "episode_id": 11801169, "episode_num": 54 }, { "episode_id": 11801173, "episode_num": 53 }, { "episode_id": 11801176, "episode_num": 52 }, { "episode_id": 11801179, "episode_num": 58 }, { "episode_id": 11801184, "episode_num": 57 }, { "episode_id": 11801187, "episode_num": 59 }, { "episode_id": 11801190, "episode_num": 60 }, { "episode_id": 11801194, "episode_num": 11 }, { "episode_id": 11801198, "episode_num": 10 }, { "episode_id": 11801201, "episode_num": 12 }, { "episode_id": 11801204, "episode_num": 13 }, { "episode_id": 11801207, "episode_num": 14 }, { "episode_id": 11801211, "episode_num": 15 }, { "episode_id": 11801214, "episode_num": 16 }, { "episode_id": 11801217, "episode_num": 17 }, { "episode_id": 11801220, "episode_num": 18 }, { "episode_id": 11801223, "episode_num": 19 }, { "episode_id": 11801226, "episode_num": 20 }, { "episode_id": 11801229, "episode_num": 21 }, { "episode_id": 11801230, "episode_num": 22 }, { "episode_id": 11801233, "episode_num": 23 }, { "episode_id": 11801237, "episode_num": 24 }, { "episode_id": 11801240, "episode_num": 25 }, { "episode_id": 11801243, "episode_num": 26 }, { "episode_id": 11801246, "episode_num": 27 }, { "episode_id": 11801249, "episode_num": 28 }, { "episode_id": 11801252, "episode_num": 29 }, { "episode_id": 11801256, "episode_num": 30 }, { "episode_id": 11801259, "episode_num": 31 }, { "episode_id": 11801262, "episode_num": 32 }, { "episode_id": 11801264, "episode_num": 33 }, { "episode_id": 11801267, "episode_num": 34 }, { "episode_id": 11801270, "episode_num": 35 }, { "episode_id": 11801273, "episode_num": 36 }, { "episode_id": 11801276, "episode_num": 37 }, { "episode_id": 11801280, "episode_num": 38 }, { "episode_id": 11801283, "episode_num": 39 }, { "episode_id": 11801286, "episode_num": 40 }, { "episode_id": 11801289, "episode_num": 41 }, { "episode_id": 11801293, "episode_num": 42 }, { "episode_id": 11801296, "episode_num": 43 }, { "episode_id": 11801299, "episode_num": 44 }, { "episode_id": 11801303, "episode_num": 46 }, { "episode_id": 11801307, "episode_num": 45 }, { "episode_id": 11801310, "episode_num": 80 }, { "episode_id": 11801314, "episode_num": 79 }, { "episode_id": 11801317, "episode_num": 81 }, { "episode_id": 11801320, "episode_num": 82 }, { "episode_id": 11801324, "episode_num": 83 }, { "episode_id": 11801327, "episode_num": 84 }, { "episode_id": 11801330, "episode_num": 85 }, { "episode_id": 11801333, "episode_num": 86 }, { "episode_id": 11801335, "episode_num": 88 }, { "episode_id": 11801337, "episode_num": 87 }, { "episode_id": 11801339, "episode_num": 89 }, { "episode_id": 11801341, "episode_num": 90 }, { "episode_id": 11801342, "episode_num": 91 }, { "episode_id": 11801344, "episode_num": 92 }, { "episode_id": 11801346, "episode_num": 93 }, { "episode_id": 11801348, "episode_num": 94 }, { "episode_id": 11801350, "episode_num": 95 }, { "episode_id": 11801352, "episode_num": 96 }, { "episode_id": 11801354, "episode_num": 97 }, { "episode_id": 11801356, "episode_num": 98 }, { "episode_id": 11801358, "episode_num": 99 }, { "episode_id": 11801360, "episode_num": 100 }, { "episode_id": 11801362, "episode_num": 101 }, { "episode_id": 11801364, "episode_num": 102 }, { "episode_id": 11801366, "episode_num": 103 }, { "episode_id": 11801368, "episode_num": 104 }, { "episode_id": 11801370, "episode_num": 105 }, { "episode_id": 11801372, "episode_num": 106 }, { "episode_id": 11801374, "episode_num": 107 }, { "episode_id": 11801376, "episode_num": 108 }, { "episode_id": 11801378, "episode_num": 109 }, { "episode_id": 11801380, "episode_num": 110 }, { "episode_id": 11801382, "episode_num": 111 }, { "episode_id": 11862403, "episode_num": 112 } ] }, "stats": { "medianduration": 1519, "medianduration_string": "25m", "episodecount": 112, "pubinterval_seconds": 604800, "pubinterval": 7, "complete_duration_value": 182894, "pubinterval_string": "w\u00f6chentlich", "pubinterval_value": 7, "pubinterval_type": 2 } }, { "title": "Bad Faith", "id": 61329, "xmlURL": "https:\/\/badfaith.libsyn.com\/rss", "htmlURL": "http:\/\/badfaithpodcast.com", "imgURL": "https:\/\/static.libsyn.com\/p\/assets\/e\/d\/7\/3\/ed736acec77125fa\/BAD-FAITH_main_thumb_2000x2000.jpg", "status": 200, "status_since": "2022-11-30 17:20:14", "slug": "bad-faith", "layoutImageURL": "https:\/\/img-1.fyyd.de\/pd\/layout\/6132904ae864f41dbbb60eaf1054cbfcc6292.jpg", "thumbImageURL": "https:\/\/img-1.fyyd.de\/pd\/thumbs\/6132904ae864f41dbbb60eaf1054cbfcc6292.png", "smallImageURL": "https:\/\/img-1.fyyd.de\/pd\/small\/6132904ae864f41dbbb60eaf1054cbfcc6292.jpg", "microImageURL": "https:\/\/img-1.fyyd.de\/pd\/micro\/6132904ae864f41dbbb60eaf1054cbfcc6292.png", "language": "en", "generator": "Libsyn WebEngine 2.0", "categories": [ 155, 160, 115 ], "lastpub": "2024-03-07T11:44:00+01:00", "rank": 64, "url_fyyd": "https:\/\/fyyd.de\/podcast\/bad-faith\/0", "description": "America's only podcast. \/\/\r\n\r\nwith Briahna Joy Gray, former National Press Secretary for Bernie Sanders' Presidential campaign \/\/\r\n\r\nand Virgil Texas \/\/\r\n\r\nSubscribe for exclusive premium episodes at patreon.com\/badfaithpodcast \/\r\n@badfaithpod \/ \r\nbadfaithpodcast at gmail dot com", "subtitle": "America's only podcast. \/\/\r\n\r\nwith Briahna Joy Gray, former National Press Secretary for Bernie Sanders' Presidential campaign \/\/\r\n\r\nand Virgil Texas \/\/\r\n\r\nSubscribe for exclusive premium episodes at patreon.com\/badfaithpodcast \/\r\n@badfaithpod \/ \r\nbadfaithpodcast at gmail dot com", "tcolor": "#fff", "color": "#0a0d2d", "episode_count": 372, "iflags": null, "paymentURL": null, "author": "Briahna Joy Gray & Virgil Texas", "seasons": { "1": [ { "episode_id": 5617631, "episode_num": 8 }, { "episode_id": 5617687, "episode_num": 8 }, { "episode_id": 5617699, "episode_num": 16 }, { "episode_id": 5617700, "episode_num": 9 }, { "episode_id": 5617701, "episode_num": 10 }, { "episode_id": 5617702, "episode_num": 11 }, { "episode_id": 5617703, "episode_num": 12 }, { "episode_id": 5617710, "episode_num": 14 }, { "episode_id": 5617711, "episode_num": 0 }, { "episode_id": 5617712, "episode_num": 13 }, { "episode_id": 5617723, "episode_num": 15 }, { "episode_id": 5617733, "episode_num": 17 }, { "episode_id": 5617749, "episode_num": 4 }, { "episode_id": 5617789, "episode_num": 1 }, { "episode_id": 5617790, "episode_num": 2 }, { "episode_id": 5617791, "episode_num": 3 }, { "episode_id": 5617797, "episode_num": 5 }, { "episode_id": 5617799, "episode_num": 6 }, { "episode_id": 5617802, "episode_num": 7 }, { "episode_id": 5619448, "episode_num": 18 }, { "episode_id": 5634216, "episode_num": 19 }, { "episode_id": 5647262, "episode_num": 20 }, { "episode_id": 5902224, "episode_num": 21 }, { "episode_id": 5933820, "episode_num": 0 }, { "episode_id": 5934658, "episode_num": 23 }, { "episode_id": 5951144, "episode_num": 24 }, { "episode_id": 5968428, "episode_num": 25 }, { "episode_id": 5990374, "episode_num": 26 }, { "episode_id": 6009685, "episode_num": 27 }, { "episode_id": 6031537, "episode_num": 28 }, { "episode_id": 6053740, "episode_num": 29 }, { "episode_id": 6074167, "episode_num": 30 }, { "episode_id": 6089543, "episode_num": 31 }, { "episode_id": 6095736, "episode_num": 30 }, { "episode_id": 6104856, "episode_num": 32 }, { "episode_id": 6106067, "episode_num": 32 }, { "episode_id": 6118339, "episode_num": 33 }, { "episode_id": 6132181, "episode_num": 34 }, { "episode_id": 6149614, "episode_num": 35 }, { "episode_id": 6167723, "episode_num": 36 }, { "episode_id": 6184906, "episode_num": 37 }, { "episode_id": 6207594, "episode_num": 38 }, { "episode_id": 6227170, "episode_num": 39 }, { "episode_id": 6253597, "episode_num": 40 }, { "episode_id": 6271522, "episode_num": 41 }, { "episode_id": 6291497, "episode_num": 42 }, { "episode_id": 6309441, "episode_num": 43 }, { "episode_id": 6332268, "episode_num": 44 }, { "episode_id": 6349904, "episode_num": 45 }, { "episode_id": 6371092, "episode_num": 46 }, { "episode_id": 6372416, "episode_num": 46 }, { "episode_id": 6386204, "episode_num": 47 }, { "episode_id": 6406070, "episode_num": 48 }, { "episode_id": 6424381, "episode_num": 49 }, { "episode_id": 6442759, "episode_num": 50 }, { "episode_id": 6460758, "episode_num": 51 }, { "episode_id": 6480598, "episode_num": 52 }, { "episode_id": 6495828, "episode_num": 0 }, { "episode_id": 6499286, "episode_num": 53 }, { "episode_id": 6521062, "episode_num": 54 }, { "episode_id": 6543472, "episode_num": 55 }, { "episode_id": 6572873, "episode_num": 56 }, { "episode_id": 6591963, "episode_num": 57 }, { "episode_id": 6614450, "episode_num": 58 }, { "episode_id": 6631759, "episode_num": 59 }, { "episode_id": 6649673, "episode_num": 60 }, { "episode_id": 6665248, "episode_num": 61 }, { "episode_id": 6682658, "episode_num": 62 }, { "episode_id": 6697017, "episode_num": 63 }, { "episode_id": 6715062, "episode_num": 64 }, { "episode_id": 6733228, "episode_num": 65 }, { "episode_id": 6756898, "episode_num": 66 }, { "episode_id": 6771215, "episode_num": 67 }, { "episode_id": 6788134, "episode_num": 68 }, { "episode_id": 6807334, "episode_num": 69 }, { "episode_id": 6824290, "episode_num": 70 }, { "episode_id": 6842954, "episode_num": 71 }, { "episode_id": 6859632, "episode_num": 72 }, { "episode_id": 6874379, "episode_num": 73 }, { "episode_id": 6893738, "episode_num": 74 }, { "episode_id": 6925474, "episode_num": 75 }, { "episode_id": 6944480, "episode_num": 76 }, { "episode_id": 6957654, "episode_num": 77 }, { "episode_id": 6976952, "episode_num": 78 }, { "episode_id": 6993396, "episode_num": 79 }, { "episode_id": 7012005, "episode_num": 80 }, { "episode_id": 7026885, "episode_num": 81 }, { "episode_id": 7044610, "episode_num": 82 }, { "episode_id": 7063132, "episode_num": 83 }, { "episode_id": 7081372, "episode_num": 84 }, { "episode_id": 7096878, "episode_num": 85 }, { "episode_id": 7114695, "episode_num": 24 }, { "episode_id": 7114918, "episode_num": 86 }, { "episode_id": 7131322, "episode_num": 87 }, { "episode_id": 7149489, "episode_num": 88 }, { "episode_id": 7168061, "episode_num": 89 }, { "episode_id": 7195618, "episode_num": 90 }, { "episode_id": 7208591, "episode_num": 91 }, { "episode_id": 7224885, "episode_num": 92 }, { "episode_id": 7240463, "episode_num": 93 }, { "episode_id": 7265401, "episode_num": 95 }, { "episode_id": 7281282, "episode_num": 96 }, { "episode_id": 7299880, "episode_num": 97 }, { "episode_id": 7315535, "episode_num": 94 }, { "episode_id": 7315920, "episode_num": 98 }, { "episode_id": 7333504, "episode_num": 99 }, { "episode_id": 7348882, "episode_num": 100 }, { "episode_id": 7369534, "episode_num": 101 }, { "episode_id": 7383160, "episode_num": 102 }, { "episode_id": 7399790, "episode_num": 103 }, { "episode_id": 7416654, "episode_num": 104 }, { "episode_id": 7431274, "episode_num": 105 }, { "episode_id": 7452592, "episode_num": 106 }, { "episode_id": 7463564, "episode_num": 107 }, { "episode_id": 7487587, "episode_num": 108 }, { "episode_id": 7503364, "episode_num": 109 }, { "episode_id": 7521164, "episode_num": 110 }, { "episode_id": 7536246, "episode_num": 111 }, { "episode_id": 7555717, "episode_num": 112 }, { "episode_id": 7571350, "episode_num": 113 }, { "episode_id": 7589364, "episode_num": 114 }, { "episode_id": 7607209, "episode_num": 115 }, { "episode_id": 7624378, "episode_num": 116 }, { "episode_id": 7639209, "episode_num": 117 }, { "episode_id": 7657241, "episode_num": 118 }, { "episode_id": 7673073, "episode_num": 119 }, { "episode_id": 7693028, "episode_num": 120 }, { "episode_id": 7711939, "episode_num": 121 }, { "episode_id": 7733832, "episode_num": 122 }, { "episode_id": 7751669, "episode_num": 123 }, { "episode_id": 7770276, "episode_num": 124 }, { "episode_id": 7804509, "episode_num": 125 }, { "episode_id": 7821372, "episode_num": 126 }, { "episode_id": 7833967, "episode_num": 127 }, { "episode_id": 7850765, "episode_num": 128 }, { "episode_id": 7864204, "episode_num": 129 }, { "episode_id": 7880602, "episode_num": 130 }, { "episode_id": 7898812, "episode_num": 131 }, { "episode_id": 7919956, "episode_num": 132 }, { "episode_id": 7938519, "episode_num": 133 }, { "episode_id": 7966099, "episode_num": 134 }, { "episode_id": 7985653, "episode_num": 135 }, { "episode_id": 8007810, "episode_num": 136 }, { "episode_id": 8021376, "episode_num": 137 }, { "episode_id": 8037098, "episode_num": 138 }, { "episode_id": 8052904, "episode_num": 139 }, { "episode_id": 8071195, "episode_num": 140 }, { "episode_id": 8094740, "episode_num": 141 }, { "episode_id": 8116919, "episode_num": 142 }, { "episode_id": 8155965, "episode_num": 144 }, { "episode_id": 8166469, "episode_num": 102 }, { "episode_id": 8174635, "episode_num": 145 }, { "episode_id": 8195240, "episode_num": 146 }, { "episode_id": 8218787, "episode_num": 147 }, { "episode_id": 8242523, "episode_num": 148 }, { "episode_id": 8259947, "episode_num": 149 }, { "episode_id": 8278095, "episode_num": 150 }, { "episode_id": 8295799, "episode_num": 151 }, { "episode_id": 8315519, "episode_num": 152 }, { "episode_id": 8332589, "episode_num": 153 }, { "episode_id": 8351811, "episode_num": 154 }, { "episode_id": 8368099, "episode_num": 155 }, { "episode_id": 8388351, "episode_num": 156 }, { "episode_id": 8406028, "episode_num": 157 }, { "episode_id": 8427751, "episode_num": 158 }, { "episode_id": 8444469, "episode_num": 159 }, { "episode_id": 8464298, "episode_num": 160 }, { "episode_id": 8490843, "episode_num": 161 }, { "episode_id": 8544725, "episode_num": 162 }, { "episode_id": 8561526, "episode_num": 163 }, { "episode_id": 8583289, "episode_num": 164 }, { "episode_id": 8599709, "episode_num": 165 }, { "episode_id": 8618360, "episode_num": 166 }, { "episode_id": 8656456, "episode_num": 167 }, { "episode_id": 8669171, "episode_num": 168 }, { "episode_id": 8682547, "episode_num": 169 }, { "episode_id": 8696670, "episode_num": 170 }, { "episode_id": 8710157, "episode_num": 171 }, { "episode_id": 8753214, "episode_num": 174 }, { "episode_id": 8765973, "episode_num": 175 }, { "episode_id": 8780669, "episode_num": 176 }, { "episode_id": 8809258, "episode_num": 178 }, { "episode_id": 8822987, "episode_num": 179 }, { "episode_id": 8837719, "episode_num": 180 }, { "episode_id": 8851218, "episode_num": 181 }, { "episode_id": 8868359, "episode_num": 182 }, { "episode_id": 8885928, "episode_num": 183 }, { "episode_id": 8901141, "episode_num": 184 }, { "episode_id": 8916165, "episode_num": 185 }, { "episode_id": 8928818, "episode_num": 186 }, { "episode_id": 8940712, "episode_num": 187 }, { "episode_id": 8955079, "episode_num": 188 }, { "episode_id": 8985161, "episode_num": 190 }, { "episode_id": 8999061, "episode_num": 158 }, { "episode_id": 9010760, "episode_num": 191 }, { "episode_id": 9022694, "episode_num": 192 }, { "episode_id": 9040366, "episode_num": 193 }, { "episode_id": 9053372, "episode_num": 194 }, { "episode_id": 9066969, "episode_num": 195 }, { "episode_id": 9079636, "episode_num": 196 }, { "episode_id": 9097589, "episode_num": 197 }, { "episode_id": 9113541, "episode_num": 198 }, { "episode_id": 9124592, "episode_num": 199 }, { "episode_id": 9133988, "episode_num": 200 }, { "episode_id": 9159294, "episode_num": 201 }, { "episode_id": 9202605, "episode_num": 202 }, { "episode_id": 9205405, "episode_num": 203 }, { "episode_id": 9231610, "episode_num": 205 }, { "episode_id": 9244424, "episode_num": 205 }, { "episode_id": 9257351, "episode_num": 206 }, { "episode_id": 9268122, "episode_num": 207 }, { "episode_id": 9286378, "episode_num": 208 }, { "episode_id": 9299003, "episode_num": 209 }, { "episode_id": 9313089, "episode_num": 210 }, { "episode_id": 9327023, "episode_num": 211 }, { "episode_id": 9341293, "episode_num": 212 }, { "episode_id": 9356955, "episode_num": 213 }, { "episode_id": 9372500, "episode_num": 214 }, { "episode_id": 9383706, "episode_num": 215 }, { "episode_id": 9398205, "episode_num": 216 }, { "episode_id": 9411842, "episode_num": 217 }, { "episode_id": 9441784, "episode_num": 219 }, { "episode_id": 9454496, "episode_num": 220 }, { "episode_id": 9472538, "episode_num": 221 }, { "episode_id": 9486968, "episode_num": 222 }, { "episode_id": 9502991, "episode_num": 223 }, { "episode_id": 9515872, "episode_num": 224 }, { "episode_id": 9532645, "episode_num": 225 }, { "episode_id": 9546066, "episode_num": 226 }, { "episode_id": 9559923, "episode_num": 227 }, { "episode_id": 9576391, "episode_num": 228 }, { "episode_id": 9604238, "episode_num": 223 }, { "episode_id": 9617387, "episode_num": 230 }, { "episode_id": 9633053, "episode_num": 231 }, { "episode_id": 9646999, "episode_num": 232 }, { "episode_id": 9664889, "episode_num": 233 }, { "episode_id": 9679677, "episode_num": 234 }, { "episode_id": 9694969, "episode_num": 235 }, { "episode_id": 9708410, "episode_num": 236 }, { "episode_id": 9720122, "episode_num": 237 }, { "episode_id": 9743365, "episode_num": 238 }, { "episode_id": 9755609, "episode_num": 239 }, { "episode_id": 9764897, "episode_num": 240 }, { "episode_id": 9779835, "episode_num": 241 }, { "episode_id": 9791306, "episode_num": 242 }, { "episode_id": 9809362, "episode_num": 243 }, { "episode_id": 9820901, "episode_num": 244 }, { "episode_id": 9836467, "episode_num": 245 }, { "episode_id": 9851349, "episode_num": 246 }, { "episode_id": 9864738, "episode_num": 247 }, { "episode_id": 9879259, "episode_num": 248 }, { "episode_id": 9892561, "episode_num": 249 }, { "episode_id": 9905131, "episode_num": 250 }, { "episode_id": 9938097, "episode_num": 251 }, { "episode_id": 9958652, "episode_num": 251 }, { "episode_id": 9972525, "episode_num": 253 }, { "episode_id": 9987485, "episode_num": 254 }, { "episode_id": 10001153, "episode_num": 255 }, { "episode_id": 10015849, "episode_num": 256 }, { "episode_id": 10028114, "episode_num": 257 }, { "episode_id": 10040651, "episode_num": 258 }, { "episode_id": 10054064, "episode_num": 259 }, { "episode_id": 10066571, "episode_num": 260 }, { "episode_id": 10080792, "episode_num": 261 }, { "episode_id": 10093011, "episode_num": 262 }, { "episode_id": 10107766, "episode_num": 263 }, { "episode_id": 10123041, "episode_num": 264 }, { "episode_id": 10138483, "episode_num": 265 }, { "episode_id": 10151607, "episode_num": 266 }, { "episode_id": 10165636, "episode_num": 267 }, { "episode_id": 10192964, "episode_num": 268 }, { "episode_id": 10192965, "episode_num": 269 }, { "episode_id": 10207430, "episode_num": 251 }, { "episode_id": 10220663, "episode_num": 270 }, { "episode_id": 10236000, "episode_num": 270 }, { "episode_id": 10250755, "episode_num": 271 }, { "episode_id": 10262144, "episode_num": 272 }, { "episode_id": 10279277, "episode_num": 272 }, { "episode_id": 10290819, "episode_num": 273 }, { "episode_id": 10304332, "episode_num": 274 }, { "episode_id": 10316615, "episode_num": 234 }, { "episode_id": 10329730, "episode_num": 275 }, { "episode_id": 10343276, "episode_num": 249 }, { "episode_id": 10353640, "episode_num": 276 }, { "episode_id": 10365177, "episode_num": 277 }, { "episode_id": 10378492, "episode_num": 278 }, { "episode_id": 10416231, "episode_num": 279 }, { "episode_id": 10435323, "episode_num": 280 }, { "episode_id": 10445545, "episode_num": 281 }, { "episode_id": 10461296, "episode_num": 282 }, { "episode_id": 10472938, "episode_num": 283 }, { "episode_id": 10493713, "episode_num": 284 }, { "episode_id": 10505587, "episode_num": 285 }, { "episode_id": 10527463, "episode_num": 286 }, { "episode_id": 10537502, "episode_num": 287 }, { "episode_id": 10551979, "episode_num": 288 }, { "episode_id": 10564510, "episode_num": 289 }, { "episode_id": 10575760, "episode_num": 290 }, { "episode_id": 10587582, "episode_num": 291 }, { "episode_id": 10613929, "episode_num": 292 }, { "episode_id": 10633654, "episode_num": 293 }, { "episode_id": 10648223, "episode_num": 294 }, { "episode_id": 10658994, "episode_num": 295 }, { "episode_id": 10668798, "episode_num": 296 }, { "episode_id": 10681105, "episode_num": 297 }, { "episode_id": 10692083, "episode_num": 298 }, { "episode_id": 10705027, "episode_num": 299 }, { "episode_id": 10715529, "episode_num": 300 }, { "episode_id": 10732824, "episode_num": 301 }, { "episode_id": 10752663, "episode_num": 302 }, { "episode_id": 10775930, "episode_num": 304 }, { "episode_id": 10786030, "episode_num": 0 }, { "episode_id": 10804955, "episode_num": 305 }, { "episode_id": 10820607, "episode_num": 306 }, { "episode_id": 10832723, "episode_num": 307 }, { "episode_id": 10846154, "episode_num": 308 }, { "episode_id": 10860463, "episode_num": 309 }, { "episode_id": 10873371, "episode_num": 310 }, { "episode_id": 10888188, "episode_num": 311 }, { "episode_id": 10909639, "episode_num": 312 }, { "episode_id": 11053018, "episode_num": 313 }, { "episode_id": 11260157, "episode_num": 314 }, { "episode_id": 11404886, "episode_num": 315 }, { "episode_id": 11423502, "episode_num": 316 }, { "episode_id": 11437941, "episode_num": 317 }, { "episode_id": 11451326, "episode_num": 318 }, { "episode_id": 11464488, "episode_num": 319 }, { "episode_id": 11481148, "episode_num": 320 }, { "episode_id": 11496700, "episode_num": 321 }, { "episode_id": 11510379, "episode_num": 322 }, { "episode_id": 11523565, "episode_num": 323 }, { "episode_id": 11537627, "episode_num": 324 }, { "episode_id": 11552207, "episode_num": 325 }, { "episode_id": 11565434, "episode_num": 326 }, { "episode_id": 11577829, "episode_num": 327 }, { "episode_id": 11596774, "episode_num": 328 }, { "episode_id": 11611348, "episode_num": 329 }, { "episode_id": 11627320, "episode_num": 330 }, { "episode_id": 11640949, "episode_num": 331 }, { "episode_id": 11667335, "episode_num": 333 }, { "episode_id": 11667339, "episode_num": 332 }, { "episode_id": 11691376, "episode_num": 332 }, { "episode_id": 11706826, "episode_num": 334 }, { "episode_id": 11715873, "episode_num": 335 }, { "episode_id": 11730651, "episode_num": 336 }, { "episode_id": 11755550, "episode_num": 337 }, { "episode_id": 11770080, "episode_num": 338 }, { "episode_id": 11786562, "episode_num": 339 }, { "episode_id": 11809812, "episode_num": 340 }, { "episode_id": 11822926, "episode_num": 341 }, { "episode_id": 11837784, "episode_num": 342 }, { "episode_id": 11851933, "episode_num": 343 }, { "episode_id": 11865377, "episode_num": 344 }, { "episode_id": 11893312, "episode_num": 345 }, { "episode_id": 11911318, "episode_num": 346 }, { "episode_id": 11925277, "episode_num": 347 }, { "episode_id": 11937813, "episode_num": 348 }, { "episode_id": 11956910, "episode_num": 344 }, { "episode_id": 11973065, "episode_num": 349 }, { "episode_id": 11983435, "episode_num": 350 }, { "episode_id": 11999886, "episode_num": 351 }, { "episode_id": 12013064, "episode_num": 352 } ] }, "stats": { "medianduration": 2820, "medianduration_string": "47m", "episodecount": 372, "pubinterval_seconds": 295090, "pubinterval": 3, "complete_duration_value": 950965, "pubinterval_string": "alle 3 Tage", "pubinterval_value": 3, "pubinterval_type": 1 } } ] }podcasts-25.2/podcasts-data/tests/fyyd/search_empty.json000066400000000000000000000006061500126606300235050ustar00rootroot00000000000000{ "status": 1, "msg": "ok", "meta": { "paging": { "count": 10, "page": 0, "first_page": 0, "last_page": -1, "next_page": 1, "prev_page": null }, "API_INFO": { "API_VERSION": "0.2" }, "SERVER": "5.75.151.179", "duration": 0 }, "data": [] }podcasts-25.2/podcasts-data/tests/fyyd/search_unicode.json000066400000000000000000000051671500126606300240040ustar00rootroot00000000000000{ "status": 1, "msg": "ok", "meta": { "paging": { "count": 10, "page": 0, "first_page": 0, "last_page": 0, "next_page": null, "prev_page": null }, "API_INFO": { "API_VERSION": "0.2" }, "SERVER": "5.75.151.179", "duration": 0 }, "data": [ { "title": "Corner Sp\u00e4ti", "id": 77182, "xmlURL": "https:\/\/feeds.fireside.fm\/cornerspaeti\/rss", "htmlURL": "https:\/\/www.operationglad.io", "imgURL": "https:\/\/assets.fireside.fm\/file\/fireside-images\/podcasts\/images\/7\/77dd176a-f2a7-4ad0-a52b-9908d5d03ea8\/cover.jpg", "status": 200, "status_since": "2022-05-14 21:58:20", "slug": "corner-spaeti", "layoutImageURL": "https:\/\/img-1.fyyd.de\/pd\/layout\/77182e877ac679f9414148fab3dddb74782c2.jpg", "thumbImageURL": "https:\/\/img-1.fyyd.de\/pd\/thumbs\/77182e877ac679f9414148fab3dddb74782c2.png", "smallImageURL": "https:\/\/img-1.fyyd.de\/pd\/small\/77182e877ac679f9414148fab3dddb74782c2.jpg", "microImageURL": "https:\/\/img-1.fyyd.de\/pd\/micro\/77182e877ac679f9414148fab3dddb74782c2.png", "language": "en", "generator": "Fireside (https:\/\/fireside.fm)", "categories": [ 155, 115, 181 ], "lastpub": "2024-03-07T10:00:00+01:00", "rank": 76, "url_fyyd": "https:\/\/fyyd.de\/podcast\/corner-spaeti\/0", "description": "Weekly discussions of a deteriorating world all from the comfort of your local smoke-filled Sp\u00e4tkauf.\nhttps:\/\/www.patreon.com\/cornerspaeti\nhttps:\/\/www.operationglad.io\/start\n", "subtitle": "Discussions of a deteriorating world all from the comfort of your local smoke-filled Sp\u00e4tkauf.", "tcolor": "#fff", "color": "#ba3935", "episode_count": 348, "iflags": null, "paymentURL": "https:\/\/www.patreon.com\/cornerspaeti", "author": "The Sp\u00e4ti Boys", "stats": { "medianduration": 4416, "medianduration_string": "1h13m", "episodecount": 348, "pubinterval_seconds": 558900, "pubinterval": 6, "complete_duration_value": 1359834, "pubinterval_string": "w\u00f6chentlich", "pubinterval_value": 7, "pubinterval_type": 2 } } ] }podcasts-25.2/podcasts-data/tests/itunes/000077500000000000000000000000001500126606300204615ustar00rootroot00000000000000podcasts-25.2/podcasts-data/tests/itunes/search_1.txt000066400000000000000000000034441500126606300227140ustar00rootroot00000000000000 { "resultCount":1, "results": [ {"wrapperType":"track", "kind":"podcast", "collectionId":1525269951, "trackId":1525269951, "artistName":"Jackson Jacker", "collectionName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "trackName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "collectionCensoredName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "trackCensoredName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/cushvlogs-audio-matt-christman-chapo-trap-house/id1525269951?uo=4", "feedUrl":"https://anchor.fm/s/19346b24/podcast/rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/cushvlogs-audio-matt-christman-chapo-trap-house/id1525269951?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2021-06-08T04:56:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":167, "trackTimeMillis":4052, "country":"USA", "currency":"USD", "primaryGenreName":"Spirituality", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/600x600bb.jpg", "genreIds":["1444", "26", "1314"], "genres":["Spirituality", "Podcasts", "Religion & Spirituality"]}] } podcasts-25.2/podcasts-data/tests/itunes/search_10.txt000066400000000000000000000401641500126606300227740ustar00rootroot00000000000000 { "resultCount":10, "results": [ {"wrapperType":"track", "kind":"podcast", "artistId":954021325, "collectionId":1440215666, "trackId":1440215666, "artistName":"VICE", "collectionName":"Chapo", "trackName":"Chapo", "collectionCensoredName":"Chapo", "trackCensoredName":"Chapo", "artistViewUrl":"https://podcasts.apple.com/us/artist/vice/954021325?uo=4", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/chapo/id1440215666?uo=4", "feedUrl":"https://feeds.acast.com/public/shows/7540dfb3-3c5f-43ae-91d3-aad22b2ede46", "trackViewUrl":"https://podcasts.apple.com/us/podcast/chapo/id1440215666?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/58/9b/8d/589b8d72-13aa-55cb-463f-ad41096ca9ea/mza_560982554884614556.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/58/9b/8d/589b8d72-13aa-55cb-463f-ad41096ca9ea/mza_560982554884614556.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/58/9b/8d/589b8d72-13aa-55cb-463f-ad41096ca9ea/mza_560982554884614556.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2023-01-25T05:01:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":17, "trackTimeMillis":2464, "country":"USA", "currency":"USD", "primaryGenreName":"News", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/58/9b/8d/589b8d72-13aa-55cb-463f-ad41096ca9ea/mza_560982554884614556.jpg/600x600bb.jpg", "genreIds":["1489", "26"], "genres":["News", "Podcasts"]}, {"wrapperType":"track", "kind":"podcast", "collectionId":1097417804, "trackId":1097417804, "artistName":"Chapo Trap House", "collectionName":"Chapo Trap House", "trackName":"Chapo Trap House", "collectionCensoredName":"Chapo Trap House", "trackCensoredName":"Chapo Trap House", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/chapo-trap-house/id1097417804?uo=4", "feedUrl":"https://feeds.soundcloud.com/users/soundcloud:users:211911700/sounds.rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/chapo-trap-house/id1097417804?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts18/v4/37/d9/a0/37d9a0b4-64f8-70d4-722e-b3a8772f3424/mza_5335192259855381405.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts18/v4/37/d9/a0/37d9a0b4-64f8-70d4-722e-b3a8772f3424/mza_5335192259855381405.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts18/v4/37/d9/a0/37d9a0b4-64f8-70d4-722e-b3a8772f3424/mza_5335192259855381405.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2024-03-05T20:55:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"explicit", "trackCount":507, "trackTimeMillis":3315, "country":"USA", "currency":"USD", "primaryGenreName":"News", "contentAdvisoryRating":"Explicit", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts18/v4/37/d9/a0/37d9a0b4-64f8-70d4-722e-b3a8772f3424/mza_5335192259855381405.jpg/600x600bb.jpg", "genreIds":["1489", "26"], "genres":["News", "Podcasts"]}, {"wrapperType":"track", "kind":"podcast", "artistId":1216352023, "collectionId":1522699497, "trackId":1522699497, "artistName":"CNN en Español", "collectionName":"El Chapo: Dos rostros de un capo Podcast", "trackName":"El Chapo: Dos rostros de un capo Podcast", "collectionCensoredName":"El Chapo: Dos rostros de un capo Podcast", "trackCensoredName":"El Chapo: Dos rostros de un capo Podcast", "artistViewUrl":"https://podcasts.apple.com/us/artist/cnn-en-espa%C3%B1ol/1216352023?uo=4", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/el-chapo-dos-rostros-de-un-capo-podcast/id1522699497?uo=4", "feedUrl":"https://feeds.megaphone.fm/WMHY2717952910", "trackViewUrl":"https://podcasts.apple.com/us/podcast/el-chapo-dos-rostros-de-un-capo-podcast/id1522699497?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/31/e9/bb/31e9bb57-31f8-447d-e619-bf94e2ee42af/mza_7685287012963567517.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/31/e9/bb/31e9bb57-31f8-447d-e619-bf94e2ee42af/mza_7685287012963567517.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/31/e9/bb/31e9bb57-31f8-447d-e619-bf94e2ee42af/mza_7685287012963567517.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2020-07-15T09:10:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":7, "trackTimeMillis":968, "country":"USA", "currency":"USD", "primaryGenreName":"True Crime", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/31/e9/bb/31e9bb57-31f8-447d-e619-bf94e2ee42af/mza_7685287012963567517.jpg/600x600bb.jpg", "genreIds":["1488", "26"], "genres":["True Crime", "Podcasts"]}, {"wrapperType":"track", "kind":"podcast", "collectionId":1525269951, "trackId":1525269951, "artistName":"Jackson Jacker", "collectionName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "trackName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "collectionCensoredName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "trackCensoredName":"CushVlogs Audio - Matt Christman - Chapo Trap House", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/cushvlogs-audio-matt-christman-chapo-trap-house/id1525269951?uo=4", "feedUrl":"https://anchor.fm/s/19346b24/podcast/rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/cushvlogs-audio-matt-christman-chapo-trap-house/id1525269951?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2021-06-08T04:56:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":167, "trackTimeMillis":4052, "country":"USA", "currency":"USD", "primaryGenreName":"Spirituality", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts124/v4/74/41/7c/74417c8a-151f-5090-5460-fe5e7ca0e671/mza_7528059777000521713.jpg/600x600bb.jpg", "genreIds":["1444", "26", "1314"], "genres":["Spirituality", "Podcasts", "Religion & Spirituality"]}, {"wrapperType":"track", "kind":"podcast", "artistId":1253698124, "collectionId":1232055341, "trackId":1232055341, "artistName":"Univision", "collectionName":"‘El Chapo’: ¿héroe o villano?", "trackName":"‘El Chapo’: ¿héroe o villano?", "collectionCensoredName":"‘El Chapo’: ¿héroe o villano?", "trackCensoredName":"‘El Chapo’: ¿héroe o villano?", "artistViewUrl":"https://podcasts.apple.com/us/artist/univision/1253698124?uo=4", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/el-chapo-h%C3%A9roe-o-villano/id1232055341?uo=4", "feedUrl":"https://audioboom.com/channels/4905580.rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/el-chapo-h%C3%A9roe-o-villano/id1232055341?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/57/34/8e/57348e38-6c9a-7579-8269-42e61786ae3b/mza_17242328811639650011.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/57/34/8e/57348e38-6c9a-7579-8269-42e61786ae3b/mza_17242328811639650011.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/57/34/8e/57348e38-6c9a-7579-8269-42e61786ae3b/mza_17242328811639650011.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2017-05-23T00:59:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":4, "trackTimeMillis":1483, "country":"USA", "currency":"USD", "primaryGenreName":"TV & Film", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/57/34/8e/57348e38-6c9a-7579-8269-42e61786ae3b/mza_17242328811639650011.jpg/600x600bb.jpg", "genreIds":["1309", "26"], "genres":["TV & Film", "Podcasts"]}, {"wrapperType":"track", "kind":"podcast", "collectionId":1567804176, "trackId":1567804176, "artistName":"Jack Gold", "collectionName":"Chapo", "trackName":"Chapo", "collectionCensoredName":"Chapo", "trackCensoredName":"Chapo", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/chapo/id1567804176?uo=4", "feedUrl":"https://anchor.fm/s/5b423b40/podcast/rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/chapo/id1567804176?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/43/67/d3/4367d309-707e-6b7e-390a-202565c1d530/mza_9950196886709505935.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/43/67/d3/4367d309-707e-6b7e-390a-202565c1d530/mza_9950196886709505935.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/43/67/d3/4367d309-707e-6b7e-390a-202565c1d530/mza_9950196886709505935.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2021-05-15T05:32:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":1, "trackTimeMillis":135, "country":"USA", "currency":"USD", "primaryGenreName":"Performing Arts", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/43/67/d3/4367d309-707e-6b7e-390a-202565c1d530/mza_9950196886709505935.jpg/600x600bb.jpg", "genreIds":["1405", "26", "1301"], "genres":["Performing Arts", "Podcasts", "Arts"]}, {"wrapperType":"track", "kind":"podcast", "collectionId":1510491714, "trackId":1510491714, "artistName":"Mijo 713", "collectionName":"Chapo", "trackName":"Chapo", "collectionCensoredName":"Chapo", "trackCensoredName":"Chapo", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/chapo/id1510491714?uo=4", "feedUrl":"https://anchor.fm/s/1e56647c/podcast/rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/chapo/id1510491714?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts123/v4/b5/7c/41/b57c410f-8b2f-d9ce-df76-44b5947ea261/mza_17707437198336862.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts123/v4/b5/7c/41/b57c410f-8b2f-d9ce-df76-44b5947ea261/mza_17707437198336862.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts123/v4/b5/7c/41/b57c410f-8b2f-d9ce-df76-44b5947ea261/mza_17707437198336862.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2020-04-25T19:46:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":1, "trackTimeMillis":35, "country":"USA", "currency":"USD", "primaryGenreName":"Government", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts123/v4/b5/7c/41/b57c410f-8b2f-d9ce-df76-44b5947ea261/mza_17707437198336862.jpg/600x600bb.jpg", "genreIds":["1511", "26"], "genres":["Government", "Podcasts"]}, {"wrapperType":"track", "kind":"podcast", "collectionId":1649359487, "trackId":1649359487, "artistName":"iHeartPodcasts", "collectionName":"Surviving El Chapo: The Twins Who Brought Down A Drug Lord", "trackName":"Surviving El Chapo: The Twins Who Brought Down A Drug Lord", "collectionCensoredName":"Surviving El Chapo: The Twins Who Brought Down A Drug Lord", "trackCensoredName":"Surviving El Chapo: The Twins Who Brought Down A Drug Lord", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/surviving-el-chapo-the-twins-who-brought-down-a-drug-lord/id1649359487?uo=4", "feedUrl":"https://www.omnycontent.com/d/playlist/e73c998e-6e60-432f-8610-ae210140c5b1/a6b57093-81fe-4401-a963-af2000fe4e3a/56b7926c-6bfd-4d8c-81f9-af2000ffad49/podcast.rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/surviving-el-chapo-the-twins-who-brought-down-a-drug-lord/id1649359487?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/aa/90/0f/aa900f15-7a67-e498-7b8c-a8efc9a667b5/mza_18039635476170983620.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/aa/90/0f/aa900f15-7a67-e498-7b8c-a8efc9a667b5/mza_18039635476170983620.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/aa/90/0f/aa900f15-7a67-e498-7b8c-a8efc9a667b5/mza_18039635476170983620.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2023-12-06T08:00:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"explicit", "trackCount":26, "trackTimeMillis":3418, "country":"USA", "currency":"USD", "primaryGenreName":"Documentary", "contentAdvisoryRating":"Explicit", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts116/v4/aa/90/0f/aa900f15-7a67-e498-7b8c-a8efc9a667b5/mza_18039635476170983620.jpg/600x600bb.jpg", "genreIds":["1543", "26", "1324", "1488"], "genres":["Documentary", "Podcasts", "Society & Culture", "True Crime"]}, {"wrapperType":"track", "kind":"podcast", "collectionId":1380498912, "trackId":1380498912, "artistName":"Chapo", "collectionName":"CHAPOS Corner", "trackName":"CHAPOS Corner", "collectionCensoredName":"CHAPOS Corner", "trackCensoredName":"CHAPOS Corner", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/chapos-corner/id1380498912?uo=4", "feedUrl":"https://anchor.fm/s/3a15ad8/podcast/rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/chapos-corner/id1380498912?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/a5/08/da/a508da5c-09c7-bc4d-3c2d-938bf406b36b/mza_14501171467539691063.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/a5/08/da/a508da5c-09c7-bc4d-3c2d-938bf406b36b/mza_14501171467539691063.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/a5/08/da/a508da5c-09c7-bc4d-3c2d-938bf406b36b/mza_14501171467539691063.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2022-10-19T00:24:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":704, "trackTimeMillis":2226, "country":"USA", "currency":"USD", "primaryGenreName":"Society & Culture", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts125/v4/a5/08/da/a508da5c-09c7-bc4d-3c2d-938bf406b36b/mza_14501171467539691063.jpg/600x600bb.jpg", "genreIds":["1324", "26"], "genres":["Society & Culture", "Podcasts"]}, {"wrapperType":"track", "kind":"podcast", "collectionId":1571776758, "trackId":1571776758, "artistName":"Bernardo", "collectionName":"EL CHAPO", "trackName":"EL CHAPO", "collectionCensoredName":"EL CHAPO", "trackCensoredName":"EL CHAPO", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/el-chapo/id1571776758?uo=4", "feedUrl":"https://anchor.fm/s/5d83aab0/podcast/rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/el-chapo/id1571776758?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts115/v4/df/3b/2c/df3b2cbc-a8ac-315b-d5db-56d055690dfc/mza_8387652467730745876.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts115/v4/df/3b/2c/df3b2cbc-a8ac-315b-d5db-56d055690dfc/mza_8387652467730745876.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts115/v4/df/3b/2c/df3b2cbc-a8ac-315b-d5db-56d055690dfc/mza_8387652467730745876.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2021-06-22T21:50:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":10, "trackTimeMillis":236, "country":"USA", "currency":"USD", "primaryGenreName":"History", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts115/v4/df/3b/2c/df3b2cbc-a8ac-315b-d5db-56d055690dfc/mza_8387652467730745876.jpg/600x600bb.jpg", "genreIds":["1487", "26"], "genres":["History", "Podcasts"]}] } podcasts-25.2/podcasts-data/tests/itunes/search_empty.txt000066400000000000000000000000521500126606300237020ustar00rootroot00000000000000 { "resultCount":0, "results": [] } podcasts-25.2/podcasts-data/tests/itunes/search_unicode.txt000066400000000000000000000030641500126606300242000ustar00rootroot00000000000000 { "resultCount":1, "results": [ {"wrapperType":"track", "kind":"podcast", "collectionId":1454202971, "trackId":1454202971, "artistName":"The Späti Boys", "collectionName":"Corner Späti", "trackName":"Corner Späti", "collectionCensoredName":"Corner Späti", "trackCensoredName":"Corner Späti", "collectionViewUrl":"https://podcasts.apple.com/us/podcast/corner-sp%C3%A4ti/id1454202971?uo=4", "feedUrl":"https://feeds.fireside.fm/cornerspaeti/rss", "trackViewUrl":"https://podcasts.apple.com/us/podcast/corner-sp%C3%A4ti/id1454202971?uo=4", "artworkUrl30":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/99/a7/0e/99a70e93-30a1-6fba-d936-6637d3114faf/mza_12553069992613760697.jpg/30x30bb.jpg", "artworkUrl60":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/99/a7/0e/99a70e93-30a1-6fba-d936-6637d3114faf/mza_12553069992613760697.jpg/60x60bb.jpg", "artworkUrl100":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/99/a7/0e/99a70e93-30a1-6fba-d936-6637d3114faf/mza_12553069992613760697.jpg/100x100bb.jpg", "collectionPrice":0.00, "trackPrice":0.00, "collectionHdPrice":0, "releaseDate":"2024-03-07T09:00:00Z", "collectionExplicitness":"notExplicit", "trackExplicitness":"cleaned", "trackCount":349, "trackTimeMillis":86, "country":"USA", "currency":"USD", "primaryGenreName":"News", "contentAdvisoryRating":"Clean", "artworkUrl600":"https://is1-ssl.mzstatic.com/image/thumb/Podcasts122/v4/99/a7/0e/99a70e93-30a1-6fba-d936-6637d3114faf/mza_12553069992613760697.jpg/600x600bb.jpg", "genreIds":["1489", "26", "1303"], "genres":["News", "Podcasts", "Comedy"]}] } podcasts-25.2/podcasts-gtk/000077500000000000000000000000001500126606300156645ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/Cargo.toml000066400000000000000000000020671500126606300176210ustar00rootroot00000000000000[package] authors = ["Jordan Petridis "] name = "podcasts-gtk" version = "0.1.0" edition.workspace = true [dependencies] adw = { package = "libadwaita", version = "0.7", features = ["v1_6"] } anyhow = { workspace = true } async-channel = "2" chrono = { workspace = true } fragile = "2" futures-util = "0.3" gettext-rs = { version = "0.7", features = ["gettext-system"] } gst = { version = "0.23", package = "gstreamer" } gst-play = { version = "0.23", package = "gstreamer-play" } gtk = { package = "gtk4", version = "0.9", features = ["gnome_47"] } html2text = "0.12" html5ever = "0.27" humansize = "2" image = { version = "0.25", features = ["gif", "jpeg", "png", "webp"] } linkify = "0.10" locale_config = "0.3" log = { workspace = true } markup5ever_rcdom = "0.3" mpris-server = "0.8" once_cell = { workspace = true } open = "5" podcasts-data = { path = "../podcasts-data" } pretty_env_logger = "0.5" regex = "1" reqwest = { workspace = true, features = ["json"] } serde_json = "1" tempfile = "3" tokio = { workspace = true } url = { workspace = true } podcasts-25.2/podcasts-gtk/po/000077500000000000000000000000001500126606300163025ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/po/LINGUAS000066400000000000000000000002631500126606300173300ustar00rootroot00000000000000# please keep this list sorted alphabetically # be bg ca cs da de el en_GB es eu fa fi fr fur gl he hi hr hu id is it ka kab ko lv nl oc pl pt pt_BR ro ru sk sl sr sv tr uk zh_CN podcasts-25.2/podcasts-gtk/po/POTFILES.in000066400000000000000000000041471500126606300200650ustar00rootroot00000000000000# List of source files containing translatable strings. # Please keep this file sorted alphabetically. # ui files podcasts-gtk/resources/gtk/discovery_found_podcast.ui podcasts-gtk/resources/gtk/discovery_page.ui podcasts-gtk/resources/gtk/discovery_search_results.ui podcasts-gtk/resources/gtk/empty_show.ui podcasts-gtk/resources/gtk/empty_view.ui podcasts-gtk/resources/gtk/episode_description.ui podcasts-gtk/resources/gtk/episode_menu.ui podcasts-gtk/resources/gtk/episode_widget.ui podcasts-gtk/resources/gtk/help-overlay.ui podcasts-gtk/resources/gtk/home_episode.ui podcasts-gtk/resources/gtk/home_view.ui podcasts-gtk/resources/gtk/player_rate.ui podcasts-gtk/resources/gtk/player_sheet.ui podcasts-gtk/resources/gtk/player_toolbar.ui podcasts-gtk/resources/gtk/secondary_menu.ui podcasts-gtk/resources/gtk/show_menu.ui podcasts-gtk/resources/gtk/show_widget.ui podcasts-gtk/resources/gtk/window.ui # resources podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in # rust files podcasts-gtk/src/app.rs podcasts-gtk/src/download_covers.rs podcasts-gtk/src/episode_description_parser.rs podcasts-gtk/src/feed_manager.rs podcasts-gtk/src/main.rs podcasts-gtk/src/manager.rs podcasts-gtk/src/settings.rs podcasts-gtk/src/thumbnail_generator.rs podcasts-gtk/src/utils.rs podcasts-gtk/src/window.rs podcasts-gtk/src/widgets/aboutdialog.rs podcasts-gtk/src/widgets/base_view.rs podcasts-gtk/src/widgets/content_stack.rs podcasts-gtk/src/widgets/discovery_page.rs podcasts-gtk/src/widgets/discovery_search_results.rs podcasts-gtk/src/widgets/download_progress_bar.rs podcasts-gtk/src/widgets/empty_show.rs podcasts-gtk/src/widgets/empty_view.rs podcasts-gtk/src/widgets/episode.rs podcasts-gtk/src/widgets/episode_description.rs podcasts-gtk/src/widgets/episode_menu.rs podcasts-gtk/src/widgets/home_view.rs podcasts-gtk/src/widgets/mod.rs podcasts-gtk/src/widgets/player.rs podcasts-gtk/src/widgets/read_more_label.rs podcasts-gtk/src/widgets/show.rs podcasts-gtk/src/widgets/show_menu.rs podcasts-gtk/src/widgets/shows_view.rs podcasts-25.2/podcasts-gtk/po/be.po000066400000000000000000000563441500126606300172440ustar00rootroot00000000000000# Belarussian translation for podcasts app. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Yahor Haurylenka k1llo2810@gmail.com, 2022. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-03-09 19:07+0000\n" "PO-Revision-Date: 2025-03-22 07:35+0300\n" "Last-Translator: Yuras Shumovich \n" "Language-Team: Belarusian \n" "Language: be\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Эпізоды: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Апошняя публікацыя" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Падпісацца" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Падпісацца на канал…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Дадаць падкасты" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Пошук" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Увядзіце URL-адрас канала або шукайце на выбраных платформах." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Адправіць пошукавы запыт" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Загрузка…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Уключыце адну з платформ ніжэй або ўвядзіце http(s) URL-адрас канала." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Платформы для пошуку" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Пошукавы запыты будуць адпраўляцца на гэтыя платформы." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Вынікі пошуку" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Нічога не знойдзена." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "У гэтай перадачы пакуль няма эпізодаў" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Калі вы лічыце, што гэта памылка, напішыце паведамленне пра памылку." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Атрымайце перадачы" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Дадавайце новыя перадачы праз URL-адрас канала" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Імпартуйце перадачы з іншай прылады" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Звесткі пра эпізод" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Меню эпізодаў" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Назва падкасту" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Працягласць - Дата" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Трансляцыя" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Прайграць" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Спампаваць" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Скасаваць" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Выдаліць" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Апісанне эпізоду" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Вокладка эпізоду" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Перайсці да перадачы" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Скапіяваць Url эпізоду" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Адзначыць як прайграны" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Адзначыць як непрайграны" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Вы ўжо праслухалі гэты эпізод." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Разлік памеру эпізоду…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Прайграць гэты эпізод" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Скасаваць спампоўванне" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Спампаваць гэты эпізод" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Эпізод без аўдыя" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Навігацыя" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Перайсці на старонку «Хатняя»" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Перайсці на старонку «Перадачы»" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Перайсці на старонку «Агляд»" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Прайгравальнік" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Паўза/прайграванне" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Пракруціць наперад" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Пракруціць назад" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Агульныя" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Праверыць наяўнасць новых эпізодаў" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Выйсці з праграмы" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Імпартаваць падпіскі" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Экспартаваць падпіскі" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Сёння" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Учора" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "На гэтым тыдні" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "У гэтым месяцы" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Раней" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Змена хуткасці прайгравання" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Назад" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Прайграць" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Паўза" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Наперад" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Апісанне" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Перамотка на 10 секунд назад" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Перамотка наперад на 10 секунд" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Адзначыць усе эпізоды як прайграныя" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Вэб-сайт" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Адпісацца" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Адкрыць сайт" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Адзначыць усе як прайграныя" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Адпісацца" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Меню падкастаў" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Эпізоды" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Праверыць наяўнасць новых эпізодаў" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Імпартаваць перадачы" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Экспартаваць перадачы" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Спалучэнні клавіш" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "Пр_а Падкасты" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:505 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Падкасты" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Дадаць новы канал" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Галоўнае меню" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Перадача" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Слухайце свае ўлюбёныя перадачы" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Прайгравайце, абнаўляйце і кіруйце сваімі падкастамі праз лёгкі інтэрфейс, " "які цалкам інтэгруецца ў GNOME. Праграма можа прайграваць розныя " "аўдыяфарматы і запамінаць, дзе вы спынілі праслухоўванне. Вы можаце " "падпісацца на перадачы праз спасылкі RSS/Atom,iTunes і Soundcloud. Падпіскі " "з іншых сэрвісаў могуць быць імпартаваны праз файлы OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "На хатняй старонцы паказваюцца самыя новыя эпізоды вашых падкастаў" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "На старонцы «Перадачы» паказваюцца вокладкі выбраных вамі падкастаў" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "У раздзеле «Перадачы» паказваюцца вокладкі і апошнія эпізоды асобных " "падкастаў" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Старонка, на якой можна дадаць новы падкаст" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Распрацоўшчыкі Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Слухайце ўлюбёныя падкасты проста са свайго працоўнага стала." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;Падкаст;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Вышыня апошняга адкрытага галоўнага акна" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Шырыня апошняга адкрытага галоўнага акна" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Разгорнуты стан апошняга адкрытага галоўнага акна" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Ці трэба перыядычна абнаўляць змесціва" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Колькі перыядаў часу трэба чакаць паміж аўтаматычнымі абнаўленнямі" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Які перыяд часу павінен прайсці паміж аўтаматычнымі абнаўленнямі" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Ці абнаўляць змесціва пасля запуску" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Колькі перыядаў часу павінна прайсці паміж аўтаматычнымі ачысткамі" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Колькі перыядаў часу павінна прайсці паміж аўтаматычнымі ачысткамі" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL скапіяваны ў буфер абмену!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Перайсці на {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Перайсці на {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Не ўдалося спампаваць: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Не ўдалося падпісацца на канал: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML-файл" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Выберыце файл, з якога вы хочаце імпартаваць перадачы." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Імпартаваць" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Не ўдалося разабраць імпартаваны файл {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Экспарт перадач ў…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Экспартаваць" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Падпіскі на падкасты GNOME" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Не ўдалося экспартаваць падкасты" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Кліент падкастаў для працоўнага стала GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "Yahor Haurylenka , 2022" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Атрыманне новых эпізодаў…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Новыя" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Перадачы" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Ход выканання спампоўвання" #: podcasts-gtk/src/widgets/episode.rs:297 msgid "{} min" msgstr "{} хв" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Медыяплэер не змог выканаць дзеянне." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Разгортвае гэта апісанне" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Чытаць далей" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Усе эпізоды адзначаны як праслуханыя" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Скасаваць" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Вы адпісаліся ад {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Загрузка..." #~ msgid "Now Playing" #~ msgstr "Прайграецца" #~ msgid "Close" #~ msgstr "Закрыць" #~ msgid "Enter Feed Address" #~ msgstr "Увядзіце адрас канала" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Усплывальнае меню ( ESC, каб закрыць)" #~ msgid "Add" #~ msgstr "Дадаць" #~ msgid "Back" #~ msgstr "Назад" #~ msgid "Show Title" #~ msgstr "Паказаць загаловак" #~ msgid "Jordan Petridis" #~ msgstr "Джордан Петрыдзіс (Jordan Petridis)" #~ msgid "Julian Hofer" #~ msgstr "Джуліян Хофер (Julian Hofer)" #~ msgid "Get some shows" #~ msgstr "Атрымайце некалькі шоу" #~ msgid "Enter feed address to add" #~ msgstr "Увядзіце адрас канала для дадавання" #~ msgid "Selected file could not be accessed." #~ msgstr "Не ўдалося атрымаць доступ да выбранага файла." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Даведайцеся больш пра падкасты GNOME" #~ msgid "Top position of the last open main window" #~ msgstr "Верхняя пазіцыя апошняга адкрытага галоўнага акна" #~ msgid "Left position of the last open main window" #~ msgstr "Левая пазіцыя апошняга адкрытага галоўнага акна" #~ msgid "Enable or disable dark theme" #~ msgstr "Уключыць ці адключыць цёмную тэму" #~ msgid "An in-app action notification" #~ msgstr "Апавяшчэнне пра дзеянне ў дадатку" podcasts-25.2/podcasts-gtk/po/bg.po000066400000000000000000000542361500126606300172440ustar00rootroot00000000000000# Bulgarian translation for podcasts. # Copyright (C) 2024, 2025 twlvnn kraftwerk # This file is distributed under the same license as the podcasts package. # twlvnn kraftwerk , 2024, 2025. # msgid "" msgstr "" "Project-Id-Version: podcasts main\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-02-23 08:07+0000\n" "PO-Revision-Date: 2025-02-05 21:12+0100\n" "Last-Translator: twlvnn kraftwerk \n" "Language-Team: Bulgarian \n" "Language: bg\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-Generator: Gtranslator 47.1\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Епизоди: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Последна публикация" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Абониране" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Абониране за поток…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Добавяне на подкасти" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Търсене" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Въведете адрес на поток или търсете в избраните платформи." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Подаване на търсенето" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Зареждане…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Включете платформа за търсене по-долу или въведете http(s) адрес на поток." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Платформи за търсене" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Заявките за търсене ще бъдат изпращани към тези платформи." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Резултати от търсенето" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Няма намерени резултати." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Този подкаст все още няма епизоди" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Ако това е грешка, молим да я докладвате." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Добавяне на подкасти" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Добавяне на нов подкаст чрез адреса на потока" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Внасяне на подкасти от друго устройство" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Подробности за епизода" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Меню за епизоди" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Заглавие на подкаст" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Продължителност — дата" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Предаване" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Изпълнение" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "Из_тегляне" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Отказване" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Изтриване" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Описание на епизода" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Корица на епизод" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Към подкаст" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Копиране на адреса на епизода" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Отбелязване като слушано" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Отбелязване като неслушано" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Вече сте слушали този епизод." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Изчисляване на размера на епизода…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Пускане на епизода" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Отмяна на изтеглянето" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Изтегляне на епизода" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Епизод без звук" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Навигация" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Към началната страница" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Към страницата с епизодите" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Към страницата за нови подкасти" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Възпроизвеждане" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Пауза" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Превъртане напред" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Превъртане назад" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Общи" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Проверка за нови епизоди" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Спиране на програмата" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Внасяне на абонаменти" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Изнасяне на абонаменти" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Днес" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Вчера" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Тази седмица" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Този месец" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Стари" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Смяна на скоростта на възпроизвеждане" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Назад" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Изпълнение" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Пауза" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Напред" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Описание" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Назад с 10 секунди" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Напред с 10 секунди" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Отбелязване на всички епизоди като слушани" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Уеб страница" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Отписване" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Към уеб страницата" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Отбелязване на всички като слушани" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Отписване" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Меню за подкасти" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Епизоди" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Проверка за нови епизоди" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Внасяне на подкасти" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Изнасяне на подкасти" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Клавишни комбинации" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Относно „Подкасти“" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:505 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Подкасти" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Добавяне на нов поток" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Основно mеню" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Подкаст" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Слушайте любимите си подкасти" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Слушайте, актуализирайте, и управлявайте своите подкасти с лек интерфейс, " "който безпроблемно се интегрира с GNOME. „Подкасти“ може да възпроизвежда " "различни аудио формати и да помни къде точно сте спрeли да слушате. Можете " "да се абонирате за подкасти чрез RSS/Atom, iTunes и SoundCloud. Абонаментите " "от други приложения могат да бъдат качени чрез OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Начален изглед, показващ най-новите епизоди на вашите подкасти" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Страни на подкастите, показващ кориците на вашите подкасти" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Графичен елемент н, показващ корицата и най-новите епизоди на определен " "подкаст" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Изгледът, в който може да се добави нов подкаст" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Разработчици на „Подкасти“" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Слушайте любимите си подкасти, директно в работната среда." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;подкаст;рсс;шоу;радио;слушане;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Височина на последно отворения главен прозорец" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Широчина на последно отворения главен прозорец" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Максимизирано състояние на последно отворения главен прозорец" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Дали периодично да се проверява за ново съдържание" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Брой периоди от време между автоматичните проверки за ново съдържание" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Период от време между автоматичните проверки за ново съдържание" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Дали да се проверява за ново съдържание при стартиране на програмата" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Брой периоди от време между автоматичните почиствания" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Период от време между автоматичните почиствания" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "Адресът е копиран в буфера за обмен!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Към {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Към {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Неуспешно изтегляне: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Неуспешно абониране за потока: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "Файл OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Изберете файла, от който искате да внесете подскасти." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Внасяне" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Неуспешно зареждане на внесения файл {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Изнасяне на подкастите към…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Изнасяне" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-изнесени-подкасти" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Абонаменти на „Подкасти“" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Неуспешно изтегляне на подкастите" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Клиент за подкасти за GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Twlvnn Kraftwerk <
kraft_werk@tutanota.com>\n" "\n" "Проектът за превод на GNOME има нужда от подкрепа.\n" "Научете повече за нас на уеб сайта ни.\n" "Докладвайте за грешки в превода в съответния раздел." #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Изтегляне на потоци…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Ново" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Подкасти" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Напредък на изтегляне" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} мин" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Медийната програма не успя да изпълни действие." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Визуално разширяване на описанието" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Още информация" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Отбелязване на всички епизоди като слушани" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Отмяна" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Отписване от {}" podcasts-25.2/podcasts-gtk/po/ca.po000066400000000000000000000343221500126606300172310ustar00rootroot00000000000000# Catalan translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2022-07-22 18:29+0000\n" "PO-Revision-Date: 2022-07-24 11:48+0200\n" "Last-Translator: maite guix \n" "Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.1.1\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Aquest programa no té cap episodi encara" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "Si sospiteu que això és un error, considereu crear un informe." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get some shows" msgstr "Obté alguns programes" #: podcasts-gtk/resources/gtk/empty_view.ui:49 msgid "Add new shows via feed URL" msgstr "Afegeix programes nous mitjançant l'URL del canal" #: podcasts-gtk/resources/gtk/empty_view.ui:70 msgid "Import shows from another device" msgstr "Importa programes des d'un altre dispositiu" #: podcasts-gtk/resources/gtk/episode_description.ui:41 msgid "Episode Details" msgstr "Detalls de l'episodi" #: podcasts-gtk/resources/gtk/episode_description.ui:51 #: podcasts-gtk/resources/gtk/headerbar.ui:146 msgid "Back" msgstr "Enrere" #: podcasts-gtk/resources/gtk/episode_description.ui:105 msgid "Podcast Title" msgstr "Títol del podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:128 msgid "Duration - Date" msgstr "Durada - Data" #: podcasts-gtk/resources/gtk/episode_description.ui:148 msgid "Episode Description" msgstr "Descripció de l'episodi" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Anar al programa" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Copia l'url de l'episodi" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Ja us heu subscrit a aquest episodi." #: podcasts-gtk/resources/gtk/episode_widget.ui:157 msgid "Calculating episode size…" msgstr "S'està calculant la mida de l'episodi…" #: podcasts-gtk/resources/gtk/episode_widget.ui:180 msgid "Play this episode" msgstr "Reprodueix aquest episodi" #: podcasts-gtk/resources/gtk/episode_widget.ui:192 msgid "Cancel the download process" msgstr "Cancel·la el procés de baixada" #: podcasts-gtk/resources/gtk/episode_widget.ui:204 msgid "Download this episode" msgstr "Baixa aquest episodi" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Comprova si hi ha episodis nous" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Importa programes" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Exporta programes" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Dreceres de teclat" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_Quant a Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:33 #: podcasts-gtk/resources/gtk/headerbar.ui:133 msgid "Add a new feed" msgstr "Afegeix un canal nou" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter feed address to add" msgstr "Introduïu l'adreça del canal que s'ha d'afegir" #: podcasts-gtk/resources/gtk/headerbar.ui:67 msgid "Add" msgstr "Afegeix" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/headerbar.ui:111 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:455 podcasts-gtk/src/widgets/aboutdialog.rs:57 #: podcasts-gtk/src/window.rs:63 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:121 msgid "Show Title" msgstr "Títol del programa" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "General" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Comprova si hi ha episodis nous" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "Surt de l'aplicació" #: podcasts-gtk/resources/gtk/home_view.ui:50 msgid "Today" msgstr "Avui" #: podcasts-gtk/resources/gtk/home_view.ui:78 msgid "Yesterday" msgstr "Ahir" #: podcasts-gtk/resources/gtk/home_view.ui:106 msgid "This Week" msgstr "Aquesta setmana" #: podcasts-gtk/resources/gtk/home_view.ui:134 msgid "This Month" msgstr "Aquest mes" #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "Older" msgstr "Més antics" #: podcasts-gtk/resources/gtk/player_dialog.ui:10 msgid "Now Playing" msgstr "Ara jugant" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Canvia la velocitat de la reproducció" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75 ×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:62 msgid "Rewind 10 seconds" msgstr "Rebobina 10 segons" #: podcasts-gtk/resources/gtk/player_toolbar.ui:70 msgid "Play" msgstr "Reprodueix" #: podcasts-gtk/resources/gtk/player_toolbar.ui:78 msgid "Pause" msgstr "Posa en pausa" #: podcasts-gtk/resources/gtk/player_toolbar.ui:86 msgid "Fast forward 10 seconds" msgstr "Avança 10 segons" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marca tots els episodis com a reproduïts" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Lloc web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Cancel·la la subscripció" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Obre el lloc web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Marca'ls tots com a reproduïts" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Cancel·la la subscripció" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Escolteu els vostres programes predilectes" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Reprodueixi, actualitzi i gestioni els seus podcasts des d'una interfície " "lleugera que s'integra perfectament amb el GNOME. Els podcasts poden " "reproduir diversos formats d'àudio i recordar on vas deixar d'escoltar. Us " "podeu subscriure a programes mitjançant enllaços RSS / Atom, iTunes i " "Soundcloud. Les subscripcions d'altres aplicacions es poden importar " "mitjançant fitxers OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:134 msgid "Jordan Petridis" msgstr "Jordan Petridis" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:135 msgid "Julian Hofer" msgstr "Julian Hofer" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "" "Escolteu els vostres podcasts predilectes, sense sortir del vostre " "escriptori." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Alçada de l'última finestra principal oberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Amplada de l'última finestra principal oberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Estat de maximització de l'última finestra principal oberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Si s'ha d'actualitzar periòdicament el contingut" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "" "Quants períodes de temps esperar entre les actualitzacions automàtiques" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Quin període de temps esperar entre les actualitzacions automàtiques" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Si s'ha d'actualitzar el contingut després d'iniciar" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Quants períodes de temps esperar entre les neteges automàtiques" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Quin període de temps esperar entre les neteges automàtiques" #: podcasts-gtk/src/app.rs:335 msgid "Copied URL to clipboard!" msgstr "URL copiat al porta-retalls!" #: podcasts-gtk/src/stacks/content.rs:58 msgid "New" msgstr "Nous" #: podcasts-gtk/src/stacks/content.rs:60 msgid "Shows" msgstr "Programes" #: podcasts-gtk/src/utils.rs:490 msgid "Select the file from which to you want to import shows." msgstr "Seleccioneu el fitxer des del qual voleu importar els programes." #: podcasts-gtk/src/utils.rs:493 msgid "_Import" msgstr "_Importa" #: podcasts-gtk/src/utils.rs:501 podcasts-gtk/src/utils.rs:553 msgid "OPML file" msgstr "Fitxer OPML" #: podcasts-gtk/src/utils.rs:519 msgid "Failed to parse the imported file" msgstr "No s'ha pogut analitzar el fitxer importat" #: podcasts-gtk/src/utils.rs:525 podcasts-gtk/src/utils.rs:574 msgid "Selected file could not be accessed." msgstr "No s'ha pogut accedir al fitxer seleccionat." #: podcasts-gtk/src/utils.rs:539 msgid "Export shows to…" msgstr "Exporta programes a…" #: podcasts-gtk/src/utils.rs:542 msgid "_Export" msgstr "_Exporta" #: podcasts-gtk/src/utils.rs:543 msgid "_Cancel" msgstr "_Cancel·la" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:549 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-programes-exportats" #: podcasts-gtk/src/utils.rs:567 msgid "GNOME Podcasts Subscriptions" msgstr "Subscripcions als podcasts del GNOME" #: podcasts-gtk/src/utils.rs:568 msgid "Failed to export podcasts" msgstr "No s'han pogut exportar podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Client de podcasts per a l'escriptori GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:59 msgid "Learn more about GNOME Podcasts" msgstr "Més informació sobre el GNOME Podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Adolfo Jayme Barrientos , 2018\n" "Maite Guix i Ribé , 2022" #: podcasts-gtk/src/widgets/episode.rs:142 msgid "{} min" msgstr "{} m" #: podcasts-gtk/src/widgets/player.rs:890 msgid "The media player was unable to execute an action." msgstr "El reproductor multimèdia ha sigut incapaç d'executar cap acció." #: podcasts-gtk/src/widgets/read_more_label.rs:31 msgid "Read More" msgstr "Llegir més" #: podcasts-gtk/src/widgets/show_menu.rs:173 msgid "Marked all episodes as listened" msgstr "S'han marcat tots els episodis com a escoltats" #: podcasts-gtk/src/widgets/show_menu.rs:174 #: podcasts-gtk/src/widgets/show_menu.rs:197 msgid "Undo" msgstr "Desfés" #: podcasts-gtk/src/widgets/show_menu.rs:193 msgid "Unsubscribed from {}" msgstr "Heu cancel·lat la subscripció a {}" #~ msgid "Top position of the last open main window" #~ msgstr "Posició superior de l'última finestra principal oberta" #~ msgid "Left position of the last open main window" #~ msgstr "Posició esquerra de l'última finestra principal oberta" #~ msgid "Enable or disable dark theme" #~ msgstr "Habilita o inhabilita el tema fosc" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicació de podcasts per al GNOME" #~ msgid "_Preferences" #~ msgstr "_Preferències" #~ msgid "_About" #~ msgstr "_Quant a" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Ja us heu subscrit a aquest canal." #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Preferències" #~ msgid "An in-app action notification" #~ msgstr "Una notificació d'acció dins l'aplicació" #~ msgid "1.5 speed rate" #~ msgstr "Velocitat d'1,5" #~ msgid "1.25 speed rate" #~ msgstr "Velocitat d'1,25" #~ msgid "Normal speed" #~ msgstr "Velocitat normal" #~ msgid "Preferences" #~ msgstr "Preferències" #~ msgid "Appearance" #~ msgstr "Aparença" #~ msgid "Dark Theme" #~ msgstr "Tema fosc" #~ msgid "Delete played episodes" #~ msgstr "Suprimeix els episodis reproduïts" #~ msgid "After" #~ msgstr "Després" #~ msgid "Fetching new episodes" #~ msgstr "S'estan recuperant els episodis nous" #~ msgid "Invalid URL" #~ msgstr "L'URL no és vàlid" #~ msgid "Seconds" #~ msgstr "Segons" #~ msgid "Minutes" #~ msgstr "Minuts" #~ msgid "Hours" #~ msgstr "Hores" #~ msgid "Days" #~ msgstr "Dies" #~ msgid "Weeks" #~ msgstr "Setmanes" podcasts-25.2/podcasts-gtk/po/cs.po000066400000000000000000000475431500126606300172640ustar00rootroot00000000000000# Czech translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # # Marek Černocký , 2018, 2020, 2021, 2022. # Václav Koterec , 2024. # msgid "" msgstr "" "Project-Id-Version: podcasts\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-08-28 20:03+0000\n" "PO-Revision-Date: 2024-09-17 18:54+0200\n" "Last-Translator: Daniel Rusek \n" "Language-Team: čeština \n" "Language: cs\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>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Epizody: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:114 msgid "0" msgstr "0" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Poslední publikace" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "Subscribe" msgstr "Přihlásit k odběru" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:171 msgid "Subscribing to feed..." msgstr "Přihlášení k odběru ze zdroje..." #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Přidat podcasty" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Vyhledat" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Zadejte adresu URL nebo prohledejte vybrané platformy." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Odeslat hledání" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 msgid "Loading..." msgstr "Načítání..." #: podcasts-gtk/resources/gtk/discovery_page.ui:107 msgid "Loading" msgstr "Načítání" #: podcasts-gtk/resources/gtk/discovery_page.ui:121 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Níže povolte vyhledávací platformu nebo zadejte http(s) adresu URL." #: podcasts-gtk/resources/gtk/discovery_page.ui:133 msgid "Search Platforms" msgstr "Vyhledávací platformy" #: podcasts-gtk/resources/gtk/discovery_page.ui:134 msgid "Search queries will be sent to these platforms." msgstr "Na tyto platformy budou odesílány vyhledávací dotazy." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Výsledky vyhledávání" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Nebyly nalezeny žádné výsledky." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Pořad zatím nemá žádné epizody" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Pokud si myslíte, že se jedná o chybu, zvažte její nahlášení." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Doplňte si pořady" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Přidat nové pořady pomocí adresy URL kanálu" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Naimportovat pořady z jiného zařízení" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Podrobnosti o epizodě" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Nabídka epizody" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Název podcastu" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Délka – datum" #: podcasts-gtk/resources/gtk/episode_description.ui:158 msgid "Stream" msgstr "Stream" #: podcasts-gtk/resources/gtk/episode_description.ui:186 #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Přehrát" #: podcasts-gtk/resources/gtk/episode_description.ui:214 msgid "Download" msgstr "Stáhnout" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Cancel" msgstr "Zrušit" #: podcasts-gtk/resources/gtk/episode_description.ui:279 msgid "Delete" msgstr "Smazat" #: podcasts-gtk/resources/gtk/episode_description.ui:299 msgid "Episode Description" msgstr "Popis epizody" #: podcasts-gtk/resources/gtk/episode_description.ui:320 msgid "Episode Cover" msgstr "Obálka epizody" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Přejít na pořad" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Zkopírovat adresu URL epizody" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Tuto epizodu jste již poslouchali." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Počítá se velikost epizody…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Přehrát tuto epizodu" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Zrušit probíhající stahování" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Stáhnout tuto epizodu" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Epizoda bez zvuku" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigace" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Přejít na domovskou stránku" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Přejít na pořad" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Přejít na vyhledávací stránku" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Přehrávač" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Pozastavit" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Přesunout dopředu" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Přesunout zpět" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Obecné" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Vyhledat nové epizody" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Ukončit aplikaci" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Import odběrů" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Export odběrů" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Dnes" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Včera" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Tento týden" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Tento měsíc" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Starší" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Změnit rychlost přehrávání" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Přetočit" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pozastavit" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Vpřed" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Převinout zpět o 10 sekund" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Posunou rychle vpřed o 10 sekund" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Oz_načit všechny epizody jako přehrané" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Webové stránky" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "O_dhlásit odběr" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Otevřít webové stránky" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Označit vše jako přehrané" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Odhlásit odběr" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Nabídka podcastu" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Epizody" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "Vy_hledat nové epizody" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "Na_importovat pořady" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "Vy_exportovat pořady" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Klávesové zkratky" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "O _aplikaci Podcasty" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:470 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:445 msgid "Podcasts" msgstr "Podcasty" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Přidat nový kanál" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Hlavní nabídka" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Pořad" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Poslouchejte své oblíbené pořady" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Přehrávejte si, aktualizujte a spravujte své podcasty v lehkém uživatelském " "rozhraní, které je hladce zaintegrováno do GNOME. Aplikace Podcasty umí " "přehrávat různé zvukové formáty a pamatuje si, kde jste přerušili poslech. " "Pořady můžete odebírat přes RSS/Atom, iTunes a odkazy Soundcloud. Odběry " "nastavené v jiných aplikacích lze naimportovat přes soubory OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Domovské zobrazení zobrazující nejnovější epizody vašich podcastů" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Zobrazení pořadů zobrazující obálky vašich podcastů" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Widget show zobrazující obálku a nejnovější epizody konkrétního podcastu" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Zobrazení, kde lze přidat nový podcast" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:186 msgid "The Podcasts developers" msgstr "Vývojáři aplikace Podcasty" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Poslouchejte své oblíbené podcasty přímo z počítače." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;pořad;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Výška naposledy otevřeného hlavního okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Šířka naposledy otevřeného hlavního okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Stav maximalizace naposledy otevřeného hlavního okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Zda pravidelně aktualizovat obsah" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Kolik časových jednotek čekat mezi automatickými aktualizacemi" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Jaká je časová jednotka pro čekání mezi automatickými aktualizacemi" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Zda aktualizovat obsah po spuštění" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Kolik časových jednotek čekat mezi automatickými vymazáními" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Jaká je časová jednotka pro čekání mezi automatickými vymazáními" #: podcasts-gtk/src/app.rs:344 msgid "Copied URL to clipboard!" msgstr "Adresa URL byla zkopírována do schránky!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Přeskočit na {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Přeskočit na {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Stahování selhalo: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Selhalo přihlášení k odběru zdroje: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "Soubor OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Vyberte soubor pro import pořadu." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "Na_importovat" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Selhalo zpracování naimportovaného souboru {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Export pořadu do…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "Vy_exportovat" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "pořad-exportovaný-z-podcastů-gnome" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Odběry z Podcastů GNOME" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Export podcastu selhal" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcastový klient pro uživatelské prostředí GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Marek Černocký \n" "Václav Koterec " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Stahují se zdroje…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Nové" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Pořady" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Průběh stahování" #: podcasts-gtk/src/widgets/episode.rs:282 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1070 msgid "The media player was unable to execute an action." msgstr "Multimediální přehrávač nebyl schopen provést žádnou činnost." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Vizuálně rozšiřuje tento popis" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Číst dále" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Všechny epizody byly označené jak přehrané" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Zpět" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Byl odhlášen odběr z {}" #~ msgid "Top position of the last open main window" #~ msgstr "Horní souřadnice naposledy otevřeného hlavního okna" #~ msgid "Left position of the last open main window" #~ msgstr "Levá souřadnice naposledy otevřeného hlavního okna" #~ msgid "Enable or disable dark theme" #~ msgstr "Vypnutý nebo zapnutý tmavý motiv" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Back" #~ msgstr "Zpět" #~ msgid "Enter feed address to add" #~ msgstr "Zadejte adresu kanálu, který chcete přidat" #~ msgid "Add" #~ msgstr "Přidat" #~ msgid "Show Title" #~ msgstr "Název pořadu" #~ msgid "An in-app action notification" #~ msgstr "Oznámení o činnosti v aplikaci" #~ msgid "Now Playing" #~ msgstr "Právě se přehrává" #~ msgid "Selected file could not be accessed." #~ msgstr "K vybranému souboru není přístup" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Dozvědět se více o Podcastech GNOME" podcasts-25.2/podcasts-gtk/po/da.po000066400000000000000000000505771500126606300172440ustar00rootroot00000000000000# Danish translation for podcasts. # Copyright (C) 2019 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # scootergrisen, 2019. # Alan Mortensen , 2022–25. # # Show = udsendelse # Podcasts = Podcasts (programmet) eller podcast (flertalsform for ikke at forveksle med programmets navn) msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-02-16 15:58+0100\n" "Last-Translator: Alan Mortensen \n" "Language-Team: Danish \n" "Language: da\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-Generator: Poedit 3.4.2\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Episoder: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Sidste udgivelse" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "A_bonnér" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Abonnerer på feed …" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Tilføj podcasts" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Søg" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Indtast URL til feed eller søg på de valgte platforme." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Send søgning" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Indlæser …" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Aktivér en søgeplatform nedenfor eller indtast en http(s)-URL til feedet." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Søg platforme" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Søgninger vil blive sendt til disse platforme." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Søgeresultater" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Ingen resultater fundet." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Udsendelsen har endnu ikke nogen episoder" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Hvis du tror det er en fejl, så overvej venligst at skrive en fejlrapport." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Hent nogle udsendelser" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Tilføj nye udsendelser via feed-URL" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Importér udsendelser fra en anden enhed" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Episodedetaljer" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Episodemenu" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Podcasttitel" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Varighed — dato" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Stream" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "A_fspil" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Download" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Annullér" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Slet" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Episodebeskrivelse" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Episodegrafik" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Gå til udsendelse" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Kopiér episodens URL" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Markér som afspillet" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Markér som ikke afspillet" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Du har allerede lyttet til episoden." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Udregner episodestørrelse …" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Afspil episoden" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Annuller downloadprocessen" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Download episoden" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Episode uden lyd" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigering" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Gå til hjemmeside" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Gå til udsendelsesside" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Gå til opdag-side" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Afspiller" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Pause til/fra" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Søg fremad" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Søg tilbage" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Generelt" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Søg efter nye episoder" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Afslut programmet" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Importér abonnementer" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Eksportér abonnementer" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "I dag" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "I går" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Denne uge" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Denne måned" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Ældre" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Skift afspilningshastigheden" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Spol tilbage" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Afspil" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pause" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Spol frem" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Beskrivelse" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Spol 10 sekunder tilbage" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Spol 10 sekunder fremad" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Markér alle episoder som afspillet" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Websted" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Afmeld abonnement" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Åbn websted" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Markér alle som afspillet" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Afmeld abonnement" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Podcastmenu" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episoder" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Søg efter nye episoder" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Importér udsendelser" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Eksportér udsendelser" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Tastaturgenveje" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Om Podcasts" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Tilføj et nyt feed" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Hovedmenu" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Udsendelse" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Lyt til dine yndlingsudsendelser" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Afspil, opdatér og håndtér dine podcast fra en enkel grænseflade som sømløst " "integrerer med GNOME. Podcasts kan afspille forskellige lydformater og " "huske, hvor du nåede til i din lytning. Du kan abonnere på udsendelser via " "RSS/Atom, iTunes og Soundcloud-links. Abonnementer fra andre programmer kan " "importeres via OPML-filer." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Hjemmeoversigten viser dine podcasts' nyeste episoder" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Udsendelsesoversigten viser grafik for dine podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Udsendelseskontrollen viser grafik og seneste episoder for en specifik " "podcast" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Oversigten hvor man kan tilføje en ny podcast" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Udviklerne af Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Lyt til dine favoritpodcasts fra dit skrivebord." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Højde på hovedvindue som sidst var åbent" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Bredde på hovedvindue som sidst var åbent" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Maksimeret tilstand på hovedvindue som sidst var åbent" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Om indholdet jævnligt skal opdateres" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Hvor lang tid der skal gå mellem automatiske opdateringer" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Hvor lang tid der skal gå mellem automatiske opdateringer" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Om indholdet skal opdateres efter opstart" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Hvor lang tid der skal gå mellem automatiske oprydninger" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Hvor lang tid der skal gå mellem automatiske oprydninger" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "Kopierede URL'en til udklipsholderen!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Hop til {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Hop til {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Download mislykkedes: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Kunne ikke abonnere på feed: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML-fil" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Vælg den fil du vil importere udsendelser fra." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Importér" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Kunne ikke fortolke den importerede fil {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Eksportér udsendelser til …" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Eksportér" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-eksporterede-udsendelser" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Abonneringer for GNOME Podcasts" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Kunne ikke eksportere podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcast-klient til GNOME-skrivebordet." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "scootergrisen\n" "Alan mortensen\n" "\n" "Dansk-gruppen\n" "Websted http://dansk-gruppen.dk\n" "E-mail " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Henter feed …" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Nyt" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Udsendelser" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Downloadstatus" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Medieafspilleren kunne ikke udføre en handling." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Udvider denne beskrivelse visuelt" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Læs mere" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Markerede alle episoder som hørt" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Fortryd" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Afmeldte abonnement fra {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Indlæser …" #~ msgid "Enter Feed Address" #~ msgstr "Indtast adresse på feed" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Pop-over-menu (ESC for at lukke)" #~ msgid "Add" #~ msgstr "Tilføj" #~ msgid "Now Playing" #~ msgstr "Afspiller nu" #~ msgid "Close" #~ msgstr "Luk" #~ msgid "Back" #~ msgstr "Tilbage" #~ msgid "Show Title" #~ msgstr "Vis titel" #~ msgid "Top position of the last open main window" #~ msgstr "Øverste placering af hovedvindue som sidst var åbent" #~ msgid "Left position of the last open main window" #~ msgstr "Venstre placering af hovedvindue som sidst var åbent" #~ msgid "Enable or disable dark theme" #~ msgstr "Aktivér eller deaktivér mørkt tema" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "An in-app action notification" #~ msgstr "En notifikation om programindbygget handling" #~ msgid "Selected file could not be accessed." #~ msgstr "Kunne ikke tilgå den valgte fil." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Lær mere om GNOME Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Podcast-program til GNOME" #~ msgid "1.5 speed rate" #~ msgstr "1,5 hastighed" #~ msgid "1.25 speed rate" #~ msgstr "1,25 hastighed" #~ msgid "Normal speed" #~ msgstr "Normal hastighed" #~ msgid "You are already subscribed to this show" #~ msgstr "Du har allerede abonneret på udsendelsen" #~ msgid "Invalid URL" #~ msgstr "Ugyldig URL" podcasts-25.2/podcasts-gtk/po/de.po000066400000000000000000000452141500126606300172400ustar00rootroot00000000000000# German translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Mario Blättermann , 2018. # Tim Sabsch , 2018-2019. # Philipp Kiemle , 2021, 2023. # Jürgen Benvenuti , 2023, 2024. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-04-06 09:46+0000\n" "PO-Revision-Date: 2024-04-08 20:14+0200\n" "Last-Translator: Jürgen Benvenuti \n" "Language-Team: Deutsch \n" "Language: 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-Generator: Poedit 3.4.2\n" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Dieser Podcast hat noch keine Folgen" #: podcasts-gtk/resources/gtk/empty_show.ui:26 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Falls Sie meinen, dass es sich um einen Fehler handeln könnte, schreiben Sie " "bitte einen Fehlerbericht." # Das ist eine Überschrift für eine Ansicht, wenn noch keine Podcasts abonniert wurden. Darunter finden sich Hinweise, wie neue Podcasts hinzugefügt werden können - pk #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Abonnieren Sie ein paar Podcasts" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Neue Podcasts mittels Quellen-Adresse hinzufügen" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Podcasts von einem anderen Gerät importieren" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Folgendetails" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Folgenmenü" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Podcast-Titel" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Dauer - Datum" #: podcasts-gtk/resources/gtk/episode_description.ui:153 msgid "Download" msgstr "Herunterladen" #: podcasts-gtk/resources/gtk/episode_description.ui:163 msgid "Cancel" msgstr "Abbrechen" #: podcasts-gtk/resources/gtk/episode_description.ui:173 msgid "Delete File" msgstr "Datei löschen" #: podcasts-gtk/resources/gtk/episode_description.ui:183 msgid "Stream" msgstr "Streamen" #: podcasts-gtk/resources/gtk/episode_description.ui:193 #: podcasts-gtk/resources/gtk/episode_description.ui:195 #: podcasts-gtk/resources/gtk/player_dialog.ui:173 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:262 msgid "Play" msgstr "Wiedergeben" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Episode Description" msgstr "Folgenbeschreibung" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Zum Podcast gehen" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Adresse der Folge kopieren" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Sie haben sich diese Folge bereits angehört." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Größe der Folge wird ermittelt …" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Diese Folge abspielen" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Herunterladen abbrechen" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Diese Folge herunterladen" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Folge ohne Ton" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "Auf neue Folgen _prüfen" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/hamburger.ui:13 msgid "_Import Shows" msgstr "Podcasts _importieren" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/hamburger.ui:18 msgid "_Export Shows" msgstr "Podcasts e_xportieren" #: podcasts-gtk/resources/gtk/hamburger.ui:24 msgid "_Keyboard Shortcuts" msgstr "Tasten_kürzel" #: podcasts-gtk/resources/gtk/hamburger.ui:32 msgid "_About Podcasts" msgstr "Info zu _Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:41 msgid "Add a new feed" msgstr "Neue Quelle hinzufügen" #: podcasts-gtk/resources/gtk/headerbar.ui:52 msgid "Main Menu" msgstr "Hauptmenü" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigation" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Zur Startseite gehen" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Zur Podcasts-Seite gehen" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Zur Entdecken-Seite gehen" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Medienwiedergabe" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Pause ein/ausschalten" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Vorspulen" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Zurückspulen" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Allgemein" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Auf neue Folgen prüfen" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Die Anwendung beenden" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Abonnements importieren" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Abonnements exportieren" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Heute" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Gestern" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Diese Woche" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Diesen Monat" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Älter" #: podcasts-gtk/resources/gtk/player_dialog.ui:7 msgid "Now Playing" msgstr "Läuft gerade" #: podcasts-gtk/resources/gtk/player_dialog.ui:28 msgid "Close" msgstr "Schließen" #: podcasts-gtk/resources/gtk/player_dialog.ui:152 msgid "Rewind" msgstr "Zurückspulen" #: podcasts-gtk/resources/gtk/player_dialog.ui:191 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:271 msgid "Pause" msgstr "Pause" #: podcasts-gtk/resources/gtk/player_dialog.ui:214 msgid "Forward" msgstr "Vorspulen" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Die Wiedergabegeschwindigkeit ändern" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "10 Sekunden zurückspulen" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "10 Sekunden vorspulen" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Alle Folgen als angehört _markieren" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Internetseite" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "Ab_bestellen" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Internetseite öffnen" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Alle als angehört markieren" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Abbestellen" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Podcast-Menü" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Folgen" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:6 #: podcasts-gtk/resources/gtk/window.ui:25 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:496 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:380 msgid "Podcasts" msgstr "Podcasts" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:35 msgid "Show" msgstr "Podcast" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Hören Sie Ihre Lieblingssendungen" # Ziemlich frei übersetzt, aber wenn ich "Play" hier mit "abspielen" oder "wiedergeben" übersetze, kommen mir das "ab" und "wieder" in die Quere. #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Hören, aktualisieren und verwalten Sie Ihre Podcasts aus einer " "leichtgewichtigen Oberfläche, die sich nahtlos in die GNOME-Umgebung " "einfügt. Podcasts kann verschiedene Audio-Formate abspielen und merkt sich, " "wo Sie beim Zuhören gestoppt haben. Sie können Sendungen via RSS/Atom, " "iTunes und Soundcloud-Verweisen abonnieren. Abonnements von anderen " "Programmen können via OPML-Datei importiert werden." #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:151 msgid "The Podcasts developers" msgstr "Die Podcasts-Entwickler" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Hören Sie Ihre Lieblings-Podcasts direkt in Ihrer Arbeitsumgebung." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Höhe des zuletzt geöffneten Hauptfensters" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Breite des zuletzt geöffneten Hauptfensters" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Maximierungsstatus des zuletzt geöffneten Hauptfensters" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "" "Legt fest, ob Inhalte in regelmäßigen Abständen aktualisiert werden sollen" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Zeitintervall zwischen automatischen Aktualisierungsvorgängen" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Zeitintervall zwischen automatischen Aktualisierungsvorgängen" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Legt fest, ob Inhalte beim Programmstart aktualisiert werden sollen" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Zeitintervall zwischen automatischen Löschvorgängen" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Zeitintervall zwischen automatischen Löschvorgängen" #: podcasts-gtk/src/app.rs:338 msgid "Copied URL to clipboard!" msgstr "Adresse in die Zwischenablage kopiert!" #: podcasts-gtk/src/manager.rs:105 msgid "Download failed: {}" msgstr "Herunterladen fehlgeschlagen: {}" #: podcasts-gtk/src/stacks/content.rs:66 msgid "New" msgstr "Neu" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/stacks/content.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:65 msgid "Shows" msgstr "Podcasts" #: podcasts-gtk/src/utils.rs:482 podcasts-gtk/src/utils.rs:522 msgid "OPML file" msgstr "OPML-Datei" #: podcasts-gtk/src/utils.rs:493 msgid "Select the file from which to you want to import shows." msgstr "Wählen Sie die Datei aus, aus der Sie Podcasts importieren wollen." #: podcasts-gtk/src/utils.rs:495 msgid "_Import" msgstr "_Importieren" #: podcasts-gtk/src/utils.rs:512 msgid "Failed to parse the imported file {}" msgstr "Die importierte Datei {} konnte nicht ausgewertet werden" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:533 msgid "Export shows to…" msgstr "Podcasts exportieren nach …" #: podcasts-gtk/src/utils.rs:534 msgid "_Export" msgstr "E_xportieren" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:538 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-sendungen-export" #: podcasts-gtk/src/utils.rs:547 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcasts Abonnements" #: podcasts-gtk/src/utils.rs:551 msgid "Failed to export podcasts" msgstr "Export der Podcasts ist fehlgeschlagen" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcast-Anwendung für die GNOME-Arbeitsumgebung." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Mario Blättermann \n" "Tim Sabsch \n" "Onno Giesmann \n" "Philipp Kiemle \n" "Jürgen Benvenuti " #: podcasts-gtk/src/widgets/episode.rs:268 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:985 msgid "The media player was unable to execute an action." msgstr "Die Medienwiedergabe konnte eine Aktion nicht ausführen." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Erweitert diese Beschreibung visuell" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Weiterlesen" #: podcasts-gtk/src/widgets/show_menu.rs:170 msgid "Marked all episodes as listened" msgstr "Alle Folgen als angehört markieren" #: podcasts-gtk/src/widgets/show_menu.rs:171 #: podcasts-gtk/src/widgets/show_menu.rs:194 msgid "Undo" msgstr "Rückgängig" #: podcasts-gtk/src/widgets/show_menu.rs:190 msgid "Unsubscribed from {}" msgstr "Abonnement von {} gekündigt" #~ msgid "Back" #~ msgstr "Zurück" #~ msgid "Enter Feed Address" #~ msgstr "Quellen-Adresse eingeben" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Einblenddialog-Menü (Esc zum Schließen)" #~ msgid "Add" #~ msgstr "Hinzufügen" #~ msgid "Show Title" #~ msgstr "Titel anzeigen" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Top position of the last open main window" #~ msgstr "Obere Position des zuletzt geöffneten Hauptfensters" #~ msgid "Left position of the last open main window" #~ msgstr "Linke Position des zuletzt geöffneten Hauptfensters" #~ msgid "Enable or disable dark theme" #~ msgstr "Dunkles Thema aktivieren oder deaktivieren" #~ msgid "An in-app action notification" #~ msgstr "Eine anwendungsinterne Aktionsmeldung" #~ msgid "Fetching new episodes" #~ msgstr "Neue Folgen werden abgerufen" #~ msgid "Selected file could not be accessed." #~ msgstr "Auf die gewählte Datei konnte nicht zugegriffen werden." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Erfahren Sie mehr über GNOME Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Podcast-Anwendung für GNOME" #~ msgid "Double speed rate" #~ msgstr "Doppelte Geschwindigkeit" #~ msgid "1.75 speed rate" #~ msgstr "1,75-fache Geschwindigkeit" #~ msgid "1.5 speed rate" #~ msgstr "1,5-fache Geschwindigkeit" #~ msgid "1.25 speed rate" #~ msgstr "1,25-fache Geschwindigkeit" #~ msgid "Normal speed" #~ msgstr "Normale Geschwindigkeit" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Einstellungen" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Sie haben diese Quelle bereits abonniert!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Einstellungen" #~ msgid "Preferences" #~ msgstr "Einstellungen" #~ msgid "Appearance" #~ msgstr "Erscheinungsbild" #~ msgid "Dark Theme" #~ msgstr "Dunkles Thema" #~ msgid "Delete played episodes" #~ msgstr "Angehörte Folgen löschen" #~ msgid "After" #~ msgstr "Nach" #~ msgid "Invalid URL" #~ msgstr "Ungültige Adresse" #~ msgid "Seconds" #~ msgstr "Sekunden" #~ msgid "Minutes" #~ msgstr "Minuten" #~ msgid "Hours" #~ msgstr "Stunden" #~ msgid "Days" #~ msgstr "Tage" #~ msgid "Weeks" #~ msgstr "Wochen" #~ msgid "_About" #~ msgstr "I_nfo" podcasts-25.2/podcasts-gtk/po/el.po000066400000000000000000000375211500126606300172520ustar00rootroot00000000000000# Greek translation for podcasts. # Copyright (C) 2023 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Efstathios Iosifidis , 2023. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2023-10-19 20:18+0000\n" "PO-Revision-Date: 2024-01-02 00:39+0200\n" "Last-Translator: Efstathios Iosifidis \n" "Language-Team: Greek \n" "Language: el\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-Generator: Poedit 3.4\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Αυτή η εκπομπή δεν έχει ακόμα επεισόδια" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "Αν πιστεύετε ότι αυτό είναι ένα σφάλμα, παρακαλούμε σκεφτείτε να γράψετε μια αναφορά σφάλματος." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get Some Shows" msgstr "Αποκτήστε μερικές εκπομπές" #: podcasts-gtk/resources/gtk/empty_view.ui:50 msgid "Add new shows via feed URL" msgstr "Προσθέστε νέες εκπομπές μέσω URL τροφοδοσίας" #: podcasts-gtk/resources/gtk/empty_view.ui:73 msgid "Import shows from another device" msgstr "Εισαγωγή εκπομπών από άλλη συσκευή" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Λεπτομέρειες Επεισοδίου" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Μενού επεισοδίου" #: podcasts-gtk/resources/gtk/episode_description.ui:90 msgid "Podcast Title" msgstr "Τίτλος Podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:113 msgid "Duration - Date" msgstr "Διάρκεια - Ημερομηνία" #: podcasts-gtk/resources/gtk/episode_description.ui:133 msgid "Episode Description" msgstr "Περιγραφή επεισοδίου" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Μετάβαση στην εκπομπή" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Αντιγραφή URL επεισοδίου" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Έχετε ήδη ακούσει αυτό το επεισόδιο." #: podcasts-gtk/resources/gtk/episode_widget.ui:157 msgid "Calculating episode size…" msgstr "Υπολογισμός μεγέθους επεισοδίου…" #: podcasts-gtk/resources/gtk/episode_widget.ui:180 msgid "Play this episode" msgstr "Αναπαραγωγή αυτού του επεισοδίου" #: podcasts-gtk/resources/gtk/episode_widget.ui:192 msgid "Cancel the download process" msgstr "Ακύρωση διαδικασίας λήψης" #: podcasts-gtk/resources/gtk/episode_widget.ui:204 msgid "Download this episode" msgstr "Λήψη αυτού του επεισοδίου" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Έλεγχος για νέα επεισόδια" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Εισαγωγή εκπομπών" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Εξαγωγή εκπομπών" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Συντομεύσεις πληκτρολογίου" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_Περί Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:33 podcasts-gtk/resources/gtk/headerbar.ui:108 msgid "Add a new feed" msgstr "Προσθήκη νέας τροφοδοσίας" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter Feed Address" msgstr "Εισαγωγή διεύθυνσης τροφοδοσίας" #: podcasts-gtk/resources/gtk/headerbar.ui:59 msgid "Popover menu (ESC to close)" msgstr "Μενού αναδυόμενου παραθύρου (ESC για κλείσιμο)" #: podcasts-gtk/resources/gtk/headerbar.ui:71 msgid "Add" msgstr "Προσθήκη" #: podcasts-gtk/resources/gtk/headerbar.ui:118 msgid "Back" msgstr "Πίσω" #: podcasts-gtk/resources/gtk/headerbar.ui:128 msgid "Main Menu" msgstr "Κυρίως μενού" #: podcasts-gtk/resources/gtk/headerbar.ui:138 msgid "Podcast Menu" msgstr "Μενού Podcast" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "Γενικά" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Έλεγχος για νέα επισόδεια" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "Έξοδος από την εφαρμογή" #: podcasts-gtk/resources/gtk/home_view.ui:57 podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Σήμερα" #: podcasts-gtk/resources/gtk/home_view.ui:88 podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Χθες" #: podcasts-gtk/resources/gtk/home_view.ui:119 podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Αυτήν την εβδομάδα" #: podcasts-gtk/resources/gtk/home_view.ui:150 podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Αυτό το μήνα" #: podcasts-gtk/resources/gtk/home_view.ui:182 podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Παλαιότερα" #: podcasts-gtk/resources/gtk/player_dialog.ui:7 msgid "Now Playing" msgstr "Αναπαράγεται τώρα" #: podcasts-gtk/resources/gtk/player_dialog.ui:28 msgid "Close" msgstr "Κλείσιμο" #: podcasts-gtk/resources/gtk/player_dialog.ui:152 msgid "Rewind" msgstr "Μεταφορά πίσω" #: podcasts-gtk/resources/gtk/player_dialog.ui:173 podcasts-gtk/resources/gtk/player_toolbar.ui:56 #: podcasts-gtk/resources/gtk/player_toolbar.ui:232 msgid "Play" msgstr "Αναπαραγωγή" #: podcasts-gtk/resources/gtk/player_dialog.ui:191 podcasts-gtk/resources/gtk/player_toolbar.ui:64 #: podcasts-gtk/resources/gtk/player_toolbar.ui:241 msgid "Pause" msgstr "Παύση" #: podcasts-gtk/resources/gtk/player_dialog.ui:214 msgid "Forward" msgstr "Μπροστά" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Αλλαγή ταχύτητας αναπαραγωγής" #: podcasts-gtk/resources/gtk/player_rate.ui:34 podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:48 msgid "Rewind 10 seconds" msgstr "Μετάβαση πίσω κατά 10 δευτερόλεπτα" #: podcasts-gtk/resources/gtk/player_toolbar.ui:72 msgid "Fast forward 10 seconds" msgstr "Μετακίνηση μπροστά κατά 10 δευτερόλεπτα" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Σήμανση όλων των επεισοδίων ως αναπαραγμένα" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Ιστότοπος" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Κατάργηση συνδρομής" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Άνοιγμα ιστοτόπου" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Σήμανση όλων ως αναπαραγμένα" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Κατάργηση εγγραφής" #: podcasts-gtk/resources/gtk/show_widget.ui:80 msgid "Episodes" msgstr "Επεισόδια" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 podcasts-gtk/src/app.rs:380 #: podcasts-gtk/src/app.rs:510 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:371 podcasts-gtk/src/window.rs:64 podcasts-gtk/src/window.rs:91 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Ακούστε τις αγαπημένες σας εκπομπές" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that seamlessly integrates with " "GNOME. Podcasts can play various audio formats and remember where you stopped listening. You can " "subscribe to shows via RSS/Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Αναπαραγωγή, ενημέρωση και διαχείριση των podcast σας από μια ελαφριά διεπαφή που συνδυάζεται " "αρμονικά με το GNOME. Η εφαρμογή Podcasts μπορεί να αναπαράγει διάφορες μορφές ήχου και να θυμάται " "πού σταματήσατε την ακρόαση. Μπορείτε να εγγραφείτε σε εκπομπές μέσω συνδέσμων RSS/Atom, iTunes και " "Soundcloud. Οι εγγραφές από άλλες εφαρμογές μπορούν να εισαχθούν μέσω αρχείων OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:150 msgid "The Podcasts developers" msgstr "Οι προγραμματιστές του Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Ακούστε τα αγαπημένα σας podcasts απευθείας από τον υπολογιστή σας." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Ύψος του τελευταίου ανοιχτού κύριου παραθύρου" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Πλάτος του τελευταίου ανοιχτού κύριου παραθύρου" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Μεγιστοποιημένη κατάσταση του τελευταίου ανοιχτού κύριου παραθύρου" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Εάν να ανανεώνεται τακτικά το περιεχόμενο" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Πόσα χρονικά διαστήματα να περιμένει ανάμεσα στις αυτόματες ανανεώσεις" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Ποιο χρονικό διάστημα να περιμένει ανάμεσα στις αυτόματες ανανεώσεις" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Εάν να ανανεώνεται το περιεχόμενο μετά την εκκίνηση" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Πόσα χρονικά διαστήματα να περιμένει ανάμεσα στις αυτόματες εκκαθαρίσεις" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Ποιο χρονικό διάστημα να περιμένει ανάμεσα στις αυτόματες εκκαθαρίσεις" #: podcasts-gtk/src/app.rs:383 msgid "Copied URL to clipboard!" msgstr "Αντιγράφηκε ο σύνδεσμος στο πρόχειρο!" #: podcasts-gtk/src/stacks/content.rs:68 msgid "New" msgstr "Νέα" #: podcasts-gtk/src/stacks/content.rs:70 podcasts-gtk/src/widgets/shows_view.rs:55 msgid "Shows" msgstr "Εκπομπές" #: podcasts-gtk/src/utils.rs:499 podcasts-gtk/src/utils.rs:541 msgid "OPML file" msgstr "Αρχείο OPML" #: podcasts-gtk/src/utils.rs:510 msgid "Select the file from which you want to import shows." msgstr "Επιλέξτε το αρχείο από το οποίο θέλετε να εισαγάγετε εκπομπές." #: podcasts-gtk/src/utils.rs:512 msgid "_Import" msgstr "_Εισαγωγή" #: podcasts-gtk/src/utils.rs:528 msgid "Failed to parse the imported file" msgstr "Απέτυχε η ανάλυση του εισαχθέντος αρχείου" #: podcasts-gtk/src/utils.rs:551 msgid "Export shows to…" msgstr "Εξαγωγή εκπομπών προς…" #: podcasts-gtk/src/utils.rs:552 msgid "_Export" msgstr "_Εξαγωγή" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:556 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:566 msgid "GNOME Podcasts Subscriptions" msgstr "Εγγραφές GNOME Podcasts" #: podcasts-gtk/src/utils.rs:567 msgid "Failed to export podcasts" msgstr "Απέτυχε η εξαγωγή των podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Πελάτης Podcast για το Γραφικό Περιβάλλον GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Ελληνική μεταφραστική ομάδα GNOME\n" " Ευστάθιος Ιωσηφίδης \n" "\n" "Για περισσότερες πληροφορίες, επισκεφθείτε τη σελίδα\n" "http://gnome.gr/" #: podcasts-gtk/src/widgets/episode.rs:374 msgid "{} min" msgstr "{} λεπτά" #: podcasts-gtk/src/widgets/player.rs:887 msgid "The media player was unable to execute an action." msgstr "Ο αναπαραγωγέας πολυμέσων δεν μπόρεσε να εκτελέσει μια ενέργεια." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Αυξάνει οπτικά αυτήν την περιγραφή" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Διαβάστε Περισσότερα" #: podcasts-gtk/src/widgets/show_menu.rs:180 msgid "Marked all episodes as listened" msgstr "Σήμανση όλων των επεισοδίων ως αναπαραγμένα" #: podcasts-gtk/src/widgets/show_menu.rs:181 podcasts-gtk/src/widgets/show_menu.rs:204 msgid "Undo" msgstr "Αναίρεση" #: podcasts-gtk/src/widgets/show_menu.rs:200 msgid "Unsubscribed from {}" msgstr "Καταργήθηκε η εγγραφή από {}" podcasts-25.2/podcasts-gtk/po/en_GB.po000066400000000000000000000514201500126606300176160ustar00rootroot00000000000000# British English translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Zander Brown , 2018-2019. # Bruce Cowan , 2023-2024. # Andi Chandler , 2024. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-08-28 20:03+0000\n" "PO-Revision-Date: 2024-09-10 11:36+0100\n" "Last-Translator: Bruce Cowan \n" "Language-Team: English - United Kingdom \n" "Language: en_GB\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-Generator: Poedit 3.4.4\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 #| msgid "Episodes" msgid "Episodes: " msgstr "Episodes: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:114 msgid "0" msgstr "0" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 #| msgctxt "shortcut window" #| msgid "Quit the application" msgid "Last publication" msgstr "Last publication" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 #| msgid "Unsubscribe" msgid "Subscribe" msgstr "Subscribe" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:171 msgid "Subscribing to feed..." msgstr "Subscribing to feed..." #: podcasts-gtk/resources/gtk/discovery_page.ui:33 #| msgid "Podcasts" msgid "Add Podcasts" msgstr "Add Podcasts" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Search" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Enter a feed URL or search the selected platforms." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Submit search" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 msgid "Loading..." msgstr "Loading..." #: podcasts-gtk/resources/gtk/discovery_page.ui:107 msgid "Loading" msgstr "Loading" #: podcasts-gtk/resources/gtk/discovery_page.ui:121 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Please enable a Search Platform below, or enter a http(s) feed URL." #: podcasts-gtk/resources/gtk/discovery_page.ui:133 msgid "Search Platforms" msgstr "Search Platforms" #: podcasts-gtk/resources/gtk/discovery_page.ui:134 msgid "Search queries will be sent to these platforms." msgstr "Search queries will be sent to these platforms." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Search results" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "No results found." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "This show does not have episodes yet" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "If you think this is an error, please consider writing a bug report." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Get Some Shows" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Add new shows via feed URL" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Import shows from another device" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Episode Details" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Episode Menu" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Podcast Title" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Duration - Date" #: podcasts-gtk/resources/gtk/episode_description.ui:158 msgid "Stream" msgstr "Stream" #: podcasts-gtk/resources/gtk/episode_description.ui:186 #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Play" #: podcasts-gtk/resources/gtk/episode_description.ui:214 msgid "Download" msgstr "Download" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Cancel" msgstr "Cancel" #: podcasts-gtk/resources/gtk/episode_description.ui:279 #| msgid "Delete File" msgid "Delete" msgstr "Delete" #: podcasts-gtk/resources/gtk/episode_description.ui:299 msgid "Episode Description" msgstr "Episode Description" #: podcasts-gtk/resources/gtk/episode_description.ui:320 #| msgid "Episode Menu" msgid "Episode Cover" msgstr "Episode Cover" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Go to Show" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Copy Episode URL" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "You’ve already listened to this episode." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Calculating episode size…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Play this episode" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Cancel download" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Download this episode" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 #| msgid "Episode Details" msgid "Episode without audio" msgstr "Episode without audio" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigation" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Go to Home Page" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 #| msgid "Go to Show" msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Go to Shows Page" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Go To Discovery Page" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 #| msgid "Play" msgctxt "shortcut window" msgid "Player" msgstr "Player" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Toggle Pause" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 #| msgid "Forward" msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Seek Forwards" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Seek Backwards" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 #| msgid "General" msgctxt "shortcut window" msgid "General" msgstr "General" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Check for new episodes" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Quit the application" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 #| msgid "GNOME Podcasts Subscriptions" msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Import Subscriptions" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 #| msgid "GNOME Podcasts Subscriptions" msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Export Subscriptions" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Today" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Yesterday" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "This Week" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "This Month" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Older" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Change the playback speed" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Rewind" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pause" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Forward" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Rewind 10 seconds" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Fast forward 10 seconds" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Mark All Episodes as Played" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Website" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Unsubscribe" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Open Website" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Mark All as Played" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Unsubscribe" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Podcast Menu" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episodes" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Check for New Episodes" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Import Shows" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Export Shows" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Keyboard Shortcuts" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_About Podcasts" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:470 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:445 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/window.ui:115 #| msgid "Add a new feed" msgid "Add a New Feed" msgstr "Add a New Feed" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Main Menu" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Show" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Listen to your favourite shows" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "The home view displaying the newest episodes of your podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "The shows view displaying the covers of your podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "The view where one can add a new podcast" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:186 msgid "The Podcasts developers" msgstr "The Podcasts developers" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Listen to your favourite podcasts, right from your desktop." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Height of the last open main window" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Width of the last open main window" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Maximised state of the last open main window" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Whether to periodically refresh content" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "How many periods of time to wait between automatic refreshes" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "What period of time to wait between automatic refreshes" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Whether to refresh content after startup" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "How many periods of time to wait between automatic cleanups" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "What period of time to wait between automatic cleanups" #: podcasts-gtk/src/app.rs:344 msgid "Copied URL to clipboard!" msgstr "Copied URL to clipboard!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Jump to {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Jump to {}:{}" #: podcasts-gtk/src/manager.rs:106 #| msgid "Download this episode" msgid "Download failed: {}" msgstr "Download failed: {}" #: podcasts-gtk/src/utils.rs:294 #| msgid "Failed to parse the imported file {}" msgid "Failed to subscribe to feed: {}" msgstr "Failed to subscribe to feed: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML file" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Select the file from which to you want to import shows." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Import" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Failed to parse the imported file {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Export shows to…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Export" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcasts Subscriptions" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Failed to export podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcast Client for the GNOME Desktop." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Zander Brown \n" "Bruce Cowan \n" "Andi Chandler " #: podcasts-gtk/src/widgets/content_stack.rs:60 #| msgid "Fetching new episodes" msgid "Fetching feeds…" msgstr "Fetching feeds…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "New" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Shows" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 #| msgid "Download" msgid "Download progress" msgstr "Download progress" #: podcasts-gtk/src/widgets/episode.rs:282 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1070 msgid "The media player was unable to execute an action." msgstr "The media player was unable to execute an action." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Visually expands this description" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Read More" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Marked all episodes as listened" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Undo" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Unsubscribed from {}" #~ msgid "Now Playing" #~ msgstr "Now Playing" #~ msgid "Close" #~ msgstr "Close" #~ msgid "Enter Feed Address" #~ msgstr "Enter Feed Address" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Popover menu (ESC to close)" #~ msgid "Add" #~ msgstr "Add" #~ msgid "Back" #~ msgstr "Back" #~ msgid "Top position of the last open main window" #~ msgstr "Top position of the last open main window" #~ msgid "Left position of the last open main window" #~ msgstr "Left position of the last open main window" #~ msgid "Enable or disable dark theme" #~ msgstr "Enable or disable dark theme" #~ msgid "Podcast app for GNOME" #~ msgstr "Podcast app for GNOME" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Show Title" #~ msgstr "Show Title" #~ msgid "An in-app action notification" #~ msgstr "An in-app action notification" #~ msgid "1.5 speed rate" #~ msgstr "1.5 speed rate" #~ msgid "1.25 speed rate" #~ msgstr "1.25 speed rate" #~ msgid "Normal speed" #~ msgstr "Normal speed" #~ msgid "You are already subscribed to this show" #~ msgstr "You are already subscribed to this show" #~ msgid "Invalid URL" #~ msgstr "Invalid URL" #~ msgid "Selected file could not be accessed." #~ msgstr "Selected file could not be accessed." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Learn more about GNOME Podcasts" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Preferences" #~ msgid "You are already subscribed to that feed!" #~ msgstr "You are already subscribed to that feed!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Preferences" #~ msgid "Preferences" #~ msgstr "Preferences" #~ msgid "Appearance" #~ msgstr "Appearance" #~ msgid "Dark Theme" #~ msgstr "Dark Theme" #~ msgid "Delete played episodes" #~ msgstr "Delete played episodes" #~ msgid "After" #~ msgstr "After" #~ msgid "Seconds" #~ msgstr "Seconds" #~ msgid "Minutes" #~ msgstr "Minutes" #~ msgid "Hours" #~ msgstr "Hours" #~ msgid "Days" #~ msgstr "Days" #~ msgid "Weeks" #~ msgstr "Weeks" podcasts-25.2/podcasts-gtk/po/es.po000066400000000000000000000542631500126606300172630ustar00rootroot00000000000000# Spanish translations for gnome-podcasts package. # Copyright (C) 2018 THE gnome-podcasts'S COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-podcasts package. # Automatically generated, 2018. # Daniel Garcia Moreno , 2018. # Rodrigo , 2018. # Rodrigo Lledó , 2019. # Daniel Mustieles , 2022-2023. # Julián Villodre , 2024-2025. # msgid "" msgstr "" "Project-Id-Version: gnome-podcasts\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-02-02 13:04+0100\n" "Last-Translator: Julián Villodre \n" "Language-Team: es_ES\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-Generator: Gtranslator 47.1\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Episodios:" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Última publicación" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Suscribirse" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Suscribiéndose al canal…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Añadir podcasts" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Buscar" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Introduzca el URL del canal o busque en las plataformas seleccionadas." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Enviar búsqueda" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Cargando…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Active una plataforma de búsqueda o indroduzca el URL http(s) del canal." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Plataformas de búsqueda" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Las consultas de búsqueda se enviarán a estas plataformas." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Resultados de la búsqueda" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "No hay resultados." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Este programa no tiene episodios todavía" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Si cree que esto es un error considere abrir un informe de error." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Consiga algunos programas" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Añadir un programa nuevo a través de canal URL" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Importar programas de otro dispositivo" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Detalles del episodio" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Menú del episodio" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Título del podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Duración - Fecha" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Flujo" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "Re_producir" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Descargar" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Cancelar" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Eliminar" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Descripción del episodio" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Portada del episodio" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Ir al programa" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Copiar URL del episodio" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Marcar como reproducido" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Marcar como no reproducido" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Ya ha escuchado este episodio." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Calculando tamaño del episodio…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Reproducir este episodio" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Cancelar el proceso de descarga" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Descargar este episodio" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Episodio sin sonido" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navegación" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Ir a la página de inicio" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Ir a la página de programas" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Ir a la página de novedades" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Reproductor" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Conmutar pausa" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Buscar hacia adelante" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Buscar hacia atrás" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "General" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Comprobar si hay episodios nuevos" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Salir de la aplicación" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Importar suscripciones" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Exportar suscripciones" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Hoy" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Ayer" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Esta semana" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Este mes" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Antiguo" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Cambiar velocidad de reproducción" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Rebobinar" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Reproducir" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pausa" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Avanzar" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Descripción" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Retroceder 10 segundos" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Avanzar 10 segundos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marcar todos los episodios como reproducidos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Página _Web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Cancelar suscripción" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Abrir Web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Marcar todo como reproducido" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Cancelar suscripción" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Menú del podcast" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episodios" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "Comprobar si hay nuevos episodios" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Importar programas" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Exportar programas" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "Atajos de _teclado" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Acerca de Podcasts" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Añadir un canal nuevo" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Menú principal" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Programa" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Escuche sus programas favoritos" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Reproduzca, actualice y administre sus podcasts desde una interfaz ligera " "que se integra a la perfección con GNOME. Podcasts pueden reproducir varios " "formatos de audio y recordar dónde dejó de escucharlos. Puede suscribirse a " "programas a través de enlaces RSS/Atom, iTunes y Soundcloud. Las " "suscripciones de otras aplicaciones se pueden importar a través de archivos " "OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "La vista de inicio muestra los episodios más recientes de sus podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "La vista de programas muestra las portadas de sus podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "El widget del programa muestra la portada y los últimos episodios de un " "podcast específico" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "La vista donde puede añadir un nuevo podcast" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Los desarrolladores de Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Escuche sus podcasts favoritos, directamente desde su escritorio." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Altura de la última ventana abierta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Anchura de la última ventana abierta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Estado de maximizado de la última ventana abierta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Indica si se debe actualizar el contenido periódicamente" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "" "Cuántos periodos de tiempo hay que esperar entre actualizaciones automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "" "Qué periodo de tiempo hay que esperar entre actualizaciones automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Indica si se debe actualizar el contenido al iniciar" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Cuántos periodos de tiempo hay que esperar entre limpiezas automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Cuántos periodos de tiempo hay que esperar entre limpiezas automáticas" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL copiado al portapapeles." #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Saltar a {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Saltar a {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Falló la descarga: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Falló al suscribirse al canal: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "Archivo OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Selecciona el fichero desde el que quiere importar programas." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Importar" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Fallo al analizar el fichero importado {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Exportar programas a…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Exportar" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Suscripciones a podcasts de GNOME" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Falló al exportar podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Aplicación de Podcast para el escritorio GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Julián Villodre , 2024\n" "Daniel Mustieles , 2018-2019\n" "Daniel García Moreno " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Obteniendo canales…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Nuevo" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Programas" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Progreso de la descarga" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "El reproductor no ha podido reproducir una acción." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Amplíe visualmente esta descripción" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Leer más" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Marcar todos los episodios como escuchados" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Deshacer" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Suscripción cancelada para {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Cargando..." #~ msgid "Now Playing" #~ msgstr "Reproduciendo ahora" #~ msgid "Close" #~ msgstr "Cerrar" #~ msgid "Back" #~ msgstr "Atrás" #~ msgid "Enter Feed Address" #~ msgstr "Introduzca la dirección del canal" #~ msgid "Add" #~ msgstr "Añadir" #~ msgid "Show Title" #~ msgstr "Título del programa" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Top position of the last open main window" #~ msgstr "Posición superior de la última ventana abierta" #~ msgid "Left position of the last open main window" #~ msgstr "Posición izquierda de la última ventana abierta" #~ msgid "Enable or disable dark theme" #~ msgstr "Activar o desactivar el tema oscuro" #~ msgid "An in-app action notification" #~ msgstr "Una notificación de acción dentro de la aplicación" #~ msgid "Selected file could not be accessed." #~ msgstr "No se ha podido acceder al archivo seleccionado." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Aprenda más sobre GNOME Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicación de Podcast para GNOME" #~| msgid "1.5 speed rate" #~ msgid "Double speed rate" #~ msgstr "Doble velocidad" #~| msgid "1.5 speed rate" #~ msgid "1.75 speed rate" #~ msgstr "velocidad 1.75" #~ msgid "1.5 speed rate" #~ msgstr "velocidad 1.5" #~ msgid "1.25 speed rate" #~ msgstr "velocidad 1.25" #~ msgid "Normal speed" #~ msgstr "Velocidad normal" #~ msgid "You are already subscribed to this show" #~ msgstr "Ya está suscrito a este programa" #~ msgid "Invalid URL" #~ msgstr "URL no válido" #~ msgid "You are already subscribed to that feed!" #~ msgstr "¡Ya está suscrito a este canal!" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Preferencias" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "Appearance" #~ msgstr "Aspecto" #~ msgid "Dark Theme" #~ msgstr "Tema oscuro" #~ msgid "Delete played episodes" #~ msgstr "Eliminar episodios reproducidos" #~ msgid "After" #~ msgstr "Después" #~ msgid "Seconds" #~ msgstr "Segundos" #~ msgid "Minutes" #~ msgstr "Minutos" #~ msgid "Hours" #~ msgstr "Horas" #~ msgid "Days" #~ msgstr "Días" #~ msgid "Weeks" #~ msgstr "Semanas" #~ msgid "_About" #~ msgstr "_Acerca de" #~ msgid "org.gnome.Podcasts" #~ msgstr "org.gnome.Podcasts" #~ msgid "Mark all episodes as listened" #~ msgstr "Marcar todos los episodios como escuchados" #~ msgid "3 Jan" #~ msgstr "3 Jan" #~ msgid "·" #~ msgstr "·" #~ msgid "42 min" #~ msgstr "42 min" #~ msgid "0 MB" #~ msgstr "0 MB" #~ msgid "/" #~ msgstr "/" #~ msgid "© 2017, 2018 Jordan Petridis" #~ msgstr "© 2017, 2018 Jordan Petridis" podcasts-25.2/podcasts-gtk/po/eu.po000066400000000000000000000503721500126606300172620ustar00rootroot00000000000000# Basque translation for podcasts. # Copyright (C) 2019 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Asier Sarasua Garmendia , 2019, 2021, 2023, 2024, 2025. # msgid "" msgstr "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-02-15 10:00+0100\n" "Last-Translator: Asier Sarasua Garmendia \n" "Language-Team: Basque \n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Pasarteak: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Azken argitalpena" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Harpidetu" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Jariora harpidetzen…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Gehitu podcastak" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Bilatu" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Sartu jario baten URLa edo bilatu hautatutako plataformetan." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Bidali bilaketa" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Kargatzen…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Gaitu plataformetan bilatzea behean, edo sartu jario baten http(s) URLa" #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Bilatu plataformetan" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Bilaketa-kontsultak plataforma horietara bidaliko da." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Bilaketaren emaitzak" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Ez da emaitzarik aurkitu." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Saio honek ez du pasarterik oraindik" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Errorea dela uste baduzu, idatzi akatsaren jakinarazpen bat." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Eskuratu zenbait saio" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Gehitu saio berriak jarioen URL baten bidez" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Inportatu saioak beste gailu batetik" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Pasartearen xehetasunak" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Pasarteen menua" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Podcastaren izenburua" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Iraupena - Data" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Transmititu" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Erreproduzitu" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Deskargatu" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Utzi" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Ezabatu" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Pasartearen deskribapena" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Pasartearen azala" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Joan aurkezpenera" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Kopiatu pasartearen URLa" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Markatu irakurri gisa" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Markatu erreproduzitu gabe gisa" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Dagoeneko entzun duzu pasarte hau" #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Pasartearen tamaina kalkulatzen…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Erreproduzitu pasarte hau" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Utzi deskarga-prozesua" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Deskargatu pasarte hau" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Audiorik gabeko pasartea" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Nabigazioa" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Joan orri nagusira" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Joan saioen orrira" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Joan saioak aurkitzeko orrira" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Erreproduzigailua" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Txandakatu pausatzea" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Bilatu aurrerantz" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Bilatu atzerantz" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Orokorra" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Begiratu pasarte berriak dauden" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Irten aplikaziotik" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Inportatu harpidetzak" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Esportatu harpidetzak" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Gaur" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Atzo" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Aste honetan" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Hilabete honetan" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Zaharragoa" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Aldatu erreprodukzioaren abiadura" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Birbobinatu" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Erreproduzitu" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pausatu" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Aurrera" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Azalpena" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Joan 10 segundo atzera" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Joan 10 segundo aurrera" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Markatu pasarte guztiak erreproduzitu gisa" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Webgunea" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "Harpidetza _kendu" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Ireki webgunea" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Markatu dena erreproduzitu gisa" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Harpidetza kendu" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Podcasten menua" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Pasarteak" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Begiratu pasarte berriak dauden" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Inportatu saioak" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Esportatu saioak" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "Las_ter-teklak" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "Podcastak aplikazioari _buruz" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podcast-ak" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Gehitu jario berria" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Menu nagusia" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Erakutsi" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Entzun zure saiorik gogokoenak" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "Erreproduzitu, eguneratu eta kudeatu zure podcastak GNOMEri primeran egokitzen zaion interfaze arin batetik. Podcastak aplikazioak hainbat audio-formatu erreproduzitu ditzake eta entzuteari non utzi zenion gogoratzen du. Saioen harpidetzak egin ditzakezu RSS/Atom, ITunes eta Soundcloud esteken bidez. Beste aplikazio batzuetako harpidetzak ere inportatu daitezke OPML fitxategien bidez." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Zure podcasten pasarte berrienak bistaratzen dituen ikuspegi nagusia" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Zure podcasten azalak bistaratzen dituen ikuspegia" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "Podcast jakin baten azala eta azken pasarteak bistaratzen dituen trepeta" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Podcast berriak gehitzea ahalbidetzen duen ikuspegia" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Podcastak aplikazioaren garatzaileak" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Entzun zure podcast gogokoenak zuzenean zure mahaigainetik." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Irekitako azken leiho nagusiaren altuera" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Irekitako azken leiho nagusiaren zabalera" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Irekitako azken leiho nagusiaren egoera maximizatua" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Edukia aldizka freskatuko den ala ez" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Zenbat denbora-tarte itxarongo den freskatze automatikoen artean" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Zenbat denbora itxarongo den freskatze automatikoen artean" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Abioaren ondoren edukia freskatuko den ala ez" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Zenbat denbora-tarte itxarongo den garbitze automatikoen artean" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Zenbat denbora itxarongo den garbitze automatikoen artean" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URLa arbelean kopiatu da!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Joan hona: {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Joan hona: {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Deskargak huts egin du: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Jariora harpidetzeak huts egin du: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML fitxategia" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Hautatu saioak inportatzeko erabiliko duzun fitxategia." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Inportatu" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Inportatutako {} fitxategiaren analisiak huts egin du" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Esportatu saioak hona…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Esportatu" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcastak harpidetzak" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Huts egin du podcastak esportatzeak" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcasten bezeroa GNOME mahaigainerako." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "Asier Sarasua Garmendia " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Jarioak atzitzen…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Berria" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Saioak" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Deskargaren aurrerapena" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Multimedia-erreproduzigailuak ezin izan da ekintza bat exekutatu." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Deskribapen hau ikusiz hedatzen du" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Irakurri gehiago" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Markatu pasarte guztiak entzundako gisa" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Desegin" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Kendu {} podcast-eko harpidetza" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Kargatzen…" #~ msgid "Now Playing" #~ msgstr "Orain erreproduzitzen" #~ msgid "Close" #~ msgstr "Itxi" #~ msgid "Enter Feed Address" #~ msgstr "Sartu jarioaren helbidea" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Bunbuilo-menua (Esc ixteko)" #~ msgid "Add" #~ msgstr "Gehitu" #~ msgid "Back" #~ msgstr "Atzera" #~ msgid "Show Title" #~ msgstr "Erakutsi titulua" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Selected file could not be accessed." #~ msgstr "Hautatutako fitxategia ezin da eskuratu." #~ msgid "Top position of the last open main window" #~ msgstr "Irekitako azken leiho nagusiaren goiko posizioa" #~ msgid "Left position of the last open main window" #~ msgstr "Irekitako azken leiho nagusiaren ezkerreko posizioa" #~ msgid "Enable or disable dark theme" #~ msgstr "Gaitu edo desgaitu gai iluna" #~ msgid "An in-app action notification" #~ msgstr "Aplikazioaren jakinarazpen bat" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Ikasi gehiago GNOME Podcastak aplikazioari buruz" #~ msgid "Podcast app for GNOME" #~ msgstr "Podcast-en aplikazioa GNOMErako" #~ msgid "1.5 speed rate" #~ msgstr "1.5 abiadura-tasa" #~ msgid "1.25 speed rate" #~ msgstr "1.25 abiadura-tasa" #~ msgid "Normal speed" #~ msgstr "Abiadura normala" podcasts-25.2/podcasts-gtk/po/fa.po000066400000000000000000000524361500126606300172420ustar00rootroot00000000000000# Persian translation for podcasts. # Copyright (C) 2023 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Danial Behzadi , 2023-2025. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-28 16:56+0330\n" "Last-Translator: Danial Behzadi \n" "Language-Team: Persian \n" "Language: fa\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==0 || n==1);\n" "X-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "قسمت‌ها: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "آخرین انتشار" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_اشتراک" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "مشترک شدن خوراک…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "افزودن پادپخش" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "جست‌وجو" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "ورود نشانی خوراک یا جست‌وجوی بن‌سازه‌های گزیده." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "ثبت جست‌وجو" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "بار کردن…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "لطفاً بن‌سازهٔ جست‌وجویی را در زیر به کارانداخته یا نشانی خوراکی را وارد کنید." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "بن‌سازه‌های جست‌وجو" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "پرس‌وجوهای جست‌وجو به این بن‌سازه‌ها فرستاده خواهند شد." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "نتایج جست‌وجو" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "هیج نتیجه‌ای پیدا نشد." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "این نمایش هنوز هیچ قسمتی ندارد" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "اگر فکر می‌کنید خطایی رخ داده، لطفاً گزارش اشکالی بنویسید." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "گرفتن نمایش‌ها" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "افزودن نمایش‌های جدید با نشانی خوراک" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "درون‌ریزی نمایش‌ها از افزارهٔ دیگر" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "جزییات قسمت" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "فهرست قسمت" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "عنوان پادپخش" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "طول - تاریخ" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_جریان" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_پخش" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_بارگیری" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "لغو" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "حذف" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "شرح قسمت" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "طرح جلد قسمت" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "رفتن به نمایش" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "رونوشت از نشانی قسمت" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "علامت به پخش شده" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "علامت به پخش نشده" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "پیش‌تر این قسمت را گوش کرده بودید." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "محاسبهٔ اندازهٔ قسمت…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "پخش این قسمت" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "لغو فرایند بارگیری" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "بارگیری این قسمت" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "قسمت بدون صدا" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "پیمایش" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "رفتن به صفحهٔ خانه" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "رفتن به صفحهٔ نمایش‌ها" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "رفتن به صفحهٔ کشف" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "پخش‌کننده" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "تغییر حالت مکث" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "جویش به پیش" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "جویش به پس" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "عمومی" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "بررسی برای قسمت‌های جدید" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "ترک برنامه" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "درون‌ریزی اشتراک‌ها" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "برون‌ریزی اشتراک‌ها" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "امروز" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "دیروز" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "این هفته" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "این ماه" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "قدیمی‌تر" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "تغییر سرعت پخش" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "×۱٫۰۰" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "×۲٫۰۰" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "×۱٫۷۵" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "×۱٫۵۰" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "×۱٫۲۵" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "×۰٫۹۰" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "×۰٫۷۵" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "پس‌روی" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "پخش" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "مکث" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "پیش‌روی" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "شرح" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "۱۰ ثانیه پس‌روی" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "۱۰ ثانیه پیشروی سریع" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_علامت زدن همهٔ قسمت‌ها به پخش شده" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "پایگاه _وب" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_قطع اشتراک" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "گشوپن پایگاه وب" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "علامت زدن همه به پخش شده" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "قطع اشتراک" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "فهرست پادپخش" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "قسمت‌ها" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_بررسی برای قسمت‌های جدید" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_درون‌ریزی نمایش‌ها" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_برون‌ریزی نمایش‌ها" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "میان‌برهای _صفحه‌کلید" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_دربارهٔ پادپخش‌ها" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "پادپخش‌ها" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "افزودن خوراکی جدید" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "فهرست اصلی" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "نمایش" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "شنیدن نمایش‌های محبوبتان" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats and " "remember where you stopped listening. You can subscribe to shows via RSS/Atom, " "iTunes, and Soundcloud links. Subscriptions from other apps can be imported via " "OPML files." msgstr "" "پخش، به‌روز رسانی و مدیریت پادپخش‌هایتان از میانایی سبک که به طور بی‌نقصی با گنوم " "یکپارچه می‌شود. پادپخش‌ها می‌تواند قالب‌های صوتی مختلفی را پخش کرده و جایی که شنیدن " "را متوقّف کردید به خاطر بسپرد. می‌توانید با پیوندهای سوندکلود، آی‌تونز و خوراک وب " "مشترک نمایش‌ها شوید. اشتراک‌های دیگر برنامه‌ها می‌توانند از طریق پرونده‌های OPML " "درون ریخته شوند." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "نمای خانه جدیدترین قسمت‌های پادپخش‌هایتان را نشان می‌دهد" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "نمای نمایش‌ها جلدهای پادپخش‌هایتان را نشان می‌دهد" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "ابزارک نمایش در حال نشان دادن جلد و جدیدترین قسمت‌های پادپخشی خاص" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "نمایی که در آن می‌توان پادپخشی جدید افزود" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "توسعه‌دهندگان پادپخش‌ها" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "شنیدن پادپخش‌های محبوبتان از روی میزکارتان." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;پادکست;پادپخش;خوراک;صدا;وب‌آوا;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "بلندای آخرین پنجرهٔ اصلی باز" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "پهنای آخرین پنجرهٔ اصلی باز" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "وضعیت بیشینگی آخرین پنجرهٔ اصلی باز" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "این که محتوا به صورت دوره‌ای تازه شود یا نه" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "مقدار زمان انتظار بین تازه سازی‌های خودکار" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "مقدار زمان انتظار بین تازه سازی‌های خودکار" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "این که محتوا پس از برپایی تازه شود یا نه" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "مقدار زمان انتظار بین پاک سازی‌های خودکار" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "مقدار زمان انتظار بین پاک سازی‌های خودکار" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "نشانی رونوشت شده در تخته‌گیره!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "پرش به {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "پرش به {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "بارگیری شکست خورد: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "شکست در اشتراک خوراک: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "پروندهٔ OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "پرونده‌ای را که می‌خواهید نمایش‌ها را از آن درون‌ریزی کنید برگزینید." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_درون‌ریزی" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "شکست در تجزیهٔ پروندهٔ درون‌ریختهٔ {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "برون‌ریزی نمایش‌ها به…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_برون‌ریزی" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "نمایش‌های-برون‌ریخته-پادپخش‌های-گنوم" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "اشتراک‌های پادپخش‌های گنوم" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "شکست در برون‌ریزی پادپخش‌ها" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "کارخواه پادپخش برای میزکار گنوم." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "دانیال بهزادی " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "واکشیدن خوراک‌ها…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "جدید" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "نمایش‌ها" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "پیشرفت بارگیری" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} دقیقه" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "پخش‌کنندهٔ رسانه در اجرای کنشی ناتوان بود." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "گستردن بصری شرح" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "بیش‌تر بخوانید" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "همهٔ قسمت‌ها به شنیده شده علامت خورد" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "برگردان" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "لغو اشتراک از {}" #~ msgid "0" #~ msgstr "۰" #~ msgid "Loading..." #~ msgstr "بار کردن…" #~ msgid "Now Playing" #~ msgstr "در حال پخش" #~ msgid "Close" #~ msgstr "بستن" #~ msgid "Enter Feed Address" #~ msgstr "ورود نشانی خوراک" #~ msgid "Popover menu (ESC to close)" #~ msgstr "فهرست رو آمدنی (گریز برای خروج)" #~ msgid "Add" #~ msgstr "افزودن" #~ msgid "Back" #~ msgstr "بازگشت" #~ msgid "Show Title" #~ msgstr "عنوان نمایش" podcasts-25.2/podcasts-gtk/po/fi.po000066400000000000000000000517321500126606300172500ustar00rootroot00000000000000# Finnish translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Jiri Grönroos , 2018. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-02-07 16:27+0200\n" "Last-Translator: Jiri Grönroos \n" "Language-Team: Finnish \n" "Language: 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-Generator: Poedit 3.4.4\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Jaksot: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Viimeisin julkaisu" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "T_ilaa" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Tilataan syöte…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Lisää podcasteja" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Etsi" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Kirjoita syötteen verkko-osoite tai etsi valituilta alustoilta." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Suorita haku" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Ladataan…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Ota hakualusta käyttöön alta, tai kirjoita syötteen http(s)-osoite." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Hakualustat" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Haut lähetetään näille alustoille." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Hakutulokset" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Ei tuloksia." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Tässä ohjelmassa ei ole vielä yhtäkään jaksoa" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Jos tämä on mielestäsi virhe, teethän bugiraportin." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Hanki ohjelmia" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Lisää uusia ohjelmia syötteen osoitteen avulla" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Tuo ohjelmia toiselta laitteelta" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Jakson tiedot" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Jaksovalikko" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Podcastin nimi" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Kesto - Päiväys" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Suoratoista" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Toista" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Lataa" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Peru" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Poista" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Jakson kuvaus" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Jakson kansi" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Siirry ohjelmaan" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Kopioi jakson verkko-osoite" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Merkitse toistetuksi" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Merkitse toistamattomaksi" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Olet jo kuunnellut tämän jakson." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Lasketaan jakson kokoa…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Toista tämä jakso" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Peru lataaminen" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Lataa tämä jakso" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Jakso ilman ääntä" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Liikkuminen" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Siirry etusivulle" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Siirry ohjelmasivulle" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Siirry hakusivulle" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Soitin" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Keskeytä/jatka" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Kelaa eteenpäin" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Kelaa taaksepäin" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Yleiset" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Tarkista uudet jaksot" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Lopeta sovellus" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Tuo tilaukset" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Vie tilaukset" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Tänään" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Eilen" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Tällä viikolla" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Tässä kuussa" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Vanhemmat" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Vaihda toistonopeutta" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Kelaa taakse" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Toista" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Keskeytä" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Kelaa eteen" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Kuvaus" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Siirry taakse 10 sekuntia" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Siirry eteen 10 sekuntia" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Merkitse kaikki jaksot toistetuiksi" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Verkkosivusto" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Lopeta tilaus" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Avaa verkkosivusto" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Merkitse kaikki toistetuiksi" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Lopeta tilaus" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Podcast-valikko" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Jaksot" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "Ta_rkista uudet jaksot" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "Tu_o jaksoja" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Vie jaksoja" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Pikanäppäimet" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Tietoja - Podcastit" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podcastit" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Lisää uusi syöte" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Päävalikko" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Ohjelma" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Kuuntele suosikkiohjelmiasi" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Toista, päivitä ja hallitse podcasteja Gnome-työpöytään integroituvasta " "käyttöliittymästä. Podcastit kykenee toistamaan lukuisia äänimuotoja ja " "muistaa, missä kohtaa lopetit kuuntelun. Voit tilata ohjelmia RSS-/Atom-, " "iTunes- ja Soundcloud-linkeillä. Tilauksia muista sovelluksista voi tuoda " "OPML-tiedostoista." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Päänäkymä esittäen podcastien uusimmat jaksot" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Ohjelmien näkymä, esillä podcastien kannet" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "Ohjelmanäkymä esittäen kansikuvan ja podcastin uusimmat jaksot" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Näkymä podcastin lisäämiseksi" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Podcasts-kehittäjät" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Kuuntele suosikkipodcastejasi suoraan työpöydältäsi." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Viimeksi avoinna olleen pääikkunan korkeus" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Viimeksi avoinna olleen pääikkunan leveys" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Viimeisimmän pääikkunan suurennettu tila" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Päivitetäänkö sisältö säännöllisesti" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Kuinka monta ajanjaksoa odottaa automaattisten päivitysten välillä" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Kuinka pitkä ajanjakso odottaa automaattisten päivitysten välillä" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Päivitetäänkö sisältö sovelluksen käynnistyttyä" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Kuinka monta ajanjaksoa odottaa automaattisten siivousten välillä" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Kuinka pitkä ajanjakso odottaa automaattisten siivousten välillä" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "Verkko-osoite kopioitu leikepöydälle!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Siirry kohtaan {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Siirry kohtaan {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Lataus epäonnistui: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Syötteen tilaaminen epäonnistui: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML-tiedosto" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Valitse tiedosto, josta haluat tuoda ohjelmia." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Tuo" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Tuotua tiedostoa {} ei voitu jäsentää" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Vie jaksot…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Vie" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasteista-viedyt-ohjelmat" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Gnomen podcastsovelluksen tilaukset" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Podcastien vienti epäonnistui" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcast-sovellus Gnome-työpöydlle." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "Jiri Grönroos" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Noudetaan syötteitä…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Uudet" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Ohjelmat" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Latauksen edistyminen" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Mediasoitin ei kyennyt suorittamaan toimintoa." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Laajentaa kuvausta" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Lue lisää" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Merkitse kaikki jaksot toistetuiksi" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Kumoa" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Lopeta syötteen {} tilaus" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Ladataan..." #~ msgid "Now Playing" #~ msgstr "Nyt toistetaan" #~ msgid "Close" #~ msgstr "Sulje" #~ msgid "Enter Feed Address" #~ msgstr "Anna syötteen osoite" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Ponnahdusvalikko (ESC sulkee)" #~ msgid "Add" #~ msgstr "Lisää" #~ msgid "Back" #~ msgstr "Takaisin" #~ msgid "Show Title" #~ msgstr "Ohjelman nimi" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Top position of the last open main window" #~ msgstr "Viimeksi avoinna olleen pääikkunan yläsijainti" #~ msgid "Left position of the last open main window" #~ msgstr "Viimeksi avoinna olleen pääikkunan vasen sijainti" #~ msgid "Enable or disable dark theme" #~ msgstr "Ota käyttöön tai poista käytöstä tumma teema" #~ msgid "An in-app action notification" #~ msgstr "Sovelluksen sisäinen ilmoitus" #~ msgid "Selected file could not be accessed." #~ msgstr "Valittua tiedostoa ei voitu käyttää." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Lisätietoja Gnomen podcast-sovelluksesta" #~ msgid "Podcast app for GNOME" #~ msgstr "Podcast-sovellus Gnomelle" #~ msgid "Double speed rate" #~ msgstr "Tuplanopeus" #~ msgid "1.75 speed rate" #~ msgstr "1,75-kertainen nopeus" #~ msgid "1.5 speed rate" #~ msgstr "1,5-kertainen nopeus" #~ msgid "1.25 speed rate" #~ msgstr "1,25-kertainen nopeus" #~ msgid "Normal speed" #~ msgstr "Tavallinen nopeus" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Olet jo tilannut kyseisen syötteen!" #~ msgid "You are already subscribed to this show" #~ msgstr "Olet jo tilannut tämän ohjelman" #~ msgid "Invalid URL" #~ msgstr "Virheellinen osoite" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Asetukset" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Asetukset" #~ msgid "Preferences" #~ msgstr "Asetukset" #~ msgid "Appearance" #~ msgstr "Ulkoasu" #~ msgid "Dark Theme" #~ msgstr "Tumma teema" #~ msgid "Delete played episodes" #~ msgstr "Poista toistetut jaksot" #~ msgid "After" #~ msgstr "Viive" #~ msgid "Seconds" #~ msgstr "sekuntia" #~ msgid "Minutes" #~ msgstr "minuuttia" #~ msgid "Hours" #~ msgstr "tuntia" #~ msgid "Days" #~ msgstr "päivää" #~ msgid "Weeks" #~ msgstr "viikkoa" #~ msgid "_About" #~ msgstr "_Tietoja" #~ msgid "org.gnome.Podcasts" #~ msgstr "org.gnome.Podcasts" podcasts-25.2/podcasts-gtk/po/fr.po000066400000000000000000000351541500126606300172610ustar00rootroot00000000000000# French translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # arverne73 , 2018. # Alexandre Franke , 2018, 2020 # Thibault Martin , 2020. # Sylvestris , 2020. # Charles Monzat , 2022. # mathieu , 2022. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2023-03-26 17:46+0000\n" "PO-Revision-Date: 2023-05-25 11:14+0900\n" "Last-Translator: Julien Humbert \n" "Language-Team: French \n" "Language: 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-Generator: Poedit 3.3.1\n" "X-DL-Team: fr\n" "X-DL-Module: podcasts\n" "X-DL-Branch: master\n" "X-DL-Domain: po\n" "X-DL-State: Translating\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Cette émission n’a pas encore d’épisode" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "Si vous pensez qu’il s’agit d’une erreur, faites un rapport de bogue." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get Some Shows" msgstr "Obtenir des émissions" #: podcasts-gtk/resources/gtk/empty_view.ui:49 msgid "Add new shows via feed URL" msgstr "Ajouter de nouvelles émissions via une URL de flux" #: podcasts-gtk/resources/gtk/empty_view.ui:71 msgid "Import shows from another device" msgstr "Importer des émissions à partir d’un autre appareil" #: podcasts-gtk/resources/gtk/episode_description.ui:41 msgid "Episode Details" msgstr "Détails de l’épisode" #: podcasts-gtk/resources/gtk/episode_description.ui:51 #: podcasts-gtk/resources/gtk/headerbar.ui:143 msgid "Back" msgstr "Retour" #: podcasts-gtk/resources/gtk/episode_description.ui:105 msgid "Podcast Title" msgstr "Titre du podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:128 msgid "Duration - Date" msgstr "Durée - date" #: podcasts-gtk/resources/gtk/episode_description.ui:148 msgid "Episode Description" msgstr "Description de l’épisode" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Aller à l’émission" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Copier l’URL de l’épisode" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Vous avez déjà écouté cet épisode." #: podcasts-gtk/resources/gtk/episode_widget.ui:157 msgid "Calculating episode size…" msgstr "Calcul de la taille de l’épisode…" #: podcasts-gtk/resources/gtk/episode_widget.ui:180 msgid "Play this episode" msgstr "Lire cet épisode" #: podcasts-gtk/resources/gtk/episode_widget.ui:192 msgid "Cancel the download process" msgstr "Annuler le téléchargement" #: podcasts-gtk/resources/gtk/episode_widget.ui:204 msgid "Download this episode" msgstr "Télécharger cet épisode" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Chercher de nouveaux épisodes" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Importer les émissions" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Exporter les émissions" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Raccourcis clavier" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "À _propos de Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:33 #: podcasts-gtk/resources/gtk/headerbar.ui:133 msgid "Add a new feed" msgstr "Ajouter un nouveau flux" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter Feed Address" msgstr "Entrer l’adresse du flux" #: podcasts-gtk/resources/gtk/headerbar.ui:67 msgid "Add" msgstr "Ajouter" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/headerbar.ui:111 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:457 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/window.rs:64 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:121 msgid "Show Title" msgstr "Titre de l’émission" #: podcasts-gtk/resources/gtk/headerbar.ui:153 msgid "Main Menu" msgstr "Menu principal" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "Général" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Chercher de nouveaux épisodes" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "Quitter l’application" #: podcasts-gtk/resources/gtk/home_view.ui:50 msgid "Today" msgstr "Aujourd’hui" #: podcasts-gtk/resources/gtk/home_view.ui:78 msgid "Yesterday" msgstr "Hier" #: podcasts-gtk/resources/gtk/home_view.ui:106 msgid "This Week" msgstr "Cette semaine" #: podcasts-gtk/resources/gtk/home_view.ui:134 msgid "This Month" msgstr "Ce mois-ci" #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "Older" msgstr "Plus ancien" #: podcasts-gtk/resources/gtk/player_dialog.ui:22 msgid "Now Playing" msgstr "Lecture en cours" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Changer la vitesse de lecture" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:55 msgid "Rewind 10 seconds" msgstr "Reculer de 10 secondes" #: podcasts-gtk/resources/gtk/player_toolbar.ui:63 msgid "Play" msgstr "Lire" #: podcasts-gtk/resources/gtk/player_toolbar.ui:71 msgid "Pause" msgstr "Pause" #: podcasts-gtk/resources/gtk/player_toolbar.ui:79 msgid "Fast forward 10 seconds" msgstr "Avancer de 10 secondes" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marquer tous les épisodes comme lus" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Site _Web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "Se _désabonner" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Aller sur le site Web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Tout marquer comme lu" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Se désabonner" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Écouter vos émissions favorites" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Lisez, mettez à jour et gérez vos podcasts à partir d’une interface légère " "qui s’intègre parfaitement à GNOME. Les podcasts peuvent lire divers formats " "audio et se rappeler où vous avez stoppé l’écoute. Vous pouvez vous abonner " "à des émissions via des liens RSS/Atom, iTunes et Soundcloud. Les " "abonnements d’autres applications peuvent être importés via des fichiers " "OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:134 msgid "Jordan Petridis" msgstr "Jordan Petridis" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:135 msgid "Julian Hofer" msgstr "Julian Hofer" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Écouter vos podcasts favoris directement sur votre bureau." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;Baladodiffusion;Émissions;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Hauteur de la dernière fenêtre principale ouverte" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Largeur de la dernière fenêtre principale ouverte" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "État maximisé de la dernière fenêtre principale ouverte" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Indique s’il faut actualiser périodiquement le contenu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Nombre de délais à attendre entre les actualisations automatiques" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Délai entre les actualisations automatiques" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Indique s’il faut actualiser le contenu au démarrage" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Nombre de périodes de délai entre les nettoyages automatiques" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Délai entre les nettoyages automatiques" #: podcasts-gtk/src/app.rs:337 msgid "Copied URL to clipboard!" msgstr "URL copiée dans le presse-papiers !" #: podcasts-gtk/src/stacks/content.rs:70 msgid "New" msgstr "Nouveau" #: podcasts-gtk/src/stacks/content.rs:72 msgid "Shows" msgstr "Émissions" #: podcasts-gtk/src/utils.rs:489 podcasts-gtk/src/utils.rs:531 msgid "OPML file" msgstr "Fichier OPML" #: podcasts-gtk/src/utils.rs:500 msgid "Select the file from which to you want to import shows." msgstr "Sélectionnez le fichier à partir duquel importer les émissions." #: podcasts-gtk/src/utils.rs:502 msgid "_Import" msgstr "_Importer" #: podcasts-gtk/src/utils.rs:518 msgid "Failed to parse the imported file" msgstr "Échec de l’analyse du fichier importé" #: podcasts-gtk/src/utils.rs:541 msgid "Export shows to…" msgstr "Exporter les émissions vers…" #: podcasts-gtk/src/utils.rs:542 msgid "_Export" msgstr "_Exporter" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:546 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-émissions-exportées" #: podcasts-gtk/src/utils.rs:556 msgid "GNOME Podcasts Subscriptions" msgstr "Abonnements GNOME Podcasts" #: podcasts-gtk/src/utils.rs:557 msgid "Failed to export podcasts" msgstr "Échec de l’exportation des podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Client de podcast pour le bureau GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Alexandre Franke\n" "Thibault Martin\n" "Charles Monzat" #: podcasts-gtk/src/widgets/episode.rs:143 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:884 msgid "The media player was unable to execute an action." msgstr "Le lecteur n’a pas pu réaliser l’action." #: podcasts-gtk/src/widgets/read_more_label.rs:32 msgid "Read More" msgstr "En savoir plus" #: podcasts-gtk/src/widgets/show_menu.rs:180 msgid "Marked all episodes as listened" msgstr "Marquer tous les épisodes comme écoutés" #: podcasts-gtk/src/widgets/show_menu.rs:181 #: podcasts-gtk/src/widgets/show_menu.rs:204 msgid "Undo" msgstr "Annuler" #: podcasts-gtk/src/widgets/show_menu.rs:200 msgid "Unsubscribed from {}" msgstr "Se désabonner de {}" #~ msgid "Top position of the last open main window" #~ msgstr "Position supérieure de la dernière fenêtre principale ouverte" #~ msgid "Left position of the last open main window" #~ msgstr "Position gauche de la dernière fenêtre principale ouverte" #~ msgid "Enable or disable dark theme" #~ msgstr "Activer ou désactiver le thème sombre" #~ msgid "An in-app action notification" #~ msgstr "Une notification d’action intégrée à l’application" #~ msgid "Fetching new episodes" #~ msgstr "Récupération des nouveaux épisodes" #~ msgid "Selected file could not be accessed." #~ msgstr "Le fichier sélectionné n’est pas accessible." #~ msgid "_Cancel" #~ msgstr "A_nnuler" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "En apprendre plus sur GNOME Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Application de podcast pour GNOME" #~| msgid "1.5 speed rate" #~ msgid "Double speed rate" #~ msgstr "Débit × 2" #~| msgid "1.5 speed rate" #~ msgid "1.75 speed rate" #~ msgstr "Débit × 1,75" #~ msgid "1.5 speed rate" #~ msgstr "Débit × 1,5" #~ msgid "1.25 speed rate" #~ msgstr "Débit × 1,25" #~ msgid "Normal speed" #~ msgstr "Vitesse normale" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Préférences" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Vous êtes déjà abonné à ce flux !" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Préférences" #~ msgid "Preferences" #~ msgstr "Préférences" #~ msgid "Appearance" #~ msgstr "Apparence" #~ msgid "Dark Theme" #~ msgstr "Thème sombre" #~ msgid "Delete played episodes" #~ msgstr "Supprimer les épisodes lus" #~ msgid "After" #~ msgstr "Après" #~ msgid "Invalid URL" #~ msgstr "URL non valide" #~ msgid "Seconds" #~ msgstr "Secondes" #~ msgid "Minutes" #~ msgstr "Minutes" #~ msgid "Hours" #~ msgstr "Heures" #~ msgid "Days" #~ msgstr "Jours" #~ msgid "Weeks" #~ msgstr "Semaines" podcasts-25.2/podcasts-gtk/po/fur.po000066400000000000000000000446251500126606300174510ustar00rootroot00000000000000# Friulian translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Fabio Tomat , 2018. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-04-19 18:35+0000\n" "PO-Revision-Date: 2024-04-23 22:40+0200\n" "Last-Translator: Fabio Tomat \n" "Language-Team: Friulian \n" "Language: fur\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.4.2\n" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Chest spetacul nol à ancjemò episodis" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Se tu pensis che chest al sedi un erôr, considere di scrivi une segnalazion " "di erôr." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Oten cualchi spetacul" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Zonte gnûfs spetacui vie URL di feed" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Impuarte i spetacui di un altri dispositîf" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Detais episodi" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Menù episodi" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Titul dal podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Durade - Date" #: podcasts-gtk/resources/gtk/episode_description.ui:153 msgid "Download" msgstr "Discjame" #: podcasts-gtk/resources/gtk/episode_description.ui:163 msgid "Cancel" msgstr "Anule" #: podcasts-gtk/resources/gtk/episode_description.ui:173 msgid "Delete File" msgstr "Elimine file" #: podcasts-gtk/resources/gtk/episode_description.ui:183 msgid "Stream" msgstr "Flus" #: podcasts-gtk/resources/gtk/episode_description.ui:193 #: podcasts-gtk/resources/gtk/episode_description.ui:195 #: podcasts-gtk/resources/gtk/player_dialog.ui:173 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:262 msgid "Play" msgstr "Riprodûs" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Episode Description" msgstr "Descrizion episodi" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Va al spetacul" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Copie Url dal episodi" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Tu âs za scoltât chest episodi." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Calcul de dimension dal episodi…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Viôt chest episodi" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Anule il procès di discjariament" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Discjarie chest episodi" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Episodi cence audio" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Controle par gnûfs episodis" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/hamburger.ui:13 msgid "_Import Shows" msgstr "_Impuarte spetacui" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/hamburger.ui:18 msgid "_Export Shows" msgstr "_Espuarte spetacui" #: podcasts-gtk/resources/gtk/hamburger.ui:24 msgid "_Keyboard Shortcuts" msgstr "_Scurtis tastiere" #: podcasts-gtk/resources/gtk/hamburger.ui:32 msgid "_About Podcasts" msgstr "_Informazions su Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:41 msgid "Add a new feed" msgstr "Zonte un gnûf feed" #: podcasts-gtk/resources/gtk/headerbar.ui:52 msgid "Main Menu" msgstr "Menù principâl" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigazion" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Va ae pagjine iniziâl" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Va ae pagjine dal spetacul" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Va ae pagjine di scuvierte" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Riprodutôr" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Ative/Disative pause" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Cîr indevant" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Cîr indaûr" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Gjenerâl" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Controle se a son gnûfs episodis" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Jes de aplicazion" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Impuarte sotscrizions" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Espuarte sotscrizions" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Vuê" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Îr" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Cheste setemane" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Chest mês" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Plui vecjo" #: podcasts-gtk/resources/gtk/player_dialog.ui:7 msgid "Now Playing" msgstr "In riproduzion" #: podcasts-gtk/resources/gtk/player_dialog.ui:28 msgid "Close" msgstr "Siere" #: podcasts-gtk/resources/gtk/player_dialog.ui:152 msgid "Rewind" msgstr "Fâs sù" #: podcasts-gtk/resources/gtk/player_dialog.ui:191 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:271 msgid "Pause" msgstr "Pause" #: podcasts-gtk/resources/gtk/player_dialog.ui:214 msgid "Forward" msgstr "Indenant" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Cambie la velocitât di riproduzion" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Torne indaûr di 10 seconts" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Va indenant di 10 minûts" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Segne ducj i episodis come viodûts" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Sît _web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Anule sotscrizion" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Vierç sît web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Segne dut come viodût" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Anule sotscrizion" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Menù podcast" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episodis" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:6 #: podcasts-gtk/resources/gtk/window.ui:25 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:446 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:380 msgid "Podcasts" msgstr "Podcasts" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:35 msgid "Show" msgstr "Spetacul" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Scolte i tiei spetacui preferîts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Riprodûs, inzorne e gjestìs i tiei podcasts cuntune interface lizere che si " "integre in maniere perfete cun GNOME. Podcasts al pues riprodusi varis " "formâts di audio e visâsi dulà che la riproduzion e je stade fermade. Tu " "puedis iscriviti a spetacui midiant colegaments RSS/Atom, iTunes e " "Soudcloud. Al è pussibil impuartâ lis iscrizions di altris aplicazions " "midiant i files OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "La viodude iniziâl visualizant i gnûfs episodis dai tiei podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "La viodude dal spetacul visualizant lis cuviertis dai tiei podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Il widget dal spetacul visualizant la cuvierte e i ultins episodis di un " "specific podcast" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "La viodude là che un al pues zontâ un gnûf podcast" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:163 msgid "The Podcasts developers" msgstr "I svilupadôrs di Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Scolte i tiei podcast preferîts, daurman dal to scritori." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;Regjistrazion;Trasmission;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Altece dal ultin barcon principâl viert" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Largjece dal ultin barcon principâl viert" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Stât slargjât dal ultin barcon principâl viert" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Indiche se inzornâ in maniere periodiche il contignût" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Trops periodis di timp di spietâ tra i inzornaments automatics" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Ce periodi di timp di spietâ tra i inzornaments automatics" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Indiche se inzornâ il contignût dopo dal inviament" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Trops periodis di timp di spietâ tra lis netisiis automatichis" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Ce periodi di timp di spietâ tra lis netisiis automatichis" #: podcasts-gtk/src/app.rs:336 msgid "Copied URL to clipboard!" msgstr "URL copiât intes notis!" #: podcasts-gtk/src/manager.rs:105 msgid "Download failed: {}" msgstr "Discjariament falît: {}" #: podcasts-gtk/src/stacks/content.rs:66 msgid "New" msgstr "Gnûf" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/stacks/content.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:65 msgid "Shows" msgstr "Spetacui" #: podcasts-gtk/src/utils.rs:474 podcasts-gtk/src/utils.rs:514 msgid "OPML file" msgstr "File OPML" #: podcasts-gtk/src/utils.rs:485 msgid "Select the file from which to you want to import shows." msgstr "Selezione il file che di chei si desidere impuartâ i spetacui." #: podcasts-gtk/src/utils.rs:487 msgid "_Import" msgstr "_Impuarte" #: podcasts-gtk/src/utils.rs:504 msgid "Failed to parse the imported file {}" msgstr "Nol è stât pussibil analizâ il file impuartât {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:525 msgid "Export shows to…" msgstr "_Espuarte spetacui su…" #: podcasts-gtk/src/utils.rs:526 msgid "_Export" msgstr "_Espuarte" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:530 msgid "gnome-podcasts-exported-shows" msgstr "programs-espuartâts-gnome-podcasts" #: podcasts-gtk/src/utils.rs:539 msgid "GNOME Podcasts Subscriptions" msgstr "Sotscrizions di GNOME Podcasts" #: podcasts-gtk/src/utils.rs:543 msgid "Failed to export podcasts" msgstr "No si è rivâts a espuartâ i podcast" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Client podcast pal scritori GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "Fabio Tomat " #: podcasts-gtk/src/widgets/episode.rs:268 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:985 msgid "The media player was unable to execute an action." msgstr "Il riprodutôr multimediâl nol è stât bon di eseguî une azion." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Al slargje la viodude di cheste descrizion" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Lei di plui" #: podcasts-gtk/src/widgets/show_menu.rs:170 msgid "Marked all episodes as listened" msgstr "Segnât ducj i episodis come scoltâts" #: podcasts-gtk/src/widgets/show_menu.rs:171 #: podcasts-gtk/src/widgets/show_menu.rs:194 msgid "Undo" msgstr "Disfe" #: podcasts-gtk/src/widgets/show_menu.rs:190 msgid "Unsubscribed from {}" msgstr "Sotscrizion anulade di {}" #~ msgid "Top position of the last open main window" #~ msgstr "Posizion superiôr dal ultin barcon principâl viert" #~ msgid "Left position of the last open main window" #~ msgstr "Posizion de çampe dal ultin barcon principâl viert" #~ msgid "Enable or disable dark theme" #~ msgstr "Abilite o disabilite il teme scûr" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Back" #~ msgstr "Indaûr" #~ msgid "Enter feed address to add" #~ msgstr "Inserìs la direzion dal feed di zontâ" #~ msgid "Add" #~ msgstr "Zonte" #~ msgid "Show Title" #~ msgstr "Titul dal spetacul" #~ msgid "An in-app action notification" #~ msgstr "Une notifiche di aplicazion in-app" #~ msgid "Fetching new episodes" #~ msgstr "Daûr a recuperâ i gnûfs episodis" #~ msgid "Selected file could not be accessed." #~ msgstr "Il file selezionât nol pues jessi doprât." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Plui informazions su GNOME Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicazion podcast par GNOME" #~ msgid "1.5 speed rate" #~ msgstr "rapuart di velocitât 1.5" #~ msgid "1.25 speed rate" #~ msgstr "rapuart di velocitât 1.25" #~ msgid "Normal speed" #~ msgstr "Velocitât normâl" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Preferencis" #~ msgid "_About" #~ msgstr "_Informazions" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Tu sês za sotscrit a chest feed!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Preferencis" #~ msgid "Preferences" #~ msgstr "Preferencis" #~ msgid "Appearance" #~ msgstr "Aspiet" #~ msgid "Dark Theme" #~ msgstr "Teme scûr" #~ msgid "Delete played episodes" #~ msgstr "Elimine i episodis viodûts" #~ msgid "After" #~ msgstr "Dopo" #~ msgid "Invalid URL" #~ msgstr "URL no valit" #~ msgid "Seconds" #~ msgstr "Seconts" #~ msgid "Minutes" #~ msgstr "Minûts" #~ msgid "Hours" #~ msgstr "Oris" #~ msgid "Days" #~ msgstr "Diis" #~ msgid "Weeks" #~ msgstr "Setemanis" podcasts-25.2/podcasts-gtk/po/gl.po000066400000000000000000000342521500126606300172520ustar00rootroot00000000000000# Spanish translations for gnome-podcasts package. # Copyright (C) 2018 THE gnome-podcasts'S COPYRIGHT HOLDER # This file is distributed under the same license as the gnome-podcasts package. # Automatically generated, 2018. # Fran Dieguez , 2018-2021. # Fran Diéguez , 2022. # msgid "" msgstr "" "Project-Id-Version: gnome-podcasts\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2023-08-28 07:05+0000\n" "PO-Revision-Date: 2023-08-30 00:45+0200\n" "Last-Translator: Fran Diéguez \n" "Language-Team: Galician \n" "Language: gl\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-Generator: Poedit 3.3.2\n" "X-Project-Style: gnome\n" "X-DL-Team: gl\n" "X-DL-Module: podcasts\n" "X-DL-Branch: master\n" "X-DL-Domain: po\n" "X-DL-State: Translating\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Este programa aínda non ten episodios" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Se cres que isto é un erro, por favor, considera abrir un informe de erro." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get Some Shows" msgstr "Obter algúns programas" #: podcasts-gtk/resources/gtk/empty_view.ui:49 msgid "Add new shows via feed URL" msgstr "Engadir novo programa a través do URL da canle" #: podcasts-gtk/resources/gtk/empty_view.ui:71 msgid "Import shows from another device" msgstr "Importar programas desde outro dispositivo" #: podcasts-gtk/resources/gtk/episode_description.ui:41 msgid "Episode Details" msgstr "Detalles do episodio" #: podcasts-gtk/resources/gtk/episode_description.ui:51 #: podcasts-gtk/resources/gtk/headerbar.ui:143 msgid "Back" msgstr "Atrás" #: podcasts-gtk/resources/gtk/episode_description.ui:106 msgid "Podcast Title" msgstr "Título do podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:129 msgid "Duration - Date" msgstr "Duración - Data" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "Episode Description" msgstr "Descrición do episodio" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Ir ao programa" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Copiar URL do episodio" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Xa escoitaches este episodio." #: podcasts-gtk/resources/gtk/episode_widget.ui:157 msgid "Calculating episode size…" msgstr "Calculando tamaño do episodio…" #: podcasts-gtk/resources/gtk/episode_widget.ui:180 msgid "Play this episode" msgstr "Reproducir este episodio" #: podcasts-gtk/resources/gtk/episode_widget.ui:192 msgid "Cancel the download process" msgstr "Cancelar o proceso de descarga" #: podcasts-gtk/resources/gtk/episode_widget.ui:204 msgid "Download this episode" msgstr "Descargar este episodio" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Comprobar se hai novos episodios" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Importar programas" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Exportar programas" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "Atallos de _teclado" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_Sobre Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:33 #: podcasts-gtk/resources/gtk/headerbar.ui:133 msgid "Add a new feed" msgstr "Engadir unha nova canle" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter Feed Address" msgstr "Escriba o enderezo do fluxo" #: podcasts-gtk/resources/gtk/headerbar.ui:67 msgid "Add" msgstr "Engadir" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/headerbar.ui:111 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:501 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/window.rs:64 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:121 msgid "Show Title" msgstr "Título do Programa" #: podcasts-gtk/resources/gtk/headerbar.ui:153 msgid "Main Menu" msgstr "Menú principal" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "Xeral" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Comprobar se hai novos episodios" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "Saír do aplicativo" #: podcasts-gtk/resources/gtk/home_view.ui:57 msgid "Today" msgstr "Hoxe" #: podcasts-gtk/resources/gtk/home_view.ui:85 msgid "Yesterday" msgstr "Onte" #: podcasts-gtk/resources/gtk/home_view.ui:113 msgid "This Week" msgstr "Esta semana" #: podcasts-gtk/resources/gtk/home_view.ui:141 msgid "This Month" msgstr "Este mes" #: podcasts-gtk/resources/gtk/home_view.ui:170 msgid "Older" msgstr "Antigo" #: podcasts-gtk/resources/gtk/player_dialog.ui:22 msgid "Now Playing" msgstr "Reproducindo agora" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Cambiar velocidade de reprodución" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:55 msgid "Rewind 10 seconds" msgstr "Atrás 10 segundos" #: podcasts-gtk/resources/gtk/player_toolbar.ui:63 msgid "Play" msgstr "Reproducir" #: podcasts-gtk/resources/gtk/player_toolbar.ui:71 msgid "Pause" msgstr "Pausa" #: podcasts-gtk/resources/gtk/player_toolbar.ui:79 msgid "Fast forward 10 seconds" msgstr "Adiante 10 segundos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marcar todos os episodios como reproducidos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Páxina _Web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Cancelar subscrición" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Abrir sitio web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Marcar todo como reproducido" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Cancelar subscrición" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Escoita os teus podcasts favoritos" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Reproduza, actualice e xestione os seus podcasts desde unha interface " "liviana que se integre con GNOME. Podcasts pode reproducir varios formatos " "de audio e recorda onde parou de escoitar. Pode subscribirse aos programas " "mediante elementos de RSS/Atom, iTunes e Soundcloud. Pode importar ficheiros " "OPML desde outras aplicacións." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:143 msgid "The Podcasts developers" msgstr "Os desenvolvedores de Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "" "Escoita os teus podcasts favoritos, directamente desde o teu escritorio." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Alto da última xanela aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Anchura da última xanela aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Estado de maximizado da última xanela aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Se refrescar o contido periodicamente" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Cantos periodos de tempo a esperar entre refrescos automáticos" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Qué periodo de tempo hai que esperar entre refrescos automáticos" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Se refrescar o contido ao iniciar" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Cantos periodos de tempo hai que esperar entre limpados automáticos" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Cantos periodos de tiempo hai que esperar entre limpiados automáticos" #: podcasts-gtk/src/app.rs:372 msgid "Copied URL to clipboard!" msgstr "URL copiada no portapapeis!" #: podcasts-gtk/src/stacks/content.rs:69 msgid "New" msgstr "Novo" #: podcasts-gtk/src/stacks/content.rs:71 msgid "Shows" msgstr "Programas" #: podcasts-gtk/src/utils.rs:490 podcasts-gtk/src/utils.rs:532 msgid "OPML file" msgstr "Fichero OPML" #: podcasts-gtk/src/utils.rs:501 msgid "Select the file from which to you want to import shows." msgstr "Seleccione o fichero desde o cal queres importar programas." #: podcasts-gtk/src/utils.rs:503 msgid "_Import" msgstr "_Importar" #: podcasts-gtk/src/utils.rs:519 msgid "Failed to parse the imported file" msgstr "Produciuse un fallo ao leer o fichero importado" #: podcasts-gtk/src/utils.rs:542 msgid "Export shows to…" msgstr "Exportar programas a…" #: podcasts-gtk/src/utils.rs:543 msgid "_Export" msgstr "_Exportar" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:547 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-programas-exportados" #: podcasts-gtk/src/utils.rs:557 msgid "GNOME Podcasts Subscriptions" msgstr "Subscricións de Podcasts de GNOME" #: podcasts-gtk/src/utils.rs:558 msgid "Failed to export podcasts" msgstr "Produciuse un fallo ao exportar os podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Aplicativo de Podcast para o ambiente GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "Fran Dieguez , 2018-2023" #: podcasts-gtk/src/widgets/episode.rs:143 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:893 msgid "The media player was unable to execute an action." msgstr "O reprodutor non puido reproducir unha acción." #: podcasts-gtk/src/widgets/read_more_label.rs:32 msgid "Read More" msgstr "Ler máis" #: podcasts-gtk/src/widgets/show_menu.rs:180 msgid "Marked all episodes as listened" msgstr "Marcar todos os episodios como escoitados" #: podcasts-gtk/src/widgets/show_menu.rs:181 #: podcasts-gtk/src/widgets/show_menu.rs:204 msgid "Undo" msgstr "Desfacer" #: podcasts-gtk/src/widgets/show_menu.rs:200 msgid "Unsubscribed from {}" msgstr "Subscrición cancelada para {}" #~ msgid "Top position of the last open main window" #~ msgstr "Posición superior da última xanela aberta" #~ msgid "Left position of the last open main window" #~ msgstr "Posición esquerda da última xanela aberta" #~ msgid "Enable or disable dark theme" #~ msgstr "Activar o desactivar el tema oscuro" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "An in-app action notification" #~ msgstr "Unha notificación de acción en aplicativo" #~ msgid "Fetching new episodes" #~ msgstr "Obtendo novos episodios" #~ msgid "Selected file could not be accessed." #~ msgstr "O fichero seleccionado non é accesíbel." #~ msgid "_Cancel" #~ msgstr "_Cancelar" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Saber máis sobre Podcasts de GNOME" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicativo de Podcast para GNOME" #~ msgid "org.gnome.Podcasts" #~ msgstr "org.gnome.Podcasts" #~ msgid "_Preferences" #~ msgstr "_Preferencias" #~ msgid "_About" #~ msgstr "_Sobre" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Xa estás suscrito a esta canle!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "1.5 speed rate" #~ msgstr "velocidade 1.5" #~ msgid "1.25 speed rate" #~ msgstr "velocidade 1.25" #~ msgid "Normal speed" #~ msgstr "Velocidade normal" #~ msgid "Preferences" #~ msgstr "Preferencias" #~ msgid "Appearance" #~ msgstr "Aspecto" #~ msgid "Dark Theme" #~ msgstr "Tema escuro" #~ msgid "Delete played episodes" #~ msgstr "Eliminar episodios reproducidos" #~ msgid "After" #~ msgstr "Despois" #~ msgid "Invalid URL" #~ msgstr "URL non válida" #~ msgid "Seconds" #~ msgstr "Segundos" #~ msgid "Minutes" #~ msgstr "Minutos" #~ msgid "Hours" #~ msgstr "Horas" #~ msgid "Days" #~ msgstr "Días" #~ msgid "Weeks" #~ msgstr "Semanas" podcasts-25.2/podcasts-gtk/po/he.po000066400000000000000000000520241500126606300172410ustar00rootroot00000000000000# Hebrew translation for podcasts. # Copyright (C) 2022 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Yosef Or Boczko , 2022-2024. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-28 11:59+0200\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew\n" "Language: he\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 ? 1 : n>10 && n%10==0 ? " "2 : 3);\n" "X-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "פרקים: " # msgctxt "shortcut window" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "פורסם לאחרונה" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "ה_רשמה" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "נרשם להזנה…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "הוספת הסכתים" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "חיפוש" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "יש להזין כתובת הזנה או לחפש בפלטפורמות הנבחרות." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "להתחיל את החיפוש" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "בטעינה…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "יש לאפשר את הפלטפורמות לחיפוש שלהלן, או להזין כתובת הזנה ‎http(s)." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "פלטפורמות לחיפוש" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "שאילתות החיפוש ישלחו לפלטפורמות אלו." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "תוצאות חיפוש" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "לא נמצאו תוצאות." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "לתכנית זו עוד אין פרקים" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "אם נראה לך שזו שגיאה, נא לשקול לדווח על תקלה." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "לקבל כמה תכניות" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "הוספת תכניות על ידי מילוי הכתובת של הערוץ" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "ייבוא תכניות מהתקן אחר" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "פרטים על הפרק" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "תפריט פרק" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "כותרת ההסכת" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "משך - תאריך" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "ה_זרמה" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "ה_שמעה" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "הור_דה" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "ביטול" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "מחיקה" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "תיאור פרק" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "תמונת נושא של פרק" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "מעבר לתכנית" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "העתקת כתובת פרק" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "סימון כנוגן" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "סימון כלאנוגן" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "כבר האזנת לפרק זה." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "גודל הפרק מחושב…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "השמעת פרק זה" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "ביטול תהליך ההורדה" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "הורדת פרק זה" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "פרק ללא שמע" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "ניווט" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "מעבר לדף הבית" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "מעבר לעמוד התכנית" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "מעבר לעמוד החשיפה" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "נגן" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "החלפת מצב השהיה" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "חתירה קדימה" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "חתירה לאחור" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "כללי" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "בדיקה אם יש פרקים חדשים" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "יציאה מהיישום" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "ייבוא מינויים" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "ייצוא מינויים" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "היום" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "אתמול" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "השבוע" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "החודש" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "ישן יותר" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "שינוי מהירות השמעה" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "חזרה לאחור" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "השמעה" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "השהיה" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "קפיצה קדימה" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "תיאור" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "חזרה 10 שניות לאחור" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "דילוג 10 שניות קדימה" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_סימון כל הפרקים כנוגנו" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_אתר אינטרנט" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_ביטול מינוי" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "פתיחת אתר אינטרנט" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "סימון הכל כנוגן" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "ביטול מינוי" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "תפריט הסכת" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "פרקים" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_בדיקה אם יש פרקים חדשים" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_ייבוא תכניות" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_ייצוא תכניות" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_צירופי מקשים" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_על הסכתים" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "הסכתים" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "הוספת ערוץ חדש" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "תפריט ראשי" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "הצגה" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "האזנה לתכניות המועדפות שלך" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "השמעה, עדכון וניהול ההסכתים בממשק קל המשתלב בצורה חלקה עם GNOME. הסכתים יכול " "להשמיע סוגים שונים של תבניות שמע וזוכר את המקום האחרון בו עצרת את ההאזנה. " "נִתן להירשם לתכניות באמצעות RSS/Atom, ‏iTunes וקישורי Soundcloud. נִתן לייבא " "מנויים והרשמות מיישומים אחרים באמצעות קובצי OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "תצוגת הבית מציגה את הפרקים החדשים בהסכתים שלך" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "תצוגת התכניות מציגה את הכיסויים של ההסכתים שלך" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "תצוגת היישום מציגה את הכיסוי והפרקים האחרונים של הסכת מסוים" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "התצוגה של המקום להוספת הסכת חדש" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "מפתחי הסכתים" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "האזנה להסכתים האהובים עליך, ישירות משולחן העבודה שלך." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "הסכתים;RSS;פודקאסט;פודקאסטים;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Height of the last open main window" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Width of the last open main window" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Maximized state of the last open main window" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Whether to periodically refresh content" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "How many periods of time to wait between automatic refreshes" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "What period of time to wait between automatic refreshes" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Whether to refresh content after startup" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "How many periods of time to wait between automatic cleanups" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "What period of time to wait between automatic cleanups" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "כתובת הועתקה ללוח הגזירים!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "קפיצה אל ‎{}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "קפיצה אל ‎{}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "ההורדה נכשלה: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "ההרשמה להזנה נכשלה: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "קובץ OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "בחירת הקובץ לייבוא תכניות." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "יי_בוא" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "פענוח הקובץ המיובא {} נכשל" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "ייצוא תכניות אל…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "יי_צוא" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "קובץ-ייצוא-תכניות-הסכתים-גנום" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "מינויי GNOME הסכתים" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "ייצוא ההסכתים נכשל" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "לקוח הסכתים עבור שולחן העבודה GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "יוסף אור בוצ׳קו \n" "ירון שהרבני \n" "מיזם תרגום GNOME לעברית https://l10n.gnome.org/teams/he/" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "הערוצים מתקבלים…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "חדש" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "תכניות" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "התקדמות ההורדה" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "‏{} דק׳" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "נגן המדיה לא הצליח לבצע את הפעולה." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "מרחיב את התיאור הזה חזותית" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "לקרוא עוד" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "כל הפרקים סומנו כהואזנו" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "ביטול" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "בוטל המינוי מהתכנית {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "בטעינה…" #~ msgid "Now Playing" #~ msgstr "מושמע כעת" #~ msgid "Close" #~ msgstr "סגירה" #~ msgid "Enter Feed Address" #~ msgstr "הזנת כתובת ערוץ" #~ msgid "Popover menu (ESC to close)" #~ msgstr "תפריט קופץ (ESC לסגירה)" #~ msgid "Add" #~ msgstr "הוספה" #~ msgid "Back" #~ msgstr "אחורה" #~ msgid "Show Title" #~ msgstr "הצגת כותרת" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Top position of the last open main window" #~ msgstr "Top position of the last open main window" #~ msgid "Left position of the last open main window" #~ msgstr "Left position of the last open main window" #~ msgid "Enable or disable dark theme" #~ msgstr "Enable or disable dark theme" #~ msgid "An in-app action notification" #~ msgstr "התרעות על פעולות בתוך היישום" #~ msgid "Selected file could not be accessed." #~ msgstr "לא נתן לגשת לקובץ הנבחר." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "ללמוד עוד על GNOME הסכתים" podcasts-25.2/podcasts-gtk/po/hi.po000066400000000000000000000560441500126606300172530ustar00rootroot00000000000000# Hindi translation for podcasts. # Copyright (C) 2024 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Scrambled777 , 2024. # msgid "" msgstr "" "Project-Id-Version: podcasts main\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-07-20 04:39+0000\n" "PO-Revision-Date: 2024-07-21 10:10+0530\n" "Last-Translator: Scrambled777 \n" "Language-Team: Hindi \n" "Language: hi\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-Generator: Gtranslator 46.1\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "एपिसोड: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:114 msgid "0" msgstr "0" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "अंतिम प्रकाशन" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "Subscribe" msgstr "सदस्यता लें" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:171 msgid "Subscribing to feed..." msgstr "फीड के लिए सदस्यता ले रहे हैं..." #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "पॉडकास्ट जोड़ें" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "खोजें" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "फीड URL दर्ज करें या चयनित प्लेटफार्म खोजें।" #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "खोज जमा करें" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 msgid "Loading..." msgstr "लोड हो रहा है..." #: podcasts-gtk/resources/gtk/discovery_page.ui:107 msgid "Loading" msgstr "लोड हो रहा है" #: podcasts-gtk/resources/gtk/discovery_page.ui:121 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "कृपया नीचे खोज प्लेटफार्म सक्षम करें, या एक http(s) फीड URL दर्ज करें।" #: podcasts-gtk/resources/gtk/discovery_page.ui:133 msgid "Search Platforms" msgstr "प्लेटफार्म खोजें" #: podcasts-gtk/resources/gtk/discovery_page.ui:134 msgid "Search queries will be sent to these platforms." msgstr "खोज क्वेरी इन प्लेटफार्मों पर भेजी जाएंगी।" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "खोज परिणाम" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "कोई परिणाम नहीं मिला।" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "इस कार्यक्रम के अभी कोई एपिसोड नहीं हैं" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "यदि आपको लगता है कि यह एक त्रुटि है, तो कृपया बग रिपोर्ट लिखने पर विचार करें।" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "कुछ कार्यक्रम प्राप्त करें" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "फीड URL के माध्यम से नए कार्यक्रम जोड़ें" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "अन्य उपकरण से कार्यक्रम आयात करें" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "एपिसोड विवरण" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "एपिसोड मेनू" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "पॉडकास्ट शीर्षक" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "अवधि - दिनांक" #: podcasts-gtk/resources/gtk/episode_description.ui:157 msgid "Stream" msgstr "स्ट्रीम" #: podcasts-gtk/resources/gtk/episode_description.ui:183 #: podcasts-gtk/resources/gtk/player_sheet.ui:150 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:262 msgid "Play" msgstr "चलाएं" #: podcasts-gtk/resources/gtk/episode_description.ui:210 msgid "Download" msgstr "डाउनलोड" #: podcasts-gtk/resources/gtk/episode_description.ui:237 msgid "Cancel" msgstr "रद्द करें" #: podcasts-gtk/resources/gtk/episode_description.ui:274 msgid "Delete" msgstr "मिटाएं" #: podcasts-gtk/resources/gtk/episode_description.ui:294 msgid "Episode Description" msgstr "एपिसोड विवरण" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "कार्यक्रम पर जाएं" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "एपिसोड URL कॉपी करें" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "आप यह एपिसोड पहले ही सुन चुके हैं।" #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "एपिसोड आकार की गणना की जा रही है…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "यह एपिसोड चलायें" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "डाउनलोड प्रक्रिया रद्द करें" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "इस एपिसोड को डाउनलोड करें" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "बिना ऑडियो वाला एपिसोड" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "नेविगेशन" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "मुखपृष्ठ पर जाएं" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "कार्यक्रम पृष्ठ पर जाएं" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "खोज पृष्ठ पर जाएं" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "प्लेयर" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "विराम टॉगल करें" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "आगे खोजें" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "पीछे खोजें" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "सामान्य" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "नए एपिसोड की जांच करें" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "अनुप्रयोग छोड़ें" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "सदस्यताएं आयात करें" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "सदस्यताएं निर्यात करें" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "आज" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "बिता कल" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "इस सप्ताह" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "इस महीने" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "पुराना" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "प्लेबैक गति बदलें" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:129 msgid "Rewind" msgstr "पीछे" #: podcasts-gtk/resources/gtk/player_sheet.ui:168 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:271 msgid "Pause" msgstr "विराम" #: podcasts-gtk/resources/gtk/player_sheet.ui:191 msgid "Forward" msgstr "आगे" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "10 सेकंड पीछे" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "10 सेकंड आगे" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "सभी एपिसोड को चलाये गये चिह्नित करें (_M)" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "वेबसाइट (_W)" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "सदस्यता छोड़ें (_U)" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "वेबसाइट खोलें" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "सभी को चलाये गये चिह्नित करें" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "सदस्यता छोड़ें" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "पॉडकास्ट मेनू" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "एपिसोड" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "नए एपिसोड की जांच करें (_C)" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "कार्यक्रम आयात करें (_I)" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "कार्यक्रम निर्यात करें (_E)" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "कीबोर्ड शॉर्टकट (_K)" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "पॉडकास्टs के बारे में (_A)" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:470 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:445 msgid "Podcasts" msgstr "पॉडकास्ट" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "नई फीड जोड़ें" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "मुख्य मेनू" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "कार्यक्रम" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "अपना पसंदीदा कार्यक्रम सुनें" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "अपने पॉडकास्ट को एक हल्के इंटरफ़ेस से चलाएं, अपडेट करें और प्रबंधित करें जो गनोम के साथ सहजता " "से एकीकृत होता है। पॉडकास्ट विभिन्न ऑडियो प्रारूप चला सकते हैं और याद रख सकते हैं कि आपने " "कहाँ सुनना बंद किया था। आप RSS/Atom, iTunes और Soundcloud लिंक के माध्यम से " "कार्यक्रमों की सदस्यता ले सकते हैं। अन्य ऐप्स से सदस्यता OPML फाइलों के माध्यम से आयात किया " "जा सकता है।" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "आपके पॉडकास्ट के नवीनतम एपिसोड प्रदर्शित करता हुआ होम दृश्य" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "आपके पॉडकास्ट के कवर को प्रदर्शित करता हुआ कार्यक्रम दृश्य" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "कार्यक्रम विजेट किसी विशिष्ट पॉडकास्ट के कवर और नवीनतम एपिसोड प्रदर्शित करता है" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "वह दृश्य जहां कोई नया पॉडकास्ट जोड़ सकता है" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:186 msgid "The Podcasts developers" msgstr "पॉडकास्ट डेवलपर्स" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "सीधे अपने डेस्कटॉप से अपने पसंदीदा पॉडकास्ट सुनें।" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "पॉडकास्ट;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "अंतिम खुली मुख्य विंडो की ऊंचाई" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "अंतिम खुली मुख्य विंडो की चौड़ाई" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "अंतिम खुली मुख्य विंडो की अधिकतम स्थिति" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "क्या सामग्री को समय-समय पर ताज़ा करना है" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "स्वचालित ताज़ा के बीच कितनी अवधि तक प्रतीक्षा करनी होगी" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "स्वचालित ताज़ा के बीच किस अवधि तक प्रतीक्षा करनी है" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "क्या स्टार्टअप के बाद सामग्री को ताज़ा करना है" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "स्वचालित सफाई के बीच कितनी अवधि तक प्रतीक्षा करनी होगी" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "स्वचालित सफाई के बीच किस अवधि तक प्रतीक्षा करनी है" #: podcasts-gtk/src/app.rs:344 msgid "Copied URL to clipboard!" msgstr "क्लिपबोर्ड पर URL कॉपी किया गया!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "{}:{}:{} पर जाएं" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "{}:{} पर जाएं" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "डाउनलोड विफल: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "फीड की सदस्यता लेने में विफल: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML फाइल" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "उस फाइल का चयन करें जिससे आप कार्यक्रम आयात करना चाहते हैं।" #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "आयात करें (_I)" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "आयातित फाइल का विश्लेषण करने में विफल {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "यहां कार्यक्रम को निर्यात करें…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "निर्यात करें (_E)" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "गनोम पॉडकास्ट सदस्यताएं" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "पॉडकास्ट निर्यात करने में विफल" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "गनोम डेस्कटॉप के लिए पॉडकास्ट क्लाइंट।" #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "Scrambled777 " #: podcasts-gtk/src/widgets/content_stack.rs:66 msgid "New" msgstr "नया" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:67 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "कार्यक्रम" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "डाउनलोड प्रगति" #: podcasts-gtk/src/widgets/episode.rs:282 msgid "{} min" msgstr "{} मिन" #: podcasts-gtk/src/widgets/player.rs:1070 msgid "The media player was unable to execute an action." msgstr "मीडिया प्लेयर कोई कार्रवाई निष्पादित करने में असमर्थ था।" #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "इस विवरण को दृष्टिगत रूप से विस्तारित करता है" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "अधिक पढ़ें" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "सभी एपिसोड को सुने गए चिह्नित किया गया" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "पूर्ववत करें" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "{} से सदस्यता समाप्त" podcasts-25.2/podcasts-gtk/po/hr.po000066400000000000000000000344001500126606300172540ustar00rootroot00000000000000# Croatian translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2021-11-28 07:07+0000\n" "PO-Revision-Date: 2022-03-11 21:15+0100\n" "Last-Translator: gogo \n" "Language-Team: Croatian \n" "Language: 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-Generator: Poedit 3.0.1\n" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Top position of the last open main window" msgstr "Gornji položaj posljednjeg otvorenog prozora" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Left position of the last open main window" msgstr "Lijevi položaj posljednjeg otvorenog prozora" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Height of the last open main window" msgstr "Visina posljednjeg otvorenog prozora" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:27 msgid "Width of the last open main window" msgstr "Širina posljednjeg otvorenog prozora" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:31 msgid "Maximized state of the last open main window" msgstr "Stanje uvećanja posljednjeg otvorenog prozora" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:36 msgid "Enable or disable dark theme" msgstr "Omogući ili onemogući tamnu temu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to periodically refresh content" msgstr "Treba li povremeno osvježiti sadržaj" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:46 msgid "How many periods of time to wait between automatic refreshes" msgstr "" "Koliko vremenskih razdoblja treba pričekati između automatskih osvježavanja" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:50 msgid "What period of time to wait between automatic refreshes" msgstr "" "Koliko vremensko razdoblje treba pričekati između automatskih osvježavanja" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:54 msgid "Whether to refresh content after startup" msgstr "Treba li osvježiti sadržaj nakon pokretanja" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:60 msgid "How many periods of time to wait between automatic cleanups" msgstr "" "Koliko vremenskih razdoblja treba pričekati između automatskih čišćenja" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:64 msgid "What period of time to wait between automatic cleanups" msgstr "Koliko vremensko razdoblje treba pričekati između automatskih čišćenja" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/gtk/headerbar.ui:137 podcasts-gtk/src/app.rs:438 #: podcasts-gtk/src/widgets/aboutdialog.rs:63 podcasts-gtk/src/window.rs:66 msgid "Podcasts" msgstr "Podcasti" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Slušajte svoje omiljene podcaste." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Slušajte svoje omiljene emisije" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Slušajte, nadopunite i upravljajte svojim podcastima s jednostavnog sučelja " "koje se neprimjetno integrira s GNOMOM. Podcasti mogu reproducirati razne " "zvučne formate i zapamtiti gdje ste prestali slušati. Možete se pretplatiti " "na emisije putem RSS/Atom, iTunes, i Soundcloud poveznica. Pretplate s " "drugih aplikacija se mogu uvesti putem OPML datoteka." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:117 msgid "Jordan Petridis" msgstr "Jordan Petridis" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:118 msgid "Julian Hofer" msgstr "Julian Hofer" #: podcasts-gtk/resources/gtk/empty_view.ui:46 msgid "This show does not have episodes yet" msgstr "Ova emisjia još nema epizoda" #: podcasts-gtk/resources/gtk/empty_view.ui:57 msgid "If you think this is an error, please consider writing a bug report." msgstr "Ako mislite da je ovo greška, prijavite izvještaj greške." #: podcasts-gtk/resources/gtk/empty_view.ui:91 msgid "Get some shows" msgstr "Nabavite nove emisije" #: podcasts-gtk/resources/gtk/empty_view.ui:123 msgid "Add new shows via feed URL" msgstr "Dodaj novu emisiju putem kanala ili URL-a" #: podcasts-gtk/resources/gtk/empty_view.ui:152 msgid "Import shows from another device" msgstr "Uvezi emisije iz drugog uređaja" #: podcasts-gtk/resources/gtk/episode_description.ui:49 msgid "Episode Details" msgstr "Pojedinosti epizode" #: podcasts-gtk/resources/gtk/episode_description.ui:64 #: podcasts-gtk/resources/gtk/headerbar.ui:186 msgid "Back" msgstr "Natrag" #: podcasts-gtk/resources/gtk/episode_description.ui:148 msgid "Podcast Title" msgstr "Naslov podcasta" #: podcasts-gtk/resources/gtk/episode_description.ui:186 msgid "Duration - Date" msgstr "Trajanje - Datum" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Episode Description" msgstr "Opis epizode" #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Idi na emisiju" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Kopiraj url epizode" #: podcasts-gtk/resources/gtk/episode_widget.ui:82 msgid "You’ve already listened to this episode." msgstr "Već ste poslušali ovu epizodu." #: podcasts-gtk/resources/gtk/episode_widget.ui:187 msgid "Calculating episode size…" msgstr "Izračunavanje veličine emisjie…" #: podcasts-gtk/resources/gtk/episode_widget.ui:214 msgid "Play this episode" msgstr "Slušaj ovu epizodu" #: podcasts-gtk/resources/gtk/episode_widget.ui:230 msgid "Cancel the download process" msgstr "Prekini preuzimanja" #: podcasts-gtk/resources/gtk/episode_widget.ui:250 msgid "Download this episode" msgstr "Preuzmi ovu epizodu" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Provjeri ima li novih epizoda" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Uvezi emisije" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Izvezi emisije" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Prečaci tipkovnice" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_O Podcasti" #: podcasts-gtk/resources/gtk/headerbar.ui:35 #: podcasts-gtk/resources/gtk/headerbar.ui:165 msgid "Add a new feed" msgstr "Dodaj novi kanal" #: podcasts-gtk/resources/gtk/headerbar.ui:53 msgid "Enter feed address to add" msgstr "Upišite adresu kanala za dodavanje" #: podcasts-gtk/resources/gtk/headerbar.ui:79 msgid "Add" msgstr "Dodaj" #: podcasts-gtk/resources/gtk/headerbar.ui:150 msgid "Show Title" msgstr "Prikaži naslov" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgid "General" msgstr "Općenito" #: podcasts-gtk/resources/gtk/help-overlay.ui:18 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Provjeri ima li novih epizoda" #: podcasts-gtk/resources/gtk/help-overlay.ui:25 msgctxt "shortcut window" msgid "Quit the application" msgstr "Zatvori aplikaciju" #: podcasts-gtk/resources/gtk/home_view.ui:56 msgid "Today" msgstr "Danas" #: podcasts-gtk/resources/gtk/home_view.ui:89 msgid "Yesterday" msgstr "Jučer" #: podcasts-gtk/resources/gtk/home_view.ui:122 msgid "This Week" msgstr "Ovaj tjedan" #: podcasts-gtk/resources/gtk/home_view.ui:155 msgid "This Month" msgstr "Ovaj mjesec" #: podcasts-gtk/resources/gtk/home_view.ui:189 msgid "Older" msgstr "Starije" #: podcasts-gtk/resources/gtk/inapp_notif.ui:69 msgid "An in-app action notification" msgstr "Obavijesti radnja iz aplikacije" #: podcasts-gtk/resources/gtk/inapp_notif.ui:75 msgid "Undo" msgstr "Vrati" #: podcasts-gtk/resources/gtk/player_dialog.ui:14 msgid "Now Playing" msgstr "Trenutna reprodukcija" #: podcasts-gtk/resources/gtk/player_rate.ui:32 msgid "Change the playback speed" msgstr "Promijeni brzinu reprodukcije" #: podcasts-gtk/resources/gtk/player_rate.ui:46 #: podcasts-gtk/resources/gtk/player_rate.ui:85 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:65 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:70 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:75 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:80 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:90 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:95 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:97 msgid "Rewind 10 seconds" msgstr "Premotaj 10 sekundi" #: podcasts-gtk/resources/gtk/player_toolbar.ui:107 msgid "Play" msgstr "Reproduciraj" #: podcasts-gtk/resources/gtk/player_toolbar.ui:118 msgid "Pause" msgstr "Pauziraj" #: podcasts-gtk/resources/gtk/player_toolbar.ui:129 msgid "Fast forward 10 seconds" msgstr "Brzo premotaj 10 sekundi" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Označi se epizode kao odslušane" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Web stranica" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Ukini pretplatu" #: podcasts-gtk/resources/gtk/show_menu.ui:36 msgid "Open Website" msgstr "Otvori web stranicu" #: podcasts-gtk/resources/gtk/show_menu.ui:40 msgid "Mark All as Played" msgstr "Označi sve kao odslušano" #: podcasts-gtk/resources/gtk/show_menu.ui:46 msgid "Unsubscribe" msgstr "Ukini pretplatu" #: podcasts-gtk/resources/gtk/show_widget.ui:98 msgid "Read More" msgstr "Pročitaj više" #: podcasts-gtk/src/app.rs:353 msgid "Fetching new episodes" msgstr "Preuzimanje novi epizoda" #: podcasts-gtk/src/stacks/content.rs:58 msgid "New" msgstr "Novo" #: podcasts-gtk/src/stacks/content.rs:59 msgid "Shows" msgstr "Emisije" #: podcasts-gtk/src/utils.rs:501 msgid "Select the file from which to you want to import shows." msgstr "Odaberite datoteku iz koje želite uvesti emisije." #: podcasts-gtk/src/utils.rs:504 msgid "_Import" msgstr "_Uvoz" #: podcasts-gtk/src/utils.rs:513 podcasts-gtk/src/utils.rs:562 msgid "OPML file" msgstr "OPML datoteka" #: podcasts-gtk/src/utils.rs:531 msgid "Failed to parse the imported file" msgstr "Neuspjela obrada uvezene datoteke" #: podcasts-gtk/src/utils.rs:536 podcasts-gtk/src/utils.rs:581 msgid "Selected file could not be accessed." msgstr "Nemoguć pristup dabranoj datoteci." #: podcasts-gtk/src/utils.rs:547 msgid "Export shows to…" msgstr "_Izvezi emisije u…" #: podcasts-gtk/src/utils.rs:550 msgid "_Export" msgstr "_Uvoz" #: podcasts-gtk/src/utils.rs:551 msgid "_Cancel" msgstr "_Odustani" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:555 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-izvezene-emisije" #: podcasts-gtk/src/utils.rs:575 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcast pretplate" #: podcasts-gtk/src/utils.rs:576 msgid "Failed to export podcasts" msgstr "Neuspjeli uvoz podcasta" #: podcasts-gtk/src/widgets/aboutdialog.rs:58 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcast klijent za GNOME radnu površinu." #: podcasts-gtk/src/widgets/aboutdialog.rs:65 msgid "Learn more about GNOME Podcasts" msgstr "Saznajte više o GNOME Podcasti" #: podcasts-gtk/src/widgets/aboutdialog.rs:69 msgid "translator-credits" msgstr "" "Launchpad Contributions:\n" " gogo https://launchpad.net/~trebelnik-stefina" #: podcasts-gtk/src/widgets/episode.rs:145 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/episode_description.rs:165 msgid "Copied URL to clipboard!" msgstr "URL je kopiran u međuspremnik!" #: podcasts-gtk/src/widgets/player.rs:902 msgid "The media player was unable to execute an action." msgstr "Medijski reproduktor nije uspio izvršiti radnju." #: podcasts-gtk/src/widgets/show_menu.rs:194 msgid "Marked all episodes as listened" msgstr "Označi sve epizodae kao odslušane" #: podcasts-gtk/src/widgets/show_menu.rs:199 msgid "Unsubscribed from {}" msgstr "Ukini pretplatu za {}" #~ msgid "Podcast app for GNOME" #~ msgstr "Podcast aplikacija za GNOME" #~ msgid "Double speed rate" #~ msgstr "Dvostruko brže" #~ msgid "1.75 speed rate" #~ msgstr "1.75 brže" #~ msgid "1.5 speed rate" #~ msgstr "1.5 brže" #~ msgid "1.25 speed rate" #~ msgstr "1.25 brže" #~ msgid "Normal speed" #~ msgstr "Normalna brzina" #~ msgid "You are already subscribed to this show" #~ msgstr "Već ste pretplaćeni na ovu emisiju" #~ msgid "Invalid URL" #~ msgstr "Nevažeći URL" #~ msgid "_Preferences" #~ msgstr "_Osobitosti" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Već ste pretplaćeni na ovaj kanal!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Osobitosti" #~ msgid "Preferences" #~ msgstr "Osobitosti" #~ msgid "Appearance" #~ msgstr "Izgled" #~ msgid "Dark Theme" #~ msgstr "Tamna tema" #~ msgid "Delete played episodes" #~ msgstr "Obriši poslušane epizode" #~ msgid "After" #~ msgstr "Poslije" #~ msgid "Seconds" #~ msgstr "Sekunda" #~ msgid "Minutes" #~ msgstr "Minuta" #~ msgid "Hours" #~ msgstr "Sata" #~ msgid "Days" #~ msgstr "Dana" #~ msgid "Weeks" #~ msgstr "Tjedana" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_About" #~ msgstr "_O programu" podcasts-25.2/podcasts-gtk/po/hu.po000066400000000000000000000472161500126606300172700ustar00rootroot00000000000000# Hungarian translation for podcasts. # Copyright (C) 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025 Free Software Foundation, Inc. # This file is distributed under the same license as the podcasts package. # # Meskó Balázs , 2018, 2023. # Balázs Úr , 2019, 2020, 2021, 2022, 2023, 2024, 2025. msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-02-23 08:07+0000\n" "PO-Revision-Date: 2025-03-06 19:14+0100\n" "Last-Translator: Balázs Úr \n" "Language-Team: Hungarian \n" "Language: 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-Generator: Lokalize 23.08.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Epizódok: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Utolsó közzététel" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 #| msgid "Subscribe" msgid "_Subscribe" msgstr "_Feliratkozás" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 #| msgid "Subscribing to feed..." msgid "Subscribing to feed…" msgstr "Feliratkozás a hírforrásra…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Podcastok hozzáadása" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Keresés" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "" "Adjon meg egy hírforrás URL-t, vagy keressen a kiválasztott platformokon." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Keresés elküldése" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 #| msgid "Loading" msgid "Loading…" msgstr "Betöltés…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Engedélyezze a keresési platformot az alábbiakban, vagy adjon meg egy " "http(s) hírforrás URL-t." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Keresési platformok" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "A keresési lekérdezések ezekre a platformokra kerülnek elküldésre." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Keresési eredmények" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Nem találhatók eredmények." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Ennek a műsornak még nincsenek epizódjai" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Ha úgy gondolja hogy ez hiba, akkor írjon egy hibajelentést." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Szerezzen be néhány műsort" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Új műsorok hozzáadása hírforrás URL-ből" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Műsorok importálása egy másik eszközről" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Epizód részletei" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Epizód menü" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Podcast címe" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Hossz – Dátum" #: podcasts-gtk/resources/gtk/episode_description.ui:149 #| msgid "Stream" msgid "_Stream" msgstr "_Közvetítés" #: podcasts-gtk/resources/gtk/episode_description.ui:167 #| msgid "Play" msgid "_Play" msgstr "_Lejátszás" #: podcasts-gtk/resources/gtk/episode_description.ui:185 #| msgid "Download" msgid "_Download" msgstr "Le_töltés" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Mégse" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Törlés" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Epizód leírása" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Epizód borítója" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Ugrás a műsorhoz" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 #| msgid "Copy Episode Url" msgid "Copy Episode URL" msgstr "Epizód URL másolása" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 #| msgid "Mark All as Played" msgid "Mark as Played" msgstr "Megjelölés lejátszottként" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 #| msgid "Mark All as Played" msgid "Mark as Unplayed" msgstr "Megjelölés lejátszatlanként" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Már meghallgatta ezt az epizódot." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Epizódméret kiszámítása…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Ezen epizód lejátszása" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "A letöltési folyamat megszakítása" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Ezen epizód letöltése" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Hang nélküli epizód" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigáció" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Ugrás a kezdőoldalra" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Ugrás a műsorok oldalra" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Ugrás a felfedezés oldalra" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Lejátszó" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Szüneteltetés be- vagy kikapcsolása" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Előretekerés" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Visszatekerés" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Általános" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Új epizódok keresése" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Kilépés az alkalmazásból" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Feliratkozások importálása" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Feliratkozások exportálása" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Ma" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Tegnap" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Ezen a héten" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Ebben a hónapban" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Régebbi" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Lejátszási sebesség módosítása" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Visszatekerés" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Lejátszás" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Szünet" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Előretekerés" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 #| msgid "Episode Description" msgid "Description" msgstr "Leírás" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Visszatekerés 10 másodperccel" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Előretekerés 10 másodperccel" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Összes epizód meg_jelölése lejátszottként" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Webhely" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Leiratkozás" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Webhely megnyitása" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Összes megjelölése lejátszottként" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Leiratkozás" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Podcast menü" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Epizódok" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "Új epizódok _keresése" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "Műsorok _importálása" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "Műsorok _exportálása" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "Gyorsbille_ntyűk" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "A Podcastok _névjegye" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:505 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podcastok" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Új hírforrás hozzáadása" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Főmenü" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Műsor" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Hallgassa kedvenc műsorait" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Játssza le, frissítse és kezelje a podcastjait egy könnyűsúlyú felületről, " "amely zökkenőmentesen illeszkedik a GNOME-hoz. A Podcastok különböző " "hangformátumokat tud lejátszani, és megjegyzi, hogy hol hagyta abba a " "hallgatásukat. Feliratkozhat a műsorokra RSS/Atom hírcsatornákkal, iTunes és " "Soundcloud hivatkozásokkal. A más alkalmazásokban lévő feliratkozások is " "importálhatók OPML-fájlok segítségével." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "A podcastok legújabb epizódjait megjelenítő kezdő nézet" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "A podcastok borítóit megjelenítő műsorok nézet" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Egy adott podcast borítóját és legújabb epizódjait megjelenítő műsor " "felületi elem" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Az a nézet, ahol új podcastot lehet hozzáadni" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "A Podcastok fejlesztői" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Hallgassa kedvenc podcastjait, közvetlenül az asztalán." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "A legutóbbi nyitott főablak magassága" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "A legutóbbi nyitott főablak szélessége" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "A legutóbbi nyitott főablak maximalizálási állapota" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Időközönként frissüljön-e a tartalom" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Mennyi időközt várjon az automatikus frissítések között" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Milyen időközöket várjon az automatikus frissítések között" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Indításkor frissüljön-e a tartalom" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Mennyi időközt várjon az automatikus tisztítások között" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Milyen időközöket várjon az automatikus tisztítások között" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL a vágólapra másolva." #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Ugrás ide: {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Ugrás ide: {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "A letöltés sikertelen: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Nem sikerült feliratkozni a hírforrásra: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML fájl" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Válasszon egy fájlt, amelyből műsorokat akar importálni." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Importálás" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Nem sikerült feldolgozni az importált fájlt: {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Műsorok exportálása ide…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Exportálás" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcastok-exportált-műsorok" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcastok feliratkozások" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Nem sikerült exportálni a podcastokat" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcast-kliens a GNOME asztali környezethez." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "Meskó Balázs " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Hírforrások lekérése…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Új" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Műsorok" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Letöltési folyamat" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} perc" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "A médialejátszó nem tudott végrehajtani egy műveletet." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Láthatóan kinyitja ezt a leírást" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Olvassa tovább" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Összes epizód megjelölve meghallgatottként" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Visszavonás" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Leiratkozott innen: {}" podcasts-25.2/podcasts-gtk/po/id.po000066400000000000000000000341521500126606300172430ustar00rootroot00000000000000# Indonesian translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Kukuh Syafaat , 2018-2024. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-02-27 18:33+0000\n" "PO-Revision-Date: 2024-03-07 17:21+0700\n" "Last-Translator: Kukuh Syafaat \n" "Language-Team: Indonesian \n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.4.2\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Acara ini belum memiliki episode" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Jika Anda pikir ini adalah kesalahan, silakan mempertimbangkan untuk menulis " "laporan kutu." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get Some Shows" msgstr "Dapatkan Beberapa Acara" #: podcasts-gtk/resources/gtk/empty_view.ui:50 msgid "Add new shows via feed URL" msgstr "Tambahkan acara baru melalui asupan URL" #: podcasts-gtk/resources/gtk/empty_view.ui:73 msgid "Import shows from another device" msgstr "Impor acara dari perangkat lain" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Rincian Episode" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Menu Episode" #: podcasts-gtk/resources/gtk/episode_description.ui:90 msgid "Podcast Title" msgstr "Judul Siniar" #: podcasts-gtk/resources/gtk/episode_description.ui:113 msgid "Duration - Date" msgstr "Durasi - Tanggal" #: podcasts-gtk/resources/gtk/episode_description.ui:133 msgid "Episode Description" msgstr "Deskripsi Episode" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Pergi ke Acara" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Salin Url Episode" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Anda sudah mendengarkan episode ini." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Menghitung ukuran episode…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Putar episode ini" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Batalkan proses pengunduhan" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Unduh episode ini" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "Periksa Episode _Baru" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Impor Acara" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Ekspor Acara" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "Pintasan Papan _Ketik" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "Tent_ang Siniar" #: podcasts-gtk/resources/gtk/headerbar.ui:33 #: podcasts-gtk/resources/gtk/headerbar.ui:108 msgid "Add a new feed" msgstr "Tambahkan asupan baru" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter Feed Address" msgstr "Masukkan Alamat Asupan" #: podcasts-gtk/resources/gtk/headerbar.ui:59 msgid "Popover menu (ESC to close)" msgstr "Menu popover (ESC untuk menutup)" #: podcasts-gtk/resources/gtk/headerbar.ui:71 msgid "Add" msgstr "Tambah" #: podcasts-gtk/resources/gtk/headerbar.ui:119 msgid "Main Menu" msgstr "Menu Utama" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "Umum" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Periksa episode baru" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "Keluar dari aplikasi" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Hari ini" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Kemarin" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Minggu Ini" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Bulan ini" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Lebih lama" #: podcasts-gtk/resources/gtk/player_dialog.ui:7 msgid "Now Playing" msgstr "Sedang Diputar" #: podcasts-gtk/resources/gtk/player_dialog.ui:28 msgid "Close" msgstr "Tutup" #: podcasts-gtk/resources/gtk/player_dialog.ui:152 msgid "Rewind" msgstr "Putar Balik" #: podcasts-gtk/resources/gtk/player_dialog.ui:173 #: podcasts-gtk/resources/gtk/player_toolbar.ui:63 #: podcasts-gtk/resources/gtk/player_toolbar.ui:253 msgid "Play" msgstr "Putar" #: podcasts-gtk/resources/gtk/player_dialog.ui:191 #: podcasts-gtk/resources/gtk/player_toolbar.ui:76 #: podcasts-gtk/resources/gtk/player_toolbar.ui:262 msgid "Pause" msgstr "Jeda" #: podcasts-gtk/resources/gtk/player_dialog.ui:214 msgid "Forward" msgstr "Maju" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Ubah kecepatan pemutaran" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "Ubah kecepatan pemutaran" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Putar mundur 10 detik" #: podcasts-gtk/resources/gtk/player_toolbar.ui:89 msgid "Fast forward 10 seconds" msgstr "Maju cepat 10 detik" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Tandai Se_mua Episode sebagai Sudah Diputar" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Situs _Web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Berhenti Berlangganan" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Buka Situs Web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Tandai Semua sebagai Sudah Diputar" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Berhenti Berlangganan" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Menu Siniar" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episode" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:6 #: podcasts-gtk/resources/gtk/window.ui:25 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:430 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:384 msgid "Podcasts" msgstr "Siniar" #: podcasts-gtk/resources/gtk/window.ui:33 msgid "Show" msgstr "Tampilkan" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Dengarkan acara favorit Anda" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Putar, mutakhirkan, dan kelola siniar Anda dari antarmuka ringan yang " "terintegrasi dengan GNOME dengan mulus. Siniar dapat memutar berbagai format " "audio dan mengingat di mana Anda berhenti mendengarkan. Anda dapat " "berlangganan acara melalui tautan RSS/ Atom, iTunes, dan Soundcloud. " "Langganan dari aplikasi lain dapat diimpor melalui berkas OPML." #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:151 msgid "The Podcasts developers" msgstr "Pengembang Siniar" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Dengarkan siniar favorit Anda, langsung dari destop Anda." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Siniar;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Tinggi dari jendela utama terbuka terakhir" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Lebar dari jendela utama terbuka terakhir" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Keadaan dimaksimalkan dari jendela utama terbuka terakhir" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Apakah akan menyegarkan konten secara berkala" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Berapa banyak periode waktu untuk menunggu antara penyegaran otomatis" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Berapa lama waktu untuk menunggu antara penyegaran otomatis" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Apakah akan menyegarkan konten setelah awal mula" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Berapa lama waktu menunggu antara pembersihan otomatis" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Berapa lama waktu untuk menunggu di antara pembersihan otomatis" #: podcasts-gtk/src/app.rs:315 msgid "Copied URL to clipboard!" msgstr "URL disalin ke papan klip!" #: podcasts-gtk/src/stacks/content.rs:68 msgid "New" msgstr "Baru" #: podcasts-gtk/src/stacks/content.rs:70 #: podcasts-gtk/src/widgets/shows_view.rs:64 msgid "Shows" msgstr "Acara" #: podcasts-gtk/src/utils.rs:480 podcasts-gtk/src/utils.rs:522 msgid "OPML file" msgstr "Berkas OPML" #: podcasts-gtk/src/utils.rs:491 msgid "Select the file from which to you want to import shows." msgstr "Pilih berkas dari mana Anda ingin mengimpor acara." #: podcasts-gtk/src/utils.rs:493 msgid "_Import" msgstr "_Impor" #: podcasts-gtk/src/utils.rs:509 msgid "Failed to parse the imported file" msgstr "Gagal mengurai berkas yang diimpor" #: podcasts-gtk/src/utils.rs:532 msgid "Export shows to…" msgstr "Ekspor acara ke…" #: podcasts-gtk/src/utils.rs:533 msgid "_Export" msgstr "_Ekspor" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:537 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:547 msgid "GNOME Podcasts Subscriptions" msgstr "Berlangganan GNOME Siniar" #: podcasts-gtk/src/utils.rs:548 msgid "Failed to export podcasts" msgstr "Gagal mengekspor siniar" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Klien Siniar untuk Destop GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Kukuh Syafaat , 2018-2024.\n" "Andika Triwidada , 2021." #: podcasts-gtk/src/widgets/episode.rs:373 msgid "{} min" msgstr "{} mnt" #: podcasts-gtk/src/widgets/player.rs:940 msgid "The media player was unable to execute an action." msgstr "Pemutar media tidak dapat melakukan aksi." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Perluas deskripsi ini secara visual" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Baca Lebih Lanjut" #: podcasts-gtk/src/widgets/show_menu.rs:178 msgid "Marked all episodes as listened" msgstr "Tandai semua episode sebagai sudah didengarkan" #: podcasts-gtk/src/widgets/show_menu.rs:179 #: podcasts-gtk/src/widgets/show_menu.rs:202 msgid "Undo" msgstr "Tak Jadi" #: podcasts-gtk/src/widgets/show_menu.rs:198 msgid "Unsubscribed from {}" msgstr "Berhenti berlangganan dari {}" #~ msgid "Back" #~ msgstr "Kembali" #~ msgid "Show Title" #~ msgstr "Tampilkan Judul" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Selected file could not be accessed." #~ msgstr "Berkas yang dipilih tidak dapat diakses." #~ msgid "_Cancel" #~ msgstr "_Batal" #~ msgid "Top position of the last open main window" #~ msgstr "Posisi atas dari jendela utama terbuka terakhir" #~ msgid "Left position of the last open main window" #~ msgstr "Posisi kiri dari jendela utama terbuka terakhir" #~ msgid "Enable or disable dark theme" #~ msgstr "Aktifkan atau nonaktifkan tema gelap" #~ msgid "An in-app action notification" #~ msgstr "Notifikasi aksi dalam aplikasi" #~ msgid "Fetching new episodes" #~ msgstr "Mengambil episode baru" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Pelajari lebih lanjut tentang GNOME Siniar" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplikasi Siniar untuk GNOME" podcasts-25.2/podcasts-gtk/po/is.po000066400000000000000000000467141500126606300172710ustar00rootroot00000000000000# Icelandic translation for podcasts. # Copyright (C) 2023 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # # Ingirafn , 2023, 2024, 2025. # SPDX-FileCopyrightText: 2023, 2024, 2025 Sveinn í Felli msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-04-12 13:27+0000\n" "PO-Revision-Date: 2025-04-18 13:32+0000\n" "Last-Translator: Sveinn í Felli \n" "Language-Team: Icelandic\n" "Language: is\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-Generator: Lokalize 23.08.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Þættir:" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Síðasta útgáfa" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Gerast áskrfandi" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Gerist áskrifandi að streymi…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Bæta við hlaðvörpum" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Leita" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Settu inn vefslóð streymis eða að leitaðu í völdum kerfum." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Senda inn leit" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Hleð..." #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Virkjaðu leitarkerfi hér fyrir neðan, eða settu inn http(s)-slóð á streymi." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Leita " #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Leitarbeiðnir verða sendar á þessi kerfi." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Leitarniðurstöður" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Engar niðurstöður fundust." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Í þessari útvarpsþáttaröð eru engir þættir ennþá." #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Ef þú heldur að þetta sé villa, gætir þú sent inn villuskýrslu." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Náðu í nokkra útvarpsþætti" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Bættu við nýjum útvarpsþáttaröðum gegnum streymi-vefslóð" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Flytja inn útvarsþætti úr öðru tæki" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Upplýsingar um þátt" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Valmynd þáttar" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Titill hlaðvarps" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Lengd - Dagsetning" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Streymi" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Spila" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Sækja" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Hætta við" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Eyða" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Lýsing á innihaldi þáttarins" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Kennimynd þáttarins" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Fara á útvarpsþáttaröð" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Afrita vefslóð þáttar" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Merkja sem spilað" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Merkja sem óspilað" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Þú er búin/n að hlusta á þennan þátt ." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Reikna út stærð þáttar..." #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Spila þennan þátt" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Hætta við niðurhal" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Hlaða niður þennan þátt" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Þáttur án hljóðrásar" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Leiðsögn" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Fara á heimasíðu" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Fara á síðu þátta" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Fara á " #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Spilari" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Víxla á " #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Leita framfyrir" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Leita afturfyrir" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Almennt" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Athuga með nýja þætti" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Slökkva á forritinu" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Flytja inn áskrftir" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Flytja út áskriftir" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Í dag" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Í gær" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Þessa viku" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Þennan mánuð" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Eldri" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Breyta afspilunarhraða" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Vinda til baka" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Spila" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Hlé" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Áfram" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Lýsing" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Vinda til baka 10 sek" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Vinda áfram um 10 sek" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Merkja alla þætti sem spilað" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Vefsvæði" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Afskrá" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Opna vefsvæði" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Merkja allt sem spilað" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Segja upp áskrift" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Titill hlaðvarps" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Þættir" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Athuga með nýja þætti" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "F_lytja inn þætti" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "F_lytja út útvarpsþætti" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Flýtilyklar" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Um Podcasts" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:505 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:476 msgid "Podcasts" msgstr "Hlaðvörp (podcasts)" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Bæta við nýju streymi" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Aðalvalmynd" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Þáttur" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Hlustaðu á uppáhalds útvarpsþættina þína" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Spila, endurnýja og halda utanum hlaðvörp með notendaviðmóti sem fellur " "hnökralaust inní GNOME gluggaumhverfið. Podcasts getur spilað ýmis " "skráarsnið og haldið áfram þar sem frá var horfið í afspilun. Þú getur gerst " "áskrifandi að þáttum í gegnum RSS/Atom, ITunes og Soundcloud hlekki. " "Áskrftir úr öðrum forritum er hægt að hlaða inn með OPML skjölum." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Heimsýnin " #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Þættirnir sýna af hlaðvörpum þínum" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Viðmótshluti þáttarins sem sýnir auðkennismynd og nýjustu þætti tiltekins " "hlaðvarps" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Glugginn sem hægt er að bæta við nýjum hlaðvörpum" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Podcast þróunarteymið" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Hlustaðu á uppáhalds hlaðvörpin þín, beint af tölvunni þinni" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Hlaðvarp;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Hæð síðasta opna aðalglugga" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Breidd síðasta opna aðalglugga" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Hámörkun síðasta opna aðalglugga" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Hvort endurlesa skuli efnið með jöfnu millibil" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Hve langt tímabil skal líða milli sjálfvirks endurlesturs" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Hve lengi skal bíða milli sjálfvirks endurlesturs" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Hvort skuli endurlesa efni eftir ræsingu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Hve langan tími skal bíða milli sjálfvirkra tilteka" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Hve langt tímabil skal líða milli sjáfvirkra tiltekta" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "Afritaði vefslóð á klippispjald!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Hoppa yfir á {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Hoppa yfir á {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Niðurhal mistókst: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Tókst ekki að gerast áskrifandi að streymi: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML-skrá" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Veldu skjalið sem þú vilt flytja inn útvarsþættina úr." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Flytja inn" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Tókst ekki að þátta innflutt skjal {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Flytja útvarpsþætti út í..." #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Flytja út" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-hlaðvörp-útflutt-útvarpsþættir" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME hlaðvarpsáskriftir" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Tókst ekki að flytja út hlaðvörp" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Hlaðvarpsforrit fyrir GNOME-skjáborðskerfið." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "Ingirafn, ingirafn at this dot is, 2023" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Sæki streymi…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Nýtt" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Útvarpsþættir" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Staða niðurhals" #: podcasts-gtk/src/widgets/episode.rs:297 msgid "{} min" msgstr "{} mín" #: podcasts-gtk/src/widgets/player.rs:1120 msgid "The media player was unable to execute an action." msgstr "Margliðlunarspilarinn gat ekki framkvæmt aðgerð." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Þenur sjónrænt út þessar útskýringar" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Lesa meira" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Merkti alla þætti sem búið að hlusta á" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Afturkalla" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Sagði upp áskrift að {}" #~ msgid "Now Playing" #~ msgstr "Nú í afspilun" #~ msgid "Close" #~ msgstr "Loka" #~ msgid "Back" #~ msgstr "Til baka" #~ msgid "Enter Feed Address" #~ msgstr "Settu inn vistfang streymislóðar" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Sprettvalmynd (Esc til að loka)" #~ msgid "Add" #~ msgstr "Bæta við" #~ msgid "Show Title" #~ msgstr "Sýna titil" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" podcasts-25.2/podcasts-gtk/po/it.po000066400000000000000000000314271500126606300172650ustar00rootroot00000000000000# Italian translation for podcasts. # Copyright (C) 2018, 2019, 2020 podcasts's Free Software Foundation, Inc. # This file is distributed under the same license as the podcasts package. # Milo Casagrande , 2018, 2019, 2020. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2021-09-14 16:55+0000\n" "PO-Revision-Date: 2021-11-03 08:44+0100\n" "Last-Translator: Davide Ferracin \n" "Language-Team: Italian \n" "Language: 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-Generator: Poedit 2.2.4\n" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Top position of the last open main window" msgstr "Posizione superiore dell'ultima finestra principale aperta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Left position of the last open main window" msgstr "Posizione sinistra dell'ultima finestra principale aperta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Height of the last open main window" msgstr "Altezza dell'ultima finestra principale aperta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:27 msgid "Width of the last open main window" msgstr "Larghezza dell'ultima finestra principale aperta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:31 msgid "Maximized state of the last open main window" msgstr "Stato di massimizzazione dell'ultima finestra principale aperta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:36 msgid "Enable or disable dark theme" msgstr "Abilita/Disabilita il tema scuro" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to periodically refresh content" msgstr "Indica se aggiornare i contenuti periodicamente" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:46 msgid "How many periods of time to wait between automatic refreshes" msgstr "Quanto tempo aspettare tra gli aggiornamenti automatici" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:50 msgid "What period of time to wait between automatic refreshes" msgstr "Che periodo di tempo aspettare tra gli aggiornamenti automatici" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:54 msgid "Whether to refresh content after startup" msgstr "Indica se aggiornare i contenuti dopo l'avvio" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:60 msgid "How many periods of time to wait between automatic cleanups" msgstr "Quanto tempo aspettare tra una pulizia automatica e la successiva" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:64 msgid "What period of time to wait between automatic cleanups" msgstr "" "Che periodo di tempo aspettare tra una pulizia automatica e la successiva" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/gtk/headerbar.ui:137 podcasts-gtk/src/app.rs:438 #: podcasts-gtk/src/widgets/aboutdialog.rs:63 podcasts-gtk/src/window.rs:66 msgid "Podcasts" msgstr "Podcast" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Ascolta i tuoi podcast preferiti, direttamente dal tuo computer." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Podcast app for GNOME" msgstr "Applicazione per podcast per GNOME" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:115 msgid "Jordan Petridis" msgstr "Jordan Petridis" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:116 msgid "Julian Hofer" msgstr "Julian Hofer" #: podcasts-gtk/resources/gtk/empty_view.ui:46 msgid "This show does not have episodes yet" msgstr "Questa trasmissione non ha ancora episodi" #: podcasts-gtk/resources/gtk/empty_view.ui:57 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Se consideri che sia un errore, invia una segnalazione, per favore." #: podcasts-gtk/resources/gtk/empty_view.ui:91 msgid "Get some shows" msgstr "Ottieni trasmissioni" #: podcasts-gtk/resources/gtk/empty_view.ui:123 msgid "Add new shows via feed URL" msgstr "Aggiungi nuove trasmissioni da un URL" #: podcasts-gtk/resources/gtk/empty_view.ui:152 msgid "Import shows from another device" msgstr "Importa trasmissioni da un altro dispositivo" #: podcasts-gtk/resources/gtk/episode_description.ui:49 msgid "Episode Details" msgstr "Dettagli dell'episodio" #: podcasts-gtk/resources/gtk/episode_description.ui:64 #: podcasts-gtk/resources/gtk/headerbar.ui:186 msgid "Back" msgstr "Indietro" #: podcasts-gtk/resources/gtk/episode_description.ui:148 msgid "Podcast Title" msgstr "Titolo del podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:186 msgid "Duration - Date" msgstr "Durata - Data" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Episode Description" msgstr "Descrizione dell'episodio" #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Vai alla trasmissione" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Copia l'URL della trasmissione" #: podcasts-gtk/resources/gtk/episode_widget.ui:82 msgid "You’ve already listened to this episode." msgstr "Questo episodio è già stato ascoltato." #: podcasts-gtk/resources/gtk/episode_widget.ui:187 msgid "Calculating episode size…" msgstr "Calcolo la dimensione dell'episodio…" #: podcasts-gtk/resources/gtk/episode_widget.ui:214 msgid "Play this episode" msgstr "Riproduci questo episodio" #: podcasts-gtk/resources/gtk/episode_widget.ui:230 msgid "Cancel the download process" msgstr "Annulla lo scaricamento" #: podcasts-gtk/resources/gtk/episode_widget.ui:250 msgid "Download this episode" msgstr "Scarica questo episodio" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "Controlla per _nuovi episodi" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Importa trasmissioni" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Esporta trasmissioni" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Scorciatoie da tastiera" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "I_nformazioni su Podcast" #: podcasts-gtk/resources/gtk/headerbar.ui:35 #: podcasts-gtk/resources/gtk/headerbar.ui:165 msgid "Add a new feed" msgstr "Aggiungi un nuovo feed" #: podcasts-gtk/resources/gtk/headerbar.ui:53 msgid "Enter feed address to add" msgstr "Inserisci un indirizzo da aggiungere" #: podcasts-gtk/resources/gtk/headerbar.ui:79 msgid "Add" msgstr "Aggiungi" #: podcasts-gtk/resources/gtk/headerbar.ui:150 msgid "Show Title" msgstr "Titolo della trasmissione" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgid "General" msgstr "Generale" #: podcasts-gtk/resources/gtk/help-overlay.ui:18 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Controlla presenza di nuovi episodi" #: podcasts-gtk/resources/gtk/help-overlay.ui:25 msgctxt "shortcut window" msgid "Quit the application" msgstr "Esci dall'applicazione" #: podcasts-gtk/resources/gtk/home_view.ui:56 msgid "Today" msgstr "Oggi" #: podcasts-gtk/resources/gtk/home_view.ui:89 msgid "Yesterday" msgstr "Ieri" #: podcasts-gtk/resources/gtk/home_view.ui:122 msgid "This Week" msgstr "Questa settimana" #: podcasts-gtk/resources/gtk/home_view.ui:155 msgid "This Month" msgstr "Questo mese" #: podcasts-gtk/resources/gtk/home_view.ui:189 msgid "Older" msgstr "Più vecchi" #: podcasts-gtk/resources/gtk/inapp_notif.ui:69 msgid "An in-app action notification" msgstr "Una notifica" #: podcasts-gtk/resources/gtk/inapp_notif.ui:75 msgid "Undo" msgstr "Annulla" #: podcasts-gtk/resources/gtk/player_dialog.ui:14 msgid "Now Playing" msgstr "In riproduzione" #: podcasts-gtk/resources/gtk/player_rate.ui:32 msgid "Change the playback speed" msgstr "Modifica la velocità di riproduzione" #: podcasts-gtk/resources/gtk/player_rate.ui:46 #: podcasts-gtk/resources/gtk/player_rate.ui:85 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:65 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:70 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:75 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:80 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:90 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:95 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:97 msgid "Rewind 10 seconds" msgstr "Indietro 10 secondi" #: podcasts-gtk/resources/gtk/player_toolbar.ui:107 msgid "Play" msgstr "Riproduci" #: podcasts-gtk/resources/gtk/player_toolbar.ui:118 msgid "Pause" msgstr "Pausa" #: podcasts-gtk/resources/gtk/player_toolbar.ui:129 msgid "Fast forward 10 seconds" msgstr "Avanti veloce 10 secondi" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Segna tutti gli episodi come ascoltati" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Sito _web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Annulla abbonamento" #: podcasts-gtk/resources/gtk/show_menu.ui:36 msgid "Open Website" msgstr "Apri sito web" #: podcasts-gtk/resources/gtk/show_menu.ui:40 msgid "Mark All as Played" msgstr "Segna tutti come ascoltati" #: podcasts-gtk/resources/gtk/show_menu.ui:46 msgid "Unsubscribe" msgstr "Annulla abbonamento" #: podcasts-gtk/resources/gtk/show_widget.ui:98 msgid "Read More" msgstr "Leggi di più" #: podcasts-gtk/src/app.rs:353 msgid "Fetching new episodes" msgstr "Scaricamento nuovi episodi" #: podcasts-gtk/src/stacks/content.rs:58 msgid "New" msgstr "Nuove puntate" #: podcasts-gtk/src/stacks/content.rs:59 msgid "Shows" msgstr "Trasmissioni" #: podcasts-gtk/src/utils.rs:501 msgid "Select the file from which to you want to import shows." msgstr "Seleziona il file da cui importare le trasmissioni." #: podcasts-gtk/src/utils.rs:504 msgid "_Import" msgstr "_Importa" #: podcasts-gtk/src/utils.rs:513 podcasts-gtk/src/utils.rs:562 msgid "OPML file" msgstr "File OPML" #: podcasts-gtk/src/utils.rs:531 msgid "Failed to parse the imported file" msgstr "Analisi del file importato non riuscita" #: podcasts-gtk/src/utils.rs:536 podcasts-gtk/src/utils.rs:581 msgid "Selected file could not be accessed." msgstr "Impossibile accedere al file selezionato." #: podcasts-gtk/src/utils.rs:547 msgid "Export shows to…" msgstr "Esporta trasmissioni su…" #: podcasts-gtk/src/utils.rs:550 msgid "_Export" msgstr "_Esporta" #: podcasts-gtk/src/utils.rs:551 msgid "_Cancel" msgstr "A_nnulla" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:555 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcast-trasmissioni-esportate" #: podcasts-gtk/src/utils.rs:575 msgid "GNOME Podcasts Subscriptions" msgstr "Abbonamenti di GNOME Podcast" #: podcasts-gtk/src/utils.rs:576 msgid "Failed to export podcasts" msgstr "Esportazione dei podcast non riuscita" #: podcasts-gtk/src/widgets/aboutdialog.rs:58 msgid "Podcast Client for the GNOME Desktop." msgstr "Client podcast per l'ambiente grafico GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:65 msgid "Learn more about GNOME Podcasts" msgstr "Maggiori informazioni su GNOME Podcast" #: podcasts-gtk/src/widgets/aboutdialog.rs:69 msgid "translator-credits" msgstr "" "Milo Casagrande \n" "Davide Ferracin " #: podcasts-gtk/src/widgets/episode.rs:145 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/episode_description.rs:165 msgid "Copied URL to clipboard!" msgstr "URL copiato negli appunti!" #: podcasts-gtk/src/widgets/player.rs:902 msgid "The media player was unable to execute an action." msgstr "Il riproduttore multimediale non ha potuto eseguire un'azione." #: podcasts-gtk/src/widgets/show_menu.rs:194 msgid "Marked all episodes as listened" msgstr "Tutti gli episodi segnati come letti" #: podcasts-gtk/src/widgets/show_menu.rs:199 msgid "Unsubscribed from {}" msgstr "Abbonamento a {} annullato" #~ msgid "1.5 speed rate" #~ msgstr "1,5 volte la velocità" #~ msgid "1.25 speed rate" #~ msgstr "1,25 volte la velocità" #~ msgid "Normal speed" #~ msgstr "Velocità normale" podcasts-25.2/podcasts-gtk/po/ka.po000066400000000000000000000614541500126606300172470ustar00rootroot00000000000000# Georgian translation for podcasts. # Copyright (C) 2023 podcasts's authors. # This file is distributed under the same license as the podcasts package. # Ekaterine Papava , 2023-2025. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-29 04:22+0100\n" "Last-Translator: Ekaterine Papava \n" "Language-Team: Georgian \n" "Language: ka\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-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "ეპიზოდები: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "ბოლო გამოცემა" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_გამოწერა" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "ლენტის გამოწერა…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "პოდკასტების დამატება" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "ძებნა" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "შეიყვანეთ ლენტის ბმული ან მოძებნეთ მონიშნულ პლატფორმებზე." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "ძებნის დამტკიცება" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "ჩატვირთვა…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "ჩართეთ ძებნი პლატფორმა ქვემოთ, ან შეიყვანეთ ლენტის http(s) ბმული." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "პლატფორმებზე ძებნა" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "ძებნის მოთხოვნები ამ პლატფორმებზე გაიგზავნება." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "ძებნის შედეგები" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "შედეგების გარეშე." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "ამ შოუს ჯერ ეპიზოდები არ აქვს" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "თუ ფიქრობთ, რომ ეს შეცდომაა, მოგვწერეთ მის შესახებ." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "უყურეთ სერიალებს" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "ახალი შოუების დამატება ლენტის URL-ის საშუალებით" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "შოუების შემოტანა სხვა მოწყობილობიდან" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "ეპიზოდის დეტალები" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "ეპიზოდის მენიუ" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "პოდკასტის სათაური" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "ხანგრძლივობა - თარიღი" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_დასტრიმვა" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_დაკვრა" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_გადმოწერა" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "გაუქმება" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "წაშლა" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "ეპიზოდის აღწერა" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "ეპიზოდის ყდა" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "სერიალზე გადასვლა" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "ეპიზოდის ბმულის კოპირება" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "დაკრულად მონიშვნა" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "დაუკრავად მონიშვნა" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "თქვენ უკვე მოუსმინეთ ამ ეპიზოდს." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "ეპიზოდის ზომის გამოთვლა…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "ამ ეპიზოდის დაკვრა" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "გადმოწერის გაუქმება" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "ამ ეპიზოდის გადმოწერა" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "ეპიზოდის ხმის გარეშე" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "ნავიგაცია" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "გადასვლა საწყის გვერდზე" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "გადასვლა სერიალების გვერდზე" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "გადასვლა აღმოჩენის გვერდზე" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "დამკვრელი" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "პაუზის გადართვა" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "წინ გადახვევა" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "უკან გადახვევა" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "ზოგადი" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "ახალი ეპიზოდების შემოწმება" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "აპლიკაციიდან გასვლა" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "გამოწერების შემოტანა" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "გამოწერების გატანა" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "დღეს" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "გუშინ" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "ამ კვირაში" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "ამ თვეში" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "უფრო ძველია" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "დაკვრის სიჩქარის შეცვლა" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "გადახვევა" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "დაკვრა" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "პაუზა" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "წინ" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "აღწერა" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "10 წამით გადახვევა" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "10 წამით წინ სწრაფი გადახვევა" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_მონიშნეთ ყველა ეპიზოდი, როგორც დაკრული" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_ვებსაიტი" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "გამოწერის _გაუქმება" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "ვებგვერდის გახსნა" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "ყველას, როგორც დაკრულის მონიშვნა" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "გამოწერის გაუქმება" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "პოდკასტის მენიუ" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "ეპიზოდები" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_ახალი ეპიზოდების შემოწმება" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_სერიალების შემოტანა" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "სერიალების _გატანა" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_კლავიატურის მალსახმობები" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "პოდკასტების _შესახებ" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "პოდკასტები" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "ახალი ლენტის დამატება" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "მთავარი მენიუ" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "სერიალი" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "მოუსმინეთ თქვენს საყვარელ შოუებს" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "დაუკარით, განაახლეთ და მართეთ თქვენი პოდკასტები მსუბუქი ინტერფეისიდან, " "რომელიც GNOME-შია ჩაშენებული. Podcasts-ს სხვადასხვა აუდიო ფორმატების დაკვრა " "და სად შეწყვიტეთ დაკვრა, იმის დამახსოვრება შეუძლია. შოუები შეგძლიათ RSS/" "Aton, iTunes და Soundcloud-ის ბმულებით დაამატოთ. სხვა აპებიდან გამოწერები " "OPML ფაილებით შეგიძლიათ, დაამატოთ." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "საწყისი ხედი, რომელიც თქვენი პოდკასტების უახლეს ეპიზოდებს აჩვენებს" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "სერიალების ხედი, რომელიც თქვენი პოდკასტების ყდებს აჩვენებს" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "ჩვენების ვიჯეტი, რომელიც მითითებული პოდკასტის ყდას და უახლეს ეპიზოდებს " "აჩვენებს" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "ხედი, სადაც მსურველს ახალი პოდკასტის დამატება შეუძლია" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Podcasts-ის პროგრამისტები" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "" "მოუსმინეთ თქვენს საყვარელ პოდკასტებს, პირდაპირ თქვენი სამუშაო მაგიდიდან." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;პოდკასტი;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "ბოლოს გახსნილი მთავარი ფანჯრის სიმაღლე" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "ბოლოს გახსნილი მთავარი ფანჯრის სიგანე" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "ბოლოს გახსნილი მთავარი ფანჯრის მთელ ეკრანზე გაშლილობა" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "პერდიოდულად განახლდება თუ არა შემცველობა" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "რამდენი დრო უნდა დაველოდოთ ავტომატურ განახლებებს შორის" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "რა პერიოდი უნდა დაველოდოთ ავტომატურ განახლებებს შორის" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "განახლდება თუ არა შემცველობა გაშვების შემდეგ" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "რამდენი პერიოდი უნდა დაველოდო ავტომატურ გაწმენდებს შორის" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "რა პერიოდი უნდა დაველოდო ავტომატურ გაწმენდებს შორის" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "ბმული დაკოპირდა გაცვლის ბაფერში!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "{}:{}:{}-ზე გადასვლა" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "{}:{}-ზე გადასვლა" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "გადმოწერა ჩავარდა: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "ჩავარდა გამოწერა ლენტისთვის: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML ფაილი" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "აირჩიეთ ფაილი, საიდანაც გნებავთ, შოუები შემოიტანოთ." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_შემოტანა" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "შემოტანილი ფაილის {} დამუშავება ჩავარდა" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "შოუების გატანა…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_გატანა" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcasts-ის გამოწერები" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "პოდკასტების გატანის შეცდომა" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "პოდკასტების კლიენტი GNOME-სთვის." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "temuri Doghonadze" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "ლენტების გამოთხოვა…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "ახალი" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "სერიალები" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "გადმოწერის მიმდინარეობა" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} წთ" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "მედიის დამკვრელმა ქმედების შესრულება ვერ შეძლო." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "ვიზუალურად გაშლის ამ აღწერას" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "დაწვრილებით" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "ყველა ეპიზოდის, როგორც მოსმენილის, მონიშვნა" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "დაბრუნება" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "{}-ის გამოწერის გაუქმება" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "იტვირთება..." #~ msgid "Now Playing" #~ msgstr "მიმდინარეობს დაკვრა" #~ msgid "Close" #~ msgstr "დახურვა" #~ msgid "Enter Feed Address" #~ msgstr "შეიყვანეთ ლენტის მისამართი" #~ msgid "Popover menu (ESC to close)" #~ msgstr "მხტუნარა (დასახურად - Esc)" #~ msgid "Add" #~ msgstr "დამატება" #~ msgid "Back" #~ msgstr "უკან" #~ msgid "Show Title" #~ msgstr "სათაურის ჩვენება" #~ msgid "Jordan Petridis" #~ msgstr "იორდან პეტრიდისი" #~ msgid "Julian Hofer" #~ msgstr "ჯულიან ჰოფერი" podcasts-25.2/podcasts-gtk/po/kab.po000066400000000000000000000366071500126606300174130ustar00rootroot00000000000000# Kabyle translation for podcasts. # Copyright (C) 2025 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # FIRST AUTHOR , YEAR. # ButterflyOfFire , 2025. # msgid "" msgstr "" "Project-Id-Version: podcasts main\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-18 14:22+0000\n" "PO-Revision-Date: 2025-01-20 10:02+0100\n" "Last-Translator: ButterflyOfFire \n" "Language-Team: Kabyle\n" "Language: kab\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-DL-Lang: kab\n" "X-DL-Module: podcasts\n" "X-DL-Branch: main\n" "X-DL-Domain: po\n" "X-DL-State: Translating\n" "Plural-Forms: nplurals=2; plural=n>1;\n" "X-Generator: Gtranslator 47.1\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode Url" msgstr "" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "" #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:502 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:447 msgid "Podcasts" msgstr "" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "" #: podcasts-gtk/src/app.rs:357 msgid "Copied URL to clipboard!" msgstr "" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "" #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "" #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "" #: podcasts-gtk/src/widgets/player.rs:1091 msgid "The media player was unable to execute an action." msgstr "" #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "" podcasts-25.2/podcasts-gtk/po/ko.po000066400000000000000000000520271500126606300172610ustar00rootroot00000000000000# Korean translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Seong-ho Cho , 2018-2021. # DaeHyun Sung , 2020-2025. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-02-23 08:07+0000\n" "PO-Revision-Date: 2025-03-09 00:33+0900\n" "Last-Translator: DaeHyun Sung \n" "Language-Team: Korean \n" "Language: ko\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-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "에피소드: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "최근 발행물" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "구독(_S)" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "피드 구독하기…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "팟캐스트 추가" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "검색" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "피드 URL을 입력하거나 선택한 플랫폼을 검색하세요." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "검색 제출" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "로드 중…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "아래에서 검색 플랫폼을 활성화하거나 http(s) 피드 URL을 입력하세요." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "검색 플랫폼" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "검색어는 이러한 플랫폼으로 전송합니다." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "검색 결과" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "결과를 찾을 수 없습니다." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "이 쇼에는 아직 에피소드가 없습니다" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "오류인 것 같다면 오류 보고서 작성을 고려하십시오." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "쇼 보기" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "피드 URL 주소로 새 쇼 추가" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "다른 장치에서 쇼 가져오기" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "에피소드 내용" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "에피소드 메뉴" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "팟캐스트 제목" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "방송시간 - 날짜" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "스트림(_S)" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "재생(_P)" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "다운로드(_D)" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "취소" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "삭제" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "에피소드 설명" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "에피소드 표지" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "쇼로 이동" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "에피소드 URL 주소 복사" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "재생됨으로 표시" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "재생되지 않음으로 표시" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "이미 에피소드를 들었습니다." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "에피소드 용량 계산 중…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "이 에피소드 재생" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "다운로드 진행 취소" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "이 에피소드 다운로드" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "오디오 없는 에피소드" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "탐색" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "홈페이지로 이동" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "쇼 페이지로 이동" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Discovery 페이지로 이동" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "플레이" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "일시 정지 전환" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "앞으로 찾기" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "뒤로 찾기" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "일반" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "새 에피소드 확인" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "프로그램 끝내기" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "구독 가져오기" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "구독 내보내기" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "오늘" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "어제" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "금주" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "이번 달" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "이전" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "재생 속도 조절" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00배속" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00배속" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75배속" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50배속" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25배속" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90배속" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75배속" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "되감기" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "재생" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "일시 정지" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "앞으로" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "설명" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "10초 되감기" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "10초 빨리감기" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "모든 에피소드를 재생 상태로 표시(_M)" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "웹사이트(_W)" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "구독 취소(_U)" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "웹사이트 열기" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "모두 재생한 항목으로 표시" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "구독 취소" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "팟캐스트 메뉴" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "에피소드" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "새 에피소드 확인(_C)" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "쇼 가져오기(_I)" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "쇼 내보내기(_E)" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "키보드 바로 가기(_K)" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "팟캐스트 정보(_A)" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:505 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "팟캐스트" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "새로운 피드 추가" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "메인 메뉴" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "쇼" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "원하는 팟캐스트를 듣기를 보여줍니다" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "그놈과 매끄럽게 통합되는 가벼운 인터페이스로 팟캐스트를 재생, 업데이트 및 관" "리합니다. 팟캐스트는 다양한 오디오 형식을 재생할 수 있고 듣다가 멈춘 곳을 기" "억할 수 있습니다. RSS/Atom, iTunes 및 Soundcloud links를 통해 프로그램을 구독" "할 수 있습니다. 다른 앱으로 구독은 OPML파일을 통해 가져올 수 있습니다." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "팟캐스트의 새로운 에피소드를 표시하는 홈 보기" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "팟캐스트의 표지를 보여주는 쇼 보기" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "특정 팟캐스트의 최신 에피소드와 표지를 표시하는 쇼 위젯" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "새로운 팟캐스트를 추가할 수 있는 보기" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "팟캐스트 개발자" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "데스크톱에서 바로 원하는 팟캐스트를 듣습니다." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;팟캐스트;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "최근 연 메인 창 높이" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "최근 연 메인 창 너비" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "최근 연 메인 창 최대화 상태" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "주기적으로 내용 새로 고침 여부" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "자동 새로 고침 기다림 주기 시간 간격" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "자동 새로 고침 기다림 주기 시간" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "시작 후 내용 새로 고침 여부" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "자동 정리 기다림 주기 시간 간격" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "자동 정리 기다림 주기 시간" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL 주소를 클립보드에 복사했습니다!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "{}:{}:{} (으)로 이동" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "{}:{} (으)로 이동" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "다운로드 실패: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "피드 구독에 실패했습니다: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML 파일" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "가져올 쇼의 파일을 선택하십시오." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "가져오기(_I)" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "가져온 파일 {} 해석에 실패했습니다" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "쇼 내보내기…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "내보내기(_E)" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "그놈 팟캐스트 구독" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "팟캐스트를 내보내지 못하였습니다" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "그놈 데스크톱용 팟캐스트 클라이언트." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "조성호 , 2018\n" "성대현 , 2020-2025" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "피드 가져오는 중…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "새 항목" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "쇼" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "다운로드 진행 상황" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{}분" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "미디어 재생기에서 이 동작을 수행할 수 없습니다." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "설명을 시각적으로 확장" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "더 읽기" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "모든 에피소드를 감청 상태로 표시했습니다" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "실행 취소" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "{}에서 구독 취소했습니다" #~ msgid "Back" #~ msgstr "뒤로" #~ msgid "Enter Feed Address" #~ msgstr "피드 주소 입력" #~ msgid "Add" #~ msgstr "추가" #~ msgid "Show Title" #~ msgstr "제목 표시" #~ msgid "Now Playing" #~ msgstr "지금 재생중" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Top position of the last open main window" #~ msgstr "최근 연 메인 창 상단 위치" #~ msgid "Left position of the last open main window" #~ msgstr "최근 연 메인 창 좌측 위치" #~ msgid "Enable or disable dark theme" #~ msgstr "어두움 테마 사용 함/안함" #~ msgid "An in-app action notification" #~ msgstr "앱 동작 알림" #~ msgid "Selected file could not be accessed." #~ msgstr "선택한 파일에 접근할 수 없습니다." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "그놈 팟캐스트에 대해 더 알아보세요" #~ msgid "Podcast app for GNOME" #~ msgstr "그놈용 팟캐스트 앱" #~ msgid "Double speed rate" #~ msgstr "2배 속도" #~ msgid "1.75 speed rate" #~ msgstr "1.75배 속도" #~ msgid "1.5 speed rate" #~ msgstr "1.5배 속도" #~ msgid "1.25 speed rate" #~ msgstr "1.25배 속도" #~ msgid "Normal speed" #~ msgstr "일반 속도" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "기본 설정(_P)" #~ msgid "_About" #~ msgstr "정보(_A)" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "기본 설정" #~ msgid "Preferences" #~ msgstr "기본 설정" #~ msgid "Appearance" #~ msgstr "모양새" #~ msgid "Dark Theme" #~ msgstr "어두움 테마" #~ msgid "Delete played episodes" #~ msgstr "재생한 에피소드 삭제" #~ msgid "After" #~ msgstr "다음" #~ msgid "Invalid URL" #~ msgstr "잘못된 URL 주소" #~ msgid "Seconds" #~ msgstr "초" #~ msgid "Minutes" #~ msgstr "분" #~ msgid "Hours" #~ msgstr "시간" #~ msgid "Days" #~ msgstr "일" #~ msgid "Weeks" #~ msgstr "주" podcasts-25.2/podcasts-gtk/po/lv.po000066400000000000000000000277021500126606300172730ustar00rootroot00000000000000# Latvian translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # # Rūdolfs Mazurs , 2018. msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2018-09-10 07:51+0000\n" "PO-Revision-Date: 2018-09-10 20:15+0200\n" "Last-Translator: Rūdolfs Mazurs \n" "Language-Team: Latvian \n" "Language: lv\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 != 0 ? 1 :" " 2);\n" "X-Generator: Lokalize 2.0\n" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:15 msgid "Top position of the last open main window" msgstr "Augšējais novietojums pēdējam atvērtajam galvenajam logam" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:19 msgid "Left position of the last open main window" msgstr "Kreisais novietojums pēdējam atvērtajam galvenajam logam" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:23 msgid "Height of the last open main window" msgstr "Pēdējā atvērtā galvenā loga augstums" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:27 msgid "Width of the last open main window" msgstr "Pēdējā atvērtā galvenā loga platums" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:31 msgid "Maximized state of the last open main window" msgstr "Pēdējā atvērtā galvenā loga maksimizēšanas stāvoklis" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:36 msgid "Enable or disable dark theme" msgstr "Ieslēgt vai izslēgt tumšo motīvu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:41 msgid "Whether to periodically refresh content" msgstr "Vai ik pa laikam atsvaidzināt saturu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:46 msgid "How many periods of time to wait between automatic refreshes" msgstr "Cik laika periodus gaidīt starp automātisko atsvaidzināšanu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:50 msgid "What period of time to wait between automatic refreshes" msgstr "Cik ilgi gaidīt starp automātisko atsvaidzināšanu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:54 msgid "Whether to refresh content after startup" msgstr "Vai atsvaidzināt saturu pēc palaišanas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:60 msgid "How many periods of time to wait between automatic cleanups" msgstr "Cik laika periodus gaidīt starp automātisko uzkopšanu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:64 msgid "What period of time to wait between automatic cleanups" msgstr "Cik ilgi gaidīt starp automātisko uzkopšanu" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/src/app.rs:91 podcasts-gtk/src/app.rs:414 #: podcasts-gtk/src/widgets/aboutdialog.rs:31 msgid "Podcasts" msgstr "Podraides" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Klausieties savas iecienītās podraides savā darbvirsmā." #. Translators: Do NOT translate or transliterate this text (this is an icon file name)! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:6 msgid "@icon@" msgstr "@icon@" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Aplāde;Podraide;Podkāsts;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Podcast app for GNOME" msgstr "Podraižu lietotne GNOME videi" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:51 msgid "Jordan Petridis" msgstr "Jordan Petridis" #: podcasts-gtk/resources/gtk/empty_view.ui:46 msgid "This show does not have episodes yet" msgstr "Šim raidījumam vēl nav epizožu" #: podcasts-gtk/resources/gtk/empty_view.ui:62 msgid "If you think this is an error, please consider writing a bug report." msgstr "Ja domājat, ka šī ir kļūda, apsveriet sūtīt kļūdas ziņojumu." #: podcasts-gtk/resources/gtk/empty_view.ui:106 msgid "Get some shows" msgstr "Tikt pie dažiem raidījumiem" #: podcasts-gtk/resources/gtk/empty_view.ui:143 msgid "Add new shows via feed URL" msgstr "Pievienojiet jaunus raidījumus ar plūsmu URL" #: podcasts-gtk/resources/gtk/empty_view.ui:172 msgid "Import shows from another device" msgstr "Importēt raidījumus no citas ierīces" #: podcasts-gtk/resources/gtk/episode_widget.ui:180 msgid "Calculating episode size…" msgstr "Aprēķina epizodes izmēru…" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Play this episode" msgstr "Atskaņot šo epizodi" #: podcasts-gtk/resources/gtk/episode_widget.ui:241 msgid "Cancel the download proccess" msgstr "Atcelt lejupielādēšanu" #: podcasts-gtk/resources/gtk/episode_widget.ui:264 msgid "Download this episode" msgstr "Lejupielādēt šo epizodi" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "Pārbaudīt jau_nos raidījumus" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Importēt raidījumus" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Preferences" msgstr "_Iestatījumi" #: podcasts-gtk/resources/gtk/hamburger.ui:29 msgid "_Keyboard Shortcuts" msgstr "_Tastatūras īsinājumtaustiņi" #: podcasts-gtk/resources/gtk/hamburger.ui:37 msgid "_About" msgstr "P_ar" #: podcasts-gtk/resources/gtk/headerbar.ui:35 #: podcasts-gtk/resources/gtk/headerbar.ui:189 msgid "Add a new feed" msgstr "Pievienot jaunu plūsmu" #: podcasts-gtk/resources/gtk/headerbar.ui:53 msgid "Enter feed address to add" msgstr "Ievadiet pievienojamās plūsmas adresi" #: podcasts-gtk/resources/gtk/headerbar.ui:89 msgid "Add" msgstr "Pievienot" #: podcasts-gtk/resources/gtk/headerbar.ui:133 msgid "You are already subscribed to that feed!" msgstr "Jūs jau esat abonējis šo plūsmu!" #: podcasts-gtk/resources/gtk/headerbar.ui:169 msgid "Show Title" msgstr "Rādīt virsrakstu" #: podcasts-gtk/resources/gtk/headerbar.ui:210 msgid "Back" msgstr "Atpakaļ" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgid "General" msgstr "Vispārīgi" #: podcasts-gtk/resources/gtk/help-overlay.ui:18 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Pārbaudīt jaunos raidījumus" #: podcasts-gtk/resources/gtk/help-overlay.ui:25 msgctxt "shortcut window" msgid "Preferences" msgstr "Iestatījumi" #: podcasts-gtk/resources/gtk/help-overlay.ui:32 msgctxt "shortcut window" msgid "Quit the application" msgstr "Iziet no lietotnes" #: podcasts-gtk/resources/gtk/home_view.ui:56 msgid "Today" msgstr "Šodien" #: podcasts-gtk/resources/gtk/home_view.ui:112 msgid "Yesterday" msgstr "Vakar" #: podcasts-gtk/resources/gtk/home_view.ui:168 msgid "This Week" msgstr "Šonedēļ" #: podcasts-gtk/resources/gtk/home_view.ui:224 msgid "This Month" msgstr "Šomēnes" #: podcasts-gtk/resources/gtk/home_view.ui:281 msgid "Older" msgstr "Vecākas" #: podcasts-gtk/resources/gtk/inapp_notif.ui:101 msgid "An in-app action notification" msgstr "Paziņojumi lietotnē" #: podcasts-gtk/resources/gtk/inapp_notif.ui:112 msgid "Undo" msgstr "Atsaukt" #: podcasts-gtk/resources/gtk/player_toolbar.ui:72 msgid "Rewind 10 seconds" msgstr "Attīt 10 sekundes" #: podcasts-gtk/resources/gtk/player_toolbar.ui:87 msgid "Play" msgstr "Atskaņot" #: podcasts-gtk/resources/gtk/player_toolbar.ui:103 msgid "Pause" msgstr "Pauzēt" #: podcasts-gtk/resources/gtk/player_toolbar.ui:119 msgid "Fast forward 10 seconds" msgstr "Patīt uz priekšu 10 sekundes" #: podcasts-gtk/resources/gtk/player_toolbar.ui:285 msgid "Change the playback speed" msgstr "Mainīt atskaņošanas ātrumu" #: podcasts-gtk/resources/gtk/player_toolbar.ui:300 #: podcasts-gtk/resources/gtk/player_toolbar.ui:380 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:344 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:348 msgid "1.5 speed rate" msgstr "1,5 ātrums" #: podcasts-gtk/resources/gtk/player_toolbar.ui:362 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:366 msgid "1.25 speed rate" msgstr "1,25 ātrums" #: podcasts-gtk/resources/gtk/player_toolbar.ui:384 msgid "Normal speed" msgstr "Normāls ātrums" #: podcasts-gtk/resources/gtk/prefs.ui:42 #: podcasts-gtk/resources/gtk/prefs.ui:295 msgid "Preferences" msgstr "Iestatījumi" #: podcasts-gtk/resources/gtk/prefs.ui:76 msgid "Appearance" msgstr "Izskats" #: podcasts-gtk/resources/gtk/prefs.ui:120 msgid "Dark Theme" msgstr "Tumšais motīvs" #: podcasts-gtk/resources/gtk/prefs.ui:166 msgid "Delete played episodes" msgstr "Dzēst atskaņotās epizodes" #: podcasts-gtk/resources/gtk/prefs.ui:211 msgid "After" msgstr "Pēc" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Atzī_mēt visas epizodes kā atskaņotas" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Tīmekļa vietne" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "An_ulēt abonementu" #: podcasts-gtk/resources/gtk/show_menu.ui:51 msgid "Open Website" msgstr "Atvērt tīmekļa vietni" #: podcasts-gtk/resources/gtk/show_menu.ui:64 msgid "Mark All as Played" msgstr "Atzīmēt visu kā atskaņotu" #: podcasts-gtk/resources/gtk/show_menu.ui:89 msgid "Unsubscribe" msgstr "Anulēt abonementu" #: podcasts-gtk/src/app.rs:332 msgid "Fetching new episodes" msgstr "Saņem jaunas epizodes" #: podcasts-gtk/src/headerbar.rs:98 msgid "You are already subscribed to this show" msgstr "Jūs jau esat abonējis šo raidījumu" #: podcasts-gtk/src/headerbar.rs:106 msgid "Invalid URL" msgstr "Nederīgs URL" #: podcasts-gtk/src/prefs.rs:59 msgid "Seconds" msgstr "Sekundes" #: podcasts-gtk/src/prefs.rs:60 msgid "Minutes" msgstr "Minūtes" #: podcasts-gtk/src/prefs.rs:61 msgid "Hours" msgstr "Stundas" #: podcasts-gtk/src/prefs.rs:62 msgid "Days" msgstr "Dienas" #: podcasts-gtk/src/prefs.rs:63 msgid "Weeks" msgstr "Nedēļas" #: podcasts-gtk/src/stacks/content.rs:35 msgid "New" msgstr "Jauns" #: podcasts-gtk/src/stacks/content.rs:36 msgid "Shows" msgstr "Raidījumi" #: podcasts-gtk/src/utils.rs:357 msgid "Select the file from which to you want to import shows." msgstr "Izvēlieties datni, no kuras vēlaties importēt raidījumus." #: podcasts-gtk/src/utils.rs:360 msgid "_Import" msgstr "_Importēt" #: podcasts-gtk/src/utils.rs:369 msgid "OPML file" msgstr "OPML datne" #: podcasts-gtk/src/utils.rs:386 msgid "Failed to parse the imported file" msgstr "Neizdevās parsēt importēto datni" #: podcasts-gtk/src/utils.rs:391 msgid "Selected file could not be accessed." msgstr "Nevarēja piekļūt izvēlētajai datnei." #: podcasts-gtk/src/widgets/aboutdialog.rs:26 msgid "Podcast Client for the GNOME Desktop." msgstr "Podraižu klients GNOME darbvirsmai." #: podcasts-gtk/src/widgets/aboutdialog.rs:33 msgid "Learn more about GNOME Podcasts" msgstr "Uzziniet vairāk par GNOME podraidēm" #: podcasts-gtk/src/widgets/aboutdialog.rs:38 msgid "translator-credits" msgstr "Rūdolfs Mazurs " #: podcasts-gtk/src/widgets/episode.rs:130 msgid "{} min" msgstr "{} min" #. sender.send(Action::ErrorNotification(format!("Player Error: {}", error))); #: podcasts-gtk/src/widgets/player.rs:301 msgid "The media player was unable to execute an action." msgstr "Mediju atskaņotājs nevarēja izpildīt darbību." #: podcasts-gtk/src/widgets/show_menu.rs:150 msgid "Marked all episodes as listened" msgstr "Visas epizodes ir atzīmētas kā noklausītas" #: podcasts-gtk/src/widgets/show_menu.rs:155 msgid "Unsubscribed from {}" msgstr "Anulēt abonementu" podcasts-25.2/podcasts-gtk/po/meson.build000066400000000000000000000003541500126606300204460ustar00rootroot00000000000000i18n.gettext(meson.project_name(), args: ['--keyword=i18n', '--keyword=i18n_f', '--keyword=i18n_k', '--keyword=ni18n:1,2', '--keyword=ni18n_f:1,2', '--keyword=ni18n_k:1,2'], preset: 'glib') podcasts-25.2/podcasts-gtk/po/nl.po000066400000000000000000000355071500126606300172650ustar00rootroot00000000000000# Dutch translation for podcasts. # Copyright (C) 2019 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Nathan Follens , 2019-2023. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2023-09-03 05:02+0000\n" "PO-Revision-Date: 2023-09-04 21:16+0200\n" "Last-Translator: Nathan Follens \n" "Language-Team: GNOME-NL https://matrix.to/#/#nl:gnome.org\n" "Language: 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-Generator: Poedit 3.3.2\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Deze show heeft nog geen afleveringen" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Als u denkt dat dit een fout is, overweeg dan een foutmelding in te dienen." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get Some Shows" msgstr "Haal enkele shows op" #: podcasts-gtk/resources/gtk/empty_view.ui:49 msgid "Add new shows via feed URL" msgstr "Nieuwe shows toevoegen via feed-URL" #: podcasts-gtk/resources/gtk/empty_view.ui:71 msgid "Import shows from another device" msgstr "Shows importeren van een ander apparaat" #: podcasts-gtk/resources/gtk/episode_description.ui:41 msgid "Episode Details" msgstr "Informatie over aflevering" #: podcasts-gtk/resources/gtk/episode_description.ui:51 #: podcasts-gtk/resources/gtk/headerbar.ui:147 msgid "Back" msgstr "Terug" #: podcasts-gtk/resources/gtk/episode_description.ui:59 msgid "Episode Menu" msgstr "Afleveringsmenu" #: podcasts-gtk/resources/gtk/episode_description.ui:107 msgid "Podcast Title" msgstr "Podcasttitel" #: podcasts-gtk/resources/gtk/episode_description.ui:130 msgid "Duration - Date" msgstr "Duur - datum" #: podcasts-gtk/resources/gtk/episode_description.ui:150 #: podcasts-gtk/resources/gtk/episode_description.ui:155 msgid "Episode Description" msgstr "Afleveringsbeschrijving" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Ga naar show" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Afleverings-URL kopiëren" #: podcasts-gtk/resources/gtk/episode_widget.ui:71 msgid "You’ve already listened to this episode." msgstr "U hebt deze aflevering reeds beluisterd." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Grootte van aflevering berekenen…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Deze aflevering afspelen" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Downloadproces annuleren" #: podcasts-gtk/resources/gtk/episode_widget.ui:205 msgid "Download this episode" msgstr "Deze aflevering downloaden" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Controleren op nieuwe afleveringen" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "Shows _importeren" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "Shows _exporteren" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Sneltoetsen" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_Over Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:33 #: podcasts-gtk/resources/gtk/headerbar.ui:137 msgid "Add a new feed" msgstr "Nieuwe feed toevoegen" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter Feed Address" msgstr "Voer feedadres in" #: podcasts-gtk/resources/gtk/headerbar.ui:59 msgid "Popover menu (ESC to close)" msgstr "Popovermenu (druk op Esc om te sluiten)" #: podcasts-gtk/resources/gtk/headerbar.ui:71 msgid "Add" msgstr "Toevoegen" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/headerbar.ui:115 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:533 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/window.rs:64 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:125 msgid "Show Title" msgstr "Titel tonen" #: podcasts-gtk/resources/gtk/headerbar.ui:157 msgid "Main Menu" msgstr "Hoofdmenu" #: podcasts-gtk/resources/gtk/headerbar.ui:167 msgid "Podcast Menu" msgstr "Podcastmenu" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "Algemeen" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Controleren op nieuwe afleveringen" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "De toepassing afsluiten" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Vandaag" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Gisteren" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Deze week" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Deze maand" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Ouder" #: podcasts-gtk/resources/gtk/player_dialog.ui:22 msgid "Now Playing" msgstr "U luistert naar" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "De afspeelsnelheid wijzigen" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:55 msgid "Rewind 10 seconds" msgstr "10 seconden terugspoelen" #: podcasts-gtk/resources/gtk/player_toolbar.ui:63 msgid "Play" msgstr "Afspelen" #: podcasts-gtk/resources/gtk/player_toolbar.ui:71 msgid "Pause" msgstr "Pauzeren" #: podcasts-gtk/resources/gtk/player_toolbar.ui:79 msgid "Fast forward 10 seconds" msgstr "10 seconden doorspoelen" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Alle afleveringen _markeren als afgespeeld" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Website" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Opzeggen" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Website openen" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Alles markeren als afgespeeld" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Opzeggen" #: podcasts-gtk/resources/gtk/show_widget.ui:80 msgid "Episodes" msgstr "Afleveringen" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Luister naar uw favoriete shows" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Speel uw favoriete podcasts af, werk ze bij en beheer ze via een lichte " "interface die naadloos integreert met GNOME. Podcasts kan verschillende " "audioformaten afspelen en onthoudt waar u gestopt bent met luisteren. U kunt " "zich abonneren op shows via RSS-/Atom-, iTunes- en SoundCloud-verwijzingen. " "Abonnementen van andere toepassingen kunnen geïmporteerd worden via OPML-" "bestanden." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:143 msgid "The Podcasts developers" msgstr "De ontwikkelaars van Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Luister naar uw favoriete podcasts, rechtstreeks vanop uw bureaublad." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Hoogte van laatst geopend hoofdvenster" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Breedte van laatst geopend hoofdvenster" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Gemaximaliseerde status van laatst geopend hoofdvenster" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Of inhoud automatisch vernieuwd moet worden" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Hoeveel tijd te wachten tussen automatisch vernieuwen" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Hoe lang te wachten tussen automatisch vernieuwen" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Of inhoud vernieuwd moet worden na opstarten" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Hoeveel tijd te wachten tussen automatische opruimbeurten" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Hoe lang te wachten tussen automatische opruimbeurten" #: podcasts-gtk/src/app.rs:404 msgid "Copied URL to clipboard!" msgstr "URL gekopieerd naar klembord!" #: podcasts-gtk/src/stacks/content.rs:69 msgid "New" msgstr "Nieuw" #: podcasts-gtk/src/stacks/content.rs:71 #: podcasts-gtk/src/widgets/shows_view.rs:55 msgid "Shows" msgstr "Shows" #: podcasts-gtk/src/utils.rs:491 podcasts-gtk/src/utils.rs:533 msgid "OPML file" msgstr "OPML-bestand" #: podcasts-gtk/src/utils.rs:502 msgid "Select the file from which to you want to import shows." msgstr "Selecteer het bestand waaruit u shows wilt importeren." #: podcasts-gtk/src/utils.rs:504 msgid "_Import" msgstr "_Importeren" #: podcasts-gtk/src/utils.rs:520 msgid "Failed to parse the imported file" msgstr "Verwerken van geïmporteerd bestand mislukt" #: podcasts-gtk/src/utils.rs:543 msgid "Export shows to…" msgstr "Shows exporteren naar…" #: podcasts-gtk/src/utils.rs:544 msgid "_Export" msgstr "_Exporteren" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:548 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-geëxporteerde-shows" #: podcasts-gtk/src/utils.rs:558 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcasts-abonnementen" #: podcasts-gtk/src/utils.rs:559 msgid "Failed to export podcasts" msgstr "Exporteren van podcasts mislukt" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Podcastcliënt voor het GNOME-bureaublad." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Nathan Follens \n" "Meer info over GNOME-NL https://nl.gnome.org/" #: podcasts-gtk/src/widgets/episode.rs:374 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:907 msgid "The media player was unable to execute an action." msgstr "De mediaspeler kon een handeling niet uitvoeren." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Opent deze beschrijving visueel" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Lees meer" #: podcasts-gtk/src/widgets/show_menu.rs:180 msgid "Marked all episodes as listened" msgstr "Alle afleveringen markeren als beluisterd" #: podcasts-gtk/src/widgets/show_menu.rs:181 #: podcasts-gtk/src/widgets/show_menu.rs:204 msgid "Undo" msgstr "Ongedaan maken" #: podcasts-gtk/src/widgets/show_menu.rs:200 msgid "Unsubscribed from {}" msgstr "Abonnement op {} opgezegd" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Top position of the last open main window" #~ msgstr "Toppositie van laatst geopend hoofdvenster" #~ msgid "Left position of the last open main window" #~ msgstr "Linkerpositie van laatst geopend hoofdvenster" #~ msgid "Enable or disable dark theme" #~ msgstr "Donker thema in- of uitschakelen" #~ msgid "An in-app action notification" #~ msgstr "Een in-app-actiemelding" #~ msgid "Fetching new episodes" #~ msgstr "Nieuwe afleveringen worden opgehaald" #~ msgid "Selected file could not be accessed." #~ msgstr "Kon geen toegang verkrijgen tot geselecteerd bestand." #~ msgid "_Cancel" #~ msgstr "_Annuleren" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Kom meer te weten over Gnome Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Podcast-toepassing voor Gnome" #~ msgid "Double speed rate" #~ msgstr "Dubbele snelheid" #~ msgid "1.75 speed rate" #~ msgstr "1,75 snelheid" #~ msgid "1.5 speed rate" #~ msgstr "1,5 snelheid" #~ msgid "1.25 speed rate" #~ msgstr "1,25 snelheid" #~ msgid "Normal speed" #~ msgstr "Normale snelheid" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Voorkeuren" #~ msgid "You are already subscribed to that feed!" #~ msgstr "U bent reeds geabonneerd op die feed!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Voorkeuren" #~ msgid "Preferences" #~ msgstr "Voorkeuren" #~ msgid "Appearance" #~ msgstr "Uiterlijk" #~ msgid "Dark Theme" #~ msgstr "Donker thema" #~ msgid "Delete played episodes" #~ msgstr "Afgespeelde afleveringen verwijderen" #~ msgid "After" #~ msgstr "Na" #~ msgid "Invalid URL" #~ msgstr "Ongeldige URL" #~ msgid "Seconds" #~ msgstr "Seconden" #~ msgid "Minutes" #~ msgstr "Minuten" #~ msgid "Hours" #~ msgstr "Uur" #~ msgid "Days" #~ msgstr "Dagen" #~ msgid "Weeks" #~ msgstr "Weken" podcasts-25.2/podcasts-gtk/po/oc.po000066400000000000000000000432231500126606300172470ustar00rootroot00000000000000# Occitan translation for podcasts. # Copyright (C) 2021 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Quentin PAGÈS , 2021. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-07-26 11:10+0000\n" "PO-Revision-Date: 2024-08-22 20:53+0200\n" "Last-Translator: Quentin PAGÈS\n" "Language-Team: Occitan \n" "Language: oc\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 3.4.3\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Episòdis : " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:114 msgid "0" msgstr "0" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Darrièra publicacion" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "Subscribe" msgstr "S'abonar" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:171 msgid "Subscribing to feed..." msgstr "Abonament al flux..." #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Apondre de podcasts" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Recercar" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Picatz un flux URL o una recèrca sus la platafòrma seleccionada" #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Enviar recèrca" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 msgid "Loading..." msgstr "Cargament..." #: podcasts-gtk/resources/gtk/discovery_page.ui:107 msgid "Loading" msgstr "Cargament" #: podcasts-gtk/resources/gtk/discovery_page.ui:121 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:133 msgid "Search Platforms" msgstr "" #: podcasts-gtk/resources/gtk/discovery_page.ui:134 msgid "Search queries will be sent to these platforms." msgstr "" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Resultats de recèrca" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Cap de resultat pas trobat." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Aqueste programa a pas encara d'episòdi" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Se pensatz qu'es una error, mercés d'escriure per senhalar l'anomalia." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Obténer mai de programas" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Apondre de programas novèls via l'URL del flux" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Importar de programas a partir d'un autre aparelh" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Detalhs episòdi" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Menú episòdi" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Títol del podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Durada - Data" #: podcasts-gtk/resources/gtk/episode_description.ui:157 msgid "Stream" msgstr "Flux continú" #: podcasts-gtk/resources/gtk/episode_description.ui:183 #: podcasts-gtk/resources/gtk/player_sheet.ui:151 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:262 msgid "Play" msgstr "Lectura" #: podcasts-gtk/resources/gtk/episode_description.ui:210 msgid "Download" msgstr "Telecargament" #: podcasts-gtk/resources/gtk/episode_description.ui:237 msgid "Cancel" msgstr "Anullar" #: podcasts-gtk/resources/gtk/episode_description.ui:274 msgid "Delete" msgstr "Suprimir" #: podcasts-gtk/resources/gtk/episode_description.ui:294 msgid "Episode Description" msgstr "Descripcion episòdi" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Anar al programa" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Copiar l'URL de l'pisòdi" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Avètz ja escotat aqueste episòdi." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Calcul de la talha de l’episòdi…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Legir aqueste episòdi" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Anullar lo telecargament" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Telecargar aqueste episòdi" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Episòdi sens àudio" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navegacion" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Anar al site" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Anar a la pagina Programa" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Anar a la pagina Descobrir" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Lector" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Alternar la pausa" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Avança rapida" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Retorn rapid" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "General" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Verificar la preséncia d'episòdis novèls" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Quitar l'aplicacion" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Importar los abonaments" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Exportar los abonaments" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Uèi" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Ièr" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Aquesta setmana" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Aqueste mes" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Mai ancians" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Cambiar la velocitat de lectura" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:130 msgid "Rewind" msgstr "Recular" #: podcasts-gtk/resources/gtk/player_sheet.ui:169 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:271 msgid "Pause" msgstr "Pausa" #: podcasts-gtk/resources/gtk/player_sheet.ui:192 msgid "Forward" msgstr "Avançar" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Rembobinar de 10 segondas" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Avançar de 10 segondas" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marcar totes los episòdis coma legits" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Site _Web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "Se _desabonar" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Dobrir lo site web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Tot marcar coma legits" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Se desabonar" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Menú podcast" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episòdis" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Cercar d'episòdis novèls" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Importar de programas" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Exportar de programas" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Acorchis de clavièr" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_A prepaus de Podcasts" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:470 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:445 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Apondre un flux novèl" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Menú principala" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Programa" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Escotatz vòstres programas preferits" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:186 msgid "The Podcasts developers" msgstr "Los desvolopaires de Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Indica se cal actualizar lo contengut aprèp l’aviada" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "" #: podcasts-gtk/src/app.rs:344 msgid "Copied URL to clipboard!" msgstr "URL copiada del quichapapièrs !" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "" #: podcasts-gtk/src/manager.rs:106 #, fuzzy #| msgid "Download this episode" msgid "Download failed: {}" msgstr "Telecargar aqueste episòdi" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "Fichièr OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "" #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Importar" #: podcasts-gtk/src/utils.rs:424 #, fuzzy #| msgid "Failed to parse the imported file" msgid "Failed to parse the imported file {}" msgstr "Analisi impossibla del fichièr importat" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Exportar los programas cap a…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Exportar" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "programas-exportats-de-gnome-podcasts" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Abonaments GNOME Podcasts" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Fracàs de l’expòrt dels podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Un client de Podcast l’environament de burèu GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "Quentin PAGÈS" #: podcasts-gtk/src/widgets/content_stack.rs:66 msgid "New" msgstr "Nòu" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:67 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Programas" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Progression del telecargament" #: podcasts-gtk/src/widgets/episode.rs:282 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1070 msgid "The media player was unable to execute an action." msgstr "Lo lector multimèdia a pas pogut executar una accion." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Espandir visualament aquesta descripcion" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Ne saber mai" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Totes los episòdis marcats coma escotats" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Anullar" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Desabonament de {}" #~ msgid "Enter Feed Address" #~ msgstr "Picar l’adreça del flux" #~ msgid "Add" #~ msgstr "Ajustar" #~ msgid "Now Playing" #~ msgstr "Actualament" #~ msgid "Close" #~ msgstr "Tampar" #~ msgid "Back" #~ msgstr "Tornar" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicacion de podcast per GNOME" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Show Title" #~ msgstr "Afichar lo títol" #~ msgid "Fetching new episodes" #~ msgstr "Recuperacion d’episòdis nòus" podcasts-25.2/podcasts-gtk/po/pl.po000066400000000000000000000330151500126606300172570ustar00rootroot00000000000000# Polish translation for podcasts. # Copyright © 2018-2023 the podcasts authors. # This file is distributed under the same license as the podcasts package. # Piotr Drąg , 2018-2023. # Aviary.pl , 2018-2023. # msgid "" msgstr "" "Project-Id-Version: podcasts\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2023-09-06 01:04+0000\n" "PO-Revision-Date: 2023-09-16 16:05+0200\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\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%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Ten program nie ma jeszcze odcinków" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "Jeśli to błąd, to prosimy go zgłosić." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get Some Shows" msgstr "Pobieranie programów" #: podcasts-gtk/resources/gtk/empty_view.ui:49 msgid "Add new shows via feed URL" msgstr "Dodaj nowe programy za pomocą adresu kanału" #: podcasts-gtk/resources/gtk/empty_view.ui:71 msgid "Import shows from another device" msgstr "Zaimportuj programy z innego urządzenia" #: podcasts-gtk/resources/gtk/episode_description.ui:41 msgid "Episode Details" msgstr "Informacje o odcinku" #: podcasts-gtk/resources/gtk/episode_description.ui:51 #: podcasts-gtk/resources/gtk/headerbar.ui:147 msgid "Back" msgstr "Wstecz" #: podcasts-gtk/resources/gtk/episode_description.ui:59 msgid "Episode Menu" msgstr "Menu odcinka" #: podcasts-gtk/resources/gtk/episode_description.ui:107 msgid "Podcast Title" msgstr "Tytuł podcastu" #: podcasts-gtk/resources/gtk/episode_description.ui:130 msgid "Duration - Date" msgstr "Czas trwania — data" #: podcasts-gtk/resources/gtk/episode_description.ui:150 #: podcasts-gtk/resources/gtk/episode_description.ui:155 msgid "Episode Description" msgstr "Opis odcinka" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Przejdź do programu" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Skopiuj adres odcinka" #: podcasts-gtk/resources/gtk/episode_widget.ui:71 msgid "You’ve already listened to this episode." msgstr "Ten odcinek został już odsłuchany" #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Obliczanie rozmiaru odcinka…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Odtwarza ten odcinek" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Anuluje pobieranie" #: podcasts-gtk/resources/gtk/episode_widget.ui:205 msgid "Download this episode" msgstr "Pobiera ten odcinek" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Wyszukaj nowe odcinki" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "Zai_mportuj programy" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "Wy_eksportuj programy" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Skróty klawiszowe" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_O programie" #: podcasts-gtk/resources/gtk/headerbar.ui:33 #: podcasts-gtk/resources/gtk/headerbar.ui:137 msgid "Add a new feed" msgstr "Dodaje nowy kanał" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter Feed Address" msgstr "Adres kanału" #: podcasts-gtk/resources/gtk/headerbar.ui:59 msgid "Popover menu (ESC to close)" msgstr "Wyskakujące menu (klawisz Esc zamyka)" #: podcasts-gtk/resources/gtk/headerbar.ui:71 msgid "Add" msgstr "Dodaj" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/headerbar.ui:115 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:533 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/window.rs:64 msgid "Podcasts" msgstr "Podcasty" #: podcasts-gtk/resources/gtk/headerbar.ui:125 msgid "Show Title" msgstr "Tytuł programu" #: podcasts-gtk/resources/gtk/headerbar.ui:157 msgid "Main Menu" msgstr "Menu główne" #: podcasts-gtk/resources/gtk/headerbar.ui:167 msgid "Podcast Menu" msgstr "Menu podcastu" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "Ogólne" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Wyszukanie nowych odcinków" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "Zakończenie działania programu" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Dzisiaj" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Wczoraj" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Ten tydzień" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Ten miesiąc" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Starsze" #: podcasts-gtk/resources/gtk/player_dialog.ui:7 msgid "Now Playing" msgstr "Odtwarzanie" #: podcasts-gtk/resources/gtk/player_dialog.ui:25 msgid "Close" msgstr "Zamyka" #: podcasts-gtk/resources/gtk/player_dialog.ui:149 msgid "Rewind" msgstr "Przewija" #: podcasts-gtk/resources/gtk/player_dialog.ui:170 #: podcasts-gtk/resources/gtk/player_toolbar.ui:63 #: podcasts-gtk/resources/gtk/player_toolbar.ui:244 msgid "Play" msgstr "Odtwarza" #: podcasts-gtk/resources/gtk/player_dialog.ui:188 #: podcasts-gtk/resources/gtk/player_toolbar.ui:71 #: podcasts-gtk/resources/gtk/player_toolbar.ui:250 msgid "Pause" msgstr "Wstrzymuje" #: podcasts-gtk/resources/gtk/player_dialog.ui:211 msgid "Forward" msgstr "Do przodu" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Zmienia prędkość odtwarzania" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:55 msgid "Rewind 10 seconds" msgstr "Przewija wstecz o 10 sekund" #: podcasts-gtk/resources/gtk/player_toolbar.ui:79 msgid "Fast forward 10 seconds" msgstr "Przewija wprzód o 10 sekund" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Oznacz wszystkie odcinki jako odtworzone" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Strona _WWW" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Zrezygnuj z subskrypcji" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Otwórz stronę WWW" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Oznacz wszystkie jako odtworzone" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Zrezygnuj z subskrypcji" #: podcasts-gtk/resources/gtk/show_widget.ui:80 msgid "Episodes" msgstr "Odcinki" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Słuchaj ulubionych programów" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Odtwarzanie, aktualizowanie i zarządzanie podcastami za pomocą lekkiego " "interfejsu zintegrowanego ze środowiskiem GNOME. Obsługuje różne formaty " "dźwięku i zapamiętuje miejsce, w którym wstrzymano słuchanie. Można " "subskrybować programy za pomocą odnośników RSS/Atom, iTunes i SoundCloud. Za " "pomocą plików OPML można także importować subskrypcje z innych programów." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:143 msgid "The Podcasts developers" msgstr "Programiści projektu Podcasty" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Słuchanie ulubionych podcastów prosto z komputera" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;Podkasty;RSS;Atom;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Wysokość ostatnio otwartego głównego okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Szerokość ostatnio otwartego głównego okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Zmaksymalizowany stan ostatnio otwartego głównego okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Czy okresowo odświeżać treści" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Ile okresów czekać między automatycznym odświeżeniem" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Jaki okres czekać między automatycznym odświeżeniem" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Czy odświeżać treści po uruchomieniu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Ile okresów czekać między automatycznym czyszczeniem" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Jaki okres czekać między automatycznym czyszczeniem" #: podcasts-gtk/src/app.rs:404 msgid "Copied URL to clipboard!" msgstr "Skopiowano adres do schowka" #: podcasts-gtk/src/stacks/content.rs:69 msgid "New" msgstr "Nowe" #: podcasts-gtk/src/stacks/content.rs:71 #: podcasts-gtk/src/widgets/shows_view.rs:55 msgid "Shows" msgstr "Programy" #: podcasts-gtk/src/utils.rs:491 podcasts-gtk/src/utils.rs:533 msgid "OPML file" msgstr "Plik OPML" #: podcasts-gtk/src/utils.rs:502 msgid "Select the file from which to you want to import shows." msgstr "Proszę wybrać plik, z którego zaimportować programy." #: podcasts-gtk/src/utils.rs:504 msgid "_Import" msgstr "Zai_mportuj" #: podcasts-gtk/src/utils.rs:520 msgid "Failed to parse the imported file" msgstr "Przetworzenie zaimportowanego pliku się nie powiodło" #: podcasts-gtk/src/utils.rs:543 msgid "Export shows to…" msgstr "Eksport programów do…" #: podcasts-gtk/src/utils.rs:544 msgid "_Export" msgstr "Wy_eksportuj" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:548 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-wyeksportowane-programy" #: podcasts-gtk/src/utils.rs:558 msgid "GNOME Podcasts Subscriptions" msgstr "Subskrypcje Podcastów GNOME" #: podcasts-gtk/src/utils.rs:559 msgid "Failed to export podcasts" msgstr "Wyeksportowanie podcastów się nie powiodło" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Klient podcastów dla środowiska GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Piotr Drąg , 2018-2023\n" "Aviary.pl , 2018-2023" #: podcasts-gtk/src/widgets/episode.rs:374 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:907 msgid "The media player was unable to execute an action." msgstr "Odtwarzacz multimediów nie może wykonać działania." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Wizualnie rozwija ten opis" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Czytaj dalej" #: podcasts-gtk/src/widgets/show_menu.rs:180 msgid "Marked all episodes as listened" msgstr "Oznaczono wszystkie odcinki jako odsłuchane" #: podcasts-gtk/src/widgets/show_menu.rs:181 #: podcasts-gtk/src/widgets/show_menu.rs:204 msgid "Undo" msgstr "Cofnij" #: podcasts-gtk/src/widgets/show_menu.rs:200 msgid "Unsubscribed from {}" msgstr "Zrezygnowano z subskrypcji „{}”" podcasts-25.2/podcasts-gtk/po/pt.po000066400000000000000000000326521500126606300172750ustar00rootroot00000000000000# Portuguese translation for podcasts. # Copyright (C) 2020 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Juliano de Souza Camargo , 2020. # Hugo Carvalho , 2021, 2022, 2023. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2023-03-26 17:46+0000\n" "PO-Revision-Date: 2023-03-27 13:36+0100\n" "Last-Translator: Hugo Carvalho \n" "Language-Team: Portuguese (https://l10n.gnome.org/teams/pt/)\n" "Language: 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-Generator: Poedit 3.0.1\n" #: podcasts-gtk/resources/gtk/empty_show.ui:16 msgid "This show does not have episodes yet" msgstr "Este podcaster ainda não publicou conteúdo" #: podcasts-gtk/resources/gtk/empty_show.ui:25 msgid "If you think this is an error, please consider writing a bug report." msgstr "Se pensa ser um erro, considere relatá-lo." #: podcasts-gtk/resources/gtk/empty_view.ui:30 msgid "Get Some Shows" msgstr "Obtenha alguns podcasts" #: podcasts-gtk/resources/gtk/empty_view.ui:49 msgid "Add new shows via feed URL" msgstr "Adicionar novo podcast via fonte URL" #: podcasts-gtk/resources/gtk/empty_view.ui:71 msgid "Import shows from another device" msgstr "Importar podcasts de outros dispositivos" #: podcasts-gtk/resources/gtk/episode_description.ui:41 msgid "Episode Details" msgstr "Detalhes do episódio" #: podcasts-gtk/resources/gtk/episode_description.ui:51 #: podcasts-gtk/resources/gtk/headerbar.ui:143 msgid "Back" msgstr "Voltar" #: podcasts-gtk/resources/gtk/episode_description.ui:105 msgid "Podcast Title" msgstr "Título de Podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:128 msgid "Duration - Date" msgstr "Duração - Data" #: podcasts-gtk/resources/gtk/episode_description.ui:148 msgid "Episode Description" msgstr "Descrição do episódio" #: podcasts-gtk/resources/gtk/episode_menu.ui:35 msgid "Go to Show" msgstr "Ir para o podcast" #: podcasts-gtk/resources/gtk/episode_menu.ui:39 msgid "Copy Episode Url" msgstr "Copiar URL do episódio" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Já ouviu este episódio." #: podcasts-gtk/resources/gtk/episode_widget.ui:157 msgid "Calculating episode size…" msgstr "A calcular o tamanho do episódio…" #: podcasts-gtk/resources/gtk/episode_widget.ui:180 msgid "Play this episode" msgstr "Reproduzir este episódio" #: podcasts-gtk/resources/gtk/episode_widget.ui:192 msgid "Cancel the download process" msgstr "Cancelar a transferência" #: podcasts-gtk/resources/gtk/episode_widget.ui:204 msgid "Download this episode" msgstr "Descarregar este episódio" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Verificar por novos episódios" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Importar podcasts" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Exportar podcasts" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Teclas de atalho" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_Acerca do Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:33 #: podcasts-gtk/resources/gtk/headerbar.ui:133 msgid "Add a new feed" msgstr "Adicionar nova fonte" #: podcasts-gtk/resources/gtk/headerbar.ui:44 msgid "Enter Feed Address" msgstr "Insira o endereço da fonte" #: podcasts-gtk/resources/gtk/headerbar.ui:67 msgid "Add" msgstr "Adicionar" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/headerbar.ui:111 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:457 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/window.rs:64 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/headerbar.ui:121 msgid "Show Title" msgstr "Título do Podcast" #: podcasts-gtk/resources/gtk/headerbar.ui:153 msgid "Main Menu" msgstr "Menu principal" #: podcasts-gtk/resources/gtk/help-overlay.ui:11 msgid "General" msgstr "Geral" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Verificar por novos episódios" #: podcasts-gtk/resources/gtk/help-overlay.ui:21 msgctxt "shortcut window" msgid "Quit the application" msgstr "Sair da aplicação" #: podcasts-gtk/resources/gtk/home_view.ui:50 msgid "Today" msgstr "Hoje" #: podcasts-gtk/resources/gtk/home_view.ui:78 msgid "Yesterday" msgstr "Ontem" #: podcasts-gtk/resources/gtk/home_view.ui:106 msgid "This Week" msgstr "Nesta semana" #: podcasts-gtk/resources/gtk/home_view.ui:134 msgid "This Month" msgstr "Neste mês" #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "Older" msgstr "Antigo" #: podcasts-gtk/resources/gtk/player_dialog.ui:22 msgid "Now Playing" msgstr "A reproduzir" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Alterar a velocidade de reprodução" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:55 msgid "Rewind 10 seconds" msgstr "Retroceder 10 segundos" #: podcasts-gtk/resources/gtk/player_toolbar.ui:63 msgid "Play" msgstr "Reproduzir" #: podcasts-gtk/resources/gtk/player_toolbar.ui:71 msgid "Pause" msgstr "Pausa" #: podcasts-gtk/resources/gtk/player_toolbar.ui:79 msgid "Fast forward 10 seconds" msgstr "Avançar 10 segundos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marcar todos os episódios como já reproduzidos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Sítio Web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "An_ular a subscrição" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Abrir sítio Web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Marcar tudo como já reproduzido" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Anular a subscrição" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Ouça os seus programas favoritos" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Reproduza, atualize e gira os seus podcasts a partir de uma interface leve " "que se integra perfeitamente com o GNOME. Os podcasts podem reproduzir " "vários formatos de áudio e lembrar onde deixou de ouvir. Pode subscrever " "programas via RSS/Atom, iTunes, e ligações Soundcloud. As subscrições de " "outras aplicações podem ser importadas através de ficheiros OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:134 msgid "Jordan Petridis" msgstr "Jordan Petridis" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:135 msgid "Julian Hofer" msgstr "Julian Hofer" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "" "Ouça os seus podcasts favoritos, diretamente do seu ambiente de trabalho." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Altura da última janela principal aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Largura da última janela principal aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Estado de maximização da última janela principal aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Se deve atualizar periodicamente o conteúdo" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Quantos compassos de espera entre as atualizações automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Qual o compasso de espera entre as atualizações automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Se deve atualizar o conteúdo após o arranque" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Quantos compassos de espera entre as limpezas automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Qual o compasso de espera entre as limpezas automáticas" #: podcasts-gtk/src/app.rs:337 msgid "Copied URL to clipboard!" msgstr "URL copiado para área de transferência!" #: podcasts-gtk/src/stacks/content.rs:70 msgid "New" msgstr "Novo" #: podcasts-gtk/src/stacks/content.rs:72 msgid "Shows" msgstr "Podcasts" #: podcasts-gtk/src/utils.rs:489 podcasts-gtk/src/utils.rs:531 msgid "OPML file" msgstr "Ficheiro OPML" #: podcasts-gtk/src/utils.rs:500 msgid "Select the file from which to you want to import shows." msgstr "Selecionar o ficheiro de onde deseja importar podcasts." #: podcasts-gtk/src/utils.rs:502 msgid "_Import" msgstr "_Importar" #: podcasts-gtk/src/utils.rs:518 msgid "Failed to parse the imported file" msgstr "Falha ao processar o ficheiro importado" #: podcasts-gtk/src/utils.rs:541 msgid "Export shows to…" msgstr "Exportar podcasts para…" #: podcasts-gtk/src/utils.rs:542 msgid "_Export" msgstr "_Exportar" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:546 msgid "gnome-podcasts-exported-shows" msgstr "podcasts-exportados-do-gnome-podcasts" #: podcasts-gtk/src/utils.rs:556 msgid "GNOME Podcasts Subscriptions" msgstr "Subscrições do GNOME Podcasts" #: podcasts-gtk/src/utils.rs:557 msgid "Failed to export podcasts" msgstr "Falha ao exportar podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Um cliente de podcasts para o ambiente de trabalho GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "" "Juliano de Souza Camargo \n" "Hugo Carvalho " #: podcasts-gtk/src/widgets/episode.rs:143 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:884 msgid "The media player was unable to execute an action." msgstr "O reprodutor multimédia foi incapaz de executar uma ação." #: podcasts-gtk/src/widgets/read_more_label.rs:32 msgid "Read More" msgstr "Ler mais" #: podcasts-gtk/src/widgets/show_menu.rs:180 msgid "Marked all episodes as listened" msgstr "Marcou todos os episódios como escutados" #: podcasts-gtk/src/widgets/show_menu.rs:181 #: podcasts-gtk/src/widgets/show_menu.rs:204 msgid "Undo" msgstr "Anular" #: podcasts-gtk/src/widgets/show_menu.rs:200 msgid "Unsubscribed from {}" msgstr "Anulou a subscrição de {}" #~ msgid "Selected file could not be accessed." #~ msgstr "Ficheiro selecionado não pôde ser processado." #~ msgid "_Cancel" #~ msgstr "_Cancelar" #~ msgid "Top position of the last open main window" #~ msgstr "Posição de topo da última janela principal aberta" #~ msgid "Left position of the last open main window" #~ msgstr "Posição à esquerda da última janela principal aberta" #~ msgid "Enable or disable dark theme" #~ msgstr "Ativar ou desativar o tema escuro" #~ msgid "An in-app action notification" #~ msgstr "Uma notificação nativa de ações" #~ msgid "Fetching new episodes" #~ msgstr "A procurar novos episódios" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Saiba mais acerca do GNOME Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicação de podcast para GNOME" #~ msgid "Double speed rate" #~ msgstr "Dobrar a velocidade" #~ msgid "1.75 speed rate" #~ msgstr "1.75 da velocidade" #~ msgid "1.5 speed rate" #~ msgstr "1.5 da velocidade" #~ msgid "1.25 speed rate" #~ msgstr "1.25 da velocidade" #~ msgid "Normal speed" #~ msgstr "Velocidade normal" podcasts-25.2/podcasts-gtk/po/pt_BR.po000066400000000000000000000515601500126606300176570ustar00rootroot00000000000000# Brazilian Portuguese translation for podcasts. # Copyright (C) 2025 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Yuri Otávio Lopes Gomes , 2018, 2020. # Enrico Nicoletto , 2020-2021. # Leônidas Araújo , 2023-2024. # Juliano de Souza Camargo , 2023-2024. # Rafael Fontenelle , 2018-2025. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-29 16:48-0300\n" "Last-Translator: Rafael Fontenelle \n" "Language-Team: Brazilian Portuguese \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-Generator: Gtranslator 47.1\n" "X-DL-Team: pt_BR\n" "X-DL-Module: podcasts\n" "X-DL-Branch: master\n" "X-DL-Domain: po\n" "X-DL-State: Translating\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Episódios: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Última publicação" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Inscrever" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Inscrevendo-se no feed…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Adicionar podcasts" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Procurar" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Insira um URL de feed ou pesquise nas plataformas selecionadas." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Enviar pesquisa" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Carregando…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Ative uma plataforma de pesquisa abaixo ou insira uma URL de feed http(s)." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Plataformas de pesquisa" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "As consultas de pesquisa serão enviadas para essas plataformas." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Resultados da pesquisa" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Nenhum resultado encontrado." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Este podcast ainda não tem episódios" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Se você acredita que isso é um erro, por favor considere preencher um " "relatório de erro." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Obter alguns podcasts" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Adicionar novos podcasts via URL da fonte" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Importar podcasts de outro dispositivo" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Detalhes do episódio" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Menu do episódio" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Título do podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Duração - Data" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Transmitir" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Reproduzir" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Baixar" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Cancelar" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Excluir" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Descrição do episódio" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Capa do episódio" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Ir para podcast" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Copiar URL do episódio" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Marcar como reproduzidos" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Marcar como não reproduzidos" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Você já ouviu este episódio." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Calculando o tamanho do episódio…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Reproduzir este episódio" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Cancelar o processo de download" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Fazer download deste episódio" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Episódio sem áudio" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navegação" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Vai para a página inicial" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Vai para a página de podcasts" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Vai para a página de descoberta" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Reprodutor" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Alterna a pausa" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Procura adiante" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Procura para trás" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Geral" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Procura por novos episódios" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Sai do aplicativo" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Importa assinaturas" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Exporta assinaturas" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Hoje" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Ontem" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Esta semana" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Este mês" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Antigos" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Alterar a velocidade de reprodução" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Rebobinar" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Reproduzir" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pausar" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Avançar" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Descrição" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Retroceder 10 segundos" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Avançar 10 segundos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marcar todos os episódios como reproduzidos" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Site" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "Cance_lar inscrição" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Abrir o site" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Marcar todos como reproduzidos" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Cancelar inscrição" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Menu do podcast" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episódios" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Procurar novos episódios" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Importar podcasts" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Exportar podcasts" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "Atalhos de _teclado" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Sobre Podcasts" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podcasts" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Adicionar um nova feed" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Menu principal" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Show" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Ouça seus shows favoritos" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Reproduza, atualize e gerencie os seus podcasts a partir de uma interface " "leve que se integra perfeitamente com o GNOME. O Podcasts pode reproduzir " "vários formatos de áudio e lembrar onde parou de ouvir. Você pode se " "inscrever em podcasts via links RSS/Atom, iTunes e Soundcloud. As " "assinaturas de outros aplicativos podem ser importadas através de arquivos " "OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "A tela inicial exibindo os episódios mais recentes de seus podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "A visualização dos podcasts exibindo as capas dos seus programas" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "O widget do podcast exibindo a capa e os episódios mais recentes de um " "programa específico" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "A visualização onde se pode adicionar um novo podcast" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Os desenvolvedores do Podcasts" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Ouça seus podcasts favoritos diretamente da sua área de trabalho." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Altura da última janela principal aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Largura da última janela principal aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Estado maximizado da última janela principal aberta" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Se deve atualizar periodicamente o conteúdo" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "" "Quantos períodos de tempo deve esperar entre as atualizações automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Qual período de tempo deve esperar entre as atualizações automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Se deve atualizar o conteúdo após a inicialização" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Quantos períodos de tempo deve esperar entre limpezas automáticas" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Qual período de tempo deve esperar entre limpezas automáticas" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL copiada para área de transferência!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Pular para {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Pular para {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Download falhou: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Falha ao se inscrever no feed: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "Arquivo OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Selecione o arquivo a partir do qual você deseja importar podcasts." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Importar" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Falha ao analisar o arquivo importado {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Exportar podcasts para…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Exportar" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-shows-exportados" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Assinaturas do GNOME Podcasts" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Falha ao exportar podcasts" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Cliente de podcast para o ambiente GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Rafael Fontenelle \n" "Yuri Otávio Lopes Gomes \n" "Enrico Nicoletto \n" "Juliano de Souza Camargo \n" "Leônidas Araújo " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Buscando feed…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Novo" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Podcasts" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Progresso do download" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "O reprodutor de mídia não conseguiu executar uma ação." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Expande visualmente esta descrição" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Leia mais" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Todos os episódios foram marcados como ouvidos" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Desfazer" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Cancelada a inscrição de {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Carregando..." #~ msgid "Now Playing" #~ msgstr "Reproduzindo agora" #~ msgid "Close" #~ msgstr "Fechar" #~ msgid "Enter Feed Address" #~ msgstr "Insira o endereço da fonte" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Menu popover (ESC para fechar)" #~ msgid "Add" #~ msgstr "Adicionar" #~ msgid "Back" #~ msgstr "Voltar" #~ msgid "Show Title" #~ msgstr "Mostrar título" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Top position of the last open main window" #~ msgstr "Posição acima da janela da última principal aberta" #~ msgid "Left position of the last open main window" #~ msgstr "Posição à esquerda da última janela principal aberta" #~ msgid "Enable or disable dark theme" #~ msgstr "Habilitar ou desabilitar o tema escuro" #~ msgid "An in-app action notification" #~ msgstr "Uma notificação de ação no aplicativo" #~ msgid "Selected file could not be accessed." #~ msgstr "O arquivo selecionado não pôde ser acessado." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Aprenda mais sobre o GNOME Podcasts" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicativo de podcast para o GNOME" podcasts-25.2/podcasts-gtk/po/ro.po000066400000000000000000000505651500126606300172750ustar00rootroot00000000000000# Romanian translation for podcasts. # Copyright (C) 2020 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Florentina Mușat , 2020. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2024-08-28 20:03+0000\n" "PO-Revision-Date: 2024-08-29 09:31+0300\n" "Last-Translator: Florentina Mușat \n" "Language-Team: Romanian \n" "Language: 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-Generator: Poedit 3.4.4\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Episoade: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:114 msgid "0" msgstr "0" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Ultima publicare" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "Subscribe" msgstr "Abonare" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:171 msgid "Subscribing to feed..." msgstr "Se abonează la feed..." #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Adaugă podcasturi" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Caută" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Introduceți un URL de flux sau căutați platformele selectate." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Trimite căutarea" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 msgid "Loading..." msgstr "Se încarcă..." #: podcasts-gtk/resources/gtk/discovery_page.ui:107 msgid "Loading" msgstr "Se încarcă" #: podcasts-gtk/resources/gtk/discovery_page.ui:121 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Activați o platformă de căutare mai jos sau introduceți un URL de flux " "http(s)" #: podcasts-gtk/resources/gtk/discovery_page.ui:133 msgid "Search Platforms" msgstr "Platforme de căutare" #: podcasts-gtk/resources/gtk/discovery_page.ui:134 msgid "Search queries will be sent to these platforms." msgstr "Interogările de căutare vor fi trimise la aceste platforme." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Rezultatele căutării" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Nu au fost găsite rezultate." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Această emisiune nu are episoade încă" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Dacă credeți că aceasta este o eroare, considerați scrierea unui raport de " "defecțiune." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Obține niște emisiuni" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Adaugă emisiuni noi via URL flux" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Importă emisiuni de la alt dispozitiv" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Detalii episod" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Meniu episod" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Titlu podcast" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Durată - Dată" #: podcasts-gtk/resources/gtk/episode_description.ui:158 msgid "Stream" msgstr "Stream" #: podcasts-gtk/resources/gtk/episode_description.ui:186 #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Redă" #: podcasts-gtk/resources/gtk/episode_description.ui:214 msgid "Download" msgstr "Descarcă" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Cancel" msgstr "Anulează" #: podcasts-gtk/resources/gtk/episode_description.ui:279 msgid "Delete" msgstr "Șterge" #: podcasts-gtk/resources/gtk/episode_description.ui:299 msgid "Episode Description" msgstr "Descrierea episodului" #: podcasts-gtk/resources/gtk/episode_description.ui:320 msgid "Episode Cover" msgstr "Copertă episod" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Navighează la emisiune" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Copiază url-ul episodului" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Ați ascultat deja la acest episod." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Se calculează dimensiunea episodului…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Redă acest episod" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Anulează procesul de descărcare" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Descarcă acest episod" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Episod fără audio" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigare" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Navighează la pagina home" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Navighează la pagina emisiuni" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Navighează la pagina descoperiri" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Player" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Comută pauza" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Derulează înainte" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Derulează înapoi" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Generale" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Verifică pentru episoade noi" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Ieșire din aplicație" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Importă abonamentele" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Exportă abonamentele" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Azi" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Ieri" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Această săptămână" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "În această lună" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Mai vechi" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Modifică viteza de playback" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Derulează înapoi" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pauză" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Derulează înainte" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Derulează înapoi 10 secunde" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Derulează înainte 10 secunde" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Marchează toate episoadele ca redate" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "Pagină _web" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Dezabonează-mă" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Deschide pagina web" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Marchează toate ca redate" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Dezabonează-mă" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Meniu podcast" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Episoade" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "Verifi_că pentru episoade noi" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Importă emisiuni" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Exportă emisiuni" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "Scurtături de _tastatură" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Despre Podcasturi" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:470 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:445 msgid "Podcasts" msgstr "Podcasturi" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Adaugă un flux nou" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Meniu principal" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Emisiune" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Ascultă emisiunile favorite" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Redați, actualizați și gestionați podcast-urile dintr-o interfață ușoară " "care se integrează perfect cu GNOME. Podcast-uri poate să redea diverse " "formate audio și poate să rețină când ați încetat să ascultați. Vă puteți " "abona la emisiuni via RSS/Atom, iTunes și legături Soundcloud. Abonamentele " "din alte aplicații pot fi importate prin fișiere OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "" "Vizualizarea home care afișează cele mai noi episoade ale podcasturilor" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Vizualizarea emisiuni care afișează copertele podcasturilor" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Widget-ul emisiune care afișează coperta și cele mai recente episoade ale " "unui podcast specific" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Vizualizarea în care se poate adăuga un podcast nou" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:186 msgid "The Podcasts developers" msgstr "Dezvoltatorii Podcast-uri" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Ascultă podcasturile preferate, chiar de pe desktop." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Înălțimea ultimei ferestre principale deschise" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Lățimea ultimei ferestre principale deschise" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Starea maximizată a ultimei ferestre principale deschise" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Dacă să se reîmprospăteze periodic conținutul" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Câte perioade de timp să se aștepte între reîmprospătări automate" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Ce perioadă de timp să se aștepte între reîmprospătări automate" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Dacă să se reîmprospăteze conținutul după pornirea sistemului" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Câte perioade de timp să se aștepte între curățări automate" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Ce perioadă de timp să se aștepte între curățări automate" #: podcasts-gtk/src/app.rs:344 msgid "Copied URL to clipboard!" msgstr "S-a copiat URL-ul la clipboard!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Sari la {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Sari la {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Descărcarea a eșuat: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Nu s-a putut abona la flux: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "Fișier OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Selectează fișierul de la care doriți să importați emisiuni." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Importă" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Nu s-a putut parsa fișierul importat {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Exportă emisiuni la…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Exportă" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Abonări GNOME Podcasturi" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Nu s-au putut exporta podcasturile" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Client de podcast pentru GNOME Desktop." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Florentina Mușat , " "2020-2021, 2024" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Se aduc fluxurile…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Nou" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Emisiuni" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Progres descărcare" #: podcasts-gtk/src/widgets/episode.rs:282 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1070 msgid "The media player was unable to execute an action." msgstr "Playerul media nu a putut executa o acțiune." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Extinde vizual această descriere" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Citește mai mult" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "S-au marcat toate episoadele ca ascultate" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Refă" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Dezabonat de la {}" #~ msgid "Back" #~ msgstr "Înapoi" #~ msgid "Enter Feed Address" #~ msgstr "Introduceți adresa de flux" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Meniu popover (ESC pentru a închide)" #~ msgid "Add" #~ msgstr "Adaugă" #~ msgid "Show Title" #~ msgstr "Arată titlul" #~ msgid "Now Playing" #~ msgstr "Acum se redă" #~ msgid "Close" #~ msgstr "Închide" #~ msgid "Top position of the last open main window" #~ msgstr "Poziția de top a ultimei ferestre principale deschise" #~ msgid "Left position of the last open main window" #~ msgstr "Poziția din stânga a ultimei ferestre principale deschise" #~ msgid "Enable or disable dark theme" #~ msgstr "Activează sau dezactivează tema întunecată" #~ msgid "Podcast app for GNOME" #~ msgstr "Aplicație podcast pentru GNOME" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "An in-app action notification" #~ msgstr "O înștiințare de acțiune în aplicație" #~ msgid "Selected file could not be accessed." #~ msgstr "Fișierul selectat nu a putut fi accesat." #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Învață mai multe despre GNOME Podcasturi" #~ msgid "Double speed rate" #~ msgstr "Dublează rata vitezei" #~ msgid "1.75 speed rate" #~ msgstr "1.75 rată viteză" #~ msgid "1.5 speed rate" #~ msgstr "1.5 rată viteză" #~ msgid "1.25 speed rate" #~ msgstr "1.25 rată viteză" #~ msgid "Normal speed" #~ msgstr "Viteză normală" podcasts-25.2/podcasts-gtk/po/ru.po000066400000000000000000000566231500126606300173040ustar00rootroot00000000000000# Russian translation for podcasts. # Copyright (C) 2022 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Alexey Rubtsov , 2022. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-28 22:44+0300\n" "Last-Translator: Artur So \n" "Language-Team: Russian \n" "Language: ru\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-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Эпизоды: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Последняя публикация" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Подписаться" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Подписка на канал…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Добавить подкасты" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Поиск" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Введите URL канала или выполните поиск по выбранным платформам." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Отправить поисковый запрос" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Загрузка…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Пожалуйста, включите поисковую платформу ниже или введите URL http(s) канала." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Поисковые платформы" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Поисковые запросы будут направляться на эти платформы." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Результаты поиска" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Результаты не найдены." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "У этого шоу пока нет эпизодов" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "" "Если вы считаете, что это ошибка, пожалуйста, напишите сообщение об ошибке." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Слушайте различные шоу" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Добавляйте новые шоу через URL канала" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Импортируйте шоу с другого устройства" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Подробности эпизода" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Меню эпизода" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Заголовок подкаста" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Продолжительность - Дата" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Транслировать" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Воспроизвести" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Скачать" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Отменить" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Удалить" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Описание эпизода" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Обложка эпизода" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Перейти к шоу" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Копировать URL эпизода" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Отметить как прослушанное" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Отметить как непрослушанное" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Вы уже прослушали этот эпизод." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Расчет размера эпизода…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Воспроизвести этот эпизод" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Отменить процесс скачивания" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Скачать этот эпизод" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Эпизод без аудио" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Навигация" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Перейти на главную страницу" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Перейти на страницу шоу" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Перейти на страницу обзора" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Проигрыватель" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Переключить паузу" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Перемотать вперед" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Перемотать назад" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Общие" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Проверить наличие новых эпизодов" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Закрыть приложение" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Импортировать подписки" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Экспортировать подписки" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Сегодня" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Вчера" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "На этой неделе" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "В этом месяце" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Раньше" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Изменение скорости воспроизведения" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Перемотка назад" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Воспроизвести" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Приостановить" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Перемотка вперед" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Описание" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Перемотка назад на 10 секунд" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Перемотка вперед на 10 секунд" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Отметить _все эпизоды как прослушанные" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Сайт" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Отказаться от подписки" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Открыть сайт" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Отметить все как прослушанные" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Отказаться от подписки" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Меню подкаста" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Эпизоды" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Проверить наличие новых эпизодов" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Импортировать шоу" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Экспортировать шоу" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Комбинации клавиш" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_О приложении" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Подкасты" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Добавить новый канал" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Главное меню" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Шоу" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Слушайте свои любимые шоу" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Воспроизводите, обновляйте и управляйте своими подкастами с помощью легкого " "интерфейса, который легко интегрируется в GNOME. Подкасты могут " "воспроизводить различные аудиоформаты и запоминать, где вы остановили " "прослушивание. Вы можете подписаться на шоу через ссылки RSS/Atom, iTunes и " "Soundcloud. Подписки из других приложений могут быть импортированы через " "файлы OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "На главной странице отображаются самые новые эпизоды ваших подкастов" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "На странице шоу отображаются обложки ваших подкастов" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Виджет шоу показывает обложку и последние эпизоды определенного подкаста" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Страница, на которой можно добавить новый подкаст" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Разработчики приложения Подкасты" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Слушайте любимые подкасты прямо со своего рабочего стола." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Подкаст;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Высота последнего открытого главного окна" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Ширина последнего открытого главного окна" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Максимизированное состояние последнего открытого главного окна" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Следует ли периодически обновлять содержимое" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "" "Сколько периодов времени нужно ждать между автоматическими обновлениями" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Какой период времени должен пройти между автоматическими обновлениями" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Обновлять ли содержимое после запуска" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Сколько периодов времени должно пройти между автоматическими очистками" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Какой период времени должен пройти между автоматическими очистками" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL скопирован в буфер обмена!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Перейти к {}:{}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Перейти к {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Скачать не удалось: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Не удалось подписаться на канал: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML-файл" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Выберите файл, из которого вы хотите импортировать шоу." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Импортировать" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Не удалось разобрать импортированный файл {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Экспорт шоу в…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Экспортировать" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Подписки на подкасты GNOME" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Не удалось экспортировать подкасты" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Клиент подкастов для рабочего стола GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Alexey Rubtsov , 2022\n" "Aleksandr Melman" #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Получение каналов…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Новые" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Шоу" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Прогресс скачивания" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} мин" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Медиаплеер не смог выполнить действие." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Визуально расширяет данное описание" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Читать далее" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Отмечены все эпизоды как прослушанные" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Отменить" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Отменена подписка на {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Загрузка..." #~ msgid "Now Playing" #~ msgstr "Воспроизводится" #~ msgid "Close" #~ msgstr "Закрыть" #~ msgid "Enter Feed Address" #~ msgstr "Введите адрес канала" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Всплывающее меню (ESC для закрытия)" #~ msgid "Add" #~ msgstr "Добавить" #~ msgid "Back" #~ msgstr "Назад" #~ msgid "Show Title" #~ msgstr "Показать заголовок" #~ msgid "Jordan Petridis" #~ msgstr "Джордан Петридис" #~ msgid "Julian Hofer" #~ msgstr "Джулиан Хофер" #~ msgid "Selected file could not be accessed." #~ msgstr "Не удалось получить доступ к выбранному файлу." #~ msgid "Top position of the last open main window" #~ msgstr "Верхняя позиция последнего открытого главного окна" #~ msgid "Left position of the last open main window" #~ msgstr "Левая позиция последнего открытого главного окна" #~ msgid "Enable or disable dark theme" #~ msgstr "Включить или отключить темную тему" #~ msgid "An in-app action notification" #~ msgstr "Уведомление о действии в приложении" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Узнайте больше о подкастах GNOME" podcasts-25.2/podcasts-gtk/po/sk.po000066400000000000000000000312361500126606300172640ustar00rootroot00000000000000# Slovak translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Dušan Kazik , 2018. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2020-08-23 07:57+0000\n" "PO-Revision-Date: 2020-09-23 10:02+0200\n" "Last-Translator: Dušan Kazik \n" "Language-Team: Slovak \n" "Language: sk\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) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" "X-Generator: Poedit 2.4.1\n" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:15 msgid "Top position of the last open main window" msgstr "Horná pozícia naposledy otvoreného hlavného okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:19 msgid "Left position of the last open main window" msgstr "Pozícia vľavo naposledy otvoreného hlavného okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:23 msgid "Height of the last open main window" msgstr "Výška naposledy otvoreného hlavného okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:27 msgid "Width of the last open main window" msgstr "Šírka naposledy otvoreného hlavného okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:31 msgid "Maximized state of the last open main window" msgstr "Maximalizovaný stav naposledy otvoreného hlavného okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:36 msgid "Enable or disable dark theme" msgstr "Povoliť alebo zakázať tmavú tému" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:41 msgid "Whether to periodically refresh content" msgstr "Určuje, či sa má pravidelne obnovovať obsah" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:46 msgid "How many periods of time to wait between automatic refreshes" msgstr "Ako dlho sa má čakať medzi automatickými obnoveniami" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:50 msgid "What period of time to wait between automatic refreshes" msgstr "Aká je doba čakania medzi automatickými obnoveniami" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:54 msgid "Whether to refresh content after startup" msgstr "Určuje, či sa má obnoviť obsah po spustení" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:60 msgid "How many periods of time to wait between automatic cleanups" msgstr "Ako dlho sa má čakať medzi automatickými čisteniami" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml:64 msgid "What period of time to wait between automatic cleanups" msgstr "Aká je doba čakania medzi automatickými čisteniami" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/gtk/headerbar.ui:158 podcasts-gtk/src/app.rs:365 #: podcasts-gtk/src/widgets/aboutdialog.rs:56 podcasts-gtk/src/window.rs:66 msgid "Podcasts" msgstr "Podcasty" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Počúvajte vaše obľúbené podcasty priamo z vášho pracovného prostredia." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Podcast app for GNOME" msgstr "Aplikácia na správu podcastov pre prostredie GNOME" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:92 msgid "Jordan Petridis" msgstr "Jordan Petridis" #: podcasts-gtk/resources/gtk/empty_view.ui:46 msgid "This show does not have episodes yet" msgstr "Táto relácia zatiaľ nemá žiadne epizódy" #: podcasts-gtk/resources/gtk/empty_view.ui:62 msgid "If you think this is an error, please consider writing a bug report." msgstr "Ak si myslíte, že je to chyba, prosím, zvážte nahlásenie chyby." #: podcasts-gtk/resources/gtk/empty_view.ui:106 msgid "Get some shows" msgstr "Zaobstarajte si nejaké relácie" #: podcasts-gtk/resources/gtk/empty_view.ui:143 msgid "Add new shows via feed URL" msgstr "Pridajte nové relácie prostredníctvom URL kanálu" #: podcasts-gtk/resources/gtk/empty_view.ui:172 msgid "Import shows from another device" msgstr "Importujte relácie z iného zariadenia" #: podcasts-gtk/resources/gtk/episode_widget.ui:79 msgid "You’ve already listened to this episode." msgstr "Túto epizódu ste už počúvali." #: podcasts-gtk/resources/gtk/episode_widget.ui:217 msgid "Calculating episode size…" msgstr "Vypočítava sa veľkosť epizódy…" # tooltip #: podcasts-gtk/resources/gtk/episode_widget.ui:260 msgid "Play this episode" msgstr "Prehrá túto epizódu" # tooltip #: podcasts-gtk/resources/gtk/episode_widget.ui:281 msgid "Cancel the download process" msgstr "Zruší preberanie" # tooltip #: podcasts-gtk/resources/gtk/episode_widget.ui:304 msgid "Download this episode" msgstr "Preberie túto epizódu" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Skontrolovať nové epizódy" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Importovať relácie" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Exportovať relácie" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "_Klávesové skratky" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_O aplikácii Podcasty" # tooltip #: podcasts-gtk/resources/gtk/headerbar.ui:35 #: podcasts-gtk/resources/gtk/headerbar.ui:186 msgid "Add a new feed" msgstr "Pridá nový kanál" #: podcasts-gtk/resources/gtk/headerbar.ui:53 msgid "Enter feed address to add" msgstr "Zadajte adresu kanálu, ktorý sa má pridať" #: podcasts-gtk/resources/gtk/headerbar.ui:89 msgid "Add" msgstr "Pridať" #: podcasts-gtk/resources/gtk/headerbar.ui:171 msgid "Show Title" msgstr "Zobrazenie názvu" # tooltip #: podcasts-gtk/resources/gtk/headerbar.ui:207 msgid "Back" msgstr "Prejde späť" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgid "General" msgstr "Všeobecné" #: podcasts-gtk/resources/gtk/help-overlay.ui:18 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Kontrola nových epizód" #: podcasts-gtk/resources/gtk/help-overlay.ui:25 msgctxt "shortcut window" msgid "Quit the application" msgstr "Ukončenie aplikácie" #: podcasts-gtk/resources/gtk/home_view.ui:56 msgid "Today" msgstr "Dnes" #: podcasts-gtk/resources/gtk/home_view.ui:112 msgid "Yesterday" msgstr "Včera" #: podcasts-gtk/resources/gtk/home_view.ui:168 msgid "This Week" msgstr "Tento týždeň" #: podcasts-gtk/resources/gtk/home_view.ui:224 msgid "This Month" msgstr "Tento mesiac" #: podcasts-gtk/resources/gtk/home_view.ui:281 msgid "Older" msgstr "Staršie" #: podcasts-gtk/resources/gtk/inapp_notif.ui:101 msgid "An in-app action notification" msgstr "Oznámenie o činnosti v aplikácii" #: podcasts-gtk/resources/gtk/inapp_notif.ui:112 msgid "Undo" msgstr "Vrátiť späť" #: podcasts-gtk/resources/gtk/player_dialog.ui:14 msgid "Now Playing" msgstr "Teraz sa prehráva" # tooltip #: podcasts-gtk/resources/gtk/player_rate.ui:32 msgid "Change the playback speed" msgstr "Zmení rýchlosť prehrávania" #: podcasts-gtk/resources/gtk/player_rate.ui:47 msgid "1.00×" msgstr "1,00×" # tooltip #: podcasts-gtk/resources/gtk/player_rate.ui:88 msgid "Double speed rate" msgstr "Dvojnásobná rýchlosť" # tooltip #: podcasts-gtk/resources/gtk/player_rate.ui:97 msgid "1.75 speed rate" msgstr "1,75 násobná rýchlosť" # tooltip #: podcasts-gtk/resources/gtk/player_rate.ui:106 msgid "1.5 speed rate" msgstr "1,5 násobná rýchlosť" #: podcasts-gtk/resources/gtk/player_rate.ui:115 msgid "1.25 speed rate" msgstr "1,25 násobná rýchlosť" # tooltip #: podcasts-gtk/resources/gtk/player_rate.ui:124 msgid "Normal speed" msgstr "Normálna rýchlosť" # tooltip #: podcasts-gtk/resources/gtk/player_toolbar.ui:97 msgid "Rewind 10 seconds" msgstr "Previnie o 10 sekúnd dozadu" # tooltip #: podcasts-gtk/resources/gtk/player_toolbar.ui:112 msgid "Play" msgstr "Prehrá" # tooltip #: podcasts-gtk/resources/gtk/player_toolbar.ui:128 msgid "Pause" msgstr "Pozastaví" # tooltip #: podcasts-gtk/resources/gtk/player_toolbar.ui:144 msgid "Fast forward 10 seconds" msgstr "Previnie o 10 sekúnd dopredu" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Oz_načiť všetky epizódy ako prehrané" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Webová stránka" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "O_dhlásiť sa z odberu" #: podcasts-gtk/resources/gtk/show_menu.ui:51 msgid "Open Website" msgstr "Otvoriť webovú stránku" #: podcasts-gtk/resources/gtk/show_menu.ui:64 msgid "Mark All as Played" msgstr "Označiť všetko ako prehrané" #: podcasts-gtk/resources/gtk/show_menu.ui:89 msgid "Unsubscribe" msgstr "Odhlásiť sa z odberu" #: podcasts-gtk/resources/gtk/show_widget.ui:99 msgid "Read More" msgstr "Prečítať viac" #: podcasts-gtk/src/app.rs:302 msgid "Fetching new episodes" msgstr "Získavajú sa nové epizódy" #: podcasts-gtk/src/stacks/content.rs:54 msgid "New" msgstr "Nové" #: podcasts-gtk/src/stacks/content.rs:55 msgid "Shows" msgstr "Relácie" #: podcasts-gtk/src/utils.rs:437 msgid "Select the file from which to you want to import shows." msgstr "Vyberte súbor, z ktorého chcete importovať relácie." #: podcasts-gtk/src/utils.rs:440 msgid "_Import" msgstr "_Importovať" #: podcasts-gtk/src/utils.rs:449 podcasts-gtk/src/utils.rs:498 msgid "OPML file" msgstr "Súbor OPML" #: podcasts-gtk/src/utils.rs:467 msgid "Failed to parse the imported file" msgstr "Zlyhala analýza importovaného súboru" #: podcasts-gtk/src/utils.rs:472 podcasts-gtk/src/utils.rs:517 msgid "Selected file could not be accessed." msgstr "Nedá sa získať prístup k vybranému súboru." # file chooser dialog title #: podcasts-gtk/src/utils.rs:483 msgid "Export shows to…" msgstr "Export relácií do…" #: podcasts-gtk/src/utils.rs:486 msgid "_Export" msgstr "_Exportovať" #: podcasts-gtk/src/utils.rs:487 msgid "_Cancel" msgstr "_Zrušiť" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:491 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasty-exportovane-relacie" #: podcasts-gtk/src/utils.rs:511 msgid "GNOME Podcasts Subscriptions" msgstr "Prihlásenia k odberu aplikácie Podcasty prostredia GNOME" #: podcasts-gtk/src/utils.rs:512 msgid "Failed to export podcasts" msgstr "Zlyhalo exportovanie podcastov" #: podcasts-gtk/src/widgets/aboutdialog.rs:51 msgid "Podcast Client for the GNOME Desktop." msgstr "Klient pre podcasty prostredia GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:58 msgid "Learn more about GNOME Podcasts" msgstr "Zistite viac o aplikácii Podcasty prostredia GNOME" #: podcasts-gtk/src/widgets/aboutdialog.rs:63 msgid "translator-credits" msgstr "Dušan Kazik " #: podcasts-gtk/src/widgets/episode.rs:149 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:847 msgid "The media player was unable to execute an action." msgstr "Prehrávač médií nedokázal vykonať akciu." #: podcasts-gtk/src/widgets/show_menu.rs:172 msgid "Marked all episodes as listened" msgstr "Všetky epizódy boli označené ako vypočuté" #: podcasts-gtk/src/widgets/show_menu.rs:177 msgid "Unsubscribed from {}" msgstr "Bol odhlásený odber z kanálu {}" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "_Nastavenia" #~ msgid "You are already subscribed to that feed!" #~ msgstr "Už ste prihlásený na odber tohoto kanálu!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "Nastavenia" #~ msgid "1.50×" #~ msgstr "1,50×" #~ msgid "1.25×" #~ msgstr "1,25×" #~ msgid "Preferences" #~ msgstr "Nastavenia" #~ msgid "Appearance" #~ msgstr "Vzhľad" #~ msgid "Dark Theme" #~ msgstr "Tmavá téma" #~ msgid "Delete played episodes" #~ msgstr "Odstránenie prehraných epizód" #~ msgid "After" #~ msgstr "Po" #~ msgid "Invalid URL" #~ msgstr "Neplatná URL" #~ msgid "Seconds" #~ msgstr "Sekundách" #~ msgid "Minutes" #~ msgstr "Minútach" #~ msgid "Hours" #~ msgstr "Hodinách" #~ msgid "Days" #~ msgstr "Dňoch" #~ msgid "Weeks" #~ msgstr "Týždňoch" podcasts-25.2/podcasts-gtk/po/sl.po000066400000000000000000000456411500126606300172720ustar00rootroot00000000000000# Slovenian translation for podcasts. # Copyright (C) 2020 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # # Matej Urbančič , 2020–2022. # Martin Srebotnjak , 2024-2025. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-27 16:07+0100\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian GNOME Translation Team \n" "Language: sl_SI\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 ? 1 : n%100==2 ? 2 : n%100==3 || n" "%100==4 ? 3 : 0);\n" "X-Poedit-SourceCharset: utf-8\n" "X-Generator: Poedit 2.2.1\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Epizode: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Zadnja objava" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Naroči se" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Naročanje na vir ..." #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Dodaj podkaste" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Išči" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Vnesite URL vira ali poiščite na izbranih platformah." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Pošlji iskanje" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Poteka nalaganje …" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Spodaj omogočite iskalno platformo ali vnesite URL vira http(s)." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Iskalne platforme" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Iskalne poizvedbe bodo poslane tem platformam." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Rezultati iskanja" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Ni najdenih zadetkov." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Ta podkast še nima določenih epizod" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Če ste mnenja, da je to napaka, razmislite o poročilu o napaki." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Pridobi oddaje" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Dodaj nove posnetke kot naslov URL vira" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Uvozi posnetke z druge naprave" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Podrobnosti epizode" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Meni Epizoda" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Naslov podkasta" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Trajanje – Datum" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "Preta_kaj" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Predvajaj" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "Prej_mi" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Prekliči" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Izbriši" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Opis epizode" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Naslovnica epizode" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Skoči na posnetek" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Kopiraj naslov URL epizode" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Označi kot predvajano" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Označi kot nepredvajano" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "To epizodo ste že poslušali." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Poteka preračunavanje velikosti izvoza …" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Predvajaj epizodo" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Prekliči prejem v teku" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Prejmi to epizodo" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Epizoda brez zvoka" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Krmarjenje" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Na domačo stran" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Pojdi na stran Oddaje" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Pojdi na stran odkrivanja" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Predvajalnik" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Preklop prekinitve" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Iskanje naprej" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Iščite nazaj" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Splošno" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Preveri za nove epizode" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Konča izvajanje programa" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Uvoz naročnin" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Izvoz naročnin" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Danes" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Včeraj" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Ta teden" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Ta mesec" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Starejše" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Spremeni hitrost predvajanja" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90 ×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75 ×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Previj nazaj" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Predvajaj" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Premor" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Previj naprej" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Opis" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Skoči nazaj za 10 sekund" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Skoči naprej za 10 sekund" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Označi vse kot predvajane" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Spletišče" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Prekliči naročnino" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Odpri spletišče" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Označi vse kot predvajane" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Prekliči naročnino" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Meni Podkast" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Epizode" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Preveri za nove epizode" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Uvozi posnetke" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Izvozi posnetke" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Tipkovne bližnjice" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_O programu" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podkasti" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Dodaj nov vir" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Glavni meni" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Pokaži" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Spremljanje priljubljenih oddaj" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Predvajanje, posodabljanje in upravljanje zbirke podkastov v preglednem " "vmesniku, skladnem z namizjem GNOME. Program omogoča predvananje različnih " "zvočnih zapisov in podpira možnost pomnjenja zadnjega poslušanega mesta. Na " "oddaje se je mogoče naročiti po povezavah RSS/Atom, iTunes in Soundcloud. " "Naročnine iz drugih prigramov je mogoče uvoziti z uporabo izvoza OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Domači pogled z najnovejšimi epizodami podkastov" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Pogled oddaj, ki prikazuje naslovnice vaših podkastov" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Pripomoček za oddajo, ki prikazuje naslovnico in najnovejše epizode " "določenega podkasta" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Pogled, kamor lahko dodate nov podkast" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Razvijalci Podkasta" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Spremljanje priljubljenih podkastov neposredno z namizja" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;podkast;viri;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Višina nazadnje odprtega glavnega okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Širina nazadnje odprtega glavnega okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Stanje nazadnje razpetega odprtega glavne okna" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Ali naj se vsebina periodično osvežuje" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Koliko ponovitev časa naj preteče med samodejnimi osveževanji" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Koliko časa naj preteče med izvajanjem samodejnega osveževanja" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Ali naj se vsebina ob zagonu samodejno osveži" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Koliko ponovitev časa naj preteče med samodejnim počiščenjem" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Koliko časa naj preteče med izvajanjem samodejnega čiščenja" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "Naslov URL je kopiran v odložišče!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Skok na {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Skok na {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Prejem ni uspel: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Naročanje na vir ni uspelo: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "Datoteka OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Izberite datoteko, iz katere želite uvoziti posnetke." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Uvozi" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Razčlenjevanje uvožene datoteke {} je spodletelo" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Izvozi posnetke v …" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Izvozi" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-izvoz-posnetkov" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Naročila Podkast GNOME" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Izvoz podkastov je spodletel" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Odjemalec posnetkov podkast za namizje GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Matej Urbančič \n" "Martin Srebotnjak " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Pridobivanje virov ..." #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Novo" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Posnetki" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Napredek prenosa" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "S predvajalnikom ni mogoče izvesti zahtevanega dejanja." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Vizualno razširi ta opis" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Preberi več" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Vse epizode so označene kot poslušane" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Razveljavi" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Odjavi {}" podcasts-25.2/podcasts-gtk/po/sr.po000066400000000000000000000363631500126606300173010ustar00rootroot00000000000000# Serbian translation for podcasts. # Copyright © 2021 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Мирослав Николић , 2021. msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2021-11-28 07:07+0000\n" "PO-Revision-Date: 2021-12-29 19:26+0200\n" "Last-Translator: Мирослав Николић \n" "Language-Team: Serbian <(nothing)>\n" "Language: sr\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? 3 : n%10==1 && n%100!=11 ? 0 : n" "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Top position of the last open main window" msgstr "Горњи положај последњег отвореног главног прозора" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Left position of the last open main window" msgstr "Леви положај последњег отвореног главног прозора" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Height of the last open main window" msgstr "Висина последњег отвореног главног прозора" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:27 msgid "Width of the last open main window" msgstr "Ширина последњег отвореног главног прозора" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:31 msgid "Maximized state of the last open main window" msgstr "Увећано стање последњег отвореног главног прозора" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:36 msgid "Enable or disable dark theme" msgstr "Укључује или искључује тамну тему" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to periodically refresh content" msgstr "Да ли повремено да освежи садржај" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:46 msgid "How many periods of time to wait between automatic refreshes" msgstr "Колико времена да чека између самосталних освежавања" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:50 msgid "What period of time to wait between automatic refreshes" msgstr "Које време да чека између самосталних освежавања" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:54 msgid "Whether to refresh content after startup" msgstr "Да ли да освежи садржај након покретања" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:60 msgid "How many periods of time to wait between automatic cleanups" msgstr "Колико времена да чека између самосталних чишћења" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:64 msgid "What period of time to wait between automatic cleanups" msgstr "Које време да чека између самосталних чишћења" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/gtk/headerbar.ui:137 podcasts-gtk/src/app.rs:438 #: podcasts-gtk/src/widgets/aboutdialog.rs:63 podcasts-gtk/src/window.rs:66 msgid "Podcasts" msgstr "Подемисије" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Слушајте ваше омиљене подемисије, управо са ваше радне површи." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Подемисија;РСС;Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 #| msgid "Listen to your favorite podcasts, right from your desktop." msgid "Listen to your favorite shows" msgstr "Слушајте ваше омиљене емисије" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Пуштајте, освежите и управљајте вашим подемисијама из лаганог сучеља које се " "неприметно обједињује са Гномом. Подемисија може да пушта разне формате " "звука и памти где сте стали са слушањем. Можете да се претплатите на емисије " "путем „RSS/Atom“-а, „iTunes“-а, и „Soundcloud“ веза. Претплате са других " "програма се могу увести путем „OPML“ датотека." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:117 msgid "Jordan Petridis" msgstr "Јордан Петридис" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:118 msgid "Julian Hofer" msgstr "Јулиан Хофер" #: podcasts-gtk/resources/gtk/empty_view.ui:46 msgid "This show does not have episodes yet" msgstr "Ова емисија још нема епизода" #: podcasts-gtk/resources/gtk/empty_view.ui:57 msgid "If you think this is an error, please consider writing a bug report." msgstr "Ако мислите да је ово грешка, размотрите писање извештаја о грешци." #: podcasts-gtk/resources/gtk/empty_view.ui:91 msgid "Get some shows" msgstr "Набавите неке емисије" #: podcasts-gtk/resources/gtk/empty_view.ui:123 msgid "Add new shows via feed URL" msgstr "Додајте нове емисије путем адресе тока" #: podcasts-gtk/resources/gtk/empty_view.ui:152 msgid "Import shows from another device" msgstr "Увезитеемисије са другог уређаја" #: podcasts-gtk/resources/gtk/episode_description.ui:49 msgid "Episode Details" msgstr "Подаци о епизоди" #: podcasts-gtk/resources/gtk/episode_description.ui:64 #: podcasts-gtk/resources/gtk/headerbar.ui:186 msgid "Back" msgstr "Назад" #: podcasts-gtk/resources/gtk/episode_description.ui:148 msgid "Podcast Title" msgstr "Наслов подемисије" #: podcasts-gtk/resources/gtk/episode_description.ui:186 msgid "Duration - Date" msgstr "Трајање – датум" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Episode Description" msgstr "Опис епизоде" #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Иди на емисију" #: podcasts-gtk/resources/gtk/episode_menu.ui:40 msgid "Copy Episode Url" msgstr "Умножи адресу епизоде" #: podcasts-gtk/resources/gtk/episode_widget.ui:82 msgid "You’ve already listened to this episode." msgstr "Већ сте одслушали ову епизоду." #: podcasts-gtk/resources/gtk/episode_widget.ui:187 msgid "Calculating episode size…" msgstr "Израчунавам величину епизоде…" #: podcasts-gtk/resources/gtk/episode_widget.ui:214 msgid "Play this episode" msgstr "Пустите ову епизоду" #: podcasts-gtk/resources/gtk/episode_widget.ui:230 msgid "Cancel the download process" msgstr "Откажите процес преузимања" #: podcasts-gtk/resources/gtk/episode_widget.ui:250 msgid "Download this episode" msgstr "Преузмите ову епизоду" #: podcasts-gtk/resources/gtk/hamburger.ui:7 msgid "_Check for New Episodes" msgstr "_Потражи нове епизоде" #: podcasts-gtk/resources/gtk/hamburger.ui:12 msgid "_Import Shows" msgstr "_Увези емисије" #: podcasts-gtk/resources/gtk/hamburger.ui:16 msgid "_Export Shows" msgstr "_Извези емисије" #: podcasts-gtk/resources/gtk/hamburger.ui:22 msgid "_Keyboard Shortcuts" msgstr "Пречице _тастатуре" #: podcasts-gtk/resources/gtk/hamburger.ui:30 msgid "_About Podcasts" msgstr "_О Подемисијама" #: podcasts-gtk/resources/gtk/headerbar.ui:35 #: podcasts-gtk/resources/gtk/headerbar.ui:165 msgid "Add a new feed" msgstr "Додајте нови ток" #: podcasts-gtk/resources/gtk/headerbar.ui:53 msgid "Enter feed address to add" msgstr "Унесите адресу тока за додавање" #: podcasts-gtk/resources/gtk/headerbar.ui:79 msgid "Add" msgstr "Додај" #: podcasts-gtk/resources/gtk/headerbar.ui:150 msgid "Show Title" msgstr "Прикажи наслов" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgid "General" msgstr "Опште" #: podcasts-gtk/resources/gtk/help-overlay.ui:18 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Потражите нове епизоде" #: podcasts-gtk/resources/gtk/help-overlay.ui:25 msgctxt "shortcut window" msgid "Quit the application" msgstr "Изађите из програма" #: podcasts-gtk/resources/gtk/home_view.ui:56 msgid "Today" msgstr "Данас" #: podcasts-gtk/resources/gtk/home_view.ui:89 msgid "Yesterday" msgstr "Јуче" #: podcasts-gtk/resources/gtk/home_view.ui:122 msgid "This Week" msgstr "Ове седмице" #: podcasts-gtk/resources/gtk/home_view.ui:155 msgid "This Month" msgstr "Овог месеца" #: podcasts-gtk/resources/gtk/home_view.ui:189 msgid "Older" msgstr "Старије" #: podcasts-gtk/resources/gtk/inapp_notif.ui:69 msgid "An in-app action notification" msgstr "Обавештење радње у програму" #: podcasts-gtk/resources/gtk/inapp_notif.ui:75 msgid "Undo" msgstr "Поништи" #: podcasts-gtk/resources/gtk/player_dialog.ui:14 msgid "Now Playing" msgstr "Тренутно пуштам" #: podcasts-gtk/resources/gtk/player_rate.ui:32 msgid "Change the playback speed" msgstr "Промените брзину пуштања" #: podcasts-gtk/resources/gtk/player_rate.ui:46 #: podcasts-gtk/resources/gtk/player_rate.ui:85 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:65 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:70 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:75 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:80 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:90 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:95 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_toolbar.ui:97 msgid "Rewind 10 seconds" msgstr "Уназад 10 секунди" #: podcasts-gtk/resources/gtk/player_toolbar.ui:107 msgid "Play" msgstr "Пусти" #: podcasts-gtk/resources/gtk/player_toolbar.ui:118 msgid "Pause" msgstr "Застани" #: podcasts-gtk/resources/gtk/player_toolbar.ui:129 msgid "Fast forward 10 seconds" msgstr "Унапред 10 секунди" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Означи све епизоде да су пуштене" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Веб страница" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Одјави се" #: podcasts-gtk/resources/gtk/show_menu.ui:36 msgid "Open Website" msgstr "Отвори Веб сајт" #: podcasts-gtk/resources/gtk/show_menu.ui:40 msgid "Mark All as Played" msgstr "Означите све да су пуштене" #: podcasts-gtk/resources/gtk/show_menu.ui:46 msgid "Unsubscribe" msgstr "Одјавите се" #: podcasts-gtk/resources/gtk/show_widget.ui:98 msgid "Read More" msgstr "Прочитајте више" #: podcasts-gtk/src/app.rs:353 msgid "Fetching new episodes" msgstr "Довлачим нове епизоде" #: podcasts-gtk/src/stacks/content.rs:58 msgid "New" msgstr "Нова" #: podcasts-gtk/src/stacks/content.rs:59 msgid "Shows" msgstr "Емисије" #: podcasts-gtk/src/utils.rs:501 msgid "Select the file from which to you want to import shows." msgstr "Изаберите датотеку из које желите да увезете емисије." #: podcasts-gtk/src/utils.rs:504 msgid "_Import" msgstr "_Увези" #: podcasts-gtk/src/utils.rs:513 podcasts-gtk/src/utils.rs:562 msgid "OPML file" msgstr "ОПМЛ датотека" #: podcasts-gtk/src/utils.rs:531 msgid "Failed to parse the imported file" msgstr "Нисам успео да обрадим увезену датотеку" #: podcasts-gtk/src/utils.rs:536 podcasts-gtk/src/utils.rs:581 msgid "Selected file could not be accessed." msgstr "Не могу да приступим изабраној датотеци." #: podcasts-gtk/src/utils.rs:547 msgid "Export shows to…" msgstr "Извези емисије у…" #: podcasts-gtk/src/utils.rs:550 msgid "_Export" msgstr "_Извези" #: podcasts-gtk/src/utils.rs:551 msgid "_Cancel" msgstr "_Откажи" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:555 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:575 msgid "GNOME Podcasts Subscriptions" msgstr "Пријављивање на Гном Подемисије" #: podcasts-gtk/src/utils.rs:576 msgid "Failed to export podcasts" msgstr "Нисам успео да увезем обележиваче" #: podcasts-gtk/src/widgets/aboutdialog.rs:58 msgid "Podcast Client for the GNOME Desktop." msgstr "Клијент подемисије за Гномову радну површ." #: podcasts-gtk/src/widgets/aboutdialog.rs:65 msgid "Learn more about GNOME Podcasts" msgstr "Сазнајте више о Гном Подемисијама" #: podcasts-gtk/src/widgets/aboutdialog.rs:69 msgid "translator-credits" msgstr "" "Мирослав Николић \n" "\n" "http://prevod.org — превод на српски језик" #: podcasts-gtk/src/widgets/episode.rs:145 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/episode_description.rs:165 msgid "Copied URL to clipboard!" msgstr "Адреса је умножена у бележницу!" #: podcasts-gtk/src/widgets/player.rs:902 msgid "The media player was unable to execute an action." msgstr "Програм за пуштање није могао да изврши радњу." #: podcasts-gtk/src/widgets/show_menu.rs:194 msgid "Marked all episodes as listened" msgstr "Све епизоде су означене да су слушане" #: podcasts-gtk/src/widgets/show_menu.rs:199 msgid "Unsubscribed from {}" msgstr "Отказали сте претплату са „{}“" #~ msgid "Podcast app for GNOME" #~ msgstr "Програм подемисије за Гном" podcasts-25.2/podcasts-gtk/po/sv.po000066400000000000000000000503221500126606300172740ustar00rootroot00000000000000# Swedish translation for podcasts. # Copyright © 2018-2025 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Anders Jonsson , 2018, 2019, 2020, 2021, 2023, 2024, 2025. # Luna Jernberg , 2021. # msgid "" msgstr "" "Project-Id-Version: podcasts main\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-27 12:32+0100\n" "Last-Translator: Anders Jonsson \n" "Language-Team: Swedish \n" "Language: sv\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-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Avsnitt: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Senaste publicering" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Prenumerera" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Prenumererar på flöde…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Lägg till poddsändningar" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Sök" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Ange en flödes-URL eller sök de valda plattformarna." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Skicka sökning" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Läser in…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Aktivera en sökplattform nedan, eller ange en http(s)-flödes-URL." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Sökplattformar" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Sökfrågor kommer skickas till dessa plattformar." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Sökresultat" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Inga resultat hittades." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Denna show har inga avsnitt ännu" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Överväg att skriva en felrapport om du tror att detta är ett fel." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Få tag på några shower" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Lägg till nya shower via flödes-URL" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Importera shower från en annan enhet" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Avsnittsdetaljer" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Avsnittsmeny" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Titel på poddsändning" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Längd - Datum" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Strömma" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "S_pela" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Hämta" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Avbryt" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Ta bort" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Avsnittsbeskrivning" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Avsnittsomslag" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Gå till show" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Kopiera avsnitts-URL" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Markera som spelat" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Markera som ospelat" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Du har redan lyssnat på detta avsnitt." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Beräknar avsnittsstorlek…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Spela detta avsnitt" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Avbryt hämtningsprocessen" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Hämta detta avsnitt" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Avsnitt utan ljud" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Navigering" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Gå till startsida" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Gå till showsida" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Gå till upptäcktssida" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Spelare" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Pausa/fortsätt" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Spola framåt" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Spola bakåt" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Allmänt" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Sök efter nya avsnitt" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Avsluta programmet" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Importera prenumerationer" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Exportera prenumerationer" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "I dag" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "I går" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Denna vecka" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Denna månad" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Äldre" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Ändra uppspelningshastigheten" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2,00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1,75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1,50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1,25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0,90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0,75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Spola tillbaka" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Spela" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Pausa" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Spola framåt" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Beskrivning" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "Spola tillbaka 10 sekunder" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "Spola framåt 10 sekunder" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "_Markera alla avsnitt som spelade" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Webbplats" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "Säg _upp prenumeration" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Öppna webbplats" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Markera alla som spelade" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Säg upp prenumeration" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Poddsändningsmeny" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Avsnitt" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "Sö_k efter nya avsnitt" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Importera shower" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Exportera shower" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Tangentbordsgenvägar" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Om Poddsändningar" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Poddsändningar" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Lägg till ett nytt flöde" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Huvudmeny" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Show" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Lyssna på dina favoritshower" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "Spela, uppdatera och hantera dina poddsändningar från ett " "lättviktsgränssnitt som sömlöst integrerar med GNOME. Poddsändningar kan " "spela upp diverse olika ljudformat och komma ihåg var du slutade lyssna. Du " "kan prenumerera på shower via RSS/Atom, iTunes och Soundcloud-länkar. " "Prenumerationer från andra program kan importeras via OPML-filer." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Hemvyn visande de nyaste avsnitten av dina poddsändningar" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Showvyn visande omslagen för dina poddsändningar" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "" "Showkomponenten visande omslaget och de senaste avsnitten av en specifik " "poddsändning" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Vyn där man kan lägga till en ny poddsändning" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Poddsändningar-utvecklarna" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Lyssna på dina favoritpoddsändningar, direkt från ditt skrivbord." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Poddsändning;Podcast;RSS;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Höjden för det senast öppna huvudfönstret" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Bredden för det senast öppna huvudfönstret" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Maximeringstillstånd för det senast öppna huvudfönstret" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Huruvida innehåll ska uppdateras periodiskt" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Hur många tidsperioder att vänta mellan automatiska uppdateringar" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Vilken sorts tidsperiod att vänta mellan automatiska uppdateringar" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Huruvida innehåll ska uppdateras efter uppstart" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Hur många tidsperioder att vänta mellan automatiska upprensningar" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Vilken sorts tidsperiod att vänta mellan automatiska upprensningar" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "Kopierade URL till urklipp!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Hoppa till {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Hoppa till {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Hämtning misslyckades: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Misslyckades med att prenumerera på flöde: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML-fil" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Välj filen från vilken du vill importera shower." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Importera" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Misslyckades med att tolka den importerade filen {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Exportera shower till…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Exportera" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-poddsändningar-exporterade-shower" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Poddsändningar-prenumerationer" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Misslyckades med att exportera poddsändningar" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Poddsändningsklient för GNOME-skrivbordet." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Anders Jonsson \n" "Luna Jernberg \n" "\n" "Skicka synpunkter på översättningen till\n" "." #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Hämtar flöden…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Nytt" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Shower" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Hämtningsförlopp" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} min" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Mediaspelaren kunde inte utföra en åtgärd." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Expanderar denna beskrivning visuellt" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Läs mer" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Markerade alla avsnitt som lyssnade" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Ångra" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Sade upp prenumeration från {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Läser in…" #~ msgid "Now Playing" #~ msgstr "Spelar nu" #~ msgid "Close" #~ msgstr "Stäng" #~ msgid "Enter Feed Address" #~ msgstr "Ange flödesadress" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Kontextfönstermeny (Esc för att stänga)" #~ msgid "Add" #~ msgstr "Lägg till" #~ msgid "Back" #~ msgstr "Bakåt" #~ msgid "Show Title" #~ msgstr "Visa titel" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Selected file could not be accessed." #~ msgstr "Den markerade filen kunde inte kommas åt." #~ msgid "Top position of the last open main window" #~ msgstr "Topposition för det senast öppna huvudfönstret" #~ msgid "Left position of the last open main window" #~ msgstr "Vänsterposition för det senast öppna huvudfönstret" #~ msgid "Enable or disable dark theme" #~ msgstr "Aktivera eller inaktivera mörkt tema" #~ msgid "An in-app action notification" #~ msgstr "En åtgärdsavisering i programmet" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Lär dig mer om GNOME Poddsändningar" podcasts-25.2/podcasts-gtk/po/tr.po000066400000000000000000000460571500126606300173030ustar00rootroot00000000000000# Turkish translation for podcasts. # Copyright (C) 2018-2024 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # # Sabri Ünal , 2019, 2023, 2024, 2025. # Emin Tufan Çetin , 2018-2025. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-30 08:00+0300\n" "Last-Translator: Emin Tufan Çetin \n" "Language-Team: Turkish \n" "Language: tr\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-Generator: Poedit 3.5\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Bölümler: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Son yayın" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Abone Ol" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Beslemeye abone ol…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Podcast Ekle" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Ara" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "Besleme URLʼsi girin ya da seçilen platformlarda arama yapın." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Aramayı gönder" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Yükleniyor…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "" "Lütfen bir Arama Platformunu etkinleştirin ya da http(s) besleme URLʼsi " "girin." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Arama Platformları" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Arama sorguları bu platformlara gönderilecek." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Arama sonuçları" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Sonuç bulunamadı." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Bu gösterinin henüz herhangi bir bölümü yok" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Bunun bir hata olduğunu düşünüyorsanız lütfen hata bildirimi yazın." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Birkaç Gösteri Edinin" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "Besleme URLʼsi ile yeni gösteriler ekle" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Gösterileri başka aygıttan içe aktar" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Bölüm Ayrıntıları" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Bölüm Menüsü" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Podcast Başlığı" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Süre - Tarih" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Akış" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Oynat" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "İ_ndir" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "İptal" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Sil" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Bölüm Açıklaması" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Bölüm Kapağı" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Gösteriye Git" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "Bölüm Adresini Kopyala" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Oynatıldı Olarak İmle" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Oynatılmadı Olarak İmle" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Bu bölümü zaten dinlediniz." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Bölüm boyutu hesaplanıyor…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Bu bölümü oynat" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "İndirme sürecini iptal et" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Bu bölümü indir" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Sesi olmayan bölüm" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Gezinti" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Ana Sayfaya Git" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Gösteriler Sayfasına Git" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Keşif Sayfasına Git" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Oynatıcı" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Durdur/Başlat" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "İleri Git" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Geri Git" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Genel" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Yeni bölümleri denetle" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Uygulamadan çık" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Abonelikleri İçe Aktar" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Abonelikleri Dışa Aktar" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Bugün" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Dün" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Bu Hafta" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Bu Ay" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Daha Eski" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Kayıttan yürütme hızını değiştir" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Geri Sar" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Oynat" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Duraklat" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "İleri" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Açıklama" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "10 saniye geri sar" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "10 saniye ileri sar" # İmleme ile ile bildirim arasında kelime uyuşmazlığı vardı. Geçici olarak dinlendi çevirisi kullanıldı. Hata da raporlanacak. #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "Tüm Bölümleri Dinlendi Olarak _İmle" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Web Sitesi" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Abonelikten Çık" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Web Sitesini Aç" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Tümünü Oynatıldı Olarak İmle" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Abonelikten Çık" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Podcast Menüsü" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Bölümler" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "Yeni Bölümleri _Denetle" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Gösterileri İçe Aktar" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Gösterileri Dışa Aktar" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Klavye Kısayolları" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "Podcastler _Hakkında" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Podcastler" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Yeni Besleme Ekle" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Ana Menü" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Gösteri" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Gözde gösterilerinizi dinleyin" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "GNOME ile pürüzsüzce tümleşen hafif arayüzde podcastlerinizi oynatın, " "güncelleyin ve yönetin. Podcastler, türlü ses biçimlerini oynatır ve " "dinlemeyi nerede durdurduğunuzu anımsar. RSS/Atom, iTunes ve Soundcloud " "bağlantıları aracılığıyla gösterilere abone olabilirsiniz. Diğer " "uygulamalardaki abonelikler OPML dosyaları aracılığıyla içe aktarılabilir." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Podcastlerin en yeni bölümlerini görüntüleyen ana görünüm" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Podcast kapaklarını gösteren gösteriler görünümü" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "Podcastin kapağını ve son bölümlerini görüntüleyen gösteri parçacığı" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Yeni podcast eklenebilecek görünüm" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Podcasts Geliştiricileri" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Gözde podcastlerinizi masaüstünüzden dinleyin." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;Pod Yayını;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Son açılan ana pencerenin yüksekliği" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Son açılan ana pencerenin genişliği" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Son açılan ana pencerenin büyütülme durumu" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Belirli aralıklarla içeriğin yenilenmesi" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Kendiliğinden yenilemeler arasında kaç zaman aralığı beklenecek" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "Kendiliğinden yenilemeler arasında beklenecek zaman aralığı" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Başlangıçtan sonra içeriğin yenilenmesi" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Kendiliğinden temizlemeler arasında kaç zaman aralığı beklenecek" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "Kendiliğinden temizlemeler arasında beklenecek zaman aralığı" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL panoya kopyalandı!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Zıpla {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Zıpla {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "İndirilemedi: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Beslemeye abone olunamadı: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML dosyası" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Gösterileri içe aktarmak istediğiniz dosyayı seçin." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_İçe Aktar" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "İçe aktarılan dosya ayrıştırılamadı: {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Gösterileri dışa aktar…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Dışa Aktar" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME Podcastler Abonelikleri" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Podcastler dışa aktarılamadı" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "GNOME Masaüstü için Podcast İstemcisi." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Emin Tufan Çetin \n" "Sabri Ünal " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Beslemeler alınıyor…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Yeni" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Gösteriler" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "İndirme süreci" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} dk" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Ortam oynatıcı eylemi yürütemiyor." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Bu açıklamayı görsel olarak genişletir" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Daha Çoğunu Oku" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Tüm bölümler dinlendi olarak imlendi" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Geri al" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "{} aboneliğinden çıkıldı" podcasts-25.2/podcasts-gtk/po/uk.po000066400000000000000000000604321500126606300172660ustar00rootroot00000000000000# Ukrainian translation for podcasts. # Copyright (C) 2020 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # # Yuri Chornoivan , 2020, 2021, 2023, 2024, 2025. msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-01-28 22:21+0200\n" "Last-Translator: Yuri Chornoivan \n" "Language-Team: Ukrainian \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "[!]Project-Id-Version: podcasts master\n" "Plural-Forms: nplurals=4; plural=n==1 ? 3 : n%10==1 && n%100!=11 ? 0 : n" "%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Lokalize 23.04.3\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "Випуски: " #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "Дата найновішого випуску" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "_Підписатись" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "Триває зчитування стрічки подкасту…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "Додати подкасти" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "Пошук" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "" "Введіть адресу стрічки подкасту або його назву, щоб шукати на вибраних нижче " "платформах." #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "Виконати пошук" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "Завантаження…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "Виберіть нижче платформи для пошуку або введіть URL-адресу стрічки." #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "Платформи для пошуку" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "Ваші пошукові запити буде спрямовано до цих платформ." #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "Результати пошуку" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "Нічого не знайдено." #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "Випусків цього подкасту ще немає." #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "Вважаєте, що це помилка? Повідомте про неї розробників." #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "Як додати подкасти?" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "За URL-адресою стрічки" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "Імпортувати з іншого пристрою" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "Докладно про випуск" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "Меню випуску" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "Заголовок подкасту" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "Тривалість — Дата" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "_Потік даних" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "_Відтворити" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "_Отримати" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "Скасувати" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "Видалити" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "Опис випуску" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "Обкладинка випуску" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "Відкрити подкаст" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 #| msgid "Copy Episode Url" msgid "Copy Episode URL" msgstr "Копіювати адресу випуску" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "Позначити як прослуханий" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "Позначити як непрослуханий" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "Ви вже прослухали цей випуск." #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "Визначається розмір випуску…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "Слухати цей випуск" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "Скасувати завантаження" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "Завантажити цей випуск" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "Випуск без звуку" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "Перехід" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "Сторінка «Випуски»" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "Сторінка «Подкасти»" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "Сторінка «Додати подкаст»" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "Програвач" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "Слухати/Зупинити" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "Промотати вперед" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "Відмотати назад" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "Загальні" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "Отримати список нових випусків" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "Вийти із застосунку" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "Імпортувати список подкастів" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "Експортувати список подкастів" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "Сьогоднішні" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "Вчорашні" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "Цього тижня" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "Цього місяця" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "Давніші" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "Змінити швидкість відтворення" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "100%" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "200%" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "175%" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "150%" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "125%" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "90%" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "75%" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "Відмотати" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "Слухати" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "Зупинити" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "Перемотати" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "Опис" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "На 10 секунд назад" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "На 10 секунд уперед" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "П_означити всі випуски як прослухані" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "_Сайт" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "_Вилучити" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "Перейти на сайт" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "Позначити всі випуски як прослухані" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "Вилучити" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "Меню подкасту" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "Випуски" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "_Отримати список нових випусків" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "_Імпортувати список подкастів" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "_Експортувати список подкастів" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "_Клавіатурні скорочення" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "_Про «Подкасти»" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "Подкасти" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "Додати подкаст за адресою стрічки" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "Головне меню" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "Подкаст" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "Слухайте улюблені подкасти" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "«Подкасти» — простий і зрозумілий застосунок для стільниці GNOME, у якому " "можна слухати подкасти. У ньому можна додавати, видаляти й оновлювати " "стрічки подкастів, і, звісно, слухати наявні випуски. На додачу застосунок " "підтримує кілька форматів аудіо й запам'ятовує місце, на якому ви закінчили " "слухати випуск. Можна додавати подкастові стрічки RSS/Atom, а також подкасти " "з iTunes і за посиланнями Soundcloud. Також можна імпортувати подкасти з " "інших застосунків за допомогою файлів OPML." #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "Головна сторінка з переліком найновіших випусків доданих подкастів" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "Сторінка «Подкасти» з обкладинками доданих подкастів" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "Віджет подкасту з його обкладинкою та найновішими випусками" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "Сторінка пошуку нових подкастів" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "Розробники «Подкастів»" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "Застосунок для стільниці GNOME, в якому можна слухати подкасти." #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;подкаст;рсс;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "Висота головного вікна минулого разу" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "Ширина головного вікна минулого разу" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "Чи було розгорнутим на весь екран головне вікно минулого разу" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "Чи слід періодично оновлювати вміст вікна" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "Тривалість інтервалу автоматичного оновлення (в одиницях)" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "" "Одиниці, в яких вимірюється інтервал автоматичного оновлення (години, " "хвилини тощо)" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "Чи оновлювали вміст вікна після запуску" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "Тривалість інтервалу автоматичного очищення (в одиницях)" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "" "Одиниці, в яких вимірюється інтервал автоматичного очищення (години, хвилини " "тощо)" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "URL-адресу скопійовано до буфера обміну!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "Перейти до {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "Перейти до {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "Не вдалося завантажити: {}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "Не вдалося додати стрічку: {}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "файл OPML" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "Виберіть файл зі списком подкастів, які треба імпортувати." #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "_Імпортувати" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "Не вдалося обробити файл {} під час імпортування" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "Експортувати подкасти…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "_Експортувати" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "Список подкастів із застосунку «Подкасти»" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "Не вдалося експортувати подкасти" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "Програма для прослуховування подкастів у стільничному оточенні GNOME." #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Юрій Чорноіван \n" "Андрій Сербовець " #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "Отримання стрічок…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "Нові випуски" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "Подкасти" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "Поступ завантаження" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} хв." #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "Програвачеві не вдалося виконати цю дію." #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "Візуально розгортає цей опис" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "Докладніше" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "Усі випуски позначено як прослухані." #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "Повернути" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "Подкаст «{}» вилучено." #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "Завантаження…" #~ msgid "Now Playing" #~ msgstr "Зараз слухаєте" #~ msgid "Close" #~ msgstr "Закрити" #~ msgid "Enter Feed Address" #~ msgstr "Введіть адресу стрічки" #~ msgid "Popover menu (ESC to close)" #~ msgstr "Накладне меню (ESC, щоб закрити)" #~ msgid "Add" #~ msgstr "Додати" #~ msgid "Back" #~ msgstr "Назад" #~ msgid "Show Title" #~ msgstr "Показати заголовок" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "Selected file could not be accessed." #~ msgstr "Не вдалося отримати доступ до вибраного файлу" #~ msgid "Top position of the last open main window" #~ msgstr "" #~ "Розташування останнього відкритого головного вікна за вертикаллю (згори)" #~ msgid "Left position of the last open main window" #~ msgstr "" #~ "Розташування останнього відкритого головного вікна за горизонталлю " #~ "(ліворуч)" #~ msgid "Enable or disable dark theme" #~ msgstr "Увімкнений чи вимкнений темний стиль" #~ msgid "An in-app action notification" #~ msgstr "Внутрішнє сповіщення про дію" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "Докладніше про «Подкасти» для GNOME" #~ msgid "Podcast app for GNOME" #~ msgstr "Слухайте улюблені подкасти у GNOME" #~ msgid "Double speed rate" #~ msgstr "Подвоєна швидкість відтворення" #~ msgid "1.75 speed rate" #~ msgstr "Швидкість відтворення 1,75" #~ msgid "1.5 speed rate" #~ msgstr "Швидкість відтворення 1,5" #~ msgid "1.25 speed rate" #~ msgstr "Швидкість відтворення 1,25" #~ msgid "Normal speed" #~ msgstr "Звичайна швидкість" podcasts-25.2/podcasts-gtk/po/zh_CN.po000066400000000000000000000506431500126606300176530ustar00rootroot00000000000000# Chinese (China) translation for podcasts. # Copyright (C) 2018 podcasts's COPYRIGHT HOLDER # This file is distributed under the same license as the podcasts package. # Tranquilo Chan , 2020. # Dingzhong Chen , 2018-2021. # lumingzh , 2022-2025. # msgid "" msgstr "" "Project-Id-Version: podcasts master\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/podcasts/issues\n" "POT-Creation-Date: 2025-01-27 10:12+0000\n" "PO-Revision-Date: 2025-02-04 09:55+0800\n" "Last-Translator: lumingzh \n" "Language-Team: Chinese (China) \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-Generator: Gtranslator 47.1\n" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:104 msgid "Episodes: " msgstr "剧集:" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:142 msgid "Last publication" msgstr "最新发行" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:163 msgid "_Subscribe" msgstr "订阅(_S)" #: podcasts-gtk/resources/gtk/discovery_found_podcast.ui:173 #: podcasts-gtk/src/widgets/discovery_search_results.rs:152 msgid "Subscribing to feed…" msgstr "订阅以获取更新…" #: podcasts-gtk/resources/gtk/discovery_page.ui:33 msgid "Add Podcasts" msgstr "添加播客" #: podcasts-gtk/resources/gtk/discovery_page.ui:64 msgid "Search" msgstr "搜索" #: podcasts-gtk/resources/gtk/discovery_page.ui:75 msgid "Enter a feed URL or search the selected platforms." msgstr "输入订阅网址或搜索选中的平台。" #: podcasts-gtk/resources/gtk/discovery_page.ui:98 msgid "Submit search" msgstr "提交搜索" #: podcasts-gtk/resources/gtk/discovery_page.ui:104 #: podcasts-gtk/src/widgets/discovery_page.rs:113 msgid "Loading…" msgstr "正在加载…" #: podcasts-gtk/resources/gtk/discovery_page.ui:115 msgid "Please enable a Search Platform below, or enter a http(s) feed URL." msgstr "请启用下面的搜索平台,或输入 http(s) 订阅网址。" #: podcasts-gtk/resources/gtk/discovery_page.ui:127 msgid "Search Platforms" msgstr "搜索平台" #: podcasts-gtk/resources/gtk/discovery_page.ui:128 msgid "Search queries will be sent to these platforms." msgstr "搜索关键词将发送至这些平台。" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:32 msgid "Search results" msgstr "搜索结果" #: podcasts-gtk/resources/gtk/discovery_search_results.ui:68 msgid "No results found." msgstr "未找到结果。" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/empty_show.ui:17 msgid "This show does not have episodes yet" msgstr "这个节目还没有剧集" #: podcasts-gtk/resources/gtk/empty_show.ui:28 msgid "If you think this is an error, please consider writing a bug report." msgstr "如果您认为这是个错误,请考虑编写个缺陷报告。" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:31 msgid "Get Some Shows" msgstr "获取一些节目" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:52 msgid "Add new shows via feed URL" msgstr "通过订阅源 URL 添加新节目" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/empty_view.ui:76 msgid "Import shows from another device" msgstr "从另一设备导入节目" #: podcasts-gtk/resources/gtk/episode_description.ui:33 msgid "Episode Details" msgstr "剧集详情" #: podcasts-gtk/resources/gtk/episode_description.ui:42 msgid "Episode Menu" msgstr "剧集菜单" #: podcasts-gtk/resources/gtk/episode_description.ui:92 msgid "Podcast Title" msgstr "播客标题" #: podcasts-gtk/resources/gtk/episode_description.ui:115 msgid "Duration - Date" msgstr "时长 - 日期" #: podcasts-gtk/resources/gtk/episode_description.ui:149 msgid "_Stream" msgstr "媒体流(_S)" #: podcasts-gtk/resources/gtk/episode_description.ui:167 msgid "_Play" msgstr "播放(_P)" #: podcasts-gtk/resources/gtk/episode_description.ui:185 msgid "_Download" msgstr "下载(_D)" #: podcasts-gtk/resources/gtk/episode_description.ui:212 msgid "Cancel" msgstr "取消" #: podcasts-gtk/resources/gtk/episode_description.ui:241 msgid "Delete" msgstr "删除" #: podcasts-gtk/resources/gtk/episode_description.ui:260 msgid "Episode Description" msgstr "剧集描述" #: podcasts-gtk/resources/gtk/episode_description.ui:281 msgid "Episode Cover" msgstr "剧集封面" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/episode_menu.ui:36 msgid "Go to Show" msgstr "前往节目" #: podcasts-gtk/resources/gtk/episode_menu.ui:41 msgid "Copy Episode URL" msgstr "复制剧集网址" #: podcasts-gtk/resources/gtk/episode_menu.ui:45 msgid "Mark as Played" msgstr "标记为已播放" #: podcasts-gtk/resources/gtk/episode_menu.ui:50 msgid "Mark as Unplayed" msgstr "标记为未播放" #: podcasts-gtk/resources/gtk/episode_widget.ui:70 msgid "You’ve already listened to this episode." msgstr "您已经听过此节目了。" #: podcasts-gtk/resources/gtk/episode_widget.ui:158 msgid "Calculating episode size…" msgstr "正在计算剧集大小…" #: podcasts-gtk/resources/gtk/episode_widget.ui:181 msgid "Play this episode" msgstr "播放此剧集" #: podcasts-gtk/resources/gtk/episode_widget.ui:193 msgid "Cancel the download process" msgstr "取消下载进程" #: podcasts-gtk/resources/gtk/episode_widget.ui:206 msgid "Download this episode" msgstr "下载此剧集" #: podcasts-gtk/resources/gtk/episode_widget.ui:220 msgid "Episode without audio" msgstr "无音频剧集" #: podcasts-gtk/resources/gtk/help-overlay.ui:12 msgctxt "shortcut window" msgid "Navigation" msgstr "导航" #: podcasts-gtk/resources/gtk/help-overlay.ui:15 msgctxt "shortcut window" msgid "Go to Home Page" msgstr "前往主页" #. Translators: Shows as a Noun #: podcasts-gtk/resources/gtk/help-overlay.ui:22 msgctxt "shortcut window" msgid "Go to Shows Page" msgstr "前往节目页" #. Translators: 'Discovery' is a page where you can add new podcasts #: podcasts-gtk/resources/gtk/help-overlay.ui:29 msgctxt "shortcut window" msgid "Go To Discovery Page" msgstr "前往发现页" #: podcasts-gtk/resources/gtk/help-overlay.ui:38 msgctxt "shortcut window" msgid "Player" msgstr "播放器" #: podcasts-gtk/resources/gtk/help-overlay.ui:41 msgctxt "shortcut window" msgid "Toggle Pause" msgstr "开关暂停" #: podcasts-gtk/resources/gtk/help-overlay.ui:47 msgctxt "shortcut window" msgid "Seek Forwards" msgstr "前进" #: podcasts-gtk/resources/gtk/help-overlay.ui:53 msgctxt "shortcut window" msgid "Seek Backwards" msgstr "后退" #: podcasts-gtk/resources/gtk/help-overlay.ui:62 msgctxt "shortcut window" msgid "General" msgstr "通用" #: podcasts-gtk/resources/gtk/help-overlay.ui:65 msgctxt "shortcut window" msgid "Check for new episodes" msgstr "检查新剧集" #: podcasts-gtk/resources/gtk/help-overlay.ui:71 msgctxt "shortcut window" msgid "Quit the application" msgstr "退出程序" #: podcasts-gtk/resources/gtk/help-overlay.ui:77 msgctxt "shortcut window" msgid "Import Subscriptions" msgstr "导入订阅" #: podcasts-gtk/resources/gtk/help-overlay.ui:83 msgctxt "shortcut window" msgid "Export Subscriptions" msgstr "导出订阅" #: podcasts-gtk/resources/gtk/home_view.ui:57 #: podcasts-gtk/resources/gtk/home_view.ui:70 msgid "Today" msgstr "今天" #: podcasts-gtk/resources/gtk/home_view.ui:88 #: podcasts-gtk/resources/gtk/home_view.ui:101 msgid "Yesterday" msgstr "昨天" #: podcasts-gtk/resources/gtk/home_view.ui:119 #: podcasts-gtk/resources/gtk/home_view.ui:132 msgid "This Week" msgstr "本周" #: podcasts-gtk/resources/gtk/home_view.ui:150 #: podcasts-gtk/resources/gtk/home_view.ui:163 msgid "This Month" msgstr "本月" #: podcasts-gtk/resources/gtk/home_view.ui:182 #: podcasts-gtk/resources/gtk/home_view.ui:195 msgid "Older" msgstr "更早" #: podcasts-gtk/resources/gtk/player_rate.ui:29 msgid "Change the playback speed" msgstr "更改播放速度" #: podcasts-gtk/resources/gtk/player_rate.ui:34 #: podcasts-gtk/resources/gtk/player_rate.ui:59 msgid "1.00×" msgstr "1.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:39 msgid "2.00×" msgstr "2.00×" #: podcasts-gtk/resources/gtk/player_rate.ui:44 msgid "1.75×" msgstr "1.75×" #: podcasts-gtk/resources/gtk/player_rate.ui:49 msgid "1.50×" msgstr "1.50×" #: podcasts-gtk/resources/gtk/player_rate.ui:54 msgid "1.25×" msgstr "1.25×" #: podcasts-gtk/resources/gtk/player_rate.ui:64 msgid "0.90×" msgstr "0.90×" #: podcasts-gtk/resources/gtk/player_rate.ui:69 msgid "0.75×" msgstr "0.75×" #: podcasts-gtk/resources/gtk/player_sheet.ui:133 msgid "Rewind" msgstr "倒带" #: podcasts-gtk/resources/gtk/player_sheet.ui:154 #: podcasts-gtk/resources/gtk/player_toolbar.ui:67 #: podcasts-gtk/resources/gtk/player_toolbar.ui:268 msgid "Play" msgstr "播放" #: podcasts-gtk/resources/gtk/player_sheet.ui:172 #: podcasts-gtk/resources/gtk/player_toolbar.ui:80 #: podcasts-gtk/resources/gtk/player_toolbar.ui:277 msgid "Pause" msgstr "暂停" #: podcasts-gtk/resources/gtk/player_sheet.ui:195 msgid "Forward" msgstr "前进" #: podcasts-gtk/resources/gtk/player_sheet.ui:230 msgid "Description" msgstr "描述" #: podcasts-gtk/resources/gtk/player_toolbar.ui:50 msgid "Rewind 10 seconds" msgstr "后退 10 秒" #: podcasts-gtk/resources/gtk/player_toolbar.ui:95 msgid "Fast forward 10 seconds" msgstr "快进 10 秒" #: podcasts-gtk/resources/gtk/secondary_menu.ui:7 msgid "_Mark All Episodes as Played" msgstr "标记所有剧集为已播放(_M)" #: podcasts-gtk/resources/gtk/secondary_menu.ui:11 msgid "_Website" msgstr "网站(_W)" #: podcasts-gtk/resources/gtk/secondary_menu.ui:15 msgid "_Unsubscribe" msgstr "退订(_U)" #: podcasts-gtk/resources/gtk/show_menu.ui:35 msgid "Open Website" msgstr "打开网站" #: podcasts-gtk/resources/gtk/show_menu.ui:39 msgid "Mark All as Played" msgstr "全部标记为已播放" #: podcasts-gtk/resources/gtk/show_menu.ui:45 msgid "Unsubscribe" msgstr "退订" #: podcasts-gtk/resources/gtk/show_widget.ui:40 msgid "Podcast Menu" msgstr "播客菜单" #: podcasts-gtk/resources/gtk/show_widget.ui:97 msgid "Episodes" msgstr "剧集" #: podcasts-gtk/resources/gtk/window.ui:6 msgid "_Check for New Episodes" msgstr "检查新剧集(_C)" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:12 msgid "_Import Shows" msgstr "导入节目(_I)" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/resources/gtk/window.ui:17 msgid "_Export Shows" msgstr "导出节目(_E)" #: podcasts-gtk/resources/gtk/window.ui:23 msgid "_Keyboard Shortcuts" msgstr "键盘快捷键(_K)" #: podcasts-gtk/resources/gtk/window.ui:27 msgid "_About Podcasts" msgstr "关于播客(_A)" #. Weird magic I copy-pasted that sets the Application Name in the Shell. #: podcasts-gtk/resources/gtk/window.ui:35 #: podcasts-gtk/resources/gtk/window.ui:102 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:4 #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:3 #: podcasts-gtk/src/app.rs:503 podcasts-gtk/src/widgets/aboutdialog.rs:56 #: podcasts-gtk/src/widgets/player.rs:465 msgid "Podcasts" msgstr "播客" #: podcasts-gtk/resources/gtk/window.ui:115 msgid "Add a New Feed" msgstr "添加新订阅源" #: podcasts-gtk/resources/gtk/window.ui:123 msgid "Main Menu" msgstr "主菜单" #. Translators: Show as a noun, meaning a Podcast-Show. #: podcasts-gtk/resources/gtk/window.ui:137 msgid "Show" msgstr "节目" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:8 msgid "Listen to your favorite shows" msgstr "收听您最爱的节目" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:10 msgid "" "Play, update, and manage your podcasts from a lightweight interface that " "seamlessly integrates with GNOME. Podcasts can play various audio formats " "and remember where you stopped listening. You can subscribe to shows via RSS/" "Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be " "imported via OPML files." msgstr "" "通过与 GNOME 无缝集成的轻量界面来播放、更新和管理您的播客。博客支持播放多种音" "频格式和记住您停止收听的位置。您可以通过 RSS/Atom、iTunes 和 Soundcloud 链接" "来订阅节目。可以通过 OPML 文件导入其它应用的订阅。" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:18 msgid "The home view displaying the newest episodes of your podcasts" msgstr "主页视图显示您播客的最新剧集" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:22 msgid "The shows view displaying the covers of your podcasts" msgstr "节目视图显示您的博客的封面" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:26 msgid "" "The show widget displaying the cover and the latest episodes of a specific " "podcast" msgstr "节目部件显示指定播客的封面和最新剧集" #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:30 msgid "The view where one can add a new podcast" msgstr "添加新播客的视图" #. developer_name tag deprecated with Appstream 1.0 #: podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in:221 msgid "The Podcasts developers" msgstr "播客应用的开发者们" #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:4 msgid "Listen to your favorite podcasts, right from your desktop." msgstr "直接在您的桌面上收听喜欢的播客。" #. Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in:13 msgid "Podcast;RSS;" msgstr "Podcast;RSS;播客;订阅;聚合;节目;秀;" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:15 msgid "Height of the last open main window" msgstr "最后打开的主窗口高度" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:19 msgid "Width of the last open main window" msgstr "最后打开的主窗口宽度" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:23 msgid "Maximized state of the last open main window" msgstr "最后打开的主窗口最大化状态" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:28 msgid "Whether to periodically refresh content" msgstr "是否定期刷新内容" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:33 msgid "How many periods of time to wait between automatic refreshes" msgstr "自动刷新之间要等待多久的时间" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:37 msgid "What period of time to wait between automatic refreshes" msgstr "自动刷新之间要等待的时间段" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:41 msgid "Whether to refresh content after startup" msgstr "启动后是否刷新内容" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:47 msgid "How many periods of time to wait between automatic cleanups" msgstr "自动清理之间要等待多久的时间" #: podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in:51 msgid "What period of time to wait between automatic cleanups" msgstr "自动清理之间要等待的时间段" #: podcasts-gtk/src/app.rs:358 msgid "Copied URL to clipboard!" msgstr "已将网址复制到剪贴板!" #: podcasts-gtk/src/episode_description_parser.rs:316 msgid "Jump to {}:{}:{}" msgstr "跳至 {}:{}:{}" #: podcasts-gtk/src/episode_description_parser.rs:335 msgid "Jump to {}:{}" msgstr "跳至 {}:{}" #: podcasts-gtk/src/manager.rs:106 msgid "Download failed: {}" msgstr "下载失败:{}" #: podcasts-gtk/src/utils.rs:294 msgid "Failed to subscribe to feed: {}" msgstr "订阅失败:{}" #: podcasts-gtk/src/utils.rs:388 podcasts-gtk/src/utils.rs:434 msgid "OPML file" msgstr "OPML 文件" #: podcasts-gtk/src/utils.rs:399 msgid "Select the file from which to you want to import shows." msgstr "选择您想要从中导入节目的文件。" #: podcasts-gtk/src/utils.rs:401 msgid "_Import" msgstr "导入(_I)" #: podcasts-gtk/src/utils.rs:424 msgid "Failed to parse the imported file {}" msgstr "无法解析已导入的文件 {}" #. Translators: Show as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/utils.rs:445 msgid "Export shows to…" msgstr "导出节目到…" #: podcasts-gtk/src/utils.rs:446 msgid "_Export" msgstr "导出(_E)" #. Translators: This is the string of the suggested name for the exported opml file #: podcasts-gtk/src/utils.rs:450 msgid "gnome-podcasts-exported-shows" msgstr "gnome-podcasts-exported-shows" #: podcasts-gtk/src/utils.rs:459 msgid "GNOME Podcasts Subscriptions" msgstr "GNOME 播客订阅" #: podcasts-gtk/src/utils.rs:463 msgid "Failed to export podcasts" msgstr "播客导出失败" #: podcasts-gtk/src/widgets/aboutdialog.rs:52 msgid "Podcast Client for the GNOME Desktop." msgstr "GNOME 桌面的播客客户端。" #: podcasts-gtk/src/widgets/aboutdialog.rs:62 msgid "translator-credits" msgstr "" "Dingzhong Chen , 2018.\n" "Tranquilo Chan , 2020.\n" "lumingzh , 2022-2025.\n" "lch , 2024." #: podcasts-gtk/src/widgets/content_stack.rs:60 msgid "Fetching feeds…" msgstr "正在获取订阅…" #: podcasts-gtk/src/widgets/content_stack.rs:67 msgid "New" msgstr "更新" #. Translators: Shows as a noun, meaning Podcast-Shows. #: podcasts-gtk/src/widgets/content_stack.rs:68 #: podcasts-gtk/src/widgets/shows_view.rs:118 msgid "Shows" msgstr "节目" #: podcasts-gtk/src/widgets/download_progress_bar.rs:67 msgid "Download progress" msgstr "下载进度" #: podcasts-gtk/src/widgets/episode.rs:279 msgid "{} min" msgstr "{} 分钟" #: podcasts-gtk/src/widgets/player.rs:1109 msgid "The media player was unable to execute an action." msgstr "媒体播放器无法执行此操作。" #: podcasts-gtk/src/widgets/read_more_label.rs:34 msgid "Visually expands this description" msgstr "直观展开该描述" #: podcasts-gtk/src/widgets/read_more_label.rs:36 msgid "Read More" msgstr "阅读更多" #: podcasts-gtk/src/widgets/show_menu.rs:181 msgid "Marked all episodes as listened" msgstr "标记所有剧集为已听过" #: podcasts-gtk/src/widgets/show_menu.rs:182 #: podcasts-gtk/src/widgets/show_menu.rs:209 msgid "Undo" msgstr "撤消" #: podcasts-gtk/src/widgets/show_menu.rs:205 msgid "Unsubscribed from {}" msgstr "已退订 {}" #~ msgid "0" #~ msgstr "0" #~ msgid "Loading..." #~ msgstr "正在加载…" #~ msgid "Now Playing" #~ msgstr "正在播放" #~ msgid "Close" #~ msgstr "关闭" #~ msgid "Enter Feed Address" #~ msgstr "输入订阅地址" #~ msgid "Popover menu (ESC to close)" #~ msgstr "弹出菜单(按 ESC 关闭)" #~ msgid "Add" #~ msgstr "添加" #~ msgid "Back" #~ msgstr "后退" #~ msgid "Show Title" #~ msgstr "显示标题" #~ msgid "Top position of the last open main window" #~ msgstr "最后打开的主窗口顶部位置" #~ msgid "Left position of the last open main window" #~ msgstr "最后打开的主窗口左侧位置" #~ msgid "Enable or disable dark theme" #~ msgstr "启用或禁用暗色主题" #~ msgid "Jordan Petridis" #~ msgstr "Jordan Petridis" #~ msgid "Julian Hofer" #~ msgstr "Julian Hofer" #~ msgid "An in-app action notification" #~ msgstr "应用内操作通知" #~ msgid "Selected file could not be accessed." #~ msgstr "所选文件无法访问。" #~ msgid "Learn more about GNOME Podcasts" #~ msgstr "了解关于 GNOME 播客的更多信息" #~ msgid "Podcast app for GNOME" #~ msgstr "GNOME 的播客应用" #~| msgid "1.5 speed rate" #~ msgid "Double speed rate" #~ msgstr "2 倍速率" #~| msgid "1.5 speed rate" #~ msgid "1.75 speed rate" #~ msgstr "1.75 倍速率" #~ msgid "1.5 speed rate" #~ msgstr "1.5 倍速率" #~ msgid "1.25 speed rate" #~ msgstr "1.25 倍速率" #~ msgid "Normal speed" #~ msgstr "正常速度" #~ msgid "@icon@" #~ msgstr "@icon@" #~ msgid "_Preferences" #~ msgstr "首选项(_P)" #~ msgid "_About" #~ msgstr "关于(_A)" #~ msgid "You are already subscribed to that feed!" #~ msgstr "您已经订阅了那条订阅源!" #~ msgctxt "shortcut window" #~ msgid "Preferences" #~ msgstr "首选项" #~ msgid "Preferences" #~ msgstr "首选项" #~ msgid "Appearance" #~ msgstr "外观" #~ msgid "Dark Theme" #~ msgstr "暗色主题" #~ msgid "Delete played episodes" #~ msgstr "删除播放过的剧集" #~ msgid "After" #~ msgstr "经过" #~ msgid "Invalid URL" #~ msgstr "无效的 URL" #~ msgid "Seconds" #~ msgstr "秒" #~ msgid "Minutes" #~ msgstr "分" #~ msgid "Hours" #~ msgstr "小时" #~ msgid "Days" #~ msgstr "天" #~ msgid "Weeks" #~ msgstr "周" podcasts-25.2/podcasts-gtk/resources/000077500000000000000000000000001500126606300176765ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/gtk/000077500000000000000000000000001500126606300204635ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/gtk/discovery_found_podcast.ui000066400000000000000000000165751500126606300257570ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/discovery_page.ui000066400000000000000000000143221500126606300240270ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/discovery_search_results.ui000066400000000000000000000057011500126606300261420ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/empty_show.ui000066400000000000000000000027331500126606300232250ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/empty_view.ui000066400000000000000000000065041500126606300232170ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/episode_description.ui000066400000000000000000000400071500126606300250560ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/episode_menu.ui000066400000000000000000000040751500126606300235040ustar00rootroot00000000000000
Go to Show episode.go-to-show action-missing Copy Episode URL episode.copy-episode-url Mark as Played episode.mark-as-played action-disabled Mark as Unplayed episode.mark-as-unplayed action-disabled
podcasts-25.2/podcasts-gtk/resources/gtk/episode_widget.ui000066400000000000000000000240631500126606300240220ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/help-overlay.ui000066400000000000000000000101411500126606300234260ustar00rootroot00000000000000 True shortcuts 12 Navigation Go to Home Page win.go-to-home Go to Shows Page win.go-to-shows Go To Discovery Page win.go-to-discovery Player Toggle Pause win.toggle-pause Seek Forwards win.seek-forwards Seek Backwards win.seek-backwards General Check for new episodes win.refresh Quit the application app.quit Import Subscriptions win.import Export Subscriptions win.export podcasts-25.2/podcasts-gtk/resources/gtk/home_episode.ui000066400000000000000000000043631500126606300234700ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/home_view.ui000066400000000000000000000227441500126606300230150ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/player_rate.ui000066400000000000000000000053011500126606300233300ustar00rootroot00000000000000 True Change the playback speed center center up rate_menu 1.00×
2.00× rate.set 2.00 1.75× rate.set 1.75 1.50× rate.set 1.50 1.25× rate.set 1.25 1.00× rate.set 1.00 0.90× rate.set 0.90 0.75× rate.set 0.75
podcasts-25.2/podcasts-gtk/resources/gtk/player_sheet.ui000066400000000000000000000304501500126606300235100ustar00rootroot00000000000000 False False True never vertical 2 6 6 6 6 vertical 18 True True center center True True vertical 256 256 True True 18 18 6 6 center center gtk-missing-image hidden 6 vertical 6 6 center Episode Title True center center Show Title True end 12 12 5 1 False True True episode_label 5 center 12 60 60 center 12 Rewind skip-back-large-symbolic 2 presentation True 80 80 Play media-playback-start-symbolic 2 presentation 80 80 Pause media-playback-pause-symbolic 2 presentation True 60 60 center 12 Forward skip-forward-large-symbolic 2 presentation 12 12 12 12 True Description podcasts-25.2/podcasts-gtk/resources/gtk/player_toolbar.ui000066400000000000000000000314401500126606300240420ustar00rootroot00000000000000 False crossfade False 6 center 6 6 42 True Rewind 10 seconds skip-back-symbolic True True 64 True Play media-playback-start-symbolic 64 True Pause media-playback-pause-symbolic 42 True Fast forward 10 seconds skip-forward-symbolic 6 center 34 image-x-generic-symbolic hidden True center vertical Show Title True end 20 0 Episode Title True end 20 0 True 1 False 130 episode_label start center 6 start center 0:00 start center / start center 0:00 vertical 6 episode_label 6 center 34 image-x-generic-symbolic hidden True center vertical True Show Title end 0 Episode Title end 0 True True True end center media-playback-start-symbolic Play media-playback-pause-symbolic Pause podcasts-25.2/podcasts-gtk/resources/gtk/secondary_menu.ui000066400000000000000000000012251500126606300240350ustar00rootroot00000000000000
_Mark All Episodes as Played _Website _Unsubscribe
podcasts-25.2/podcasts-gtk/resources/gtk/show_menu.ui000066400000000000000000000032161500126606300230300ustar00rootroot00000000000000
Open Website show.open-website Mark All as Played show.mark-played
Unsubscribe show.unsubscribe
podcasts-25.2/podcasts-gtk/resources/gtk/show_widget.ui000066400000000000000000000114031500126606300233440ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/gtk/style.css000066400000000000000000000037501500126606300223420ustar00rootroot00000000000000progressbar.playback-progress trough { border-radius: 3px; } progressbar.playback-progress trough progress { border-radius: 3px; border-right: none; border-left: none; } popover modelbutton > box > label { font-feature-settings: "tnum"; } /* Episode description */ .episode_description_label link { text-decoration: none; } .episode_description .button-row { padding: 15px 0px; } .episode_description .button-row button { padding: 10px 20px; } .episode_description .button-row button image { padding-right: 10px; } .episode_description .button-row .cancel-button { padding: 0px 0px; } .episode_description .button-row .cancel-button .cancel-button-text { padding: 12px 20px; padding-bottom: 11px; } .episode_description .button-row .cancel-button progressbar { padding: 0px; } .episode_description progressbar { padding-left: 12px; padding-right: 12px; padding-top: 15px; } .episode_description progressbar trough { min-height: 3px; border-top-left-radius: 0px; border-top-right-radius: 0px; } .episode_description progressbar trough progress { border-top-left-radius: 0px; border-top-right-radius: 0px; } /* Discovery Search */ .rounded-big { border-radius: 8px; } .rounded-small { border-radius: 4px; } .show-button { padding: 0px; } .taller-button { padding-top: 4px; padding-bottom: 4px; } .shows-grid { background: transparent; padding-top: 12px; padding-bottom: 26px; padding-left: 6px; padding-right: 6px; } .shows-grid > * { padding: 0px; margin: 6px; } .home-view-episode-cover { background: color-mix(in srgb, currentColor 25%, transparent); color: color-mix(in srgb, currentColor 40%, transparent); } .shows-view-cover { background: color-mix(in srgb, currentColor 15%, transparent); } .in-button-progress > progressbar > trough { background-color: transparent; } .in-button-progress > progressbar > trough > progress{ border-radius: 0px; } podcasts-25.2/podcasts-gtk/resources/gtk/window.ui000066400000000000000000000160031500126606300223310ustar00rootroot00000000000000
_Check for New Episodes win.refresh <primary>r _Import Shows win.import _Export Shows win.export
_Keyboard Shortcuts win.show-help-overlay _About Podcasts win.about
podcasts-25.2/podcasts-gtk/resources/icons/000077500000000000000000000000001500126606300210115ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/hicolor/000077500000000000000000000000001500126606300224505ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/hicolor/scalable/000077500000000000000000000000001500126606300242165ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/hicolor/scalable/apps/000077500000000000000000000000001500126606300251615ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/hicolor/scalable/apps/org.gnome.Podcasts.Devel.svg000066400000000000000000000224771500126606300324260ustar00rootroot00000000000000 podcasts-25.2/podcasts-gtk/resources/icons/hicolor/scalable/apps/org.gnome.Podcasts.svg000066400000000000000000000456611500126606300313700ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template podcasts-25.2/podcasts-gtk/resources/icons/hicolor/symbolic/000077500000000000000000000000001500126606300242715ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/hicolor/symbolic/apps/000077500000000000000000000000001500126606300252345ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/hicolor/symbolic/apps/org.gnome.Podcasts-symbolic.svg000066400000000000000000000053701500126606300332530ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme podcasts-25.2/podcasts-gtk/resources/icons/meson.build000066400000000000000000000010741500126606300231550ustar00rootroot00000000000000scalable_dir = join_paths('hicolor', 'scalable', 'apps') install_data( join_paths(scalable_dir, 'org.gnome.Podcasts.svg'), install_dir: join_paths(datadir, 'icons', scalable_dir), ) install_data( join_paths(scalable_dir, 'org.gnome.Podcasts.Devel.svg'), install_dir: join_paths(datadir, 'icons', scalable_dir), ) symbolic_dir = join_paths('hicolor', 'symbolic', 'apps') install_data( join_paths(symbolic_dir, 'org.gnome.Podcasts-symbolic.svg'), install_dir: join_paths(datadir, 'icons', symbolic_dir), rename: '@0@-symbolic.svg'.format(application_id) ) podcasts-25.2/podcasts-gtk/resources/icons/scalable/000077500000000000000000000000001500126606300225575ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/scalable/actions/000077500000000000000000000000001500126606300242175ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/resources/icons/scalable/actions/skip-back-large-symbolic.svg000066400000000000000000000143201500126606300315130ustar00rootroot00000000000000 image/svg+xml podcasts-25.2/podcasts-gtk/resources/icons/scalable/actions/skip-back-symbolic.svg000066400000000000000000000142031500126606300304230ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme podcasts-25.2/podcasts-gtk/resources/icons/scalable/actions/skip-forward-large-symbolic.svg000066400000000000000000000143351500126606300322650ustar00rootroot00000000000000 image/svg+xml podcasts-25.2/podcasts-gtk/resources/icons/scalable/actions/skip-forward-symbolic.svg000066400000000000000000000141111500126606300311650ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme podcasts-25.2/podcasts-gtk/resources/meson.build000066400000000000000000000037271500126606300220510ustar00rootroot00000000000000subdir('icons') desktop_conf = configuration_data() desktop_conf.set('icon', application_id) desktop_file = i18n.merge_file ( type: 'desktop', input: configure_file( input: files('org.gnome.Podcasts.desktop.in.in'), output: 'org.gnome.Podcasts.desktop.in', configuration: desktop_conf ), output: '@0@.desktop'.format(application_id), po_dir: podir, install: true, install_dir: join_paths (datadir, 'applications') ) desktop_file_validate = find_program('desktop-file-validate', required: false) if desktop_file_validate.found() test( 'validate-desktop', desktop_file_validate, args: [ desktop_file.full_path() ] ) endif appdata_conf = configuration_data() appdata_conf.set('appid', application_id) appdata_file = i18n.merge_file ( input: configure_file( input: files('org.gnome.Podcasts.appdata.xml.in.in'), output: 'org.gnome.Podcasts.appdata.xml.in', configuration: appdata_conf ), output: '@0@.appdata.xml'.format(application_id), po_dir: podir, install: true, install_dir: join_paths (datadir, 'metainfo') ) appstreamcli = find_program('appstreamcli', required: false) if appstreamcli.found() test( 'validate-appdata', appstreamcli, args: [ 'validate', '--no-net', '--explain', appdata_file.full_path() ] ) endif configure_file( input: 'org.gnome.Podcasts.gschema.xml.in', output: 'org.gnome.Podcasts.gschema.xml', configuration: podcasts_conf, install: true, install_dir: join_paths(datadir, 'glib-2.0', 'schemas') ) configure_file( input: 'org.gnome.Podcasts.service.in', output: '@0@.service'.format(application_id), configuration: podcasts_conf, install_dir: join_paths(datadir,'dbus-1', 'services') ) podcasts_resources = gnome.compile_resources( 'resources', 'resources.xml', gresource_bundle: true, source_dir: meson.current_build_dir() )[0] # Validating schemas test('Validate schema file', gschemas, args: ['--strict', '--dry-run', meson.current_source_dir()] ) podcasts-25.2/podcasts-gtk/resources/org.gnome.Podcasts.appdata.xml.in.in000066400000000000000000000226731500126606300265270ustar00rootroot00000000000000 @appid@ Podcasts GPL-3.0 CC0-1.0 Listen to your favorite shows

Play, update, and manage your podcasts from a lightweight interface that seamlessly integrates with GNOME. Podcasts can play various audio formats and remember where you stopped listening. You can subscribe to shows via RSS/Atom, iTunes, and Soundcloud links. Subscriptions from other apps can be imported via OPML files.

The home view displaying the newest episodes of your podcasts https://gitlab.gnome.org/World/podcasts/-/raw/af5d0c78f8e5d8d3398ad01178d248bbe64adf5b/screenshots/home_view.png The shows view displaying the covers of your podcasts https://gitlab.gnome.org/World/podcasts/-/raw/af5d0c78f8e5d8d3398ad01178d248bbe64adf5b/screenshots/shows_view.png The show widget displaying the cover and the latest episodes of a specific podcast https://gitlab.gnome.org/World/podcasts/-/raw/af5d0c78f8e5d8d3398ad01178d248bbe64adf5b/screenshots/show_widget.png The view where one can add a new podcast https://gitlab.gnome.org/World/podcasts/-/raw/af5d0c78f8e5d8d3398ad01178d248bbe64adf5b/screenshots/add_podcast.png #fb6969 #7b1824

Small release with a couple of bugfixes and quality of life improvements!

We also changed the version scheme!

  • We added a description button to the Episode Sheet
  • The position of the episode and length are now reported in MPRIS
  • Timestamp links work again if you are streaming an episode
  • And we also fixed a couple of pesky bugs!

New year, New release 🎉! This release brings lots of small improvements to make everything a little bit better! The following are now possible:

  • You can now mark individual episodes as played
  • The Shows will now scale based on the window size
  • You can close the window with the Control + W shortcut

While we also changed some internal things

  • Rework the download machinery to be faster and more efficient
  • Improved application startup times

And fixes a couple of pesky bugs

  • Automatically detect the image format for thumbnails
  • Dates are now displayed and calculated using localtime instead of sometimes using UTC
  • Fix accessibility warnings in the Episode Description
  • Correctly trigger a download when thumbnail cover for mpris is missing
  • Correctly calculate the episode download size if its missing from the xml metadata

Fix screenshot links.

Replace add button popover with dedicated page.

Add streaming support.

Add additional keyboard shortcuts.

Several fixes and performance improvements.

This release fixes a couple of accessibility bugs and includes behind-the-scenes improvements.

This version brings the long awaited GTK 4 port, dark mode support, a new Show description widget and a handful of fixes all around.

The 0.5.1 includes a couple of bugfixes and quality of life updates.

  • Render lists in episode descriptions.
  • Avoid redundant network request for the mpris cover art.

The 0.5.0 release brings a bunch of long awaited changes.

  • View episode descriptions and show notes.
  • Continue playback from where you left off.
  • Inhibit suspend during playback.
  • Soundcloud playlists now be added as feeds.
  • 75% and 90% playback rate options.

This release brings support for soundcloud feeds, a hnadful of bugfixes and performance improvements.

Another Incremental relase, bringing more quality of life changes, bugfixes and continuing to improves adaptiveness of the application. Most notable is the addition of a "Now Playing" widget and the new 1.75 and 2.0 playback speed options.

Small release including OPML export functionality, partial support for premium feeds, a couple of bugfixes and overall life improvements.

After a month of work, the new version of Podcasts brings Desktop integration with MPRIS, further UX polish, and a couple bugfixes and improvements. Additionally existing translations were updated and 9 new were added.

  • Brazilian Portuguese, Swedish, Italian, Friulian, Hungarian, Croatian, Latvian, Czech and Catalan Translations were added.
  • The audio player now can integrate with Desktop Environments (MPRIS2)
  • Further UI and UX polishing and a handful bug fixes
  • Update to account for the GNOME 3.32 HIG changes
  • Openssl 1.1 support
  • Fixed a performance regression when updating RSS Feeds

Podcasts 0.4.5 brings a month of bug fixes, performance improvements and initial translations support.

  • Finish, Polish, Turkish, Spanish, German, Galician, Indonesian and Korean Translations were added.
  • Views now adapt better to different window sizes, thanks to libhandy HdyColumn
  • The update indacator was moved to an In-App notification
  • Performance improvements when loading Show Cover images.
  • Improved handling of HTTP Redirects

This is the first release available from Flathub!

keyboard pointing touch 360 @appid@.desktop https://apps.gnome.org/Podcasts/ https://gitlab.gnome.org/World/podcasts/issues/ https://www.gnome.org/donate/ https://gitlab.gnome.org/World/podcasts#translations https://gitlab.gnome.org/World/podcasts https://welcome.gnome.org/en/app/Podcasts/ gnome-podcasts GNOME jpetridis@gnome.org The Podcasts developers The Podcasts developers [(250, 181, 174)]
podcasts-25.2/podcasts-gtk/resources/org.gnome.Podcasts.desktop.in.in000066400000000000000000000011451500126606300257560ustar00rootroot00000000000000[Desktop Entry] Name=Podcasts Comment=Listen to your favorite podcasts, right from your desktop. # Translators: Do NOT translate or transliterate this text (this is an icon file name)! Icon=@icon@ Exec=gnome-podcasts Terminal=false Type=Application StartupNotify=true Categories=GNOME;GTK;AudioVideo;Audio; # Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! Keywords=Podcast;RSS; DBusActivatable=true # Translators: Do NOT translate or transliterate this text (these are enum types)! X-Purism-FormFactor=Workstation;Mobile; podcasts-25.2/podcasts-gtk/resources/org.gnome.Podcasts.gschema.xml.in000066400000000000000000000043201500126606300261040ustar00rootroot00000000000000 640 Height of the last open main window 860 Width of the last open main window false Maximized state of the last open main window true Whether to periodically refresh content 1 How many periods of time to wait between automatic refreshes 'hours' What period of time to wait between automatic refreshes true Whether to refresh content after startup 2 How many periods of time to wait between automatic cleanups 'days' What period of time to wait between automatic cleanups podcasts-25.2/podcasts-gtk/resources/org.gnome.Podcasts.service.in000066400000000000000000000001201500126606300253300ustar00rootroot00000000000000[D-BUS Service] Name=@appid@ Exec=@bindir@/gnome-podcasts --gapplication-servicepodcasts-25.2/podcasts-gtk/resources/resources.xml000066400000000000000000000041341500126606300224340ustar00rootroot00000000000000 gtk/discovery_page.ui gtk/discovery_search_results.ui gtk/discovery_found_podcast.ui gtk/episode_widget.ui gtk/episode_menu.ui gtk/episode_description.ui gtk/show_widget.ui gtk/empty_view.ui gtk/empty_show.ui gtk/home_view.ui gtk/home_episode.ui gtk/show_menu.ui gtk/help-overlay.ui gtk/player_rate.ui gtk/player_sheet.ui gtk/player_toolbar.ui gtk/window.ui icons/scalable/actions/skip-back-large-symbolic.svg icons/scalable/actions/skip-back-symbolic.svg icons/scalable/actions/skip-forward-large-symbolic.svg icons/scalable/actions/skip-forward-symbolic.svg gtk/style.css podcasts-25.2/podcasts-gtk/resources/test/000077500000000000000000000000001500126606300206555ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/src/000077500000000000000000000000001500126606300164535ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/src/app.rs000066400000000000000000000470731500126606300176140ustar00rootroot00000000000000// app.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::subclass::prelude::*; use anyhow::Result; use async_channel::{Receiver, Sender}; use gettextrs::{LocaleCategory, bindtextdomain, setlocale, textdomain}; use glib::clone; use gtk::prelude::*; use gtk::{gio, glib}; use std::cell::RefCell; use std::collections::HashSet; use std::env; use std::sync::Arc; use crate::config::{APP_ID, LOCALEDIR}; use crate::download_covers; use crate::feed_manager::FeedManager; use crate::i18n::i18n; use crate::settings; use crate::utils; use crate::widgets::player::StreamMode; use crate::widgets::show_menu::{mark_all_notif, remove_show_notif}; use crate::widgets::{EpisodeDescription, SearchResults, ShowWidget}; use crate::window::MainWindow; use podcasts_data::dbqueries; use podcasts_data::discovery::FoundPodcast; use podcasts_data::{Episode, EpisodeId, EpisodeModel, Show, ShowId}; // FIXME: port Optionals to OnceCell #[derive(Debug)] pub struct PdApplicationPrivate { sender: Sender, receiver: RefCell>>, window: RefCell>, settings: RefCell>, inhibit_cookie: RefCell, todo_unsub_ids: RefCell>, undo_marked_ids: RefCell>, } #[glib::object_subclass] impl ObjectSubclass for PdApplicationPrivate { const NAME: &'static str = "PdApplication"; type Type = PdApplication; type ParentType = adw::Application; fn new() -> Self { let (sender, r) = async_channel::unbounded(); let receiver = RefCell::new(Some(r)); Self { sender: sender.clone(), receiver, window: RefCell::new(None), settings: RefCell::new(None), inhibit_cookie: RefCell::new(0), todo_unsub_ids: RefCell::new(HashSet::default()), undo_marked_ids: RefCell::new(vec![]), } } } impl ObjectImpl for PdApplicationPrivate {} impl ApplicationImpl for PdApplicationPrivate { fn activate(&self) { debug!("GtkApplication::activate"); self.parent_activate(); if let Some(ref window) = *self.window.borrow() { // Ideally Gtk4/GtkBuilder make this irrelvent window.present(); info!("Window presented"); return; } let app = self.obj(); app.setup_gactions(); let window = MainWindow::new(&app, &self.sender); window.present(); self.window.replace(Some(window)); app.setup_accels(); // Setup action channel let receiver = self.receiver.take().unwrap(); crate::MAINCONTEXT.spawn_local(clone!( #[weak] app, async move { while let Ok(action) = receiver.recv().await { app.do_action(action); } } )); } fn startup(&self) { debug!("GtkApplication::startup"); self.parent_startup(); let settings = gio::Settings::new(APP_ID); let cleanup_date = settings::get_cleanup_date(&settings); // Garbage collect watched episodes from the disk utils::cleanup(cleanup_date); self.settings.replace(Some(settings)); } fn shutdown(&self) { // complete any pending unsubscribe actions let mut todo_unsub_ids = self.todo_unsub_ids.borrow_mut(); for id in todo_unsub_ids.drain() { if let Ok(pd) = dbqueries::get_podcast_from_id(id) { if let Err(err) = podcasts_data::utils::delete_show(&pd) { error!("Error: {}", err); error!("Failed to delete {}", pd.title()); } } } if let Err(err) = download_covers::clean_unfinished_downloads() { error!("Failed to cleanup downloads: {err}"); } self.parent_shutdown(); } } impl GtkApplicationImpl for PdApplicationPrivate {} impl AdwApplicationImpl for PdApplicationPrivate {} glib::wrapper! { pub struct PdApplication(ObjectSubclass) @extends gio::Application, gtk::Application, adw::Application, @implements gio::ActionMap, gio::ActionGroup; } #[derive(Debug, Clone)] pub(crate) enum Action { RefreshAllViews, RefreshEpisodesView, RefreshEpisodesViewBGR, RefreshShowsView, ReplaceWidget(Arc), RefreshWidgetIfSame(ShowId), GoToEpisodeDescription(Arc, Arc), GoToShow(Arc), GoToFoundPodcasts(Arc>), CopiedUrlNotification, CopyUrl(EpisodeId), MarkAllPlayerNotification(Arc), MarkAsPlayed(bool, EpisodeId), FeedRefreshed(u64), StartUpdating, StopUpdating, RemoveShow(Arc), ErrorNotification(String), InitEpisode(EpisodeId), InitEpisodeAt(EpisodeId, i32), StreamEpisode(EpisodeId), UpdateMprisCover(ShowId, bool), // bool = download success, use local file EmptyState, PopulatedState, RaiseWindow, InhibitSuspend, UninhibitSuspend, } impl PdApplication { pub(crate) fn new() -> Self { glib::Object::builder() .property("application-id", APP_ID) .property("resource-base-path", "/org/gnome/Podcasts") .build() } fn setup_gactions(&self) { let i32_variant_type = glib::VariantTy::INT32; let actions = [ gio::ActionEntryBuilder::new("quit") .activate(|app: &Self, _, _| { app.quit(); }) .build(), gio::ActionEntryBuilder::new("go-to-episode") .parameter_type(Some(i32_variant_type)) .activate(|app: &Self, _, id_variant_option| { if let Err(e) = app.go_to_episode(id_variant_option) { error!("failed action app.go-to-episode: {e}"); } }) .build(), gio::ActionEntryBuilder::new("go-to-show") .parameter_type(Some(i32_variant_type)) .activate(|app: &Self, _, id_variant_option| { if let Err(e) = app.go_to_show(id_variant_option) { error!("failed action app.go-to-show: {e}"); } }) .build(), gio::ActionEntryBuilder::new("undo-mark-all") .parameter_type(Some(i32_variant_type)) .activate(|app: &Self, _, id_variant_option| { let data = app.imp(); let id = ShowId(id_variant_option.unwrap().get::().unwrap()); let mut ids = data.undo_marked_ids.borrow_mut(); if !ids.contains(&id) { ids.push(id); } send_blocking!(data.sender, Action::RefreshWidgetIfSame(id)); }) .build(), gio::ActionEntryBuilder::new("undo-remove-show") .parameter_type(Some(i32_variant_type)) .activate(|app: &Self, _, id_variant_option| { let data = app.imp(); let id = ShowId(id_variant_option.unwrap().get::().unwrap()); let mut ids = data.todo_unsub_ids.borrow_mut(); ids.remove(&id); let res = utils::unignore_show(id); debug_assert!(res.is_ok()); send_blocking!(data.sender, Action::RefreshAllViews); }) .build(), ]; self.add_action_entries(actions); } /// We check if a show is still on the todo_remove list. /// Also removes it from the list, /// as this should only be called before removal. pub fn is_show_marked_delete(&self, pd: &Show) -> bool { let data = self.imp(); let id = pd.id(); let mut todo_unsub_ids = data.todo_unsub_ids.borrow_mut(); todo_unsub_ids.remove(&id) } pub fn is_show_marked_mark(&self, pd: &Show) -> bool { let data = self.imp(); let id = pd.id(); let mut undo_marked_ids = data.undo_marked_ids.borrow_mut(); if let Some(pos) = undo_marked_ids.iter().position(|x| *x == id) { undo_marked_ids.remove(pos); return false; } true } fn go_to_episode(&self, id_variant_option: Option<&glib::Variant>) -> Result<()> { let id_variant = id_variant_option.expect("missing action_target_value"); let id = id_variant.get::().expect("invalid variant type"); let ep = dbqueries::get_episode_from_id(EpisodeId(id))?; let show = dbqueries::get_podcast_from_id(ep.show_id())?; let data = self.imp(); send_blocking!( data.sender, Action::GoToEpisodeDescription(Arc::new(show), Arc::new(ep)) ); Ok(()) } fn go_to_show(&self, id_variant_option: Option<&glib::Variant>) -> Result<()> { let id_variant = id_variant_option.expect("missing action_target_value"); let id = id_variant.get::().expect("invalid variant type"); let show = dbqueries::get_podcast_from_id(ShowId(id))?; let data = self.imp(); send_blocking!(data.sender, Action::GoToShow(Arc::new(show))); Ok(()) } fn setup_accels(&self) { self.set_accels_for_action("app.quit", &["q"]); self.set_accels_for_action("win.refresh", &["r", "F5"]); self.set_accels_for_action("win.toggle-pause", &["space"]); self.set_accels_for_action("win.seek-forwards", &["Right"]); self.set_accels_for_action("win.seek-backwards", &["Left"]); self.set_accels_for_action("win.go-to-home", &["F1"]); self.set_accels_for_action("win.go-to-shows", &["F2"]); // plan: use F3 for Queue page self.set_accels_for_action("win.go-to-discovery", &["F4"]); self.set_accels_for_action("win.import", &["o"]); self.set_accels_for_action("win.export", &["e"]); self.set_accels_for_action("window.close", &["w"]); // Make sure to add new shortcuts to help-overlay.ui !!! } fn do_action(&self, action: Action) { let data = self.imp(); let w = data.window.borrow(); let window = w.as_ref().expect("Window is not initialized"); info!("Incoming channel action: {:?}", action); match action { Action::RefreshAllViews => window.content().update(), Action::RefreshShowsView => window.content().update_shows(), Action::RefreshWidgetIfSame(id) => { if let Err(e) = window.update_show_widget(id) { error!("failed to refresh show {e}"); } } Action::RefreshEpisodesView => window.content().update_home(), Action::RefreshEpisodesViewBGR => window.content().update_home_if_background(), Action::ReplaceWidget(pd) => { let widget = ShowWidget::new(pd.clone(), &data.sender); window.replace_show_widget(Some(widget), pd.title()); } Action::GoToEpisodeDescription(show, ep) => { let description_widget = EpisodeDescription::new(ep, show, window.sender().clone()); window.pop_episode_description(); window.push_page(&description_widget); } Action::GoToShow(pd) => { self.do_action(Action::ReplaceWidget(pd)); window.go_to_show_widget(); } Action::GoToFoundPodcasts(found) => { let widget = SearchResults::new(&found, window.sender()); window.push_page(&widget); } Action::CopyUrl(id) => { if let Some(uri) = dbqueries::get_episode_from_id(id) .ok() .and_then(|e| e.uri().map(|s| s.to_string())) { copy_text(&uri); self.do_action(Action::CopiedUrlNotification); } } Action::CopiedUrlNotification => { let text = i18n("Copied URL to clipboard!"); let toast = adw::Toast::new(&text); self.send_toast(toast); } Action::MarkAllPlayerNotification(pd) => { let toast = mark_all_notif(pd, &data.sender); self.send_toast(toast); } Action::MarkAsPlayed(played, id) => { if let Ok(mut ep) = dbqueries::get_episode_widget_from_id(id) { if played { let _ = ep.set_played_now(); } else { let _ = ep.set_unplayed(); } if let Some(description) = window.episode_description() { let pid = ep.show_id(); if let Ok(show) = dbqueries::get_podcast_from_id(pid) { description.update_episode_menu(&data.sender, &ep, Arc::new(show)); } } // This is slow. As it should only refresh the episode. self.do_action(Action::RefreshWidgetIfSame(ep.show_id())); self.do_action(Action::RefreshEpisodesView); self.do_action(Action::RefreshShowsView); } } Action::RemoveShow(pd) => { window.pop_show_widget(); window.pop_episode_description(); data.todo_unsub_ids.borrow_mut().insert(pd.id()); let toast = remove_show_notif(pd); self.send_toast(toast); if let Err(e) = window.content().check_empty_state() { error!("Failed to check for empty db state {e}"); } } Action::ErrorNotification(err) => { error!("An error notification was triggered: {}", err); let toast = adw::Toast::new(&err); window.add_toast(toast); } Action::StartUpdating => { window.set_updating(true); let progress = window.progress_bar(); let updating_timeout = glib::timeout_add_local( std::time::Duration::from_millis(100), clone!( #[weak] progress, #[upgrade_or] glib::ControlFlow::Break, move || { progress.set_visible(true); progress.pulse(); glib::ControlFlow::Continue } ), ); window.set_updating_timeout(Some(updating_timeout)); } Action::StopUpdating => { window.set_updating(false); window.set_updating_timeout(None); window.progress_bar().set_visible(false); } Action::FeedRefreshed(id) => { FeedManager::refresh_done(data.sender.clone(), id); } Action::InitEpisode(id) => { let res = window.init_episode(id, None, StreamMode::LocalOnly); debug_assert!(res.is_ok()); } Action::InitEpisodeAt(id, second) => { let res = window.init_episode(id, Some(second), StreamMode::StreamFallback); debug_assert!(res.is_ok()); } Action::StreamEpisode(id) => { let res = window.init_episode(id, None, StreamMode::StreamOnly); debug_assert!(res.is_ok()); } Action::UpdateMprisCover(id, dl_success) => { let res = window.player().update_mpris_cover(id, dl_success); debug_assert!(res.is_ok()); } Action::EmptyState => { if let Some(refresh_action) = window .lookup_action("refresh") .and_then(|action| action.downcast::().ok()) { refresh_action.set_enabled(false) } window.top_switcher().set_sensitive(false); window.bottom_switcher_bar().set_sensitive(false); window.content().switch_to_empty_views(); } Action::PopulatedState => { if let Some(refresh_action) = window .lookup_action("refresh") .and_then(|action| action.downcast::().ok()) { refresh_action.set_enabled(true) } window.top_switcher().set_sensitive(true); window.bottom_switcher_bar().set_sensitive(true); window.content().switch_to_populated(); } Action::RaiseWindow => window.present(), Action::InhibitSuspend => { let window: Option<>k::Window> = None; let old_cookie = *data.inhibit_cookie.borrow(); let cookie = self.inhibit( window, gtk::ApplicationInhibitFlags::SUSPEND, Some("podcast playing"), ); *data.inhibit_cookie.borrow_mut() = cookie; if old_cookie != 0 { self.uninhibit(old_cookie); } } Action::UninhibitSuspend => { let cookie = *data.inhibit_cookie.borrow(); if cookie != 0 { self.uninhibit(cookie); *data.inhibit_cookie.borrow_mut() = 0; } } }; } pub(crate) fn run() -> glib::ExitCode { // Set up the textdomain for gettext setlocale(LocaleCategory::LcAll, ""); bindtextdomain("gnome-podcasts", LOCALEDIR).expect("Unable to bind the text domain"); textdomain("gnome-podcasts").expect("Unable to switch to the text domain"); // Make sure the app icon shows up in PulseAudio settings unsafe { env::set_var("PULSE_PROP_application.icon_name", APP_ID); } let application = Self::new(); // Weird magic I copy-pasted that sets the Application Name in the Shell. glib::set_application_name(&i18n("Podcasts")); gtk::Window::set_default_icon_name(APP_ID); let args: Vec = env::args().collect(); ApplicationExtManual::run_with_args(&application, &args) } pub(crate) fn send_toast(&self, toast: adw::Toast) { self.imp() .window .borrow() .as_ref() .unwrap() .add_toast(toast); } } fn copy_text(text: &str) -> Option<()> { let display = gtk::gdk::Display::default()?; let clipboard = display.clipboard(); clipboard.set_text(text); Some(()) } podcasts-25.2/podcasts-gtk/src/config.rs.in000066400000000000000000000017051500126606300206760ustar00rootroot00000000000000/* config.rs.in * * Copyright 2019 Christopher Davis * * 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 . * * SPDX-License-Identifier: GPL-3.0-or-later */ pub static APP_ID: &str = @APP_ID@; pub static VERSION: &str = @VERSION@; pub static LOCALEDIR: &str = @LOCALEDIR@; pub static RESOURCEFILE: &[u8] = include_bytes!(@RESOURCEFILE@); podcasts-25.2/podcasts-gtk/src/download_covers.rs000066400000000000000000000411401500126606300222110ustar00rootroot00000000000000use anyhow::{Result, anyhow, bail}; use glib::WeakRef; use gtk::gdk; use gtk::glib; use gtk::prelude::*; use once_cell::sync::Lazy; use std::collections::{HashMap, HashSet}; use std::io::Cursor; use std::path::PathBuf; use tokio::sync::RwLock; // also works from gtk, unlike tokio::fs use crate::thumbnail_generator::ThumbSize; use podcasts_data::errors::DownloadError; use podcasts_data::errors::DownloadError::NoLongerNeeded; use podcasts_data::utils::get_cover_dir_path; use podcasts_data::xdg_dirs::TMP_DIR; use podcasts_data::{ShowCoverModel, ShowId}; // Downloader v3 // if a textures is in the COVER_TEXTURES cache: // - return texture from HashMap cache. // if download lock is set: // - sleep for 30 seconds in 250ms intervals // - if the lock disapears check if the texture is in cache and return // - else try to get a lock for loading. // - if the lock was aquired by another task, // sleep for 30 seconds in 25ms intervals // - if the lock disapears check if the texture is in cache and return // - else bail! and return an error // if the image is outdated (past the 4 week cache date) // - download a copy, then generate thumbnails for it and override the original // if the image file exists: // - load it into cache form fs at the right thumb size and return it // if the thumb doesn't exist but the file exists: // - download a copy, then generate thumbnails for it and override the original // if the file doesn't exist: // - download it, then generate thumbs, cache the requested thumb size // and return the texture. static CACHE_VALID_DURATION: Lazy = Lazy::new(|| chrono::Duration::weeks(4)); #[derive(Copy, Clone, Eq, Hash, PartialEq)] struct CoverId(ShowId, ThumbSize); impl From for (ShowId, ThumbSize) { fn from(cover_id: CoverId) -> (ShowId, ThumbSize) { let CoverId(id, size) = cover_id; (id, size) } } // Enum to store failed loads in cache, to avoid repeated retry failures. #[derive(Clone, Eq, Hash, PartialEq)] enum CachedTexture { Cached(gdk::Texture), FailedToLoad, } // Thumbs that are already loaded static COVER_TEXTURES: Lazy>> = Lazy::new(|| RwLock::new(HashMap::new())); // Each cover should only be downloaded once static COVER_DL_REGISTRY: Lazy>> = Lazy::new(|| RwLock::new(HashSet::new())); // Each thumb should only be loaded once static THUMB_LOAD_REGISTRY: Lazy>> = Lazy::new(|| RwLock::new(HashSet::new())); fn filename_for_download(response: &reqwest::Response) -> String { let mime = response.headers().get(reqwest::header::CONTENT_TYPE); // image-rs can get confused when the suffix is missing or wrong // Appending the suffix from the mime fixes some covers from not generating. let headers = HashMap::from([ ("image/apng", ".png"), ("image/avif", ".avif"), ("image/gif", ".gif"), ("image/jpeg", ".jpeg"), ("image/png", ".png"), ("image/svg", ".svg"), ("image/webp", ".webp"), ]); let mime_extension = mime .and_then(|m| m.to_str().ok()) .and_then(|m| headers.get(m)) .unwrap_or(&""); // Get filename from url if possible let ext = response .url() .path_segments() .and_then(|segments| segments.last()) .unwrap_or("tmp-donwload.bin"); if ext.is_empty() { return ["tmp-donwload", mime_extension].join(""); } [ext, mime_extension].join("") } pub fn clean_unfinished_downloads() -> Result<()> { info!("Starting cover locks cleanup"); let dir = TMP_DIR.clone(); for entry in std::fs::read_dir(dir)? { // keep going if any one file fails match entry.map(|e| e.path()) { Ok(path) => { if let Err(err) = cleanup_entry(&path) { error!("failed to cleanup: {} {err}", path.display()); } } Err(err) => error!("failed to get path {err}"), } } Ok(()) } fn cleanup_entry(path: &PathBuf) -> Result<()> { if path.is_file() && path.ends_with(".part") { std::fs::remove_file(path)?; } // remove tmp directories of unfinished downloads if path.is_dir() { if let Some(filename) = path.to_str() { if filename.contains("-pdcover.part") { info!("Removing unfinished download: {}", path.display()); // remove_dir_all can be risky if xdg would break, // but we are filtering for a "*-pdcover.part*" dir-name // and in a "Covers/" subdir, so it should be fine. std::fs::remove_dir_all(path)?; } } } Ok(()) } /// Covers are: XDG_CACHE/{show_title}/cover /// Thumbs are: XDG_CACHE/{show_title}/cover-{size} /// Also updates (see `determin_cover_path_for_update`) pub fn determin_cover_path(pd: &ShowCoverModel, size: Option) -> PathBuf { let mut dir = get_cover_dir_path(pd.title()); if let Some(size) = size { dir.push(format!("cover-{size}")); } else { dir.push("cover"); }; dir } /// Updates are: XDG_CACHE/Covers/{show_id}-update fn determin_cover_path_for_update(pd: &ShowCoverModel) -> PathBuf { let mut dir = get_cover_dir_path(pd.title()); let filename = format!("{}-update", pd.id().0); dir.push(filename); dir } async fn download( pd: &ShowCoverModel, cover_id: &CoverId, path: &PathBuf, just_download: bool, ) -> Result> { let url = pd .image_uri() .ok_or(anyhow!("invalid cover uri"))? .to_owned(); if url.is_empty() { bail!("No download location"); } // download into tmp_dir and move to filename let tmp_dir = tempfile::Builder::new() .suffix(&format!("{}-pdcover.part", pd.id().0)) .tempdir_in(&*TMP_DIR)?; let client = podcasts_data::downloader::client_builder().build()?; let uri = pd.image_uri().ok_or(anyhow!("No image uri for podcast"))?; let response = client.get(uri).send().await?; //FIXME: check for 200 or redirects, retry for 5xx debug!("Status Resp: {}", response.status()); let filename = filename_for_download(&response); let filename = tmp_dir.path().join(filename); info!("Downloading file into: '{:?}'", filename); { let mut dest = tokio::fs::File::create(&filename).await?; let mut content = Cursor::new(response.bytes().await?); tokio::io::copy(&mut content, &mut dest).await?; dest.sync_all().await?; } // Download done, lets generate thumbnails let thumbs = crate::thumbnail_generator::generate(pd, &filename).await?; if let Err(err) = pd.update_image_cache_values() { error!( "Failed to update cache date on Cover image: {err} for {}", path.display() ); } if just_download { tokio::fs::rename(&filename, &path).await?; return Ok(None); } if let Some(thumb_texture) = thumbs.get(&cover_id.1) { info!("Cached img into: '{}'", &path.display()); let cached = CachedTexture::Cached(thumb_texture.clone()); COVER_TEXTURES .write() .await .insert(*cover_id, cached.clone()); // Finalize // we only rename after thumbnails are generated, // so thumbnails can be presumed to exist if the orginal file exists tokio::fs::rename(&filename, &path).await?; return Ok(Some(cached)); } bail!("failed to generate thumbnails"); } async fn from_web( pd: &ShowCoverModel, cover_id: &CoverId, path: &PathBuf, ) -> Result { let result = download(pd, cover_id, path, false).await; if let Ok(Some(texture)) = result { Ok(texture) } else { // Avoid redownloading COVER_TEXTURES .write() .await .insert(*cover_id, CachedTexture::FailedToLoad); result.map(|_| CachedTexture::FailedToLoad) } } async fn cover_is_downloading(show_id: ShowId) -> bool { COVER_DL_REGISTRY.read().await.contains(&show_id) } const SLEEP_TIME: std::time::Duration = std::time::Duration::from_millis(250); const SLEEP_LIMIT: std::time::Duration = std::time::Duration::from_secs(30); async fn wait_for_download(pd: &ShowCoverModel, cover_id: &CoverId) -> Result { let mut time_waited = std::time::Duration::new(0, 0); while { // wait for download to finish tokio::time::sleep(SLEEP_TIME).await; time_waited += SLEEP_TIME; if time_waited > SLEEP_LIMIT { bail!("Waited too long for download."); } cover_is_downloading(cover_id.0).await } {} from_cache_or_fs(pd, cover_id).await } async fn from_cache_or_fs(pd: &ShowCoverModel, cover_id: &CoverId) -> Result { if let Some(texture) = from_cache(cover_id).await { Ok(texture) } else { // check if someone else is load the thumb if THUMB_LOAD_REGISTRY.read().await.contains(cover_id) { let mut time_waited = std::time::Duration::new(0, 0); while { // wait for load to finish tokio::time::sleep(std::time::Duration::from_millis(25)).await; time_waited += SLEEP_TIME; if time_waited > SLEEP_LIMIT { bail!("Waited too long for thumb read."); } THUMB_LOAD_REGISTRY.read().await.contains(cover_id) } {} return from_cache(cover_id) .await .ok_or(anyhow!("Failed to wait for thumbnail form cache.")); } let got_lock = THUMB_LOAD_REGISTRY.write().await.insert(*cover_id); if got_lock { let result = from_fs(pd, cover_id).await; THUMB_LOAD_REGISTRY.write().await.remove(cover_id); result } else { from_cache(cover_id).await.ok_or(anyhow!( "Failed to wait for thumbnail form cache (failed lock)." )) } } } async fn from_cache(cover_id: &CoverId) -> Option { COVER_TEXTURES.read().await.get(cover_id).cloned() } async fn from_fs(pd: &ShowCoverModel, cover_id: &CoverId) -> Result { let thumb = determin_cover_path(pd, Some(cover_id.1)); if let Ok(texture) = gdk::Texture::from_filename(thumb) { let cached = CachedTexture::Cached(texture); COVER_TEXTURES .write() .await .insert(*cover_id, cached.clone()); Ok(cached) } else { bail!("failed to load texture") } } async fn from_update( pd: &ShowCoverModel, cover_id: &CoverId, cover: &PathBuf, ) -> Result { // Download a potentially updated cover and replace the old. // It won't update all images instantly, // but that shouldn't be a big problem. let update_path = determin_cover_path_for_update(pd); let texture = from_web(pd, cover_id, &update_path).await?; tokio::fs::rename(&update_path, &cover).await?; Ok(texture) } async fn aquire_dl_lock(show_id: ShowId) -> bool { COVER_DL_REGISTRY.write().await.insert(show_id) } async fn drop_dl_lock(show_id: ShowId) { COVER_DL_REGISTRY.write().await.remove(&show_id); } /// Only make sure cover is downloaded without caching any textures. pub async fn just_download(pd: &ShowCoverModel) -> Result<()> { let show_id = pd.id(); if aquire_dl_lock(show_id).await { let cover = determin_cover_path(pd, None); // Won't be used because we pass `true` for just_download let unused_cover_id = CoverId(show_id, crate::Thumb64); let result = download(pd, &unused_cover_id, &cover, true).await; drop_dl_lock(show_id).await; result?; } else { while { // wait for download to finish tokio::time::sleep(std::time::Duration::from_millis(250)).await; cover_is_downloading(show_id).await } {} } Ok(()) } /// Caches and returns the texture, may also download and update it. async fn load_texture(pd: &ShowCoverModel, thumb_size: ThumbSize) -> Result { if pd.image_uri().is_none() { bail!("no image_uri for this show: {}", pd.title()); } let show_id = pd.id(); let cover_id = CoverId(show_id, thumb_size); // early return from memory cache if let Some(texture) = from_cache(&cover_id).await { return Ok(texture); } // already loading if cover_is_downloading(show_id).await { return wait_for_download(pd, &cover_id).await; } // other task is already loading it. if !aquire_dl_lock(show_id).await { return wait_for_download(pd, &cover_id).await; } // check for invalid cache if !pd.is_cached_image_valid(&CACHE_VALID_DURATION) { let cover = determin_cover_path(pd, None); let result = from_update(pd, &cover_id, &cover).await; let result = if let Err(err) = result { warn!("Failed to update cover, reusing the already download one. {err}"); from_fs(pd, &cover_id).await } else { result }; drop_dl_lock(show_id).await; return result; } // load from fs if let Ok(texture) = from_fs(pd, &cover_id).await { drop_dl_lock(show_id).await; return Ok(texture); } // So isn't downloaded yet or something is broken. let cover = determin_cover_path(pd, None); let thumb = determin_cover_path(pd, Some(thumb_size)); let cover_exists = cover.exists(); // Fallback for if we add more/different thumb sizes, // or the user messed with the cache, or the DL was broken (e.g.: html error page). if !thumb.exists() && cover_exists { warn!( "Cover exists, but thumb is missing, Maybe Download was broken. Redownloading Cover!" ); let result = from_update(pd, &cover_id, &cover).await; drop_dl_lock(show_id).await; return result; } // load from web if !cover_exists { info!("Downloading cover: {}", cover.display()); let result = from_web(pd, &cover_id, &cover).await; drop_dl_lock(show_id).await; result } else { drop_dl_lock(show_id).await; bail!("The cover exists, but we can't load it?") } } pub trait TextureWidget { fn set_from_texture(&self, texture: &gdk::Texture); } impl TextureWidget for gtk::Image { fn set_from_texture(&self, texture: &gdk::Texture) { self.set_paintable(Some(texture)); } } impl TextureWidget for gtk::Picture { fn set_from_texture(&self, texture: &gdk::Texture) { self.set_paintable(Some(texture)); } } async fn load_paintable_async( image: &WeakRef, podcast_id: ShowId, size: ThumbSize, ) -> Result<()> where T: TextureWidget + IsA, { use podcasts_data::dbqueries; let pd = crate::RUNTIME .spawn_blocking(move || dbqueries::get_podcast_cover_from_id(podcast_id).unwrap()) .await?; if let Some(image) = image.upgrade() { image.set_tooltip_text(Some(pd.title())); } else { return Err(NoLongerNeeded.into()); } let result = crate::RUNTIME .spawn(async move { load_texture(&pd, size).await }) .await; match result { Ok(Ok(CachedTexture::Cached(texture))) => { if let Some(image) = image.upgrade() { image.set_from_texture(&texture); return Ok(()); } Err(NoLongerNeeded.into()) } Ok(Ok(CachedTexture::FailedToLoad)) => { bail!("Ignoring cover after failure to load. {podcast_id:#?}") } Ok(Err(err)) => bail!("Failed to load Show Cover: {err}"), Err(err) => bail!("Failed to load Show Cover with thread-error: {err}"), } } pub fn load_widget_texture(widget: &T, show_id: ShowId, size: ThumbSize) -> glib::JoinHandle<()> where T: TextureWidget + IsA, { // TODO Surface has scale() fn that returns a f64 dpi-scale, maybe use that? // TODO maybe load the full size image when bigger than 512 is requested? let size = size.hidpi(widget.scale_factor()).unwrap_or(crate::Thumb512); let widget = widget.downgrade(); crate::MAINCONTEXT.spawn_local_with_priority(glib::source::Priority::LOW, async move { if let Err(err) = load_paintable_async(&widget, show_id, size).await { if let Some(DownloadError::NoLongerNeeded) = err.downcast_ref::() { // weak image reference couldn't be upgraded, no need to print this return; } error!("Failed to load image: {err}"); } }) } podcasts-25.2/podcasts-gtk/src/episode_description_parser.rs000066400000000000000000001435201500126606300244350ustar00rootroot00000000000000// episode_description.rs // // Copyright 2021 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use linkify::LinkFinder; use linkify::LinkKind; use regex::Regex; use crate::i18n::i18n_f; use html5ever::tendril::TendrilSink; use html5ever::tree_builder::TreeBuilderOpts; use html5ever::{ParseOpts, expanded_name, parse_document}; use markup5ever_rcdom::{ Handle, NodeData::{Document, Element, Text}, RcDom, }; const INDENT: i32 = 4; // used by li tags #[derive(Clone)] enum NewlineHandling { ToSpace, Remove, Keep, } fn escape_text(t: &str, newline_handling: NewlineHandling) -> String { // TODO prevent escaping escape-sequances let escaped = t .replace('&', "&") .replace('<', "<") .replace('>', ">"); let newlined_text = match newline_handling { NewlineHandling::ToSpace => escaped.replace('\n', " "), NewlineHandling::Remove => escaped.replace('\n', ""), NewlineHandling::Keep => escaped, }; collapse_whitespaces(newlined_text) } // remove spaces that follow on another space or a newline fn collapse_whitespaces(string: String) -> String { let mut was_space = false; string .chars() .filter(|c| { let is_space = c.eq(&' '); if is_space && was_space { return false; } was_space = is_space || c.eq(&'\n'); true }) .collect() } // Does the description use \n Text newlines or

Tag newlines #[derive(Debug)] enum NewlineStyle { Text, Tag, } enum ListStyle { Ordered(i32), Unordered, } struct ParserState { nl_style: NewlineStyle, skip_leading_spaces: bool, indent: i32, list_style: Vec, inside_link: i32, } fn find_newline_style(node: &Handle) -> NewlineStyle { match &node.data { Document => { let children = node.children.borrow(); for el in children.iter() { if let NewlineStyle::Tag = find_newline_style(el) { return NewlineStyle::Tag; } } } Element { name, .. } => { match name.expanded() { expanded_name!(html "p") => { return NewlineStyle::Tag; } expanded_name!(html "br") => { return NewlineStyle::Tag; } _ => (), }; let children = node.children.borrow(); for el in children.iter() { if let NewlineStyle::Tag = find_newline_style(el) { return NewlineStyle::Tag; } } } _ => (), } NewlineStyle::Text } fn handle_child(buffer: &mut String, node: &Handle, state: &mut ParserState) { match &node.data { Document => { let children = node.children.borrow(); for el in children.iter() { handle_child(buffer, el, state); } } Element { name, attrs, .. } => { let mut wrapper_href = None; let mut is_p_tag = false; let mut is_list_tag = false; let wrapper_tag = match name.expanded() { // Supported tags in pango markup // https://docs.gtk.org/Pango/pango_markup.html expanded_name!(html "a") => { let local_name = local_name!("href"); wrapper_href = attrs .borrow() .iter() .find(|attr| attr.name.local == local_name) .cloned(); // Pango does not support a tags without href, // so return a None in that case wrapper_href.as_ref().map(|_| "a") } expanded_name!(html "p") => { is_p_tag = true; None } expanded_name!(html "br") => { buffer.push('\n'); state.skip_leading_spaces = true; None } expanded_name!(html "img") => { let local_name = local_name!("alt"); let alt = attrs .borrow() .iter() .find(|attr| attr.name.local == local_name) .cloned(); if let Some(alt_text) = alt { let escaped_alt = escape_text(&alt_text.value, NewlineHandling::ToSpace); buffer.push('['); buffer.push_str(escaped_alt.as_str()); buffer.push_str("]\n"); state.skip_leading_spaces = true; } None } expanded_name!(html "ol") => { state.list_style.push(ListStyle::Ordered(1)); state.indent += INDENT; is_list_tag = true; is_p_tag = true; None } expanded_name!(html "ul") => { state.list_style.push(ListStyle::Unordered); state.indent += INDENT; is_list_tag = true; is_p_tag = true; None } expanded_name!(html "li") => { buffer.push('\n'); for _ in 0..state.indent { buffer.push(' '); } if let Some(style) = state.list_style.last_mut() { match style { ListStyle::Unordered => buffer.push_str("• "), ListStyle::Ordered(i) => { buffer.push_str(&format!("{}. ", i)); *style = ListStyle::Ordered(*i + 1); } } } state.skip_leading_spaces = true; None } expanded_name!(html "b") => Some("b"), expanded_name!(html "i") => Some("i"), expanded_name!(html "s") => Some("s"), expanded_name!(html "u") => Some("u"), expanded_name!(html "tt") => Some("tt"), expanded_name!(html "pre") => Some("tt"), expanded_name!(html "code") => Some("tt"), expanded_name!(html "sub") => Some("sub"), expanded_name!(html "sup") => Some("sup"), _ => None, }; // Invalid link tag, links that point to # lead nowhere, skip the tag. let skip_tag = if let Some(href) = wrapper_href.as_ref() { wrapper_tag == Some("a") && (href.value.trim_start().starts_with('#') || href.value.trim().is_empty() || href.value.trim_start().starts_with("jump:")) } else { false }; let wrote_tag = if skip_tag { false } else if let Some(tag) = wrapper_tag { buffer.push('<'); buffer.push_str(tag); let is_link; if let Some(href) = wrapper_href { buffer.push_str(" href=\""); buffer.push_str(&escape_text(&href.value, NewlineHandling::Remove)); buffer.push('"'); state.inside_link += 1; is_link = true; } else { is_link = false; } buffer.push('>'); let children = node.children.borrow(); for el in children.iter() { handle_child(buffer, el, state); } buffer.push_str("'); if is_link { state.inside_link -= 1; } true } else { false }; if !wrote_tag { let children = node.children.borrow(); for el in children.iter() { handle_child(buffer, el, state); } if is_p_tag { buffer.push_str("\n\n"); state.skip_leading_spaces = true; } if is_list_tag { state.indent -= INDENT; state.list_style.pop(); } } } Text { contents } => { let nl_handling = match state.nl_style { NewlineStyle::Tag => NewlineHandling::ToSpace, NewlineStyle::Text => NewlineHandling::Keep, }; if state.skip_leading_spaces { let text = escape_text(contents.borrow().trim_start(), nl_handling.clone()); if !text.is_empty() { state.skip_leading_spaces = false; } if state.inside_link > 0 { // avoid nested links push_remaining_text(buffer, &text) } else { push_timestamped_text(buffer, &text, nl_handling) } } else { let text = escape_text(&contents.borrow(), nl_handling.clone()); if state.inside_link > 0 { // avoid nested links push_remaining_text(buffer, &text) } else { push_timestamped_text(buffer, &text, nl_handling) } } } _ => (), } } fn push_timestamped_text(buffer: &mut String, text: &str, nl_handling: NewlineHandling) { let mut position = 0; if let Ok(re) = Regex::new(r"([0-9]+):([0-9]+)(?::([0-9]+))?") { for captures in re.captures_iter(text) { let first: Option = captures.get(1).and_then(|c| c.as_str().parse().ok()); let second: Option = captures.get(2).and_then(|c| c.as_str().parse().ok()); let third: Option = captures.get(3).and_then(|c| c.as_str().parse().ok()); if let (Some(hours), Some(minutes), Some(seconds)) = (first, second, third) { let jump_time = (hours * 60 * 60) + (minutes * 60) + seconds; // Jump to Hours:Minutes:Seconds let title = i18n_f( "Jump to {}:{}:{}", &[ &format!("{:02}", hours), &format!("{:02}", minutes), &format!("{:02}", seconds), ], ); let range = captures.get(0).unwrap().range(); push_text_with_links(buffer, &text[position..range.start], nl_handling.clone()); buffer .push_str(format!("").as_str()); buffer.push_str(&text[range.start..range.end]); buffer.push_str(""); position = range.end; } else if let (Some(minutes), Some(seconds)) = (first, second) { let jump_time = (minutes * 60) + seconds; // Jump to Minutes:Seconds let title = i18n_f( "Jump to {}:{}", &[&format!("{:02}", minutes), &format!("{:02}", seconds)], ); let range = captures.get(0).unwrap().range(); push_text_with_links(buffer, &text[position..range.start], nl_handling.clone()); buffer.push_str( format!("", jump_time).as_str(), ); buffer.push_str(&text[range.start..range.end]); buffer.push_str(""); position = range.end; } } push_text_with_links(buffer, &text[position..], nl_handling); } else { push_text_with_links(buffer, text, nl_handling); } } fn push_text_with_links(buffer: &mut String, text: &str, nl_handling: NewlineHandling) { let mut finder = LinkFinder::new(); finder.url_must_have_scheme(false); let mut position = 0; for link in finder.links(text) { let link_str = link.as_str(); let remaining_link_text = escape_text(&text[position..link.start()], nl_handling.clone()); push_remaining_text(buffer, &remaining_link_text); match link.kind() { LinkKind::Email => { buffer.push_str(format!("", link_str).as_str()); } LinkKind::Url => { if link.as_str().starts_with("http://") || link_str.starts_with("https://") { buffer.push_str(format!("", link_str).as_str()); } else { buffer.push_str(format!("", link_str).as_str()); } } _ => { buffer.push_str(format!("", link_str).as_str()); } } push_remaining_text(buffer, &text[(link.start())..(link.end())]); buffer.push_str(""); position = link.end(); } let end_text = escape_text(&text[position..], nl_handling); push_remaining_text(buffer, &end_text); } pub fn push_remaining_text(buffer: &mut String, text: &str) { // start adding new plaintext replacements here buffer.push_str(text); } pub fn html2pango_markup(t: &str) -> String { let mut buffer = String::with_capacity(t.len()); let opts = ParseOpts { tree_builder: TreeBuilderOpts { drop_doctype: true, ..Default::default() }, ..Default::default() }; let dom: RcDom = parse_document(RcDom::default(), opts) .from_utf8() .read_from(&mut t.as_bytes()) .unwrap(); let root: Handle = dom.document; let nl_style = find_newline_style(&root); handle_child( &mut buffer, &root, &mut ParserState { nl_style, skip_leading_spaces: true, indent: 0, list_style: vec![], inside_link: 0, }, ); buffer } #[cfg(test)] mod tests { use super::*; // feeds to test the UI // // html/images/escape-codes/lots of spacing & weirdly placed newlines: // http://faif.us/feeds/cast-ogg/ // // html/jump-codes: // https://soundcloud.com/jimquisition // // html/jump-codes/lists: // https://rustacean-station.org/podcast.rss // // text-newlines: // https://podcasts.apple.com/de/podcast/liv-agar/id1535599001 // https://soundcloud.com/qanonanonymous // https://soundcloud.com/chapo-trap-house // // html/lists/lots of text & linked sources: // http://feeds.libsyn.com/152597/rss #[test] fn test_html_based() { let description = "

Here is an unlocked Britainology to hopefully brighten your weekend. Yes, it was a little bit late when it came out. It was late because Nate and Milo got exposed to covid right before Christmas and couldn\'t record while waiting on test results. But we have in fact discussed Jim Davidson\'s terrible bawdy Cinderella pantomime from 1995, entitled \'Sinderella\', and discussed the concept of pantos in general. Hope you enjoy!

\n\n

We\'ve also unlocked the Dogging episode of Britainology to the $5 tier--get it here: https://www.patreon.com/posts/51682396

\n\n

And if you want two (2) Britainologies a month, sign up on the $10 tier on Patreon! This month\'s second episode features us discussing the British Army with friend of the show, veteran, leftist, and author Joe Glenton: https://www.patreon.com/posts/56590770

\n\n

*MILO ALERT* Smoke returns for another night of new material from pro-comics featuring Edinburgh Comedy Award winner Jordan Brookes. See it all, for the low price of £5, on September 28 at 8 pm at The Sekforde Arms (34 Sekforde Street London EC1R 0HA): https://www.eventbrite.co.uk/e/smoke-comedy-featuring-jordan-brookes-tickets-171869475227

\n\n

Trashfuture are: Riley (@raaleh), Milo (@Milo_Edwards), Hussein (@HKesvani), Nate (@inthesedeserts), and Alice (@AliceAvizandum)

"; let expected = "Here is an unlocked Britainology to hopefully brighten your weekend. Yes, it was a little bit late when it came out. It was late because Nate and Milo got exposed to covid right before Christmas and couldn't record while waiting on test results. But we have in fact discussed Jim Davidson's terrible bawdy Cinderella pantomime from 1995, entitled 'Sinderella', and discussed the concept of pantos in general. Hope you enjoy!\n\nWe've also unlocked the Dogging episode of Britainology to the $5 tier--get it here: https://www.patreon.com/posts/51682396\n\nAnd if you want two (2) Britainologies a month, sign up on the $10 tier on Patreon! This month's second episode features us discussing the British Army with friend of the show, veteran, leftist, and author Joe Glenton: https://www.patreon.com/posts/56590770\n\n*MILO ALERT* Smoke returns for another night of new material from pro-comics featuring Edinburgh Comedy Award winner Jordan Brookes. See it all, for the low price of £5, on September 28 at 8 pm at The Sekforde Arms (34 Sekforde Street London EC1R 0HA): https://www.eventbrite.co.uk/e/smoke-comedy-featuring-jordan-brookes-tickets-171869475227\n\nTrashfuture are: Riley (@raaleh), Milo (@Milo_Edwards), Hussein (@HKesvani), Nate (@inthesedeserts), and Alice (@AliceAvizandum)\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_html_based2() { let description = "

So, in rank defiance of our recent promise to \'get back to the nazis\' instead we continue our James Lindsay coverage.  (What... me? Irony? How dare you?)  This time, Daniel patiently walks a distracted, slightly hyperactive, and increasingly incredulous Jack through the infamous \'Grievance Studies Hoax\' (AKA \'Sokal Squared\') in which Lindsay and colleagues Helen Pluckrose and Peter Boghossian tried (and then claimed) to prove something or other about modern Humanities academia by submitting a load of stupid fake papers to various feminist and fat studies journals.  As Daniel reveals, the episode was an orgy of dishonesty and tactical point-missing that actually proved the opposite of what the team of snickering tricksters thought they were proving.  Sadly, however, because we live in Hell, the trio have only raised their profiles as a result.  A particular highlight of the episode is Lindsay revealing his staggering ignorance when \'responding\' to criticism.

Content warnings, as ever.

Podcast Notes:

Please consider donating to help us make the show and stay independent.  Patrons get exclusive access to one full extra episode a month.

Daniel\'s Patreon: https://www.patreon.com/danielharper

Jack\'s Patreon: https://www.patreon.com/user?u=4196618

IDSG Twitter: https://twitter.com/idsgpod

Daniel\'s Twitter: @danieleharper

Jack\'s Twitter: @_Jack_Graham_

IDSG on Apple Podcasts: https://podcasts.apple.com/us/podcast/i-dont-speak-german/id1449848509?ls=1

 

Show Notes:

James Lindsay, New Discourses, \"Why You Can Be Transgender But Not Transracial.\"\" https://newdiscourses.com/2021/06/why-you-can-be-transgender-but-not-transracial/

James Lindsay has a day job, apparently. \"Maryville man walks path of healing and combat.\" https://www.thedailytimes.com/news/maryville-man-walks-path-of-healing-and-combat/article_5ea3c0ca-2e98-5283-9e59-06861b8588cb.html

Areo Magazine, Academic Grievance Studies and the Corruption of Scholarship. https://areomagazine.com/2018/10/02/academic-grievance-studies-and-the-corruption-of-scholarship/

Full listing of Grievance Studies Papers and Reviews. https://drive.google.com/drive/folders/19tBy_fVlYIHTxxjuVMFxh4pqLHM_en18

\'Mein Kampf\' and the \'Feminazis\': What Three Academics\' Hitler Hoax Really Reveals About \'Wokeness\'. https://web.archive.org/web/20210328112901/https://www.haaretz.com/us-news/.premium-hitler-hoax-academic-wokeness-culture-war-1.9629759

\"First and foremost, the source material. The chapter the hoaxers chose, not by coincidence, one of the least ideological and racist parts of Hitler\'s book. Chapter 12, probably written in April/May 1925, deals with how the newly refounded NSDAP should rebuild as a party and amplify its program.

\"According to their own account, the writers took parts of the chapter and inserted feminist \"buzzwords\"; they \"significantly changed\" the \"original wording and intent” of the text to make the paper \"publishable and about feminism.\" An observant reader might ask: what could possibly remain of any Nazi content after that? But no one in the media, apparently, did.\"

New Discourses, \"There Is No Good Part of Hitler\'s Mein Kampf\" https://newdiscourses.com/2021/03/there-is-no-good-part-of-hitlers-mein-kampf/

On this episode of the New Discourses Podcast, James Lindsay, who helped to write the paper and perpetrate the Grievance Studies Affair, talks about the project and the creation of this particular paper at unprecedented length and in unprecedented detail, revealing Nilssen not to know what he’s talking about. If you have ever wondered about what the backstory of the creation of the “Feminist Mein Kampf” paper really was, including why its authors did it, you won’t want to miss this long-form discussion and rare response to yet another underinformed critic of Lindsay, Boghossian, and Pluckrose’s work.

The Grieveance Studies Affair Revealed. https://www.youtube.com/watch?v=kVk9a5Jcd1k

Reviewer 1 Comments on Dog Park Paper

\"page 9 - the human subjects are afforded anonymity and not asked about income, etc for ethical reasons. yet, the author as researcher intruded into the dogs\' spaces to examine and record genitalia. I realize this was necessary to the project, but could the author acknowledge/explain/justify this (arguably, anthropocentric) difference? Indicating that it was necessary to the research would suffice but at least the difference should be acknowledged.\"

Nestor de Buen, Anti-Science Humping in the Dog Park. https://conceptualdisinformation.substack.com/p/anti-science-humping-in-the-dog-park

\"What is even more striking is that if the research had actually been conducted and the results showed what the paper says they show, there is absolutely no reason why it should not have been published. And moreover, what it proves is the opposite of what its intention is. It shows that one can make scientifically testable claims based on the conceptual framework of gender studies, and that the field has all the markings of a perfectly functional research programme.\"

\"Yes, the dog park paper is based on false data and, like Sokal’s, contains a lot of unnecessary jargon, but it is not nonsense, and the distinction is far from trivial. Nonsense implies one cannot even obtain a truth value from a proposition. In fact, the paper being false, if anything, proves that it is not nonsense, yet the grievance hoaxers try to pass falsity as nonsense. Nonsense is something like Chomsky’s famous sentence “colorless green ideas sleep furiously.” It is nonsense because it is impossible to decide how one might evaluate whether it is true. A false sentence would be “the moon is cubical.” It has a definite meaning, it just happens not to be true. 

\"So, if the original Sokal Hoax is like Chomsky’s sentence, the dog park paper is much more like “the moon is cubical.” And in fact, a more accurate analogy would be “the moon is cubical and here is a picture that proves it,” and an attached doctored picture of the cubical moon.\"

Reviewer 2 Comments on the Dog-Park Paper

\"I am a bit curious about your methodology. Can you say more? You describe your methods here (procedures for collecting data), but not really your overall approach to methodology. Did you just show up, observe, write copious notes, talk to people when necessary, and then leave? If so, it might be helpful to explicitly state this. It sounds to me like you did a kind of ethnography (methodology — maybe multispecies ethnography?) but that’s not entirely clear here. Or are you drawing on qualitative methods in social behaviorism/symbolic interactionism? In either case, the methodology chosen should be a bit more clearly articulated.\"

Counterweight. https://counterweightsupport.com/

\"Welcome to Counterweight, the home of scholarship and advice on [Critical Social Justice](https://counterweightsupport.com/2021/02/17/what-do-we-mean-by-critical-social-justice/) ideology. We are here to connect you with the resources, advice and guidance you need to address CSJ beliefs as you encounter them in your day-to-day life. The Counterweight community is a non-partisan, grassroots movement advocating for liberal concepts of social justice including individualism, universalism, viewpoint diversity and the free exchange of ideas. [Subscribe](https://counterweightsupport.com/subscribe-to-counterweight/) today to become part of the Counterweight movement.\"\"

Inside Higher Ed, \"Blowback Against a Hoax.\" https://www.insidehighered.com/news/2019/01/08/author-recent-academic-hoax-faces-disciplinary-action-portland-state

Peter Boghossian Resignation Latter from PSU. https://bariweiss.substack.com/p/my-university-sacrificed-ideas-for

 

"; let expected = "So, in rank defiance of our recent promise to 'get back to the nazis' instead we continue our James Lindsay coverage.\u{a0} (What... me? Irony? How dare you?)\u{a0} This time, Daniel patiently walks a distracted, slightly hyperactive, and increasingly incredulous Jack through the infamous 'Grievance Studies Hoax' (AKA 'Sokal Squared') in which Lindsay and colleagues Helen Pluckrose and Peter Boghossian tried (and then claimed) to prove something or other about modern Humanities academia by submitting a load of stupid fake papers to various feminist and fat studies journals.\u{a0} As Daniel reveals, the episode was an orgy of dishonesty and tactical point-missing that actually proved the opposite of what the team of snickering tricksters thought they were proving.\u{a0} Sadly, however, because we live in Hell, the trio have only raised their profiles as a result.\u{a0} A particular highlight of the episode is Lindsay revealing his staggering ignorance when 'responding' to criticism.\n\nContent warnings, as ever.\n\nPodcast Notes:\n\nPlease consider donating to help us make the show and stay independent.\u{a0} Patrons get exclusive access to one full extra episode a month.\n\nDaniel's Patreon: https://www.patreon.com/danielharper\n\nJack's Patreon: https://www.patreon.com/user?u=4196618\n\nIDSG Twitter: https://twitter.com/idsgpod\n\nDaniel's Twitter: @danieleharper\n\nJack's Twitter: @_Jack_Graham_\n\nIDSG on Apple Podcasts: https://podcasts.apple.com/us/podcast/i-dont-speak-german/id1449848509?ls=1\n\n\n\nShow Notes:\n\nJames Lindsay, New Discourses, \"Why You Can Be Transgender But Not Transracial.\"\" https://newdiscourses.com/2021/06/why-you-can-be-transgender-but-not-transracial/\n\nJames Lindsay has a day job, apparently. \"Maryville man walks path of healing and combat.\" https://www.thedailytimes.com/news/maryville-man-walks-path-of-healing-and-combat/article_5ea3c0ca-2e98-5283-9e59-06861b8588cb.html\n\nAreo Magazine, Academic Grievance Studies and the Corruption of Scholarship. https://areomagazine.com/2018/10/02/academic-grievance-studies-and-the-corruption-of-scholarship/\n\nFull listing of Grievance Studies Papers and Reviews. https://drive.google.com/drive/folders/19tBy_fVlYIHTxxjuVMFxh4pqLHM_en18\n\n'Mein Kampf' and the 'Feminazis': What Three Academics' Hitler Hoax Really Reveals About 'Wokeness'. https://web.archive.org/web/20210328112901/https://www.haaretz.com/us-news/.premium-hitler-hoax-academic-wokeness-culture-war-1.9629759\n\n\"First and foremost, the source material. The chapter the hoaxers chose, not by coincidence, one of the least ideological and racist parts of Hitler's book. Chapter 12, probably written in April/May 1925, deals with how the newly refounded NSDAP should rebuild as a party and amplify its program.\n\n\"According to their own account, the writers took parts of the chapter and inserted feminist \"buzzwords\"; they \"significantly changed\" the \"original wording and intent” of the text to make the paper \"publishable and about feminism.\" An observant reader might ask: what could possibly remain of any Nazi content after that? But no one in the media, apparently, did.\"\n\nNew Discourses, \"There Is No Good Part of Hitler's Mein Kampf\" https://newdiscourses.com/2021/03/there-is-no-good-part-of-hitlers-mein-kampf/\n\nOn this episode of the New Discourses Podcast, James Lindsay, who helped to write the paper and perpetrate the Grievance Studies Affair, talks about the project and the creation of this particular paper at unprecedented length and in unprecedented detail, revealing Nilssen not to know what he’s talking about. If you have ever wondered about what the backstory of the creation of the “Feminist Mein Kampf” paper really was, including why its authors did it, you won’t want to miss this long-form discussion and rare response to yet another underinformed critic of Lindsay, Boghossian, and Pluckrose’s work.\n\nThe Grieveance Studies Affair Revealed. https://www.youtube.com/watch?v=kVk9a5Jcd1k\n\nReviewer 1 Comments on Dog Park Paper\n\n\"page 9 - the human subjects are afforded anonymity and not asked about income, etc for ethical reasons. yet, the author as researcher intruded into the dogs' spaces to examine and record genitalia. I realize this was necessary to the project, but could the author acknowledge/explain/justify this (arguably, anthropocentric) difference? Indicating that it was necessary to the research would suffice but at least the difference should be acknowledged.\"\n\nNestor de Buen, Anti-Science Humping in the Dog Park. https://conceptualdisinformation.substack.com/p/anti-science-humping-in-the-dog-park\n\n\"What is even more striking is that if the research had actually been conducted and the results showed what the paper says they show, there is absolutely no reason why it should not have been published. And moreover, what it proves is the opposite of what its intention is. It shows that one can make scientifically testable claims based on the conceptual framework of gender studies, and that the field has all the markings of a perfectly functional research programme.\"\n\n\"Yes, the dog park paper is based on false data and, like Sokal’s, contains a lot of unnecessary jargon, but it is not nonsense, and the distinction is far from trivial. Nonsense implies one cannot even obtain a truth value from a proposition. In fact, the paper being false, if anything, proves that it is not nonsense, yet the grievance hoaxers try to pass falsity as nonsense. Nonsense is something like Chomsky’s famous sentence “colorless green ideas sleep furiously.” It is nonsense because it is impossible to decide how one might evaluate whether it is true. A false sentence would be “the moon is cubical.” It has a definite meaning, it just happens not to be true.\u{a0}\n\n\"So, if the original Sokal Hoax is like Chomsky’s sentence, the dog park paper is much more like “the moon is cubical.” And in fact, a more accurate analogy would be “the moon is cubical and here is a picture that proves it,” and an attached doctored picture of the cubical moon.\"\n\nReviewer 2 Comments on the Dog-Park Paper\n\n\"I am a bit curious about your methodology. Can you say more? You describe your methods here (procedures for collecting data), but not really your overall approach to methodology. Did you just show up, observe, write copious notes, talk to people when necessary, and then leave? If so, it might be helpful to explicitly state this. It sounds to me like you did a kind of ethnography (methodology — maybe multispecies ethnography?) but that’s not entirely clear here. Or are you drawing on qualitative methods in social behaviorism/symbolic interactionism? In either case, the methodology chosen should be a bit more clearly articulated.\"\n\nCounterweight. https://counterweightsupport.com/\n\n\"Welcome to Counterweight, the home of scholarship and advice on [Critical Social Justice](https://counterweightsupport.com/2021/02/17/what-do-we-mean-by-critical-social-justice/) ideology. We are here to connect you with the resources, advice and guidance you need to address CSJ beliefs as you encounter them in your day-to-day life. The Counterweight community is a non-partisan, grassroots movement advocating for liberal concepts of social justice including individualism, universalism, viewpoint diversity and the free exchange of ideas. [Subscribe](https://counterweightsupport.com/subscribe-to-counterweight/) today to become part of the Counterweight movement.\"\"\n\nInside Higher Ed, \"Blowback Against a Hoax.\" https://www.insidehighered.com/news/2019/01/08/author-recent-academic-hoax-faces-disciplinary-action-portland-state\n\nPeter Boghossian Resignation Latter from PSU. https://bariweiss.substack.com/p/my-university-sacrificed-ideas-for\n\n\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_newline_based() { let description = "Also available in video form at https://youtu.be/NUPWY_evu30\n\ \n\ In a recent view by Contrapoints, she goes over her account of envy and its connection with online politics. In doing so she utilizes Nietzsche (alongside a critique of Nietzsche). How accurate is this account to Nietzsche\'s work and where does it go wrong? \n\ \n\ Thank you to We\'re in Hell, BadEmpanada, and Chelsea Manning for the voice lines! \n\ \n\ Edited by Lexi Fontaine: https://twitter.com/softgothoutlaw \n\ \n\ Music by Alex Ballantyne: https://transistorriot.bandcamp.com \n\ \n\ This was an early release to my patrons at https://pateron.com/livagar \n\ \n\ Watch me stream on twitch at https://twitch.tv/livagar \n\ \n\ All of my links at https:// livagar.com"; let expected = "Also available in video form at https://youtu.be/NUPWY_evu30\n\ \n\ In a recent view by Contrapoints, she goes over her account of envy and its connection with online politics. In doing so she utilizes Nietzsche (alongside a critique of Nietzsche). How accurate is this account to Nietzsche's work and where does it go wrong? \n\ \n\ Thank you to We're in Hell, BadEmpanada, and Chelsea Manning for the voice lines! \n\ \n\ Edited by Lexi Fontaine: https://twitter.com/softgothoutlaw \n\ \n\ Music by Alex Ballantyne: https://transistorriot.bandcamp.com \n\ \n\ This was an early release to my patrons at https://pateron.com/livagar \n\ \n\ Watch me stream on twitch at https://twitch.tv/livagar \n\ \n\ All of my links at https:// livagar.com"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_newline_based2() { let description = "We’re back to a normal-style ep after a week of interviews. We’re taking a look at the fast-tracked aid package to intelligence agents suffering unreality issues, the Biden administration addressing just the optics at the border, and AOC addressing just the optics of the Iron Dome bill. Finally, we having a reading series that functions as a bit of a coda to Will and Matt’s visit to Ozy Fest way back in 2018.\n\nOne last time, go subscribe to https://www.youtube.com/chapotraphouse\n\nAnd go grab some of Simon Roy’s great posters over at https://shop.chapotraphouse.com/\nMore merch coming soon!"; let expected = "We’re back to a normal-style ep after a week of interviews. We’re taking a look at the fast-tracked aid package to intelligence agents suffering unreality issues, the Biden administration addressing just the optics at the border, and AOC addressing just the optics of the Iron Dome bill. Finally, we having a reading series that functions as a bit of a coda to Will and Matt’s visit to Ozy Fest way back in 2018.\n\nOne last time, go subscribe to https://www.youtube.com/chapotraphouse\n\nAnd go grab some of Simon Roy’s great posters over at https://shop.chapotraphouse.com/\nMore merch coming soon!"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_list_ordered() { let description = "
  1. first
  2. second
  3. third
"; let expected = "\n 1. first\n 2. second\n 3. third\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_list_unordered() { let description = "
  • first
  • second
  • third
"; let expected = "\n • first\n • second\n • third\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_list_ordered_nested() { let description = "
  1. first
  2. second
    1. sub list first
    2. sub list second
  3. third
"; let expected = "\n 1. first\n 2. second\n 1. sub list first\n 2. sub list second\n\n\n 3. third\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_timecode() { let description = "\ \

Timestamps

\
    \
  • [@0:33] - Daniel’s introduction
  • \
  • [@3:38] - Tauri’s focus on safety and security
  • \
  • [@6:50] - Tauri’s mission to reduce their footprint
  • \
  • [@14:48] - How does Tauri handles features that are not supported across different platforms
  • \
  • [@23:56] - How does Tauri monetize to keep the project going?
  • \
  • [@26:16] - Why choose Tauri over other solutions?
  • \
  • [@28:57] - What are the tools being built with Tauri?
  • \
  • [@31:09] - Tyler’s programming background
  • \
  • [@35:11] - Tauri’s future release and features
  • \
  • [@38:38] - ‘Tauri Foundations’ book by Daniel Thompson-Yvetot and Lucas Nogueira
  • \
  • [@40:00] - Requirement on building a Tauri app
  • \
  • [@43:13] - Parting thoughts
  • \
"; let expected = "\n\ \x20 • Twitter: @rustaceanfm\n\ \x20 • Discord: Rustacean Station\n\ \x20 • Github: @rustacean-station\n\ \x20 • Email: hello@rustacean-station.org\n\ \n\ Timestamps\n\ \x20 • [@0:33] - Daniel’s introduction\n\ \x20 • [@3:38] - Tauri’s focus on safety and security\n\ \x20 • [@6:50] - Tauri’s mission to reduce their footprint\n\ \x20 • [@14:48] - How does Tauri handles features that are not supported across different platforms\n\ \x20 • [@23:56] - How does Tauri monetize to keep the project going?\n\ \x20 • [@26:16] - Why choose Tauri over other solutions?\n\ \x20 • [@28:57] - What are the tools being built with Tauri?\n\ \x20 • [@31:09] - Tyler’s programming background\n\ \x20 • [@35:11] - Tauri’s future release and features\n\ \x20 • [@38:38] - ‘Tauri Foundations’ book by Daniel Thompson-Yvetot and Lucas Nogueira\n\ \x20 • [@40:00] - Requirement on building a Tauri app\n\ \x20 • [@43:13] - Parting thoughts\n\ \n\ "; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_escape_lt_gt_and_img() { let description = "

\ \"[Direct\n\ \"[Direct\n\

\n\

\n\ Come to FOSSY 2023!\n\

\n\

Show Notes:

\n\ \n\ FOSSY 2023 will happen next week in Portland, OR, USA.\n\ \n\
\n\ \n\

Send feedback and comments on the cast\n\ to <oggcast@faif.us>.\n\ You can keep in touch with Free as in Freedom\n\ by following Conservancy on\n\ on Twitter and and FaiF on Twitter. We are working on setting up a group chat again, too!

\n\ \n\

Free as in Freedom is produced by Dan Lynch\n\ of danlynch.org.\n\ Theme\n\ music written and performed\n\ by Mike Tarantino\n\ with Charlie Paxson on drums.

\n\ \n\

\"Creative\n\ The content\n\ of this\n\ audcast, and the accompanying show notes and music are licensed\n\ under the Creative\n\ Commons Attribution-Share-Alike 4.0 license (CC BY-SA 4.0).\n\

"; let expected = "[[Direct download of cast in Ogg/Vorbis format]]\n\ [[Direct download of cast in MP3 format]]\n\ \n\ \n\ Come to FOSSY 2023! \n\ \n\ Show Notes: FOSSY 2023 will happen next week in Portland, OR, USA. Send feedback and comments on the cast to <oggcast@faif.us>. You can keep in touch with Free as in Freedom by following Conservancy on on Twitter and and FaiF on Twitter. We are working on setting up a group chat again, too!\n\ \n\ Free as in Freedom is produced by Dan Lynch of danlynch.org. Theme music written and performed by Mike Tarantino with Charlie Paxson on drums.\n\ \n\ [Creative Commons License]\n\ The content of this audcast, and the accompanying show notes and music are licensed under the Creative Commons Attribution-Share-Alike 4.0 license (CC BY-SA 4.0). \n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_hash_invalid_link() { let description = r#"

🤷

"#; let expected = "🤷\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_empty_link() { let description = r#"

🤷

"#; let expected = "🤷\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } #[test] fn test_hash_timestamp_link() { let description = r##"

13:12

"##; let expected = "13:12\n\n"; let markup = html2pango_markup(description); assert_eq!(expected, markup); } } podcasts-25.2/podcasts-gtk/src/feed_manager.rs000066400000000000000000000167301500126606300214250ustar00rootroot00000000000000// feed_manager.rs // // Copyright 2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use anyhow::Result; use async_channel::Sender; use once_cell::sync::Lazy; use podcasts_data::Source; use podcasts_data::dbqueries; use podcasts_data::pipeline::pipeline; use std::collections::HashMap; use std::sync::RwLock; use tokio::sync::watch; use crate::app::Action; use crate::glib::Priority; pub(crate) static FEED_MANAGER: Lazy = Lazy::new(FeedManager::default); type RefreshId = u64; #[derive(Debug)] struct RefreshBatch { represents_full_refresh: bool, feeds: Vec, receiver: watch::Receiver, } #[derive(Debug, Default)] struct State { next_id: RefreshId, currently_running: HashMap, } #[derive(Debug, Default)] pub struct FeedManager { state: RwLock, } impl FeedManager { /// refresh all feeds, or waits for a running refresh to finish #[allow(dead_code)] pub async fn full_refresh(&self, sender: &Sender) { if let Some(mut refresh_done) = self.schedule_full_refresh(sender) { if let Err(e) = refresh_done.wait_for(|v| *v).await { error!("Failed to receive feed_manager {e}"); } }; } /// The non-async variant of full_refresh /// returns None when skipped due to empty database pub fn schedule_full_refresh(&self, sender: &Sender) -> Option> { // If we try to update the whole db, but the db is empty, exit early match dbqueries::is_source_populated(&[]) { Ok(false) => { info!("No feed sources in db, skipping refresh"); return None; } Err(err) => error!("Failed to check for empty podcast DB: {err}"), _ => (), }; let running_full_refresh = if let Ok(state) = self.state.read() { state .currently_running .iter() .find(|(_, v)| v.represents_full_refresh) .map(|(_, v)| v.receiver.clone()) } else { error!("Couldn't lock feed_manager state to schedule_full_refresh"); return None; }; running_full_refresh.or_else(|| Some(self.add_refresh(sender, None))) } /// Refresh only specific feeds, /// if a running refresh already contains a subset of the requested sources /// It will wait for these to complete while also starting new refresh batches for /// Feeds that don't have running updates yet. pub async fn refresh(&self, sender: &Sender, source: Vec) { let receivers = self.schedule_refresh(sender, source); let handles: Vec<_> = receivers .into_iter() .map(|mut r| async move { let _ = r.wait_for(|v| *v).await; }) .collect(); futures_util::future::join_all(handles).await; } /// The non-async variant of schedule_refresh pub fn schedule_refresh( &self, sender: &Sender, source: Vec, ) -> Vec> { // figure out what part of the feeds are already scheduled let (mut receivers, not_scheduled) = if let Ok(state) = self.state.read() { let scheduled_or_not: Vec> = source .iter() .map(|requested| { if let Some(refresh_id) = state.currently_running.iter().find_map(|(k, v)| { if v.feeds.contains(requested) { Some(k) } else { None } }) { Ok(*refresh_id) } else { Err(requested.clone()) } }) .collect(); let already_scheduled: Vec<_> = scheduled_or_not .iter() .filter_map(|r| r.clone().ok()) .collect(); let not_scheduled: Vec<_> = scheduled_or_not .into_iter() .filter_map(|r| r.err()) .collect(); let receivers: Vec<_> = already_scheduled .into_iter() .map(|id| state.currently_running.get(&id).unwrap().receiver.clone()) .collect(); (receivers, not_scheduled) } else { error!("Couldn't lock feed_manager state to schedule_refresh"); return Vec::new(); }; if !not_scheduled.is_empty() { receivers.push(self.add_refresh(sender, Some(not_scheduled))); } receivers } fn add_refresh( &self, sender: &Sender, source: Option>, ) -> watch::Receiver { let (watch_sender, watch_receiver) = watch::channel(false); let (sources, is_all) = source .map(|s| (s, false)) .unwrap_or(dbqueries::get_sources().map(|s| (s, true)).unwrap()); let id = if let Ok(mut state) = self.state.write() { let id = state.next_id; state.next_id = id + 1; state.currently_running.insert( id, RefreshBatch { represents_full_refresh: is_all, feeds: sources.clone(), receiver: watch_receiver.clone(), }, ); Some(id) } else { None }; let sender = sender.clone(); if let Some(id) = id { crate::RUNTIME.spawn(async move { send!(sender, Action::StartUpdating); if let Err(err) = pipeline(sources.into_iter()).await { error!("Failed to fetch feed: {err}"); } if let Err(e) = watch_sender.send(true) { error!("Failed to send feed done: {e}"); } send!(sender, Action::FeedRefreshed(id)); }); } watch_receiver } /// Call this from app.rs when an update is done pub(crate) fn refresh_done(sender: Sender, id: RefreshId) { crate::MAINCONTEXT.spawn_local_with_priority(Priority::LOW, async move { let all_done = if let Ok(mut state) = FEED_MANAGER.state.write() { if state.currently_running.remove(&id).is_none() { error!("Failed to remove refreshId: {id}"); } state.currently_running.is_empty() } else { error!("refresh_done: Failed to lock feed_manager state"); false }; if all_done { send!(sender, Action::StopUpdating); send!(sender, Action::RefreshAllViews); } }); } } podcasts-25.2/podcasts-gtk/src/i18n.rs000066400000000000000000000110561500126606300176030ustar00rootroot00000000000000// i18n.rs // // Copyright 2018 Daniel Garcia Moreno // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use gettextrs::gettext; use gettextrs::ngettext; use regex::Captures; use regex::Regex; #[allow(dead_code)] fn freplace(input: String, args: &[&str]) -> String { let mut parts = input.split("{}"); let mut output = parts.next().unwrap_or("").to_string(); for (p, a) in parts.zip(args.iter()) { output += &(a.to_string() + p); } output } #[allow(dead_code)] fn kreplace(input: String, kwargs: &[(&str, &str)]) -> String { let mut s = input; for (k, v) in kwargs { if let Ok(re) = Regex::new(&format!("\\{{{}\\}}", k)) { s = re .replace_all(&s, |_: &Captures<'_>| v.to_string()) .to_string(); } } s } #[allow(dead_code)] pub(crate) fn i18n(format: &str) -> String { gettext(format) } #[allow(dead_code)] pub(crate) fn i18n_f(format: &str, args: &[&str]) -> String { let s = gettext(format); freplace(s, args) } #[allow(dead_code)] pub(crate) fn i18n_k(format: &str, kwargs: &[(&str, &str)]) -> String { let s = gettext(format); kreplace(s, kwargs) } #[allow(dead_code)] pub(crate) fn ni18n(single: &str, multiple: &str, number: u32) -> String { ngettext(single, multiple, number) } #[allow(dead_code)] pub(crate) fn ni18n_f(single: &str, multiple: &str, number: u32, args: &[&str]) -> String { let s = ngettext(single, multiple, number); freplace(s, args) } #[allow(dead_code)] pub(crate) fn ni18n_k( single: &str, multiple: &str, number: u32, kwargs: &[(&str, &str)], ) -> String { let s = ngettext(single, multiple, number); kreplace(s, kwargs) } #[cfg(test)] mod tests { use super::*; #[test] fn test_i18n() { let out = i18n("translate1"); assert_eq!(out, "translate1"); let out = ni18n("translate1", "translate multi", 1); assert_eq!(out, "translate1"); let out = ni18n("translate1", "translate multi", 2); assert_eq!(out, "translate multi"); } #[test] fn test_i18n_f() { let out = i18n_f("{} param", &["one"]); assert_eq!(out, "one param"); let out = i18n_f("middle {} param", &["one"]); assert_eq!(out, "middle one param"); let out = i18n_f("end {}", &["one"]); assert_eq!(out, "end one"); let out = i18n_f("multiple {} and {}", &["one", "two"]); assert_eq!(out, "multiple one and two"); let out = ni18n_f("singular {} and {}", "plural {} and {}", 2, &["one", "two"]); assert_eq!(out, "plural one and two"); let out = ni18n_f("singular {} and {}", "plural {} and {}", 1, &["one", "two"]); assert_eq!(out, "singular one and two"); } #[test] fn test_i18n_k() { let out = i18n_k("{one} param", &[("one", "one")]); assert_eq!(out, "one param"); let out = i18n_k("middle {one} param", &[("one", "one")]); assert_eq!(out, "middle one param"); let out = i18n_k("end {one}", &[("one", "one")]); assert_eq!(out, "end one"); let out = i18n_k("multiple {one} and {two}", &[("one", "1"), ("two", "two")]); assert_eq!(out, "multiple 1 and two"); let out = i18n_k("multiple {two} and {one}", &[("one", "1"), ("two", "two")]); assert_eq!(out, "multiple two and 1"); let out = i18n_k("multiple {one} and {one}", &[("one", "1"), ("two", "two")]); assert_eq!(out, "multiple 1 and 1"); let out = ni18n_k( "singular {one} and {two}", "plural {one} and {two}", 1, &[("one", "1"), ("two", "two")], ); assert_eq!(out, "singular 1 and two"); let out = ni18n_k( "singular {one} and {two}", "plural {one} and {two}", 2, &[("one", "1"), ("two", "two")], ); assert_eq!(out, "plural 1 and two"); } } podcasts-25.2/podcasts-gtk/src/main.rs000066400000000000000000000065501500126606300177530ustar00rootroot00000000000000// main.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use gtk::{gio, glib}; #[macro_use] extern crate log; #[macro_use] extern crate html5ever; // Exports the macros defined in utils to the namespace of the crate so they can be used // easily without import #[macro_use] mod utils; // Auto generated by meson.build #[rustfmt::skip] mod config; mod app; mod download_covers; mod episode_description_parser; mod feed_manager; mod i18n; mod manager; mod settings; mod thumbnail_generator; mod widgets; mod window; use crate::app::PdApplication; pub use crate::thumbnail_generator::ThumbSize::*; use once_cell::sync::Lazy; // tokio should be used when doing http fetches, since reqwest depens on it. pub static RUNTIME: Lazy = Lazy::new(|| tokio::runtime::Runtime::new().unwrap()); pub static MAINCONTEXT: Lazy = Lazy::new(glib::MainContext::default); pub static CHRONO_LOCALE: Lazy = Lazy::new(|| { use std::str::FromStr; let system_locale = locale_config::Locale::current(); let time_locale = system_locale.tags_for("time").next(); let time_locale_str = time_locale.as_ref().map(|l| l.as_ref()).unwrap_or("C"); let unix_formatted = time_locale_str.replace('-', "_"); chrono::Locale::from_str(&unix_formatted).unwrap_or(chrono::Locale::POSIX) }); #[cfg(test)] fn init_gtk_tests() -> anyhow::Result<()> { gst::init()?; gtk::init()?; adw::init()?; register_resources()?; Ok(()) } fn main() -> glib::ExitCode { pretty_env_logger::init(); gst::init().expect("Error initializing gstreamer"); gtk::init().expect("Error initializing gtk."); register_resources().expect("Error registering resources"); PdApplication::run() } fn register_resources() -> anyhow::Result<()> { // Create Resource it will live as long the value lives. let gbytes = glib::Bytes::from_static(crate::config::RESOURCEFILE); let resource = gio::Resource::from_data(&gbytes)?; // Register the resource so it won't be dropped and will continue to live in // memory. gio::resources_register(&resource); Ok(()) } #[test] // Even while running the tests with -j 1 and --test-threads=1, // cargo seems to create new threads and gtk refuses to initialize again. // So we run every gtk related test here. fn test_stuff() -> anyhow::Result<()> { use crate::widgets::*; init_gtk_tests()?; // If a widget does not exist in the `GtkBuilder`(.ui) file this should panic and fail. BaseView::default(); ShowWidget::default(); HomeEpisode::default(); EpisodeWidget::default(); show_menu::ShowMenu::default(); episode_menu::EpisodeMenu::default(); Ok(()) } podcasts-25.2/podcasts-gtk/src/manager.rs000066400000000000000000000153321500126606300204370ustar00rootroot00000000000000// manager.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later #![allow(clippy::type_complexity)] use anyhow::{Result, anyhow}; use async_channel::Sender; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::{Arc, Mutex, RwLock}; use crate::app::Action; use crate::i18n::i18n_f; use podcasts_data::dbqueries; use podcasts_data::downloader::{DownloadProgress, get_episode}; use podcasts_data::errors::DownloadError; use podcasts_data::{EpisodeId, EpisodeModel}; // This is messy, undocumented and hacky af. // I am terrible at writing downloaders and download managers. pub(crate) type ActiveProgress = Arc>; pub(crate) type DownloadProgressLock = Arc>>; pub(crate) static ACTIVE_DOWNLOADS: Lazy = Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); #[derive(Debug, Default)] pub(crate) struct Progress { total_bytes: u64, downloaded_bytes: u64, cancel: bool, } impl Progress { pub(crate) fn get_fraction(&self) -> f64 { let ratio = self.downloaded_bytes as f64 / self.total_bytes as f64; debug!("{:?}", self); debug!("Ratio completed: {}", ratio); if ratio >= 1.0 { return 1.0; }; ratio } } impl DownloadProgress for Progress { fn get_downloaded(&self) -> u64 { self.downloaded_bytes } fn set_downloaded(&mut self, downloaded: u64) { self.downloaded_bytes = downloaded } fn set_size(&mut self, bytes: u64) { self.total_bytes = bytes; } fn get_size(&self) -> u64 { self.total_bytes } fn should_cancel(&self) -> bool { self.cancel } fn cancel(&mut self) { self.cancel = true; } } pub(crate) fn add(sender: Sender, id: EpisodeId, directory: String) -> Result<()> { // Create a new `Progress` struct to keep track of dl progress. let prog = Arc::new(Mutex::new(Progress::default())); match ACTIVE_DOWNLOADS.write() { Ok(mut guard) => guard.insert(id, prog.clone()), Err(err) => return Err(anyhow!("ActiveDownloads: {}.", err)), }; crate::RUNTIME.spawn(async move { if let Ok(mut episode) = dbqueries::get_episode_widget_from_id(id) { let id = episode.id(); match get_episode(&mut episode, directory.as_str(), Some(prog)).await { Ok(_) => (), Err(DownloadError::DownloadCancelled) => (), Err(e) => { send!( sender, Action::ErrorNotification(i18n_f("Download failed: {}", &[&e.to_string()])) ); } } if let Ok(mut m) = ACTIVE_DOWNLOADS.write() { let progress = m.remove(&id); debug!("Removed: {:?}", progress); } } }); Ok(()) } #[cfg(test)] mod tests { use super::*; use podcasts_data::dbqueries; use podcasts_data::pipeline::pipeline; use podcasts_data::utils::get_download_dir; use podcasts_data::{Episode, EpisodeModel, Save, Source}; use podcasts_data::downloader::get_episode; use std::fs; use std::path::Path; use std::{thread, time}; #[test] // This test inserts an rss feed to your `XDG_DATA/podcasts/podcasts.db` so we make it explicit // to run it. #[ignore] // THIS IS NOT A RELIABLE TEST // Just quick sanity check fn test_start_dl() -> Result<()> { let url = "https://web.archive.org/web/20180120110727if_/https://rss.acast.com/thetipoff"; // Create and index a source let mut source = Source::from_url(url)?; // Copy its id let sid = source.id(); source.set_http_etag(None); source.set_last_modified(None); source.save()?; let rt = tokio::runtime::Runtime::new()?; rt.block_on(pipeline(vec![source]))?; // Get the podcast let pd = dbqueries::get_podcast_from_source_id(sid)?; let title = "Coming soon... The Tip Off"; let guid = "tag:soundcloud,2010:tracks/327539708"; // Get an episode let episode: Episode = dbqueries::get_episode(Some(guid), title, pd.id())?; let download_dir = get_download_dir(pd.title())?; let dir2 = download_dir.clone(); let (sender, _) = async_channel::unbounded(); add(sender, episode.id(), download_dir)?; assert_eq!(ACTIVE_DOWNLOADS.read().unwrap().len(), 1); // Give it some time to download the file thread::sleep(time::Duration::from_secs(20)); let final_path = format!("{}/{}.mp3", &dir2, episode.id().0); assert_eq!(ACTIVE_DOWNLOADS.read().unwrap().len(), 0); assert!(Path::new(&final_path).exists()); fs::remove_file(final_path)?; Ok(()) } #[test] // This test needs access to local system so we ignore it by default. #[ignore] fn test_dl_steal_the_stars() -> Result<()> { let url = "https://web.archive.org/web/20180120104957if_/https://rss.art19.com/steal-the-stars"; // Create and index a source let mut source = Source::from_url(url)?; // Copy its id let sid = source.id(); source.set_http_etag(None); source.set_last_modified(None); source.save()?; let rt = tokio::runtime::Runtime::new()?; rt.block_on(pipeline(vec![source]))?; // Get the podcast let pd = dbqueries::get_podcast_from_source_id(sid)?; let title = "Introducing Steal the Stars"; let guid = "gid://art19-episode-locator/V0/S6kmOE2cviFS0HD-IUYOPRO0fvjTPYmCsMDe5bjABnA"; // Get an episode let mut episode = dbqueries::get_episode(Some(guid), title, pd.id())?.into(); let download_dir = get_download_dir(pd.title())?; rt.block_on(get_episode(&mut episode, &download_dir, None))?; let final_path = format!("{}/{}.mp3", &download_dir, episode.id().0); assert!(Path::new(&final_path).exists()); fs::remove_file(final_path)?; Ok(()) } } podcasts-25.2/podcasts-gtk/src/meson.build000066400000000000000000000032551500126606300206220ustar00rootroot00000000000000global_conf = configuration_data() global_conf.set_quoted('APP_ID', application_id) global_conf.set_quoted('VERSION', podcasts_version + version_suffix) global_conf.set_quoted('LOCALEDIR', podcasts_localedir) # include_bytes! only takes a string literal global_conf.set_quoted('RESOURCEFILE', podcasts_resources.full_path()) config_rs = configure_file( input: 'config.rs.in', output: 'config.rs', configuration: global_conf ) run_command( 'cp', config_rs, meson.current_source_dir(), check: true ) cargo_options = [ '--manifest-path', meson.project_source_root() / 'Cargo.toml' ] cargo_options += [ '--target-dir', meson.project_build_root() / 'podcasts-gtk' / 'src' ] if get_option('profile') == 'default' cargo_options += [ '--release' ] rust_target = 'release' message('Building in release mode') else rust_target = 'debug' message('Building in debug mode') endif cargo_env = [ 'CARGO_HOME=' + meson.project_build_root() / 'cargo-home' ] cargo_release = custom_target('cargo-build', build_by_default: true, build_always_stale: true, output: ['gnome-podcasts'], install: true, install_dir: podcasts_bindir, console: true, depends: podcasts_resources, command: ['env', cargo_env, cargo, 'build', cargo_options, '&&', 'cp', 'podcasts-gtk' / 'src' / rust_target / 'podcasts-gtk', '@OUTPUT@', ]) podcasts-25.2/podcasts-gtk/src/settings.rs000066400000000000000000000104141500126606300206610ustar00rootroot00000000000000// prefs.rs // // Copyright 2018 Measly Twerp // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use gio::{Settings, prelude::SettingsExt}; use gtk::gio; use gtk::prelude::GtkWindowExt; use chrono::Duration; use chrono::prelude::*; pub(crate) struct WindowGeometry { width: i32, height: i32, is_maximized: bool, } impl WindowGeometry { pub(crate) fn from_window(window: &adw::ApplicationWindow) -> WindowGeometry { let size = window.default_size(); let width = size.0; let height = size.1; let is_maximized = window.is_maximized(); WindowGeometry { width, height, is_maximized, } } pub(crate) fn from_settings(settings: &gio::Settings) -> WindowGeometry { let width = settings.int("persist-window-geometry-width"); let height = settings.int("persist-window-geometry-height"); let is_maximized = settings.boolean("persist-window-geometry-maximized"); WindowGeometry { width, height, is_maximized, } } pub(crate) fn apply(&self, window: &adw::ApplicationWindow) { if self.width > 0 && self.height > 0 { window.set_default_size(self.width, self.height); } if self.is_maximized { window.maximize(); } } pub(crate) fn write(&self, settings: &gio::Settings) { settings .set_int("persist-window-geometry-width", self.width) .unwrap(); settings .set_int("persist-window-geometry-height", self.height) .unwrap(); settings .set_boolean("persist-window-geometry-maximized", self.is_maximized) .unwrap(); } } pub(crate) fn get_refresh_interval(settings: &Settings) -> Duration { let time = i64::from(settings.int("refresh-interval-time")); let period = settings.string("refresh-interval-period"); time_period_to_duration(time, period.as_str()) } pub(crate) fn get_cleanup_date(settings: &Settings) -> DateTime { let time = i64::from(settings.int("cleanup-age-time")); let period = settings.string("cleanup-age-period"); let duration = time_period_to_duration(time, period.as_str()); Utc::now() - duration } pub(crate) fn time_period_to_duration(time: i64, period: &str) -> Duration { match period { "weeks" => Duration::weeks(time), "days" => Duration::days(time), "hours" => Duration::hours(time), "minutes" => Duration::minutes(time), _ => Duration::seconds(time), } } #[test] fn test_time_period_to_duration() { let time = 2; let week = 604800 * time; let day = 86400 * time; let hour = 3600 * time; let minute = 60 * time; assert_eq!(week, time_period_to_duration(time, "weeks").num_seconds()); assert_eq!(day, time_period_to_duration(time, "days").num_seconds()); assert_eq!(hour, time_period_to_duration(time, "hours").num_seconds()); assert_eq!( minute, time_period_to_duration(time, "minutes").num_seconds() ); assert_eq!(time, time_period_to_duration(time, "seconds").num_seconds()); } // #[test] // fn test_apply_window_geometry() { // gtk::init().expect("Error initializing gtk."); // let window = gtk::Window::new(gtk::WindowType::Toplevel); // let _geometry = WindowGeometry { // left: 0, // top: 0, // width: 100, // height: 100, // is_maximized: true // }; // assert!(!window.is_maximized()); // window.show(); // window.activate(); // geometry.apply(&window); // assert!(window.is_maximized()); // } podcasts-25.2/podcasts-gtk/src/thumbnail_generator.rs000066400000000000000000000072471500126606300230640ustar00rootroot00000000000000use anyhow::{Result, anyhow}; use image::imageops::FilterType; use std::collections::HashMap; use std::fmt::Display; use std::path::Path; use crate::download_covers::determin_cover_path; use podcasts_data::ShowCoverModel; use podcasts_data::utils::get_cover_dir_path; use std::sync::Arc; // we only generate a fixed amount of thumbnails // This enum is to avoid accidentally passing a thumb-size we didn't generate #[derive(Copy, Clone, Hash, PartialEq, Eq)] pub enum ThumbSize { Thumb64, Thumb128, Thumb256, Thumb512, } pub use self::ThumbSize::*; impl ThumbSize { fn pixels(&self) -> u32 { match &self { Thumb64 => 64, Thumb128 => 128, Thumb256 => 256, Thumb512 => 512, } } pub fn hidpi(self, scale: i32) -> Option { // meh code if scale >= 5 { match self { Thumb64 => Some(Thumb512), Thumb128 => None, Thumb256 => None, Thumb512 => None, } } else if scale >= 3 { match self { Thumb64 => Some(Thumb256), Thumb128 => Some(Thumb512), Thumb256 => None, Thumb512 => None, } } else if scale >= 2 { match self { Thumb64 => Some(Thumb128), Thumb128 => Some(Thumb256), Thumb256 => Some(Thumb512), Thumb512 => None, } } else { Some(self) } } } impl Display for ThumbSize { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.pixels()) } } pub async fn generate( pd: &ShowCoverModel, path: &Path, ) -> Result> { let sizes: [ThumbSize; 4] = [Thumb64, Thumb128, Thumb256, Thumb512]; // All thumbs must generate, we rely on them existing if the main image exists. let path = path.to_path_buf(); let image_full_size = crate::RUNTIME .spawn_blocking(move || { anyhow::Ok(Arc::new( image::ImageReader::open(path)? .with_guessed_format()? .decode()?, )) }) .await??; let dir = get_cover_dir_path(pd.title()); tokio::fs::create_dir_all(dir).await?; let handles: Vec<_> = sizes .into_iter() .map(|size| { let pixels = size.pixels(); let thumb_path = determin_cover_path(pd, Some(size)); let image_full_size = image_full_size.clone(); crate::RUNTIME.spawn(async move { let tmp_path = thumb_path.with_extension(".part"); let tmp_path2 = tmp_path.clone(); // save and read gdk texture let texture = crate::RUNTIME .spawn_blocking(move || { let image = image_full_size.resize(pixels, pixels, FilterType::Lanczos3); image.save_with_format(&tmp_path2, image::ImageFormat::Png)?; gtk::gdk::Texture::from_filename(&tmp_path2) .map_err(|_| anyhow!("failed to read gtk texture")) }) .await??; tokio::fs::rename(&tmp_path, &thumb_path).await?; Ok((size, texture)) }) }) .collect(); let result: Result> = futures_util::future::join_all(handles) .await .into_iter() .map(|r| r.unwrap_or(Err(anyhow!("Failed to write cover thumbnail.")))) .collect(); result } podcasts-25.2/podcasts-gtk/src/utils.rs000066400000000000000000000541541500126606300201720ustar00rootroot00000000000000// utils.rs // // Copyright 2018 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use anyhow::{Context, Result, anyhow, bail}; use async_channel::Sender; use async_channel::unbounded; use chrono::prelude::*; use futures_util::StreamExt; use glib::clone; use glib::object::WeakRef; use gtk::FileFilter; use gtk::Widget; use gtk::prelude::*; use gtk::{gio, glib}; use once_cell::sync::Lazy; use regex::Regex; use serde_json::Value; use std::collections::HashSet; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use url::Url; use crate::app::Action; use crate::feed_manager::FEED_MANAGER; use crate::i18n::{i18n, i18n_f}; use podcasts_data::dbqueries; use podcasts_data::downloader::client_builder; use podcasts_data::opml; use podcasts_data::utils::checkup; use podcasts_data::{ShowId, Source}; /// Copied from the gtk-macros crate /// /// Send an event through a async_channel::Sender /// /// - Before: /// /// Example: /// /// ```no_run /// sender.send(Action::DoThing).expect("Failed to send DoThing through the glib channel?"); /// ``` /// /// - After: /// /// Example: /// /// ```no_run /// send!(self.sender, Action::DoThing); /// ``` #[macro_export] macro_rules! send { ($sender:expr, $action:expr) => { if let Err(err) = $sender.send($action).await { panic!( "Failed to send \"{}\" action due to {}", stringify!($action), err ); } }; } /// Same as send! but not async. /// Should not be used from async functions. #[macro_export] macro_rules! send_blocking { ($sender:expr, $action:expr) => { if let Err(err) = $sender.send_blocking($action) { panic!( "Failed to send \"{}\" action due to {}", stringify!($action), err ); } }; } /// Lazy evaluates and loads widgets to the parent `container` widget. /// /// Accepts an `IntoIterator`, `data`, as the source from which each widget /// will be constructed. An `FnMut` function that returns the desired /// widget should be passed as the widget `constructor`. You can also specify /// a `callback` that will be executed when the iteration finish. /// /// ```no_run /// # struct Message; /// # struct MessageWidget(gtk::Label); /// /// # impl MessageWidget { /// # fn new(_: Message) -> Self { /// # MessageWidget(gtk::Label::new("A message")) /// # } /// # } /// /// let messages: Vec = Vec::new(); /// let list = gtk::ListBox::new(); /// let constructor = |m| MessageWidget::new(m).0; /// lazy_load(messages, list, constructor, || {}); /// ``` /// /// If you have already constructed the widgets and only want to /// load them to the parent you can pass a closure that returns it's /// own argument to the constructor. /// /// ```no_run /// # use std::collections::binary_heap::BinaryHeap; /// let widgets: BinaryHeap = BinaryHeap::new(); /// let list = gtk::ListBox::new(); /// lazy_load(widgets, list, |w| w, || {}); /// ``` pub(crate) async fn lazy_load( data: Vec, container: WeakRef, constructor: C, ) -> Vec> where T: 'static, W: IsA + Sized + 'static, C: Fn(T) -> W + 'static, { let (sender, receiver) = unbounded::(); let h1 = crate::MAINCONTEXT.spawn_local_with_priority(glib::source::Priority::LOW, async move { let mut total_duration = Duration::default(); let mut count = 0; for item in data { let start = Instant::now(); let widget = constructor(item); let duration = start.elapsed(); trace!("Created single widget in: {:?}", duration); total_duration += duration; count += 1; if let Err(err) = sender.send(widget).await { debug!("Got SendError, Channel is closed: {}", err); return; }; tokio::task::yield_now().await; } debug!("Created {} widgets in: {:?}", count, total_duration); }); let h2 = crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::DEFAULT_IDLE, async move { receiver .chunks(25) .for_each(move |widgets| { trace!("Received {} widgets", &widgets.len()); insert_widgets_idle(widgets, container.clone()) }) .await }, ); futures_util::future::join_all([h1, h2]).await } async fn insert_widgets_idle(data: Vec, container: WeakRef) where W: IsA + Sized + 'static, { let widget_count = data.len(); let mut count = 0; let mut start = Instant::now(); let mut batch_construction_time_total = Duration::default(); for widget in data { let w_start = Instant::now(); insert_widget_dynamic(widget, &container); let w_duration = w_start.elapsed(); trace!("Inserted single widget in: {:?}", w_duration); count += 1; batch_construction_time_total += w_duration; let duration = start.elapsed(); if duration > Duration::from_millis(1) { trace!("Inserted batch of {} widgets in: {:?}", count, duration); tokio::task::yield_now().await; count = 0; start = Instant::now(); } } debug!( "Inserted {} widgets in: {:?}", widget_count, batch_construction_time_total ); } fn insert_widget_dynamic + Sized>(widget: W, container: &WeakRef) { let container = match container.upgrade() { Some(c) => c, None => return, }; if let Some(listbox) = container.dynamic_cast_ref::() { listbox.append(&widget); } else if let Some(flowbox) = container.dynamic_cast_ref::() { flowbox.append(&widget); } else { unreachable!("Failed to downcast widget. {}", container.value_type()); } widget.set_visible(true); } static IGNORESHOWS: Lazy>>> = Lazy::new(|| Arc::new(Mutex::new(HashSet::new()))); pub(crate) fn ignore_show(id: ShowId) -> Result { IGNORESHOWS .lock() .map(|mut guard| guard.insert(id)) .map_err(|err| anyhow!("{err}")) } pub(crate) fn unignore_show(id: ShowId) -> Result { IGNORESHOWS .lock() .map(|mut guard| guard.remove(&id)) .map_err(|err| anyhow!("{err}")) } pub(crate) fn get_ignored_shows() -> Result> { IGNORESHOWS .lock() .map(|guard| guard.iter().cloned().collect::>()) .map_err(|err| anyhow!("{err}")) } pub(crate) fn cleanup(cleanup_date: DateTime) { if let Err(err) = checkup(cleanup_date) { error!("Check up failed: {err}"); } } pub(crate) async fn subscribe(sender: &Sender, feed: String) { let mut error_source = None; // <- auto unsub from this if let Err(e) = async { let source = dbqueries::get_source_from_uri(&feed).or_else(|_| Source::from_url(&feed))?; error_source = Some(source.clone()); let source_id = source.id(); info!("Subscribing to {feed}"); FEED_MANAGER.refresh(sender, vec![source]).await; let show = dbqueries::get_podcast_from_source_id(source_id)?; send!(sender, Action::RefreshAllViews); send!(sender, Action::GoToShow(Arc::new(show.clone()))); Ok::<(), anyhow::Error>(()) } .await { error!("Failed to subscribe: {feed} {e}"); // auto unsubscribe if let Some(error_source) = error_source { // only unsub if no Show was imported from the source. if dbqueries::get_podcast_from_source_id(error_source.id()).is_err() { if let Err(remove_err) = dbqueries::remove_source(&error_source) { error!("failed to remove failed source! {remove_err} {feed}"); } else { info!("auto removed source that failed to import {feed}"); } } } // TODO show the actual error (like "content didn't start with rss feed"), // but pipeline doesn't pass useful errors yet send!( sender, Action::ErrorNotification(i18n_f( "Failed to subscribe to feed: {}", &[&feed.to_string()] )) ); } } // FIXME: the signature should be `fn foo(s: Url) -> Result` pub(crate) async fn itunes_to_rss(url: &str) -> Result { let id = itunes_id_from_url(url).ok_or_else(|| anyhow!("Failed to find an iTunes ID."))?; itunes_lookup_id(id).await } fn itunes_id_from_url(url: &str) -> Option { static RE: Lazy = Lazy::new(|| Regex::new(r"/id([0-9]+)").unwrap()); // Get the itunes id from the url let itunes_id = RE.captures_iter(url).next()?.get(1)?.as_str(); // Parse it to a u32, this *should* never fail itunes_id.parse::().ok() } async fn itunes_lookup_id(id: u32) -> Result { let url = format!("https://itunes.apple.com/lookup?id={}&entity=podcast", id); let req: Value = client_builder() .build()? .get(&url) .send() .await? .json() .await?; let rssurl = || -> Option<&str> { req.get("results")?.get(0)?.get("feedUrl")?.as_str() }; rssurl() .map(From::from) .ok_or_else(|| anyhow!("Failed to get url from itunes response")) } /// Convert soundcloud page links to rss feed links. /// Works for users and playlists. pub(crate) async fn soundcloud_to_rss(url: &Url) -> Result { // Turn: https://soundcloud.com/chapo-trap-house // into: https://feeds.soundcloud.com/users/soundcloud:users:211911700/sounds.rss let (user_id, playlist_id) = soundcloud_lookup_id(url) .await .ok_or_else(|| anyhow!("Failed to find a soundcloud ID."))?; if playlist_id != 0 { let url = format!( "https://feeds.soundcloud.com/playlists/soundcloud:playlists:{}/sounds.rss", playlist_id ); Ok(Url::parse(&url)?) } else if user_id != 0 { let url = format!( "https://feeds.soundcloud.com/users/soundcloud:users:{}/sounds.rss", user_id ); Ok(Url::parse(&url)?) } else { Err(anyhow!("No valid id's in soundcloud page.")) } } /// Extract (user, playlist) id's from a soundcloud page. /// The id's are 0 if none was found. /// If fetching the html page fails an Error is returned. async fn soundcloud_lookup_id(url: &Url) -> Option<(u64, u64)> { static RE_U: Lazy = Lazy::new(|| Regex::new(r"soundcloud://users:([0-9]+)").unwrap()); static RE_P: Lazy = Lazy::new(|| Regex::new(r"soundcloud://playlists:([0-9]+)").unwrap()); let url_str = url.to_string(); let client = client_builder().build().ok()?; let response = client.get(&url_str).send(); let response_text = response.await.ok()?.text().await.ok()?; let user_id = RE_U .captures_iter(&response_text) .next() .and_then(|r| r.get(1).map(|u| u.as_str())); let playlist_id = RE_P .captures_iter(&response_text) .next() .and_then(|r| r.get(1).map(|u| u.as_str())); // Parse it to a u64, this *should* never fail Some(( user_id.and_then(|id| id.parse::().ok()).unwrap_or(0), playlist_id .and_then(|id| id.parse::().ok()) .unwrap_or(0), )) } pub(crate) async fn on_import_clicked(window: >k::ApplicationWindow, sender: &Sender) { // Set a filter to show only xml files let filter = FileFilter::new(); FileFilter::set_name(&filter, Some(i18n("OPML file").as_str())); filter.add_mime_type("application/xml"); filter.add_mime_type("text/xml"); filter.add_mime_type("text/x-opml"); let filters = gio::ListStore::new::(); filters.append(&filter); // Create the FileChooser Dialog let dialog = gtk::FileDialog::builder() .title(i18n( "Select the file from which to you want to import shows.", )) .accept_label(i18n("_Import")) .filters(&filters) .build(); if let Ok(file) = dialog.open_future(Some(window)).await { if let Some(path) = file.peek_path() { // spawn a thread to avoid blocking ui during import let result = gio::spawn_blocking(clone!( #[strong] sender, move || { // Parse the file and import the feeds opml::import_from_file(path) .map(|sources| { // Refresh the successfully parsed feeds to index them FEED_MANAGER.schedule_refresh(&sender, sources); }) .context("PARSE") } )) .await; if let Err(err) = result.unwrap_or_else(|e| bail!("Import Thread Error {e:#?}")) { let text = i18n_f("Failed to parse the imported file {}", &[&err.to_string()]); send!(sender, Action::ErrorNotification(text)); } } }; } pub(crate) async fn on_export_clicked(window: >k::ApplicationWindow, sender: &Sender) { // Set a filter to show only xml files let filter = FileFilter::new(); FileFilter::set_name(&filter, Some(i18n("OPML file").as_str())); filter.add_mime_type("application/xml"); filter.add_mime_type("text/xml"); filter.add_mime_type("text/x-opml"); let filters = gio::ListStore::new::(); filters.append(&filter); // Create the FileChooser Dialog let dialog = gtk::FileDialog::builder() // Translators: Show as a noun, meaning Podcast-Shows. .title(i18n("Export shows to…")) .accept_label(i18n("_Export")) .initial_name(format!( "{}.opml", // Translators: This is the string of the suggested name for the exported opml file i18n("gnome-podcasts-exported-shows") )) .filters(&filters) .build(); if let Ok(file) = dialog.save_future(Some(window)).await { if let Some(path) = file.peek_path() { debug!("File selected: {:?}", path); let result = gio::spawn_blocking(move || { opml::export_from_db(path, &i18n("GNOME Podcasts Subscriptions")) }) .await; if let Ok(Err(err)) = result { let text = i18n("Failed to export podcasts"); error!("Failed to export podcasts: {err}"); send!(sender, Action::ErrorNotification(text)); } } }; } #[cfg(test)] mod tests { use super::*; use anyhow::Result; use podcasts_data::database::truncate_db; use podcasts_data::dbqueries; use podcasts_data::pipeline::pipeline; use podcasts_data::utils::get_download_dir; use podcasts_data::{Save, Source}; use std::fs; use std::path::PathBuf; #[tokio::test] async fn test_itunes_to_rss() -> Result<()> { let itunes_url = "https://itunes.apple.com/podcast/id1195206601"; // they keep changing the urls let rss_url = "https://feeds.acast.com/public/shows/f5b64019-68c3-57d4-b70b-043e63e5cbf6"; let rss_url2 = "https://rss.acast.com/intercepted-with-jeremy-scahill"; let result_url = itunes_to_rss(itunes_url).await?; assert!(result_url == rss_url || result_url == rss_url2); let itunes_url = "https://itunes.apple.com/podcast/id000000000000000"; assert!(itunes_to_rss(itunes_url).await.is_err()); Ok(()) } #[test] fn test_itunes_id() -> Result<()> { let id = 1195206601; let itunes_url = "https://itunes.apple.com/podcast/id1195206601"; assert_eq!(id, itunes_id_from_url(itunes_url).unwrap()); Ok(()) } #[tokio::test] async fn test_itunes_lookup_id() -> Result<()> { let id = 1195206601; // they keep changing the urls let rss_url = "https://feeds.acast.com/public/shows/f5b64019-68c3-57d4-b70b-043e63e5cbf6"; let rss_url2 = "https://rss.acast.com/intercepted-with-jeremy-scahill"; let result_url = itunes_lookup_id(id).await?; assert!(result_url == rss_url || result_url == rss_url2); let id = 000000000; assert!(itunes_lookup_id(id).await.is_err()); Ok(()) } #[tokio::test] async fn test_soundcloud_to_rss() -> Result<()> { let soundcloud_url = Url::parse("https://soundcloud.com/chapo-trap-house")?; let rss_url = String::from( "https://feeds.soundcloud.com/users/soundcloud:users:211911700/sounds.rss", ); assert_eq!( Url::parse(&rss_url)?, soundcloud_to_rss(&soundcloud_url).await? ); let soundcloud_url = Url::parse("https://soundcloud.com/id000000000000000ajlsfhlsfhwoerzuweioh")?; assert!(soundcloud_to_rss(&soundcloud_url).await.is_err()); Ok(()) } #[tokio::test] async fn test_soundcloud_playlist_to_rss() -> Result<()> { // valid playlist let soundcloud_url = Url::parse("https://soundcloud.com/languagetransfer/sets/introduction-to-italian")?; let rss_url = String::from( "https://feeds.soundcloud.com/playlists/soundcloud:playlists:220248349/sounds.rss", ); assert_eq!( Url::parse(&rss_url)?, soundcloud_to_rss(&soundcloud_url).await? ); // invalid playlist link let soundcloud_url = Url::parse("https://soundcloud.com/languagetransfer/sets/does-not-exist")?; assert!(soundcloud_to_rss(&soundcloud_url).await.is_err()); // user page with a playlist pinned at the top, should return user rss not playlist let soundcloud_url = Url::parse("https://soundcloud.com/yung-chomsky")?; let rss_url = String::from( "https://feeds.soundcloud.com/users/soundcloud:users:418603470/sounds.rss", ); assert_eq!( Url::parse(&rss_url)?, soundcloud_to_rss(&soundcloud_url).await? ); // playlist without rss entries let soundcloud_url = Url::parse("https://soundcloud.com/yung-chomsky/sets/music-for-podcasts-volume-1")?; let rss_url = String::from( "https://feeds.soundcloud.com/playlists/soundcloud:playlists:1165448311/sounds.rss", ); assert_eq!( Url::parse(&rss_url)?, soundcloud_to_rss(&soundcloud_url).await? ); Ok(()) } #[test] #[ignore] fn should_refresh_cached_image_when_the_image_uri_changes() -> Result<()> { truncate_db()?; let mut original_feed = PathBuf::from(env!("CARGO_MANIFEST_DIR")); original_feed.push("resources/test/feeds/2018-01-20-LinuxUnplugged.xml"); let original_url = format!( "{}{}", "file:/", fs::canonicalize(original_feed)?.to_str().unwrap() ); println!("Made it here! (1)"); let mut source = Source::from_url(&original_url)?; println!("Made it here! (2)"); source.set_http_etag(None); source.set_last_modified(None); let sid = source.save()?.id(); println!("Made it here! (3)"); let rt = tokio::runtime::Runtime::new()?; rt.block_on(pipeline(vec![source]))?; println!("Made it here! (4)"); println!("The source id is {}!", sid.0); dbqueries::get_sources().unwrap().iter().for_each(|s| { println!("{}:{}", s.id().0, s.uri()); }); let original = dbqueries::get_podcast_from_source_id(sid)?; println!("Made it here! (5)"); let original_image_uri = original.image_uri(); let original_image_uri_hash = original.image_uri_hash(); let original_image_cached = original.image_cached(); let download_dir = get_download_dir(original.title())?; let image_path = download_dir + "/cover.jpeg"; let original_image_file_size = fs::metadata(&image_path)?.len(); // 693,343 println!("Made it here! (6)"); // Update the URI and refresh the feed let mut new_feed = PathBuf::from(env!("CARGO_MANIFEST_DIR")); new_feed.push("resources/test/feeds/2020-12-19-LinuxUnplugged.xml"); let mut source = dbqueries::get_source_from_id(sid)?; let new_url = format!( "{}{}", "file:/", fs::canonicalize(new_feed)?.to_str().unwrap() ); source.set_uri(new_url); source.set_http_etag(None); source.set_last_modified(None); source.save()?; println!("Made it here! (7)"); let rt = tokio::runtime::Runtime::new()?; rt.block_on(pipeline(vec![source]))?; println!("Made it here! (8)"); let new = dbqueries::get_podcast_from_source_id(sid)?; let new_image_uri = new.image_uri(); let new_image_uri_hash = new.image_uri_hash(); let new_image_cached = new.image_cached(); let new_image_file_size = fs::metadata(&image_path)?.len(); println!("Made it here! (9)"); assert_eq!(original.title(), new.title()); assert_ne!(original_image_uri, new_image_uri); assert_ne!(original_image_uri_hash, new_image_uri_hash); assert_ne!(original_image_cached, new_image_cached); assert_ne!(original_image_file_size, new_image_file_size); fs::remove_file(image_path)?; Ok(()) } } podcasts-25.2/podcasts-gtk/src/widgets/000077500000000000000000000000001500126606300201215ustar00rootroot00000000000000podcasts-25.2/podcasts-gtk/src/widgets/aboutdialog.rs000066400000000000000000000043631500126606300227670ustar00rootroot00000000000000// aboutdialog.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use crate::config::{APP_ID, VERSION}; use adw::prelude::*; use crate::i18n::i18n; /// Takes a `window` and creates and attaches an `adw::AboutWindow` to it. pub(crate) fn about_dialog(window: >k::ApplicationWindow) { // Feel free to add yourself if you contributed. // Please keep it sorted alphabetically let developers = vec![ "Alexandre Franke", "Carlos Soriano", "Constantin Nickel", "Daniel García Moreno", "Felix Häcker", "Gabriele Musco", "Ivan Augusto", "James Wykeham-Martin", "Jordan Petridis", "Jordan Williams", "Julian Hofer", "Julian Sparber", "Matthew Martin", "Piotr Drąg", "Rowan Lewis", "Zander Brown", ]; let designers = vec!["Tobias Bernard", "Sam Hewitt"]; let dialog = adw::AboutDialog::builder() .application_icon(APP_ID) .comments(i18n("Podcast Client for the GNOME Desktop.").as_str()) .copyright("© 2017-2021 Jordan Petridis") .license_type(gtk::License::Gpl30) .version(VERSION) .application_name(i18n("Podcasts")) .website("https://gitlab.gnome.org/World/podcasts") .issue_url("https://gitlab.gnome.org/World/podcasts/-/issues") .developer_name("Jordan Petridis, et al.") .developers(developers) .designers(designers) .translator_credits(i18n("translator-credits").as_str()) .build(); dialog.present(Some(window)); } podcasts-25.2/podcasts-gtk/src/widgets/base_view.rs000066400000000000000000000054251500126606300224410ustar00rootroot00000000000000// base_view.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use gtk::PolicyType; use gtk::glib; use gtk::prelude::*; use gtk::subclass::prelude::*; use adw::prelude::*; use adw::subclass::prelude::*; use once_cell::sync::Lazy; #[derive(Debug, Default)] pub struct BaseViewPriv { pub scrolled_window: gtk::ScrolledWindow, } #[glib::object_subclass] impl ObjectSubclass for BaseViewPriv { const NAME: &'static str = "PdBaseView"; type Type = super::BaseView; type ParentType = adw::Bin; } impl ObjectImpl for BaseViewPriv { fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); self.scrolled_window .set_policy(PolicyType::Never, PolicyType::Automatic); obj.set_size_request(360, -1); obj.set_child(Some(&self.scrolled_window)); } fn properties() -> &'static [glib::ParamSpec] { static PROPERTIES: Lazy> = Lazy::new(|| { vec![ glib::ParamSpecObject::builder::("child") .readwrite() .build(), ] }); PROPERTIES.as_ref() } fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { match pspec.name() { "child" => self.scrolled_window.child().to_value(), _ => unimplemented!(), } } fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "child" => self .scrolled_window .set_child(value.get::().ok().as_ref()), _ => unimplemented!(), }; } } impl WidgetImpl for BaseViewPriv {} impl BinImpl for BaseViewPriv {} glib::wrapper! { pub struct BaseView(ObjectSubclass) @extends gtk::Widget, adw::Bin; } impl Default for BaseView { fn default() -> Self { glib::Object::new() } } impl BaseView { pub(crate) fn set_content>(&self, widget: &T) { self.imp().scrolled_window.set_child(Some(widget)); } } podcasts-25.2/podcasts-gtk/src/widgets/content_stack.rs000066400000000000000000000131411500126606300233260ustar00rootroot00000000000000// content_stack.rs // // Copyright 2017 Jordan Petridis // Copyright 2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::*; use anyhow::Result; use async_channel::Sender; use std::cell::OnceCell; use std::rc::Rc; use crate::app::Action; use crate::i18n::i18n; use crate::utils::get_ignored_shows; use crate::widgets::{EmptyView, HomeView, ShowsView}; use podcasts_data::dbqueries::is_episodes_populated; #[derive(Debug, Clone)] pub(crate) struct Content { overlay: gtk::Overlay, sender: Sender, progress_bar: gtk::ProgressBar, stack: adw::ViewStack, shows_bin: adw::Bin, shows: OnceCell, // TODO drop the home_bin and just update the model // of HomeView once ported to ListView home_bin: adw::Bin, empty: EmptyView, } impl Content { pub(crate) fn new(sender: Sender) -> Rc { let stack = adw::ViewStack::new(); let shows_bin = adw::Bin::new(); let shows = OnceCell::new(); let home_bin = adw::Bin::new(); let home = HomeView::new(sender.clone()); let overlay = gtk::Overlay::new(); let empty = EmptyView::default(); let progress_bar = gtk::ProgressBar::builder() .valign(gtk::Align::Start) .halign(gtk::Align::Fill) .visible(false) .tooltip_text(i18n("Fetching feeds…")) .build(); progress_bar.add_css_class("osd"); overlay.set_child(Some(&stack)); overlay.add_overlay(&progress_bar); let home_page = stack.add_titled(&home_bin, Some("home"), &i18n("New")); let shows_page = stack.add_titled(&shows_bin, Some("shows"), &i18n("Shows")); stack.add_named(&empty, Some("empty")); home_page.set_icon_name(Some("document-open-recent-symbolic")); shows_page.set_icon_name(Some("audio-input-microphone-symbolic")); home_bin.set_child(Some(&home)); let this = Rc::new(Self { overlay, sender, progress_bar, stack: stack.clone(), shows_bin, shows, home_bin, empty, }); let weak = Rc::downgrade(&this); stack.connect_visible_child_notify(move |s| { if let Some(name) = s.visible_child_name() { if name == "shows" { if let Some(this) = weak.upgrade() { this.init_shows(); } } } }); if let Err(e) = this.check_empty_state() { error!("Failed to check for empty db state {e}"); } this } pub(crate) fn update(&self) { self.update_home(); self.update_shows(); if let Err(e) = self.check_empty_state() { error!("Failed to check for empty db state {e}"); } } pub(crate) fn update_home(&self) { let home = HomeView::new(self.sender.clone()); self.home_bin.set_child(Some(&home)); } pub(crate) fn update_home_if_background(&self) { if self.stack.visible_child_name() != Some("home".into()) { self.update_home(); } } pub(crate) fn update_shows(&self) { if let Some(shows) = self.shows.get() { shows.update_model(); } } pub(crate) fn progress_bar(&self) -> >k::ProgressBar { &self.progress_bar } pub(crate) fn stack(&self) -> &adw::ViewStack { &self.stack } pub(crate) fn overlay(&self) -> >k::Overlay { &self.overlay } fn init_shows(&self) { if self.shows.get().is_none() { self.shows .set({ info!("Init Shows View"); let new_shows = ShowsView::new(self.sender.clone()); self.shows_bin.set_child(Some(&new_shows)); new_shows }) .unwrap(); } } pub(crate) fn go_to_home(&self) { if !self.is_in_empty_view() { self.stack.set_visible_child_name("home"); } } pub(crate) fn go_to_shows(&self) { if !self.is_in_empty_view() { self.stack.set_visible_child_name("shows"); } } pub(crate) fn switch_to_empty_views(&self) { self.stack.set_visible_child(&self.empty); } pub(crate) fn switch_to_populated(&self) { self.stack.set_visible_child(&self.home_bin); } pub(crate) fn is_in_empty_view(&self) -> bool { self.stack .visible_child_name() .is_some_and(|name| name == "empty") } pub fn check_empty_state(&self) -> Result<()> { let ign = get_ignored_shows()?; debug!("IGNORED SHOWS {:?}", ign); if is_episodes_populated(&ign)? { send_blocking!(self.sender, Action::PopulatedState) } else { send_blocking!(self.sender, Action::EmptyState) }; Ok(()) } } podcasts-25.2/podcasts-gtk/src/widgets/discovery_page.rs000066400000000000000000000171331500126606300234770ustar00rootroot00000000000000// discovery_settings.rs // // Copyright 2022-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::*; use adw::subclass::prelude::*; use anyhow::Result; use async_channel::Sender; use glib::clone; use glib::subclass::InitializingObject; use gtk::CompositeTemplate; use gtk::glib; use std::sync::Arc; use url::Url; use crate::app::Action; use crate::i18n::i18n; use crate::utils::{itunes_to_rss, soundcloud_to_rss}; use podcasts_data::dbqueries; use podcasts_data::discovery::SearchError::NoSearchPlatformsSelected; use podcasts_data::discovery::{ALL_PLATFORM_IDS, SearchError, search}; #[derive(Debug, CompositeTemplate, Default)] #[template(resource = "/org/gnome/Podcasts/gtk/discovery_page.ui")] pub struct DiscoveryPagePriv { #[template_child] list: TemplateChild, #[template_child] entry: TemplateChild, #[template_child] search_button: TemplateChild, #[template_child] loading_spinner: TemplateChild, #[template_child] no_platforms_selected_label: TemplateChild, } impl DiscoveryPagePriv { fn init(&self, sender: &Sender) { let (loading_done, receiver) = async_channel::bounded(1); crate::MAINCONTEXT.spawn_local(clone!( #[weak(rename_to = this)] self, async move { while let Ok(result) = receiver.recv().await { if let Err(NoSearchPlatformsSelected) = result { this.entry.add_css_class("error"); this.no_platforms_selected_label.set_visible(true); this.no_platforms_selected_label.announce( &this.no_platforms_selected_label.text(), gtk::AccessibleAnnouncementPriority::High, ); } this.search_button.set_visible(true); this.loading_spinner.set_visible(false); } } )); // create platform settings switches let settings = dbqueries::get_discovery_settings(); for id in ALL_PLATFORM_IDS { let switch = adw::SwitchRow::new(); let active = *settings.get(id).unwrap_or(&false); switch.set_active(active); switch.set_title(id); switch.set_selectable(false); switch.connect_active_notify(clone!( #[weak(rename_to = this)] self, move |s| { if let Err(e) = dbqueries::set_discovery_setting(id, s.is_active()) { error!("failed setting search preference: {e}"); } else if s.is_active() { this.entry.remove_css_class("error"); this.no_platforms_selected_label.set_visible(false); } } )); self.list.add(&switch); } self.entry.connect_activate(clone!( #[weak(rename_to = this)] self, #[strong] sender, move |entry| { let entry_text = entry.text().to_string(); let url = Url::parse(&entry_text); let this = this.clone(); this.search_button.set_visible(false); this.loading_spinner.set_visible(true); this.loading_spinner.announce( &this .loading_spinner .tooltip_text() .unwrap_or(i18n("Loading…").into()), gtk::AccessibleAnnouncementPriority::High, ); this.entry.remove_css_class("error"); this.no_platforms_selected_label.set_visible(false); let loading_done = loading_done.clone(); crate::RUNTIME.spawn(clone!( #[strong] sender, async move { if let Err(e) = match url { Ok(url) => add_podcast_from_url(url.to_string(), &sender) .await .map_err(SearchError::from), Err(_) => search_podcasts(entry_text, &sender).await, } { match e { NoSearchPlatformsSelected => (), _ => send!(sender, Action::ErrorNotification(format!("{e}"))), }; send!(loading_done, Err(e)); } else { send!(loading_done, Ok(())) } } )); } )); self.search_button.connect_clicked(clone!( #[weak(rename_to = this)] self, move |_| { this.entry.emit_activate(); } )); } } #[glib::object_subclass] impl ObjectSubclass for DiscoveryPagePriv { const NAME: &'static str = "PdDiscoveryPage"; type Type = DiscoveryPage; type ParentType = adw::NavigationPage; fn class_init(klass: &mut Self::Class) { klass.bind_template(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl WidgetImpl for DiscoveryPagePriv {} impl ObjectImpl for DiscoveryPagePriv {} impl NavigationPageImpl for DiscoveryPagePriv {} glib::wrapper! { pub struct DiscoveryPage(ObjectSubclass) @extends adw::NavigationPage, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } impl DiscoveryPage { pub(crate) fn new(sender: &Sender) -> Self { let widget: Self = glib::Object::new(); widget.imp().init(sender); widget } } async fn add_podcast_from_url(url_input: String, sender: &Sender) -> Result<()> { let mut url = url_input; if !(url.starts_with("https://") || url.starts_with("http://")) { url = format!("http://{}", url); }; debug!("Url: {}", url); let url = if url.contains("itunes.com") || url.contains("apple.com") { info!("Detected itunes url."); let itunes_url = itunes_to_rss(&url).await?; info!("Resolved to {}", itunes_url); itunes_url } else if url.contains("soundcloud.com") && !url.contains("feeds.soundcloud.com") { info!("Detected soundcloud url."); let soundcloud_url = soundcloud_to_rss(&Url::parse(&url)?).await?; info!("Resolved to {}", soundcloud_url); soundcloud_url.to_string() } else { url.to_owned() }; crate::utils::subscribe(sender, url).await; Ok(()) } async fn search_podcasts(text: String, sender: &Sender) -> Result<(), SearchError> { let results = search(&text).await; send!(sender, Action::GoToFoundPodcasts(Arc::new(results?))); Ok(()) } podcasts-25.2/podcasts-gtk/src/widgets/discovery_search_results.rs000066400000000000000000000200261500126606300256040ustar00rootroot00000000000000// discovery_search_result.rs // // Copyright 2022-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::*; use adw::subclass::prelude::*; use async_channel::Sender; use glib::clone; use glib::subclass::InitializingObject; use gtk::CompositeTemplate; use gtk::glib; use crate::app::Action; use crate::i18n::i18n; use podcasts_data::discovery::FoundPodcast; #[derive(Debug, CompositeTemplate, Default)] #[template(resource = "/org/gnome/Podcasts/gtk/discovery_search_results.ui")] pub struct SearchResultsPriv { #[template_child] list: TemplateChild, #[template_child] no_results: TemplateChild, } impl SearchResultsPriv { pub(crate) fn init(&self, entries: &Vec, sender: &Sender) { for e in entries { let entry_widget = Podcast::new(e, sender); self.list.append(&entry_widget); } if entries.is_empty() { self.no_results.set_visible(true); self.list.set_visible(false); } } } #[glib::object_subclass] impl ObjectSubclass for SearchResultsPriv { const NAME: &'static str = "PdDiscoverySearchResults"; type Type = SearchResults; type ParentType = adw::NavigationPage; fn class_init(klass: &mut Self::Class) { klass.bind_template(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl WidgetImpl for SearchResultsPriv {} impl ObjectImpl for SearchResultsPriv {} impl NavigationPageImpl for SearchResultsPriv {} glib::wrapper! { pub struct SearchResults(ObjectSubclass) @extends adw::NavigationPage, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } impl SearchResults { pub(crate) fn new(entries: &Vec, sender: &Sender) -> Self { let widget: Self = glib::Object::new(); widget.imp().init(entries, sender); widget } } #[derive(Debug, CompositeTemplate, Default)] #[template(resource = "/org/gnome/Podcasts/gtk/discovery_found_podcast.ui")] pub struct PodcastPriv { #[template_child] subscribe: TemplateChild, #[template_child] cover: TemplateChild, #[template_child] description: TemplateChild, #[template_child] feed_url: TemplateChild, #[template_child] title: TemplateChild, #[template_child] author: TemplateChild, #[template_child] episode_count: TemplateChild, #[template_child] episode_count_label: TemplateChild, #[template_child] last_publication: TemplateChild, #[template_child] loading_spinner: TemplateChild, #[template_child] subscribe_stack: TemplateChild, } impl PodcastPriv { fn init(&self, p: &FoundPodcast, sender: &Sender) { self.title.set_text(&p.title); self.feed_url.set_text(&p.feed); self.author.set_text(&p.author); let description = p.description.trim(); if !description.is_empty() { self.description.set_text(description); self.description.set_tooltip_text(Some(description)); self.description.set_visible(true); } if let Some(ep_count) = p.episode_count { self.episode_count_label.set_text(&format!("{}", ep_count)); self.episode_count_label.set_visible(true); } if let Some(last_publication) = p.last_publication { let date = last_publication .format_localized("%e %b %Y", *crate::CHRONO_LOCALE) .to_string(); self.last_publication.set_text(&date); self.last_publication.set_visible(true); } let url = p.feed.clone(); self.subscribe.connect_clicked(clone!( #[weak(rename_to = this)] self, #[strong] sender, move |_| { let (loading_done, receiver) = async_channel::bounded(1); this.subscribe_stack .set_visible_child(&this.loading_spinner.get()); this.loading_spinner.announce( &this .loading_spinner .tooltip_text() .unwrap_or(i18n("Subscribing to feed…").into()), gtk::AccessibleAnnouncementPriority::High, ); crate::RUNTIME.spawn(clone!( #[strong] sender, #[strong] url, async move { crate::utils::subscribe(&sender, url).await; send!(loading_done, ()); } )); crate::MAINCONTEXT.spawn_local(clone!( #[weak] this, async move { while receiver.recv().await.is_ok() { this.subscribe_stack .set_visible_child(&this.subscribe.get()); } } )); } )); let art = p.art.clone(); let (sender, receiver) = async_channel::bounded(1); crate::RUNTIME.spawn(async move { if let Err(e) = async { let response = reqwest::get(&art).await?; let bytes = response.bytes().await?; let texture = { let strm = gtk::gio::MemoryInputStream::from_bytes(&glib::Bytes::from(&bytes)); let pixbuf = gtk::gdk_pixbuf::Pixbuf::from_stream(&strm, gtk::gio::Cancellable::NONE)?; gtk::gdk::Texture::for_pixbuf(&pixbuf) }; sender .send(texture) .await .expect("failed to send img to main thread"); Ok::<(), anyhow::Error>(()) } .await { error!("failed to load image for search result: {art} {e}"); } }); crate::MAINCONTEXT.spawn_local(clone!( #[weak(rename_to = this)] self, async move { if let Ok(texture) = receiver.recv().await { this.cover.set_paintable(Some(&texture)); } } )); } } #[glib::object_subclass] impl ObjectSubclass for PodcastPriv { const NAME: &'static str = "PdDiscoveryFoundPodcast"; type Type = Podcast; type ParentType = adw::PreferencesRow; fn class_init(klass: &mut Self::Class) { klass.bind_template(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl ObjectImpl for PodcastPriv {} impl WidgetImpl for PodcastPriv {} impl ListBoxRowImpl for PodcastPriv {} impl PreferencesRowImpl for PodcastPriv {} glib::wrapper! { pub struct Podcast(ObjectSubclass) @extends adw::PreferencesRow, gtk::ListBoxRow, gtk::Widget, @implements gtk::Actionable, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } impl Podcast { pub(crate) fn new(p: &FoundPodcast, sender: &Sender) -> Self { let widget: Self = glib::Object::new(); widget.imp().init(p, sender); widget } } podcasts-25.2/podcasts-gtk/src/widgets/download_progress_bar.rs000066400000000000000000000227121500126606300250520ustar00rootroot00000000000000// download_progress.rs // // Copyright 2017 Jordan Petridis // Copyright 2023-2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::BinExt; use adw::subclass::prelude::*; use anyhow::{Result, anyhow, bail}; use glib::Properties; use glib::clone; use gtk::glib; use gtk::prelude::*; use once_cell::sync::OnceCell; use std::cell::Cell; use std::sync::{Arc, Mutex, TryLockError}; use std::time::Duration; use crate::i18n::i18n; use crate::manager; use crate::manager::ActiveProgress; use podcasts_data::EpisodeId; use podcasts_data::dbqueries; use podcasts_data::downloader::DownloadProgress; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = DownloadProgressBar)] pub struct DownloadProgressPriv { progressbar: gtk::ProgressBar, id: OnceCell, listener: Cell, // lock for update callback #[property(get, set)] local_size: Cell, #[property(get, set)] total_size: Cell, } #[glib::object_subclass] impl ObjectSubclass for DownloadProgressPriv { const NAME: &'static str = "PdDownloadProgress"; type Type = super::DownloadProgressBar; type ParentType = adw::Bin; } #[glib::derived_properties] impl ObjectImpl for DownloadProgressPriv { fn constructed(&self) { self.parent_constructed(); self.progressbar.set_visible(false); self.progressbar.set_hexpand(true); self.progressbar.set_pulse_step(0.0); self.progressbar .update_property(&[gtk::accessible::Property::Label(&i18n("Download progress"))]); } } impl WidgetImpl for DownloadProgressPriv {} impl BinImpl for DownloadProgressPriv {} impl DownloadProgressPriv {} glib::wrapper! { pub struct DownloadProgressBar(ObjectSubclass) @extends adw::Bin, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } impl DownloadProgressBar { pub fn init(&self, episode_id: EpisodeId) { let _ = self.imp().id.set(episode_id); self.set_child(Some(&self.imp().progressbar)); } /// Notifies when downloading started/stopped pub fn connect_state_change(&self, f: F) { self.imp().progressbar.connect_visible_notify(f); } pub fn id(&self) -> EpisodeId { *self.imp().id.get().unwrap() } fn has_listener(&self) -> bool { self.imp().listener.get() } pub fn check_if_downloading(&self) -> Result { let id = self.id(); // Check if the episode is being downloaded if let Some(prog) = self.active_dl()? { // avoid putting up multiple callbacks if self.has_listener() { return Ok(true); } debug!("Download is happening, starting download bar."); // set a callback that will update the state when the download finishes let callback = clone!( #[weak(rename_to = this)] self, #[upgrade_or] glib::ControlFlow::Break, move || { if let Ok(guard) = manager::ACTIVE_DOWNLOADS.read() { if !guard.contains_key(&id) { this.imp().progressbar.set_visible(false); this.imp().progressbar.set_fraction(0.0); this.imp().listener.set(false); debug!("Download bar done, hiding it now."); return glib::ControlFlow::Break; } } glib::ControlFlow::Continue } ); glib::timeout_add_local(Duration::from_millis(250), callback); self.imp().listener.set(true); self.imp().progressbar.set_visible(true); // Setup a callback that will update the total_size label // with the http ContentLength header number rather than // relying to the RSS feed. update_total_size_callback(self, &prog); // Setup a callback that will update the progress bar. update_progressbar_callback(self, &prog, id); return Ok(true); } Ok(false) } fn active_dl(&self) -> Result> { let id = self.id(); let m = manager::ACTIVE_DOWNLOADS .read() .map_err(|_| anyhow!("Failed to get a lock on the mutex."))?; Ok(m.get(&id).cloned()) } pub fn cancel(&self) -> Result<()> { let id = self.id(); if let Some(prog) = self.active_dl()? { if let Ok(mut m) = prog.lock() { m.cancel(); } // Cancel is not instant so we have to wait a bit glib::timeout_add_local( Duration::from_millis(50), clone!( #[weak(rename_to = this)] self, #[upgrade_or] glib::ControlFlow::Break, move || { if let Ok(thing) = this.active_dl() { if thing.is_none() { // Recalculate the widget state if let Err(err) = dbqueries::get_episode_widget_from_id(id) { error!("Error: {}", err); } this.imp().progressbar.set_visible(false); this.imp().progressbar.set_fraction(0.0); return glib::ControlFlow::Break; } } glib::ControlFlow::Continue } ), ); } Ok(()) } fn update_progress(&self, local_size: u64, fraction: f64) { self.set_local_size(local_size); self.imp().progressbar.set_fraction(fraction); } } // Setup a callback that will update the progress bar. #[inline] fn update_progressbar_callback( widget: &DownloadProgressBar, prog: &Arc>, episode_id: EpisodeId, ) { let callback = clone!( #[weak] widget, #[strong] prog, #[upgrade_or] glib::ControlFlow::Break, move || { progress_bar_helper(&widget, &prog, episode_id).unwrap_or(glib::ControlFlow::Break) } ); glib::timeout_add_local(Duration::from_millis(100), callback); } fn progress_bar_helper( widget: &DownloadProgressBar, prog: &Arc>, episode_id: EpisodeId, ) -> Result { let (fraction, downloaded, cancel) = match prog.try_lock() { Ok(guard) => ( guard.get_fraction(), guard.get_downloaded(), guard.should_cancel(), ), Err(TryLockError::WouldBlock) => return Ok(glib::ControlFlow::Continue), Err(TryLockError::Poisoned(_)) => bail!("Progress Mutex is poisoned"), }; // Update the progress_bar. if (0.0..=1.0).contains(&fraction) && (!fraction.is_nan()) { widget.update_progress(downloaded, fraction); } // Check if the download is still active let active = match manager::ACTIVE_DOWNLOADS.read() { Ok(guard) => guard.contains_key(&episode_id), Err(_) => return Err(anyhow!("Failed to get a lock on the mutex.")), }; if (fraction >= 1.0 && !fraction.is_nan()) || !active || cancel { Ok(glib::ControlFlow::Break) } else { Ok(glib::ControlFlow::Continue) } } // Setup a callback that will update the total_size label // with the http ContentLength header number rather than // relying to the RSS feed. #[inline] fn update_total_size_callback(widget: &DownloadProgressBar, prog: &Arc>) { let callback = clone!( #[strong] prog, #[weak] widget, #[upgrade_or] glib::ControlFlow::Break, move || total_size_helper(&widget, &prog).unwrap_or(glib::ControlFlow::Continue), ); glib::timeout_add_local(Duration::from_millis(100), callback); } fn total_size_helper( widget: &DownloadProgressBar, prog: &Arc>, ) -> Result { // Get the total_bytes. let total_bytes = match prog.try_lock() { Ok(guard) => guard.get_size(), Err(TryLockError::WouldBlock) => return Ok(glib::ControlFlow::Continue), Err(TryLockError::Poisoned(_)) => bail!("Progress Mutex is poisoned"), }; debug!("Total Size: {}", total_bytes); if total_bytes != 0 { // Update the total_size label widget.set_total_size(total_bytes); // Do not call again the callback Ok(glib::ControlFlow::Break) } else { Ok(glib::ControlFlow::Continue) } } podcasts-25.2/podcasts-gtk/src/widgets/empty_show.rs000066400000000000000000000033111500126606300226630ustar00rootroot00000000000000// empty_show.rs // // Copyright 2022 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use gtk::subclass::prelude::*; use gtk::{CompositeTemplate, glib}; #[derive(Debug, Default, CompositeTemplate)] #[template(resource = "/org/gnome/Podcasts/gtk/empty_show.ui")] pub struct EmptyShowPriv {} #[glib::object_subclass] impl ObjectSubclass for EmptyShowPriv { const NAME: &'static str = "PdEmptyShow"; type Type = EmptyShow; type ParentType = gtk::Box; fn class_init(klass: &mut Self::Class) { klass.bind_template(); } // You must call `Widget`'s `init_template()` within `instance_init()`. fn instance_init(obj: &glib::subclass::InitializingObject) { obj.init_template(); } } impl ObjectImpl for EmptyShowPriv {} impl WidgetImpl for EmptyShowPriv {} impl BoxImpl for EmptyShowPriv {} glib::wrapper! { pub struct EmptyShow(ObjectSubclass) @extends gtk::Widget, gtk::Box; } impl Default for EmptyShow { fn default() -> Self { glib::Object::new() } } podcasts-25.2/podcasts-gtk/src/widgets/empty_view.rs000066400000000000000000000040161500126606300226600ustar00rootroot00000000000000// empty_view.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use crate::config::APP_ID; use adw::subclass::prelude::*; use gtk::{CompositeTemplate, glib}; #[derive(Debug, CompositeTemplate)] #[template(resource = "/org/gnome/Podcasts/gtk/empty_view.ui")] pub struct EmptyViewPriv { #[template_child] pub status_page: TemplateChild, } #[glib::object_subclass] impl ObjectSubclass for EmptyViewPriv { const NAME: &'static str = "PdEmptyView"; type Type = EmptyView; type ParentType = adw::Bin; fn new() -> Self { Self { status_page: TemplateChild::default(), } } fn class_init(klass: &mut Self::Class) { klass.bind_template(); } // You must call `Widget`'s `init_template()` within `instance_init()`. fn instance_init(obj: &glib::subclass::InitializingObject) { obj.init_template(); } } impl ObjectImpl for EmptyViewPriv { fn constructed(&self) { self.parent_constructed(); self.status_page.set_icon_name(Some(APP_ID)); } } impl WidgetImpl for EmptyViewPriv {} impl BinImpl for EmptyViewPriv {} glib::wrapper! { pub struct EmptyView(ObjectSubclass) @extends gtk::Widget, adw::Bin; } impl Default for EmptyView { fn default() -> Self { glib::Object::new() } } podcasts-25.2/podcasts-gtk/src/widgets/episode.rs000066400000000000000000000412441500126606300221240ustar00rootroot00000000000000// episode.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::subclass::prelude::*; use anyhow::Result; use async_channel::Sender; use chrono::prelude::*; use glib::clone; use glib::subclass::InitializingObject; use gtk::CompositeTemplate; use gtk::glib; use gtk::prelude::*; use once_cell::sync::Lazy; use crate::app::Action; use crate::i18n::i18n_f; use crate::manager; use crate::widgets::{DownloadProgressBar, EpisodeMenu}; use podcasts_data::EpisodeId; use podcasts_data::dbqueries; use podcasts_data::utils::get_download_dir; use podcasts_data::{EpisodeModel, EpisodeWidgetModel}; static SIZE_OPTS: Lazy = Lazy::new(|| { // Declare a custom humansize option struct // See: https://docs.rs/humansize/2.1.3/humansize/struct.FormatSizeOptions.html humansize::FormatSizeOptions::from(humansize::WINDOWS).decimal_places(0) }); #[derive(Debug, CompositeTemplate, Default)] #[template(resource = "/org/gnome/Podcasts/gtk/episode_widget.ui")] pub struct EpisodeWidgetPriv { #[template_child] progressbar: TemplateChild, // InfoLabels #[template_child] title: TemplateChild, #[template_child] date: TemplateChild, #[template_child] separator1: TemplateChild, #[template_child] duration: TemplateChild, #[template_child] separator2: TemplateChild, #[template_child] local_size: TemplateChild, #[template_child] size_separator: TemplateChild, #[template_child] total_size: TemplateChild, #[template_child] played_checkmark: TemplateChild, // Buttons #[template_child] play: TemplateChild, #[template_child] download: TemplateChild, #[template_child] cancel: TemplateChild, #[template_child] text_only: TemplateChild, } impl EpisodeWidgetPriv { pub(crate) fn init( &self, sender: &Sender, episode: EpisodeWidgetModel, add_show_link: bool, ) { crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak(rename_to = this)] self, #[strong] sender, async move { let id = episode.id(); this.init_info(&episode); if episode.uri().is_none() { this.state_no_uri(id); return; } this.init_progressbar(id); this.init_buttons(&sender, id); if let Err(err) = this.determine_buttons_state(&episode) { error!("Error: {}", err); } this.init_context_menu(sender, episode, add_show_link); } ), ); } // Rare case when an episode does not have // any audio files attached as enclosure tags. fn state_no_uri(&self, id: EpisodeId) { self.cancel.set_visible(false); self.play.set_visible(false); self.local_size.set_visible(false); self.size_separator.set_visible(false); self.download.set_visible(false); self.text_only.set_visible(true); self.text_only.set_action_name(Some("app.go-to-episode")); self.text_only .set_action_target_value(Some(&id.0.to_variant())); } // InProgress State: // * Show ProgressBar and Cancel Button. // * Show `total_size`, `local_size` labels and `size_separator`. // * Hide `date`, `duration` labels and `separator1`. // * Hide Download and Play Buttons fn state_prog(&self) { self.cancel.set_visible(true); self.total_size.set_visible(true); self.local_size.set_visible(true); self.size_separator.set_visible(true); self.date.set_visible(false); self.separator1.set_visible(false); self.duration.set_visible(false); self.play.set_visible(false); self.download.set_visible(false); self.update_separator2_visibility(); } // Playable State: // * Hide ProgressBar and Cancel, Download Buttons. // * Hide `local_size` labels and `size_separator`. // * Show `date`, `duration` labels and `separator1`. // * Show Play Button and `total_size` label fn state_playable(&self) { self.cancel.set_visible(false); self.download.set_visible(false); self.local_size.set_visible(false); self.size_separator.set_visible(false); self.date.set_visible(true); self.separator1.set_visible(true); self.duration.set_visible(true); self.total_size.set_visible(true); self.play.set_visible(true); self.update_separator2_visibility(); } // NotDownloaded State: // * Hide ProgressBar and Cancel, Play Buttons. // * Hide `local_size` labels and `size_separator`. // * Show Download Button // * Show `date`, `duration` labels and `separator1`. // * Determine `total_size` label state (Comes from `episode.lenght`). fn state_download(&self) { self.cancel.set_visible(false); self.play.set_visible(false); self.local_size.set_visible(false); self.size_separator.set_visible(false); self.date.set_visible(true); self.separator1.set_visible(true); self.duration.set_visible(true); self.download.set_visible(true); self.update_separator2_visibility(); } /// Change the state of the `EpisodeWidget`. /// /// Function Flowchart: /// /// ------------------- -------------- /// | Does the Episode| YES | State: | /// | not have a | ----> | NoUri | /// | download link? | | | /// ------------------- -------------- /// | /// | NO /// | /// \_/ /// ------------------- -------------- /// | Is the Episode | YES | State: | /// | currently being | ----> | InProgress | /// | downloaded? | | | /// ------------------- -------------- /// | /// | NO /// | /// \_/ /// ------------------- -------------- /// | is the episode | YES | State: | /// | downloaded | ----> | Playable | /// | already? | | | /// ------------------- -------------- /// | /// | NO /// | /// \_/ /// ------------------- /// | State: | /// | NotDownloaded | /// ------------------- fn determine_buttons_state(&self, episode: &EpisodeWidgetModel) -> Result<()> { let is_downloading = self.progressbar.check_if_downloading()?; if is_downloading { // State InProgress self.state_prog(); } else if episode.local_uri().is_some() { // State: Playable self.state_playable(); } else { // State: NotDownloaded self.state_download(); } Ok(()) } fn init_info(&self, episode: &EpisodeWidgetModel) { self.set_title(episode); self.set_date(episode.epoch()); self.set_duration(episode.duration()); self.set_size(episode.length()); self.set_played(episode.played().is_some()); } fn set_title(&self, episode: &EpisodeWidgetModel) { self.title.set_text(episode.title()); } fn set_played(&self, played: bool) { if played { self.title.add_css_class("dim-label"); self.played_checkmark.set_visible(true); } else { self.title.remove_css_class("dim-label"); self.played_checkmark.set_visible(false); } } // Set the date label of the episode widget. fn set_date(&self, epoch: NaiveDateTime) { let now: DateTime = Local::now(); let ts = DateTime::::from(epoch.and_utc()); // If the episode is from a different year, print year as well if now.year() != ts.year() { self.date.set_text( ts.format_localized("%e %b %Y", *crate::CHRONO_LOCALE) .to_string() .trim(), ); // Else omit the year from the label } else { self.date.set_text( ts.format_localized("%e %b", *crate::CHRONO_LOCALE) .to_string() .trim(), ); } } // Set the duration label of the episode widget. fn set_duration(&self, seconds: Option) { // If length is provided if let Some(s) = seconds { // Convert seconds to minutes let minutes = chrono::Duration::seconds(s.into()).num_minutes(); // If the length is 1 or more minutes if minutes != 0 { // Set the label and show them. self.duration .set_text(&i18n_f("{} min", &[&minutes.to_string()])); self.duration.set_visible(true); self.separator1.set_visible(true); return; } } // Else hide the labels self.separator1.set_visible(false); self.duration.set_visible(false); } // Set the size label of the episode widget. fn set_size(&self, bytes: Option) { // Convert the bytes to a String label let size = bytes.and_then(|s| { if s <= 0 { None } else { Some(humansize::format_size(s as u32, *SIZE_OPTS)) } }); if let Some(s) = size { self.total_size.set_text(&s); self.total_size.set_visible(true); } else { self.total_size.set_visible(false); } } fn init_progressbar(&self, id: EpisodeId) { self.progressbar.init(id); self.progressbar.connect_state_change(clone!( #[weak(rename_to = this)] self, move |_| { if let Err(err) = dbqueries::get_episode_widget_from_id(id) .map(|ep| this.determine_buttons_state(&ep)) { error!("Could not get episode info: {err}"); } } )); self.progressbar .bind_property("local_size", &*self.local_size, "label") .transform_to(move |_, downloaded: u64| { Some(humansize::format_size(downloaded, *SIZE_OPTS)) }) .flags(glib::BindingFlags::SYNC_CREATE) .build(); self.progressbar.connect_total_size_notify(clone!( #[weak(rename_to = this)] self, move |_| { // try_from should handle NaN case this.set_size(i32::try_from(this.progressbar.total_size()).ok()); } )); } fn init_buttons(&self, sender: &Sender, id: EpisodeId) { self.cancel.connect_clicked(clone!( #[weak(rename_to = this)] self, move |_| { if let Err(e) = this.progressbar.cancel() { error!("failed to cancel download {e}"); } } )); self.play.connect_clicked(clone!( #[weak(rename_to = this)] self, #[strong] sender, move |_| { if let Ok(episode) = dbqueries::get_episode_widget_from_id(id) { // Grey out the title this.set_title(&episode); // Play the episode send_blocking!(sender, Action::InitEpisode(episode.id())); // Refresh background views to match the normal/greyout title state send_blocking!(sender, Action::RefreshEpisodesViewBGR); } } )); self.download.connect_clicked(clone!( #[weak(rename_to = this)] self, #[strong] sender, move |dl| { if let Ok(ep) = dbqueries::get_episode_widget_from_id(id) { let result = on_download_clicked(&ep, &sender).and_then(|_| { info!("Download started successfully."); this.determine_buttons_state(&ep) }); if let Err(err) = result { error!("Failed to start download {err}"); } else { this.progressbar.grab_focus(); } } // Restore sensitivity after operations above complete dl.set_sensitive(true); } )); } fn init_context_menu( &self, sender: Sender, episode: EpisodeWidgetModel, add_show_link: bool, ) { let on_rightclick = clone!( #[weak(rename_to = this)] self, move |(x, y)| { let pid = episode.show_id(); let show = if add_show_link { Some(pid) } else { None }; let menu = EpisodeMenu::new(&sender, &episode, show); let popover = gtk::PopoverMenu::from_model(Some(&menu.menu)); popover.set_parent(&*this.obj()); popover.insert_action_group("episode", Some(&menu.group)); popover.set_pointing_to(Some(>k::gdk::Rectangle::new(x as i32, y as i32, 1, 1))); popover.set_has_arrow(false); popover.popup(); } ); let on_long_press = on_rightclick.clone(); let long_press = gtk::GestureLongPress::new(); long_press.connect_pressed(move |_, x, y| { on_long_press((x, y)); }); let right_click = gtk::GestureClick::builder() .button(gtk::gdk::BUTTON_SECONDARY) .build(); right_click.connect_pressed(move |_, _, x, y| { on_rightclick((x, y)); }); self.obj().add_controller(long_press); self.obj().add_controller(right_click); } fn update_separator2_visibility(&self) { self.separator2 .set_visible(self.date.is_visible() && self.total_size.is_visible()); } } fn on_download_clicked(ep: &EpisodeWidgetModel, sender: &Sender) -> Result<()> { let pd = dbqueries::get_podcast_from_id(ep.show_id())?; let download_dir = get_download_dir(pd.title())?; // Start a new download. manager::add(sender.clone(), ep.id(), download_dir)?; // Update Views send_blocking!(sender, Action::RefreshEpisodesViewBGR); Ok(()) } #[glib::object_subclass] impl ObjectSubclass for EpisodeWidgetPriv { const NAME: &'static str = "PdEpisode"; type Type = EpisodeWidget; type ParentType = gtk::Box; fn class_init(klass: &mut Self::Class) { klass.bind_template(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl WidgetImpl for EpisodeWidgetPriv {} impl ObjectImpl for EpisodeWidgetPriv {} impl BoxImpl for EpisodeWidgetPriv {} glib::wrapper! { pub struct EpisodeWidget(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } impl EpisodeWidget { pub(crate) fn new( sender: &Sender, episode: EpisodeWidgetModel, add_show_link: bool, ) -> Self { let widget = Self::default(); widget.init(sender, episode, add_show_link); widget } pub(crate) fn init( &self, sender: &Sender, episode: EpisodeWidgetModel, add_show_link: bool, ) { self.imp().init(sender, episode, add_show_link); } } impl Default for EpisodeWidget { fn default() -> Self { let widget: Self = glib::Object::new(); widget } } podcasts-25.2/podcasts-gtk/src/widgets/episode_description.rs000066400000000000000000000302051500126606300245220ustar00rootroot00000000000000// episode_description.rs // // Copyright 2020 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::subclass::prelude::*; use anyhow::Result; use async_channel::Sender; use chrono::prelude::*; use glib::clone; use glib::subclass::InitializingObject; use gtk::CompositeTemplate; use gtk::glib; use gtk::prelude::*; use std::borrow::Borrow; use std::sync::Arc; use crate::app::Action; use crate::episode_description_parser; use crate::widgets::DownloadProgressBar; use crate::widgets::EpisodeMenu; use podcasts_data::EpisodeWidgetModel; use podcasts_data::{Episode, EpisodeId, EpisodeModel, Show, ShowId}; use podcasts_data::{dbqueries, downloader}; pub enum EpisodeDescriptionAction { EpisodeSpecificImage(gtk::gdk::Texture), } #[derive(Debug, CompositeTemplate, Default)] #[template(resource = "/org/gnome/Podcasts/gtk/episode_description.ui")] pub struct EpisodeDescriptionPriv { #[template_child] menu_button: TemplateChild, #[template_child] cover: TemplateChild, #[template_child] podcast_title: TemplateChild, #[template_child] episode_title: TemplateChild, #[template_child] episode_duration: TemplateChild, #[template_child] description: TemplateChild, #[template_child] episode_specific_cover: TemplateChild, #[template_child] progressbar: TemplateChild, #[template_child] stream_button: TemplateChild, #[template_child] download_button: TemplateChild, #[template_child] cancel_button: TemplateChild, #[template_child] play_button: TemplateChild, #[template_child] delete_button: TemplateChild, } impl EpisodeDescriptionPriv { fn init(&self, sender: Sender, ep: Arc, show: Arc) { let (ed_sender, r) = async_channel::unbounded(); crate::MAINCONTEXT.spawn_local(clone!( #[weak(rename_to = this)] self, async move { while let Ok(action) = r.recv().await { this.do_action(action); } } )); self.set_description(&ep); self.set_duration(&ep); self.episode_title.set_text(ep.title()); self.podcast_title.set_text(show.title()); self.set_cover(ep.show_id()); if let Some(uri) = ep.image_uri().as_ref() { // don't show if it's the same as the show cover if *uri != show.image_uri().unwrap_or("") { let _ = self.set_episode_specific_cover(ed_sender, ep.show_id(), uri); } } let id = ep.id(); self.description.connect_activate_link(clone!( #[strong] sender, move |_, url| { if let Some(seconds_str) = url.strip_prefix("jump:") { if let Ok(seconds) = seconds_str.parse() { send_blocking!(sender, Action::InitEpisodeAt(id, seconds)); } else { error!("failed to parse jump link: {}", url); } glib::Propagation::Stop } else { glib::Propagation::Proceed } } )); let ep: &Episode = ep.borrow(); if ep.uri().is_some() { self.init_buttons(sender, ep, id); self.determine_button_state(&ep.clone().into()); } } fn init_buttons(&self, sender: Sender, ep: &Episode, id: EpisodeId) { self.stream_button.connect_clicked(clone!( #[strong] sender, move |_| { send_blocking!(sender, Action::StreamEpisode(id)); } )); self.play_button.connect_clicked(clone!( #[strong] sender, move |_| { send_blocking!(sender, Action::InitEpisode(id)); } )); let show_id = ep.show_id(); self.download_button.connect_clicked(clone!( #[weak(rename_to = this)] self, #[strong] sender, move |_| { use podcasts_data::utils::get_download_dir; if let Err(e) = (|| { let pd = dbqueries::get_podcast_from_id(show_id)?; let download_dir = get_download_dir(pd.title())?; crate::manager::add(sender.clone(), id, download_dir)?; Ok::<(), anyhow::Error>(()) })() { error!("failed to start download {e}"); } this.refresh_buttons(id); this.progressbar.grab_focus(); send_blocking!(sender, Action::RefreshEpisodesView); send_blocking!(sender, Action::RefreshWidgetIfSame(show_id)); } )); self.delete_button.connect_clicked(clone!( #[weak(rename_to = this)] self, #[strong] sender, move |_| { if let Ok(ep) = dbqueries::get_episode_from_id(id) { let mut cleaner_ep = podcasts_data::EpisodeCleanerModel::from(ep); if let Err(e) = podcasts_data::utils::delete_local_content(&mut cleaner_ep) { error!("failed to delete ep {e}"); } } this.refresh_buttons(id); send_blocking!(sender, Action::RefreshEpisodesView); send_blocking!(sender, Action::RefreshWidgetIfSame(show_id)); } )); self.progressbar.init(ep.id()); self.progressbar.connect_state_change(clone!( #[weak(rename_to = this)] self, move |_| { this.refresh_buttons(id); } )); self.cancel_button.connect_clicked(clone!( #[weak(rename_to = this)] self, move |_| { if let Err(e) = this.progressbar.cancel() { error!("failed to cancel download {e}"); } } )); } fn refresh_buttons(&self, id: EpisodeId) { match dbqueries::get_episode_widget_from_id(id) { Ok(ep) => self.determine_button_state(&ep), Err(e) => error!("failed to fetch episode for description refresh {e}"), } } fn determine_button_state(&self, ep: &EpisodeWidgetModel) { let is_downloading = self.progressbar.check_if_downloading().unwrap_or(false); self.cancel_button.set_visible(is_downloading); let is_downloaded = ep.local_uri().is_some(); self.download_button .set_visible(!is_downloaded && !is_downloading); self.stream_button.set_visible(!is_downloaded); self.delete_button.set_visible(is_downloaded); self.play_button.set_visible(is_downloaded); } fn set_description(&self, ep: &Episode) { if let Some(t) = ep.description() { let default_text = self.description.text(); let markup = episode_description_parser::html2pango_markup(t); self.description.set_markup(&markup); // recover from invalid markup if self.description.text() == default_text { let plain = html2text::config::plain() .string_from_read(t.as_bytes(), t.len()) .unwrap_or_else(|_| t.to_string()); self.description.set_text(&plain); } }; } fn set_duration(&self, ep: &Episode) { let duration = ep.duration().map(|s| { let seconds = s % 60; let minutes = (s / 60) % 60; let hours = (s / 60) / 60; format!("{:02}:{:02}:{:02}", hours, minutes, seconds) }); let now = Local::now(); let ep_local = DateTime::::from(ep.epoch().and_utc()); // If the episode is from a different year, print year as well let date = if now.year() != ep_local.year() { ep_local .format_localized("%e %b %Y", *crate::CHRONO_LOCALE) .to_string() // Else omit the year from the label } else { ep_local .format_localized("%e %b", *crate::CHRONO_LOCALE) .to_string() }; let duration_date = match duration { Some(duration) => format!("{} · {}", duration, date), None => date, }; self.episode_duration.set_text(&duration_date); } fn set_cover(&self, show_id: ShowId) { crate::download_covers::load_widget_texture(&self.cover.get(), show_id, crate::Thumb64); } fn set_episode_specific_cover( &self, sender: Sender, show_id: ShowId, uri: &str, ) -> Result<()> { let pd = dbqueries::get_podcast_cover_from_id(show_id)?; let uri = uri.to_owned(); crate::RUNTIME.spawn(clone!( #[strong] pd, async move { if let Err(e) = async move { let path = downloader::cache_episode_image(&pd, &uri, true).await?; let texture = gtk::gdk::Texture::from_filename(path)?; send!( sender, EpisodeDescriptionAction::EpisodeSpecificImage(texture) ); Ok::<(), anyhow::Error>(()) } .await { error!("failed to get episode specific cover: {e}"); } } )); Ok(()) } fn do_action(&self, action: EpisodeDescriptionAction) -> glib::ControlFlow { match action { EpisodeDescriptionAction::EpisodeSpecificImage(texture) => { self.episode_specific_cover.set_paintable(Some(&texture)); self.episode_specific_cover.set_visible(true); } } glib::ControlFlow::Continue } } #[glib::object_subclass] impl ObjectSubclass for EpisodeDescriptionPriv { const NAME: &'static str = "PdEpisodeDescription"; type Type = EpisodeDescription; type ParentType = adw::NavigationPage; fn class_init(klass: &mut Self::Class) { klass.bind_template(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl WidgetImpl for EpisodeDescriptionPriv {} impl ObjectImpl for EpisodeDescriptionPriv {} impl NavigationPageImpl for EpisodeDescriptionPriv { fn shown(&self) { self.description.set_selectable(true); } } glib::wrapper! { pub struct EpisodeDescription(ObjectSubclass) @extends adw::NavigationPage, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } impl EpisodeDescription { pub(crate) fn new(ep: Arc, show: Arc, sender: Sender) -> Self { let widget: Self = glib::Object::new(); widget.update_episode_menu(&sender, ep.as_ref(), show.clone()); widget.imp().init(sender, ep, show); widget } pub(crate) fn update_episode_menu( &self, sender: &Sender, ep: &dyn EpisodeModel, show: Arc, ) { let menu = EpisodeMenu::new(sender, ep, Some(show.id())); self.imp().menu_button.set_menu_model(Some(&menu.menu)); self.insert_action_group("episode", Some(&menu.group)); } } podcasts-25.2/podcasts-gtk/src/widgets/episode_menu.rs000066400000000000000000000101441500126606300231430ustar00rootroot00000000000000// episode_menu.rs // // Copyright 2021 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use async_channel::Sender; use glib::clone; use gtk::prelude::*; use gtk::{gio, glib}; use crate::app::Action; use podcasts_data::ShowId; use podcasts_data::{EpisodeId, EpisodeModel}; #[derive(Debug, Clone)] pub(crate) struct EpisodeMenu { pub(crate) menu: gio::MenuModel, go_to_show: gio::SimpleAction, copy_episode_url: gio::SimpleAction, mark_as_played: gio::SimpleAction, mark_as_unplayed: gio::SimpleAction, pub(crate) group: gio::SimpleActionGroup, } impl Default for EpisodeMenu { fn default() -> Self { let builder = gtk::Builder::from_resource("/org/gnome/Podcasts/gtk/episode_menu.ui"); let menu = builder.object("episode_menu").unwrap(); let go_to_show = gio::SimpleAction::new("go-to-show", None); let copy_episode_url = gio::SimpleAction::new("copy-episode-url", None); let mark_as_played = gio::SimpleAction::new("mark-as-played", None); let mark_as_unplayed = gio::SimpleAction::new("mark-as-unplayed", None); let group = gio::SimpleActionGroup::new(); EpisodeMenu { menu, go_to_show, copy_episode_url, mark_as_played, mark_as_unplayed, group, } } } impl EpisodeMenu { pub fn new(sender: &Sender, ep: &dyn EpisodeModel, show: Option) -> Self { let s = Self::default(); s.init(sender, ep, show); s } fn init(&self, sender: &Sender, ep: &dyn EpisodeModel, show: Option) { if let Some(show_id) = show { self.connect_go_to_show(show_id); } self.connect_mark_as_played(sender, ep.id()); self.update_played_state(ep); self.connect_copy_episode_url(sender, ep); } fn update_played_state(&self, ep: &dyn EpisodeModel) { let played = ep.played(); self.mark_as_played.set_enabled(played.is_none()); self.mark_as_unplayed.set_enabled(played.is_some()); } fn connect_go_to_show(&self, id: ShowId) { self.go_to_show.connect_activate(move |_, _| { if let Some(app) = gio::Application::default() { app.activate_action("go-to-show", Some(&id.0.into())); } }); self.group.add_action(&self.go_to_show); } fn connect_copy_episode_url(&self, sender: &Sender, ep: &dyn EpisodeModel) { let ep_id = ep.id(); if ep.uri().is_some() { self.copy_episode_url.connect_activate(clone!( #[strong] sender, move |_, _| { send_blocking!(sender, Action::CopyUrl(ep_id)); } )); self.group.add_action(&self.copy_episode_url); } } fn connect_mark_as_played(&self, sender: &Sender, ep_id: EpisodeId) { self.mark_as_played.connect_activate(clone!( #[strong] sender, move |_, _| { send_blocking!(sender, Action::MarkAsPlayed(true, ep_id)); } )); self.group.add_action(&self.mark_as_played); self.mark_as_unplayed.connect_activate(clone!( #[strong] sender, move |_, _| { send_blocking!(sender, Action::MarkAsPlayed(false, ep_id)); } )); self.group.add_action(&self.mark_as_unplayed); } } podcasts-25.2/podcasts-gtk/src/widgets/home_view.rs000066400000000000000000000207031500126606300224530ustar00rootroot00000000000000// home_view.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::subclass::prelude::*; use anyhow::Result; use async_channel::Sender; use chrono::prelude::*; use glib::subclass::InitializingObject; use gtk::gio; use gtk::{CompositeTemplate, glib, prelude::*}; use crate::app::Action; use crate::utils::{self, lazy_load}; use crate::widgets::{BaseView, EpisodeWidget}; use podcasts_data::dbqueries; use podcasts_data::{EpisodeModel, EpisodeWidgetModel, ShowId}; #[derive(Debug, Clone)] enum ListSplit { Today, Yday, Week, Month, Rest, } #[derive(Debug, Clone)] struct DateBox(ListSplit, Vec); #[derive(Debug, CompositeTemplate, Default)] #[template(resource = "/org/gnome/Podcasts/gtk/home_view.ui")] pub struct HomeViewPriv { #[template_child] view: TemplateChild, #[template_child] today_box: TemplateChild, #[template_child] yday_box: TemplateChild, #[template_child] week_box: TemplateChild, #[template_child] month_box: TemplateChild, #[template_child] rest_box: TemplateChild, #[template_child] today_list: TemplateChild, #[template_child] yday_list: TemplateChild, #[template_child] week_list: TemplateChild, #[template_child] month_list: TemplateChild, #[template_child] rest_list: TemplateChild, } #[glib::object_subclass] impl ObjectSubclass for HomeViewPriv { const NAME: &'static str = "PdHomeView"; type Type = HomeView; type ParentType = adw::Bin; fn class_init(klass: &mut Self::Class) { BaseView::ensure_type(); klass.bind_template(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl WidgetImpl for HomeViewPriv {} impl ObjectImpl for HomeViewPriv {} impl BinImpl for HomeViewPriv {} glib::wrapper! { pub struct HomeView(ObjectSubclass) @extends BaseView, gtk::Widget, adw::Bin, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } impl HomeView { pub(crate) fn new(sender: Sender) -> Self { let home: Self = glib::Object::new(); crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::DEFAULT_IDLE, glib::clone!( #[weak] home, async move { let results = home.add_to_boxes(sender).await; for result in results { if let Err(e) = result { log::error!("Error: {:?}", e); } } } ), ); home } async fn add_to_boxes(&self, sender: Sender) -> Vec> { let data = gio::spawn_blocking(get_episodes).await; let mut handles = Vec::with_capacity(5); if let Ok(Ok(data)) = data { for datebox in data { if datebox.1.is_empty() { continue; } let handle = self.add_to_box(datebox, &sender); handles.push(handle); } } let results = futures_util::future::join_all(handles).await; results.into_iter().flatten().collect() } async fn add_to_box( &self, datebox: DateBox, sender: &Sender, ) -> Vec> { use self::ListSplit::*; let DateBox(date, model) = datebox; let box_ = match &date { Today => &self.imp().today_box, Yday => &self.imp().yday_box, Week => &self.imp().week_box, Month => &self.imp().month_box, Rest => &self.imp().rest_box, }; let list = match &date { Today => &self.imp().today_list, Yday => &self.imp().yday_list, Week => &self.imp().week_list, Month => &self.imp().month_list, Rest => &self.imp().rest_list, }; box_.set_visible(true); let sender = sender.clone(); let constructor = move |ep: EpisodeWidgetModel| HomeEpisode::new(&sender, ep).upcast(); let list = list.upcast_ref::().downgrade(); lazy_load(model, list, constructor.clone()).await } } fn get_episodes() -> Result> { let ignore = utils::get_ignored_shows()?; let episodes = dbqueries::get_episodes_widgets_filter_limit(&ignore, 100)?; Ok(split_model(episodes)) } fn split(now: &DateTime, ep: &DateTime) -> ListSplit { let days_now = now.num_days_from_ce(); let days_ep = ep.num_days_from_ce(); let weekday = now.weekday().num_days_from_monday() as i32; if days_ep == days_now { ListSplit::Today } else if days_ep == days_now - 1 { ListSplit::Yday } else if days_ep >= days_now - weekday { ListSplit::Week } else if now.month() == ep.month() && now.year() == ep.year() { ListSplit::Month } else { ListSplit::Rest } } fn split_model(model: Vec) -> Vec { use self::ListSplit::*; let now = Local::now(); let (mut today, mut yday, mut week, mut month, mut rest) = (Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()); for ep in model { let epoch_local = DateTime::::from(ep.epoch().and_utc()); match split(&now, &epoch_local) { Today => today.push(ep), Yday => yday.push(ep), Week => week.push(ep), Month => month.push(ep), Rest => rest.push(ep), } } vec![ DateBox(Today, today), DateBox(Yday, yday), DateBox(Week, week), DateBox(Month, month), DateBox(Rest, rest), ] } #[derive(Debug, CompositeTemplate, Default)] #[template(resource = "/org/gnome/Podcasts/gtk/home_episode.ui")] pub struct HomeEpisodePriv { #[template_child] cover: TemplateChild, #[template_child] episode: TemplateChild, } impl HomeEpisodePriv { fn init(&self, sender: &Sender, episode: EpisodeWidgetModel) { let pid = episode.show_id(); self.set_cover(pid); self.episode.init(sender, episode, true); // Assure the image is read out along with the Episode title self.cover.set_accessible_role(gtk::AccessibleRole::Label); } fn set_cover(&self, show_id: ShowId) { crate::download_covers::load_widget_texture(&self.cover.get(), show_id, crate::Thumb64); } } #[glib::object_subclass] impl ObjectSubclass for HomeEpisodePriv { const NAME: &'static str = "PdHomeEpisode"; type Type = HomeEpisode; type ParentType = gtk::ListBoxRow; fn class_init(klass: &mut Self::Class) { klass.bind_template(); } fn instance_init(obj: &InitializingObject) { obj.init_template(); } } impl WidgetImpl for HomeEpisodePriv {} impl ObjectImpl for HomeEpisodePriv {} impl ListBoxRowImpl for HomeEpisodePriv {} glib::wrapper! { pub struct HomeEpisode(ObjectSubclass) @extends gtk::ListBoxRow, gtk::Widget, @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; } impl HomeEpisode { pub(crate) fn new(sender: &Sender, episode: EpisodeWidgetModel) -> Self { let widget = Self::default(); widget.set_action_name(Some("app.go-to-episode")); widget.set_action_target_value(Some(&episode.id().0.to_variant())); widget.imp().init(sender, episode); widget } } impl Default for HomeEpisode { fn default() -> Self { let widget: Self = glib::Object::new(); widget } } podcasts-25.2/podcasts-gtk/src/widgets/mod.rs000066400000000000000000000036261500126606300212550ustar00rootroot00000000000000// mod.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later mod aboutdialog; mod base_view; mod content_stack; mod discovery_page; mod discovery_search_results; mod download_progress_bar; mod empty_show; mod empty_view; mod episode; mod episode_description; pub(crate) mod episode_menu; mod home_view; pub(crate) mod player; mod read_more_label; mod show; pub(crate) mod show_menu; mod shows_view; pub(crate) use self::aboutdialog::about_dialog; pub(crate) use self::base_view::BaseView; pub(crate) use self::content_stack::Content; pub(crate) use self::discovery_page::DiscoveryPage; pub(crate) use self::discovery_search_results::SearchResults; pub(crate) use self::download_progress_bar::DownloadProgressBar; pub(crate) use self::empty_show::EmptyShow; pub(crate) use self::empty_view::EmptyView; pub(crate) use self::episode::EpisodeWidget; pub(crate) use self::episode_description::EpisodeDescription; pub(crate) use self::episode_menu::EpisodeMenu; pub(crate) use self::home_view::HomeView; pub(crate) use self::read_more_label::ReadMoreLabel; pub(crate) use self::show::ShowWidget; pub(crate) use self::show_menu::ShowMenu; pub(crate) use self::shows_view::ShowsView; #[cfg(test)] pub(crate) use self::home_view::HomeEpisode; podcasts-25.2/podcasts-gtk/src/widgets/player.rs000066400000000000000000001225121500126606300217660ustar00rootroot00000000000000// player.rs // // Copyright 2018 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::*; use anyhow::Result; use async_channel::Sender; use chrono::prelude::*; use fragile::Fragile; use gio::File; use glib::clone; use glib::{SignalHandlerId, WeakRef}; use gst::ClockTime; use gtk::{gio, glib}; use mpris_server::{Metadata, PlaybackStatus, Player}; use once_cell::sync::Lazy; use std::cell::{RefCell, RefMut}; use std::ops::Deref; use std::path::Path; use std::rc::Rc; use std::sync::Mutex; use url::Url; use crate::app::Action; use crate::config::APP_ID; use crate::download_covers::load_widget_texture; use crate::i18n::i18n; use podcasts_data::{ EpisodeId, EpisodeModel, EpisodeWidgetModel, ShowCoverModel, ShowId, USER_AGENT, dbqueries, }; #[derive(Debug, Clone, Copy)] pub(crate) enum SeekDirection { Backwards, Forward, } pub(crate) trait PlayerExt { fn play(&self); fn pause(&mut self); fn toggle_pause(&mut self); fn stop(&mut self); fn seek(&self, offset: ClockTime, direction: SeekDirection) -> Option<()>; fn fast_forward(&self); fn rewind(&self); fn set_playback_rate(&self, _: f64); } #[derive(Debug, Clone)] struct PlayerInfo { show: gtk::Label, episode: gtk::Label, cover: gtk::Image, show_small: gtk::Label, episode_small: gtk::Label, cover_small: gtk::Image, mpris: Option>, restore_position: Option, finished_restore: bool, ep: Option, episode_id: RefCell>, } impl PlayerInfo { fn create_bindings(&self) { self.show .bind_property("label", &self.show_small, "label") .flags(glib::BindingFlags::SYNC_CREATE) .build(); self.episode .bind_property("label", &self.episode_small, "label") .flags(glib::BindingFlags::SYNC_CREATE) .build(); } // FIXME: create a Diesel Model of the joined episode and podcast query instead fn init( &mut self, sender: &Sender, episode: &EpisodeWidgetModel, podcast: &ShowCoverModel, ) { self.ep = Some(episode.clone()); self.episode_id.replace(Some(episode.id())); self.set_cover_image(podcast); self.set_show_title(podcast); self.set_episode_title(episode); let mut metadata = Metadata::new(); metadata.set_artist(Some(vec![podcast.title().to_string()])); metadata.set_title(Some(episode.title().to_string())); metadata.set_length( episode .duration() .map(|s| mpris_server::Time::from_secs(s as i64)), ); // Set the cover if it is already cached. let art_path = crate::download_covers::determin_cover_path(podcast, None); if art_path.exists() { metadata.set_art_url(Url::from_file_path(art_path).ok()); } else { // If the cover art doesn't already exist, download it let sender = sender.clone(); let podcast = podcast.clone(); crate::RUNTIME.spawn(async move { let id = podcast.id(); if let Err(err) = crate::download_covers::just_download(&podcast).await { error!("Cover download failed {err}"); send!(sender, Action::UpdateMprisCover(id, false)); } else { send!(sender, Action::UpdateMprisCover(id, true)); } }); } if let Some(mpris) = self.mpris.as_ref() { crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak] mpris, async move { if let Err(err) = mpris.set_metadata(metadata).await { warn!("Failed to set MPRIS metadata: {err:?}"); } if let Err(err) = mpris.set_can_pause(true).await { warn!("Failed to set MPRIS pause capability: {err:?}"); } if let Err(err) = mpris.set_can_play(true).await { warn!("Failed to set MPRIS play capability: {err:?}"); } if let Err(err) = mpris.set_can_seek(true).await { warn!("Failed to set MPRIS seek capability: {err:?}"); } } ), ); } } // hook for when the async download finished fn update_mpris_cover(&self, show_id: ShowId, dl_success: bool) -> Result<()> { if let Some(ep) = self.ep.as_ref() { if ep.show_id() != show_id { // Download took too long, we are no longer on the same show. return Ok(()); } } if let Some(mpris) = self.mpris.as_ref() { let pd = dbqueries::get_podcast_cover_from_id(show_id)?; let mut metadata = mpris.metadata().clone(); if dl_success { let art_path = crate::download_covers::determin_cover_path(&pd, None); if art_path.exists() { metadata.set_art_url(Url::from_file_path(art_path).ok()); crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak] mpris, async move { if let Err(err) = mpris.set_metadata(metadata).await { error!("failed to update mpris metadata {err}"); } } ), ); return Ok(()); } else { error!( "cover does not exist after successful download? {}", art_path.display() ); } } // Fallback to web url, it could still work, // because of different http agent or no disk space. metadata.set_art_url(pd.image_uri()); crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak] mpris, async move { if let Err(err) = mpris.set_metadata(metadata).await { error!("failed to update mpris metadata {err}"); } } ), ); } Ok(()) } fn set_episode_title(&self, episode: &EpisodeWidgetModel) { self.episode.set_text(episode.title()); self.episode.set_tooltip_text(Some(episode.title())); } fn set_show_title(&self, show: &ShowCoverModel) { self.show.set_text(show.title()); self.show.set_tooltip_text(Some(show.title())); } fn set_cover_image(&self, show: &ShowCoverModel) { load_widget_texture(&self.cover, show.id(), crate::Thumb64); load_widget_texture(&self.cover_small, show.id(), crate::Thumb64); } fn update_mpris_position(&self, position: Position) -> Option<()> { let time = mpris_server::Time::from_secs(position.seconds() as i64); self.mpris.as_ref()?.set_position(time); Some(()) } } #[derive(Debug, Clone)] struct PlayerTimes { progressed: gtk::Label, duration: gtk::Label, slider: gtk::Scale, slider_update: Rc, progress_bar: gtk::ProgressBar, } #[derive(Debug, Clone, Copy)] struct Duration(ClockTime); impl Deref for Duration { type Target = ClockTime; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Debug, Clone, Copy)] struct Position(ClockTime); impl Deref for Position { type Target = ClockTime; fn deref(&self) -> &Self::Target { &self.0 } } impl PlayerTimes { /// Update the duration `gtk::Label` and the max range of the `gtk::SclaeBar`. pub(crate) fn on_duration_changed(&self, duration: Duration) { let seconds = duration.seconds(); self.slider.block_signal(&self.slider_update); self.slider.set_range(0.0, seconds as f64); self.slider.unblock_signal(&self.slider_update); self.duration.set_text(&format_duration(seconds as u32)); self.update_progress_bar(); } /// Update the `gtk::Scale` bar when the pipeline position is changed. pub(crate) fn on_position_updated(&self, position: Position) { let seconds = position.seconds(); self.slider.block_signal(&self.slider_update); self.slider.set_value(seconds as f64); self.slider.unblock_signal(&self.slider_update); self.progressed.set_text(&format_duration(seconds as u32)); self.update_progress_bar(); } fn update_progress_bar(&self) { let fraction = self.slider.value() / self.slider.adjustment().upper(); self.progress_bar.set_fraction(fraction); } } fn format_duration(seconds: u32) -> String { let time = NaiveTime::from_num_seconds_from_midnight_opt(seconds, 0); if time.is_none() { return "-".to_string(); } let time = time.unwrap(); if seconds >= 3600 { time.format("%T").to_string() } else { time.format("%M:%S").to_string() } } #[derive(Debug, Clone)] struct PlayerRate { action: gio::SimpleAction, btn: gtk::MenuButton, } impl PlayerRate { fn new() -> Self { let builder = gtk::Builder::from_resource("/org/gnome/Podcasts/gtk/player_rate.ui"); // This needs to be a string to work with GMenuModel let variant_type = glib::VariantTy::new("s").expect("Could not parse variant type"); let action = gio::SimpleAction::new_stateful("set", Some(variant_type), &"1.00".to_variant()); let btn: gtk::MenuButton = builder.object("rate_button").unwrap(); PlayerRate { action, btn } } fn connect_signals(&self, widget: &Rc>) { let group = gio::SimpleActionGroup::new(); self.action.connect_activate(clone!( #[weak] widget, move |action, rate_v| { let variant = rate_v.unwrap(); action.set_state(variant); let rate = variant .get::() .expect("Could not get rate from variant") .parse::() .expect("Could not parse float from variant string"); widget.borrow().on_rate_changed(rate); } )); group.add_action(&self.action); widget .borrow() .container .insert_action_group("rate", Some(&group)); widget .borrow() .sheet .sheet .insert_action_group("rate", Some(&group)); } } #[derive(Debug, Clone)] struct PlayerControls { play: gtk::Button, pause: gtk::Button, play_pause_big: gtk::Stack, play_small: gtk::Button, pause_small: gtk::Button, play_pause_small: gtk::Stack, forward: gtk::Button, rewind: gtk::Button, last_pause: RefCell>>, } #[derive(Debug, Clone)] pub(crate) struct PlayerSheet { pub(crate) sheet: adw::Bin, cover: gtk::Image, play_pause: gtk::Stack, play: gtk::Button, pause: gtk::Button, duration: gtk::Label, progressed: gtk::Label, slider: gtk::Scale, forward: gtk::Button, rewind: gtk::Button, rate: PlayerRate, show: gtk::Label, episode: gtk::Label, go_to_episode: gtk::Button, } impl PlayerSheet { fn new(rate: PlayerRate) -> Self { let builder = gtk::Builder::from_resource("/org/gnome/Podcasts/gtk/player_sheet.ui"); let sheet = builder.object("sheet").unwrap(); let cover = builder.object("cover").unwrap(); let play_pause = builder.object("play_pause").unwrap(); let play = builder.object("play").unwrap(); let pause = builder.object("pause").unwrap(); let duration = builder.object("duration").unwrap(); let progressed = builder.object("progressed").unwrap(); let slider = builder.object("slider").unwrap(); let rewind = builder.object("rewind").unwrap(); let forward = builder.object("forward").unwrap(); let rate_container: adw::Bin = builder.object("rate_container").unwrap(); let show = builder.object("show_label").unwrap(); let episode = builder.object("episode_label").unwrap(); let go_to_episode: gtk::Button = builder.object("go_to_episode").unwrap(); rate_container.set_child(Some(&rate.btn)); go_to_episode.connect_clicked(|button| { let app = gio::Application::default() .expect("Could not get default application") .downcast::() .unwrap(); app.activate_action("go-to-episode", button.action_target_value().as_ref()); let window = app.active_window().expect("No active window"); if let Err(e) = window.activate_action("win.close-bottom-sheet", None) { warn!("Failed to win.close-bottom-sheet {e}"); } }); PlayerSheet { sheet, cover, play_pause, play, pause, duration, progressed, slider, forward, rewind, rate, show, episode, go_to_episode, } } fn initialize_episode(&self, episode: &EpisodeWidgetModel, show: &ShowCoverModel) { self.episode.set_text(episode.title()); self.show.set_text(show.title()); load_widget_texture(&self.cover, show.id(), crate::Thumb256); self.go_to_episode .set_action_target(Some::(episode.id().0.into())); } } #[derive(Debug, Clone)] pub(crate) struct PlayerWidget { pub(crate) container: gtk::Box, player: gst_play::Play, player_signals: gst_play::PlaySignalAdapter, controls: PlayerControls, pub(crate) sheet: PlayerSheet, full: gtk::Box, small: gtk::Box, stack: gtk::Stack, timer: PlayerTimes, info: PlayerInfo, rate: PlayerRate, sender: Option>, } impl Default for PlayerWidget { fn default() -> Self { let player = gst_play::Play::default(); let player_signals = gst_play::PlaySignalAdapter::new(&player); // A few podcasts have a video track of the thumbnail, which GStreamer displays in a new // window. Make sure it doesn't do that. player.set_video_track_enabled(false); let mpris = crate::RUNTIME.block_on(async move { let mpris_result = Player::builder(APP_ID) .identity(i18n("Podcasts")) .desktop_entry(APP_ID) .can_raise(true) .can_pause(false) .can_play(false) .can_seek(false) .can_set_fullscreen(false) .can_go_next(false) .can_go_previous(false) .build() .await; match mpris_result { Err(e) => { error!("mpris initialization: {e}"); None } Ok(mpris) => Some(Rc::new(mpris)), } }); if let Some(mpris) = mpris.as_ref() { crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak] mpris, async move { let task = mpris.run(); task.await; } ), ); } let mut config = player.config(); config.set_user_agent(USER_AGENT); config.set_position_update_interval(250); player.set_config(config).unwrap(); let builder = gtk::Builder::from_resource("/org/gnome/Podcasts/gtk/player_toolbar.ui"); let play = builder.object("play_button").unwrap(); let pause = builder.object("pause_button").unwrap(); let play_small = builder.object("play_button_small").unwrap(); let pause_small = builder.object("pause_button_small").unwrap(); let forward: gtk::Button = builder.object("ff_button").unwrap(); let rewind: gtk::Button = builder.object("rewind_button").unwrap(); let play_pause_small = builder.object("play_pause_small").unwrap(); let play_pause_big = builder.object("play_pause_big").unwrap(); let controls = PlayerControls { play, pause, play_pause_big, play_small, pause_small, play_pause_small, forward, rewind, last_pause: RefCell::new(None), }; let progressed = builder.object("progress_time_label").unwrap(); let duration = builder.object("total_duration_label").unwrap(); let slider: gtk::Scale = builder.object("seek").unwrap(); slider.set_range(0.0, 1.0); let player_weak = player.downgrade(); let slider_update = Rc::new(Self::connect_update_slider(&slider, player_weak)); let progress_bar = builder.object("progress_bar").unwrap(); let timer = PlayerTimes { progressed, duration, slider, slider_update, progress_bar, }; let show = builder.object("show_label").unwrap(); let episode = builder.object("episode_label").unwrap(); let cover = builder.object("show_cover").unwrap(); let show_small = builder.object("show_label_small").unwrap(); let episode_small = builder.object("episode_label_small").unwrap(); let cover_small = builder.object("show_cover_small").unwrap(); let ep = None; let info = PlayerInfo { mpris, show, ep, episode, cover, show_small, episode_small, cover_small, restore_position: None, finished_restore: false, episode_id: RefCell::new(None), }; info.create_bindings(); let dialog_rate = PlayerRate::new(); let dialog = PlayerSheet::new(dialog_rate); let container = builder.object("container").unwrap(); let full: gtk::Box = builder.object("full").unwrap(); let small: gtk::Box = builder.object("small").unwrap(); let stack = builder.object("stack").unwrap(); let rate = PlayerRate::new(); full.append(&rate.btn); PlayerWidget { player, player_signals, container, controls, sheet: dialog, full, small, stack, timer, info, rate, sender: None, } } } #[derive(PartialEq)] pub enum StreamMode { LocalOnly, StreamOnly, StreamFallback, } impl PlayerWidget { fn on_rate_changed(&self, rate: f64) { self.set_playback_rate(rate); self.rate.btn.set_label(&format!("{:.2}×", rate)); self.sheet.rate.btn.set_label(&format!("{:.2}×", rate)); } fn reveal(&self) { self.container.set_visible(true); } pub(crate) fn initialize_episode( &mut self, sender: &Sender, id: EpisodeId, stream: StreamMode, second: Option, ) -> Result<()> { let ep = dbqueries::get_episode_widget_from_id(id)?; let pd = dbqueries::get_podcast_cover_from_id(ep.show_id())?; self.sheet.initialize_episode(&ep, &pd); self.info.restore_position = second.or_else(|| { let episode_position = ep.play_position(); if episode_position == 0 { None } else { Some(episode_position) } }); self.info.finished_restore = false; self.info.init(sender, &ep, &pd); if stream == StreamMode::StreamOnly { if let Some(uri) = ep.uri() { self.init_uri(uri, second); return Ok(()); } else { error!("No uri for episode"); } // Currently that will always be the case since the play button is // only shown if the file is downloaded } else if let Some(ref path) = ep.local_uri() { if Path::new(path).exists() { // path is an absolute fs path ex. "foo/bar/baz". // Convert it so it will have a "file:///" // FIXME: convert it properly let uri = File::for_path(path).uri(); self.init_uri(uri.as_str(), second); return Ok(()); } else { error!("failed to create path for episode {:#?}", ep); } } else if stream == StreamMode::StreamFallback { if let Some(uri) = ep.uri() { self.init_uri(uri, second); return Ok(()); } else { error!("No uri for episode"); } } else { error!("Episode not downloaded yet."); } Ok(()) } fn init_uri(&mut self, uri: &str, second: Option) { // If it's not the same file load the uri, otherwise just unpause if self.player.uri().is_none_or(|s| s != uri) { self.player.set_uri(Some(uri)); } else if second.is_some() { // force a jump now if already playing and a jump is given self.restore_play_position(); } else { // just unpause, no restore required self.info.finished_restore = true; } // play the file self.play(); } fn connect_update_slider( slider: >k::Scale, player: WeakRef, ) -> SignalHandlerId { slider.connect_value_changed(move |slider| { let player = match player.upgrade() { Some(p) => p, None => return, }; let value = slider.value() as u64; player.seek(ClockTime::from_seconds(value)); }) } pub fn update_mpris_cover(&self, show_id: ShowId, dl_success: bool) -> Result<()> { self.info.update_mpris_cover(show_id, dl_success) } fn smart_rewind(&self) -> Option<()> { static LAST_KNOWN_EPISODE: Lazy>> = Lazy::new(|| Mutex::new(None)); // Figure out the time delta, in seconds, between the last pause and now let now = Local::now(); let last: &Option> = &*self.controls.last_pause.borrow(); let delta = (now - (*last)?).num_seconds(); // Get interval passed in the gst stream let seconds_passed = self.player.position()?.seconds(); // get the last known episode id let mut last = LAST_KNOWN_EPISODE.lock().unwrap(); // get the current playing episode id let current_id = *self.info.episode_id.borrow(); // Only rewind on pause if the stream position is passed a certain point, // and the player has been paused for more than a minute, // and the episode id is the same if seconds_passed >= 90 && delta >= 60 && current_id == *last { self.seek(ClockTime::from_seconds(5), SeekDirection::Backwards); } // Set the last knows episode to the current one *last = current_id; Some(()) } /// Seek to the `play_position` stored in the episode. /// Returns Some(()) if the restore was successful and None otherwise. fn restore_play_position(&self) -> Option<()> { let pos = self.info.restore_position; if let Some(pos) = pos { let s: u64 = pos.try_into().ok()?; self.player.seek(ClockTime::from_seconds(s)); Some(()) } else { None } } pub fn set_small(&self, small: bool) { if small { self.stack.set_visible_child(&self.small); } else { self.stack.set_visible_child(&self.full); } } } impl PlayerExt for PlayerWidget { fn play(&self) { self.sheet.play_pause.set_visible_child(&self.sheet.pause); self.reveal(); self.controls .play_pause_big .set_visible_child(&self.controls.pause); self.controls .play_pause_small .set_visible_child(&self.controls.pause_small); self.smart_rewind(); self.player.play(); if let Some(mpris) = self.info.mpris.as_ref() { crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak] mpris, async move { if let Err(err) = mpris.set_playback_status(PlaybackStatus::Playing).await { warn!("Failed to set MPRIS playback status: {err:?}"); } } ), ); } if let Some(sender) = &self.sender { send_blocking!(sender, Action::InhibitSuspend); } } fn pause(&mut self) { self.sheet.play_pause.set_visible_child(&self.sheet.play); self.controls .play_pause_big .set_visible_child(&self.controls.play); self.controls .play_pause_small .set_visible_child(&self.controls.play_small); self.player.pause(); if let Some(mpris) = self.info.mpris.as_ref() { crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak] mpris, async move { if let Err(err) = mpris.set_playback_status(PlaybackStatus::Paused).await { warn!("Failed to set MPRIS playback status: {err:?}"); } } ), ); } if let Some(sender) = &self.sender { send_blocking!(sender, Action::UninhibitSuspend); } self.controls.last_pause.replace(Some(Local::now())); let pos = self.player.position(); self.info.ep.as_mut().map(|ep| { ep.set_play_position(pos.and_then(|s| s.seconds().try_into().ok()).unwrap_or(0)) }); } fn stop(&mut self) { // hide pause buttons and restore focus for accessibility let is_focus = self.controls.pause.is_focus(); self.controls .play_pause_big .set_visible_child(&self.controls.play); if is_focus { self.controls.play.grab_focus(); } let is_focus = self.controls.pause_small.is_focus(); self.controls .play_pause_small .set_visible_child(&self.controls.play_small); if is_focus { self.controls.play_small.grab_focus(); } let is_focus = self.sheet.pause.is_focus(); self.sheet.play_pause.set_visible_child(&self.sheet.play); if is_focus { self.sheet.play.grab_focus(); } self.info.ep = None; self.info.restore_position = None; self.player.stop(); if let Some(mpris) = self.info.mpris.as_ref() { crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::LOW, clone!( #[weak] mpris, async move { if let Err(err) = mpris.set_playback_status(PlaybackStatus::Paused).await { warn!("Failed to set MPRIS playback status: {err:?}"); } } ), ); } // Reset the slider bar to the start self.timer .on_position_updated(Position(ClockTime::from_seconds(0))); if let Some(sender) = &self.sender { send_blocking!(sender, Action::UninhibitSuspend); } } fn toggle_pause(&mut self) { if let Some(mpris) = self.info.mpris.as_ref() { match mpris.playback_status() { PlaybackStatus::Paused => self.play(), PlaybackStatus::Stopped => self.play(), _ => self.pause(), }; } } // Adapted from https://github.com/philn/glide/blob/b52a65d99daeab0b487f79a0e1ccfad0cd433e22/src/player_context.rs#L219-L245 fn seek(&self, offset: ClockTime, direction: SeekDirection) -> Option<()> { // How far into the podcast we are let position = self.player.position()?; if offset.is_zero() { return Some(()); } // How much podcast we have let duration = self.player.duration()?; let destination = match direction { // If we are more than `offset` into the podcast, jump back that far SeekDirection::Backwards if position >= offset => position.checked_sub(offset), // If we haven't played `offset` yet just restart the podcast SeekDirection::Backwards if position < offset => Some(ClockTime::from_seconds(0)), // If we have more than `offset` remaining jump forward they amount SeekDirection::Forward if !duration.is_zero() && position + offset <= duration => { position.checked_add(offset) } // We don't have `offset` remaining just move to the end (ending playback) SeekDirection::Forward if !duration.is_zero() && position + offset > duration => { Some(duration) } // Who knows what's going on ¯\_(ツ)_/¯ _ => None, }; // If we calucated a new position, jump to it if let Some(destination) = destination { self.player.seek(destination) } Some(()) } fn rewind(&self) { let r = self.seek(ClockTime::from_seconds(10), SeekDirection::Backwards); if r.is_none() { warn!("Failed to rewind"); } } fn fast_forward(&self) { let r = self.seek(ClockTime::from_seconds(10), SeekDirection::Forward); if r.is_none() { warn!("Failed to fast-forward"); } } fn set_playback_rate(&self, rate: f64) { self.player.set_rate(rate); } } #[derive(Debug, Clone)] pub(crate) struct PlayerWrapper(pub Rc>); impl Default for PlayerWrapper { fn default() -> Self { PlayerWrapper(Rc::new(RefCell::new(PlayerWidget::default()))) } } impl Deref for PlayerWrapper { type Target = Rc>; fn deref(&self) -> &Self::Target { &self.0 } } impl PlayerWrapper { pub(crate) fn borrow_mut(&self) -> RefMut<'_, PlayerWidget> { self.0.borrow_mut() } pub(crate) fn new(sender: &Sender) -> Self { let w = PlayerWrapper::default(); w.init(sender); w } fn init(&self, sender: &Sender) { self.borrow_mut().sender = Some(sender.clone()); self.connect_control_buttons(); self.connect_rate_buttons(); self.connect_mpris_buttons(sender); self.connect_gst_signals(sender); self.connect_sheet(); } fn connect_sheet(&self) { let widget = self.borrow(); widget .timer .duration .bind_property("label", &widget.sheet.duration, "label") .flags(glib::BindingFlags::SYNC_CREATE) .build(); widget .timer .progressed .bind_property("label", &widget.sheet.progressed, "label") .flags(glib::BindingFlags::SYNC_CREATE) .build(); widget .sheet .slider .set_adjustment(&widget.timer.slider.adjustment()); } /// Connect the `PlayerControls` buttons to the `PlayerExt` methods. fn connect_control_buttons(&self) { let this = self.deref(); let widget = self.borrow(); // Connect the play button to the gst Player. widget.controls.play.connect_clicked(clone!( #[weak] this, move |_| { this.borrow().play(); this.borrow().controls.pause.grab_focus(); // keep focus for accessibility } )); // Connect the pause button to the gst Player. widget.controls.pause.connect_clicked(clone!( #[weak] this, move |_| { this.borrow_mut().pause(); this.borrow().controls.play.grab_focus(); // keep focus for accessibility } )); // Connect the play button to the gst Player. widget.controls.play_small.connect_clicked(clone!( #[weak] this, move |_| { this.borrow().play(); this.borrow().controls.pause_small.grab_focus(); // keep focus for accessibility } )); // Connect the pause button to the gst Player. widget.controls.pause_small.connect_clicked(clone!( #[weak] this, move |_| { this.borrow_mut().pause(); this.borrow().controls.play_small.grab_focus(); // keep focus for accessibility } )); // Connect the rewind button to the gst Player. widget.controls.rewind.connect_clicked(clone!( #[weak] this, move |_| { this.borrow().rewind(); } )); // Connect the fast-forward button to the gst Player. widget.controls.forward.connect_clicked(clone!( #[weak] this, move |_| { this.borrow().fast_forward(); } )); // Connect the play button to the gst Player. widget.sheet.play.connect_clicked(clone!( #[weak] this, move |_| { this.borrow().play(); this.borrow().sheet.pause.grab_focus(); // keep focus for accessibility } )); // Connect the pause button to the gst Player. widget.sheet.pause.connect_clicked(clone!( #[weak] this, move |_| { this.borrow_mut().pause(); this.borrow().sheet.play.grab_focus(); // keep focus for accessibility } )); // Connect the rewind button to the gst Player. widget.sheet.rewind.connect_clicked(clone!( #[weak] this, move |_| { this.borrow().rewind(); } )); // Connect the fast-forward button to the gst Player. widget.sheet.forward.connect_clicked(clone!( #[weak] this, move |_| { this.borrow().fast_forward(); } )); } fn connect_gst_signals(&self, sender: &Sender) { let signal_adapter = &self.borrow().player_signals; // Log gst warnings. signal_adapter .connect_warning(move |_, warn, details| warn!("gst warning: {} {:#?}", warn, details)); // Log gst errors. signal_adapter.connect_error(clone!( #[strong] sender, move |_, _error, details| { error!("gstreamer error: {} {:#?}", _error, details); send_blocking!( sender, Action::ErrorNotification(format!("Player Error: {}", _error)) ); let s = i18n("The media player was unable to execute an action."); send_blocking!(sender, Action::ErrorNotification(s)); } )); // The following callbacks require `Send` but are handled by the gtk main loop let weak = Fragile::new(Rc::downgrade(self)); signal_adapter.connect_uri_loaded(clone!( #[strong] weak, move |_, _| { if let Some(player_widget) = weak.get().upgrade() { player_widget.borrow().restore_play_position(); player_widget.borrow_mut().info.finished_restore = true; } } )); // Update the duration label and the slider signal_adapter.connect_duration_changed(clone!( #[strong] weak, move |_, clock| { if let Some(player_widget) = weak.get().upgrade() { if let Some(c) = clock { player_widget .borrow() .timer .on_duration_changed(Duration(c)); } } } )); // Update the position label and the slider signal_adapter.connect_position_updated(clone!( #[strong] weak, move |_, clock| { if let Some(player_widget) = weak.get().upgrade() { // write to db if let Some(c) = clock { let pos = Position(c); let finished_restore = player_widget.borrow().info.finished_restore; player_widget.borrow_mut().info.ep.as_mut().map(|ep| { if finished_restore { ep.set_play_position_if_divergent(pos.seconds() as i32) } else { Ok(()) } }); player_widget.borrow().timer.on_position_updated(pos); player_widget.borrow().info.update_mpris_position(pos); } } } )); // Reset the slider to 0 and show a play button signal_adapter.connect_end_of_stream(clone!( #[strong] sender, #[strong] weak, move |_| { if let Some(player_widget) = weak.get().upgrade() { // write postion to db player_widget.borrow_mut().info.ep.as_mut().map(|ep| { ep.set_play_position(0)?; send_blocking!(sender, Action::MarkAsPlayed(true, ep.id())); let ok: Result<(), podcasts_data::errors::DataError> = Ok(()); ok }); player_widget.borrow_mut().stop() } } )); } fn connect_rate_buttons(&self) { self.deref().borrow().rate.connect_signals(self.deref()); self.deref() .borrow() .sheet .rate .connect_signals(self.deref()); } fn connect_mpris_buttons(&self, sender: &Sender) { let widget = self.borrow(); if let Some(mpris) = widget.info.mpris.as_ref() { mpris.connect_play_pause(clone!( #[strong(rename_to = player)] self, move |mpris| { match mpris.playback_status() { PlaybackStatus::Paused => player.borrow().play(), PlaybackStatus::Stopped => player.borrow().play(), _ => player.borrow_mut().pause(), }; } )); mpris.connect_play(clone!( #[strong(rename_to = player)] self, move |_| { player.borrow().play(); } )); mpris.connect_pause(clone!( #[strong(rename_to = player)] self, move |_| { player.borrow_mut().pause(); } )); mpris.connect_seek(clone!( #[strong(rename_to = player)] self, move |_, offset: mpris_server::Time| { let direction = if offset.is_positive() { SeekDirection::Forward } else { SeekDirection::Backwards }; player.borrow().seek( ClockTime::from_useconds(offset.as_micros().unsigned_abs()), direction, ); } )); mpris.connect_raise(clone!( #[strong] sender, move |_| { send_blocking!(sender, Action::RaiseWindow); } )); }; } } podcasts-25.2/podcasts-gtk/src/widgets/read_more_label.rs000066400000000000000000000116731500126606300235730ustar00rootroot00000000000000use gtk::glib; use gtk::prelude::*; use gtk::subclass::prelude::*; use std::cell::Cell; use crate::i18n::i18n; #[derive(Debug, Default)] pub struct ReadMoreLabelPriv { pub short_label: gtk::Label, pub short_desc: gtk::Box, pub long_label: gtk::Label, pub button: gtk::Button, pub expanded: Cell, } #[glib::object_subclass] impl ObjectSubclass for ReadMoreLabelPriv { const NAME: &'static str = "PdReadMoreLabel"; type Type = ReadMoreLabel; type ParentType = gtk::Widget; } impl ObjectImpl for ReadMoreLabelPriv { fn constructed(&self) { self.parent_constructed(); let obj = self.obj(); self.button .update_property(&[gtk::accessible::Property::Description(&i18n( "Visually expands this description", ))]); self.button.set_label(&i18n("Read More")); self.button.set_halign(gtk::Align::Center); self.button.connect_clicked(glib::clone!( #[weak] obj, move |_| { obj.set_expanded(true); } )); self.short_label.set_halign(gtk::Align::Center); self.short_label.set_valign(gtk::Align::Center); self.short_label.set_use_markup(true); self.short_label.set_wrap(true); // See https://gitlab.gnome.org/GNOME/gtk/-/issues/4714. self.short_label.set_lines(4); self.short_label .set_wrap_mode(gtk::pango::WrapMode::WordChar); self.short_label.set_justify(gtk::Justification::Center); self.short_label .set_ellipsize(gtk::pango::EllipsizeMode::End); self.short_desc.set_orientation(gtk::Orientation::Vertical); self.short_desc.set_spacing(6); self.short_desc.append(&self.short_label); self.short_desc.append(&self.button); self.short_desc.set_child_visible(true); self.long_label.set_use_markup(true); self.long_label.set_justify(gtk::Justification::Center); self.long_label.set_wrap(true); self.long_label .set_wrap_mode(gtk::pango::WrapMode::WordChar); self.long_label.set_valign(gtk::Align::Center); self.long_label.set_halign(gtk::Align::Center); self.long_label.set_child_visible(false); self.long_label.set_parent(obj.upcast_ref::()); self.short_desc.set_parent(obj.upcast_ref::()); } fn dispose(&self) { self.short_desc.unparent(); self.long_label.unparent(); } } impl WidgetImpl for ReadMoreLabelPriv { fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) { if self.expanded.get() { let (min_h, nat_h, min_b, nat_b) = self.long_label.measure(orientation, for_size); if orientation == gtk::Orientation::Vertical { (nat_h, nat_h, min_b, nat_b) } else { (min_h, nat_h, min_b, nat_b) } } else { self.short_desc.measure(orientation, for_size) } } fn request_mode(&self) -> gtk::SizeRequestMode { gtk::SizeRequestMode::WidthForHeight } fn size_allocate(&self, width: i32, height: i32, baseline: i32) { let long_nat_h = self.long_label.measure(gtk::Orientation::Vertical, width).1; // If we have enough space to allocate the long label, we directly // allocate it. if long_nat_h < height { self.obj().set_expanded_inner(true); } if self.expanded.get() { self.long_label.allocate(width, height, baseline, None); } else { self.short_desc.allocate(width, height, baseline, None); } } } glib::wrapper! { pub struct ReadMoreLabel(ObjectSubclass) @extends gtk::Widget, @implements gtk::Accessible; } impl Default for ReadMoreLabel { fn default() -> Self { glib::Object::new() } } impl ReadMoreLabel { pub(crate) fn init(&self) { self.set_focusable(true); self.set_accessible_role(gtk::AccessibleRole::Label); } fn set_expanded_inner(&self, expanded: bool) { let imp = self.imp(); if expanded == imp.expanded.replace(expanded) { return; } // This should be only set once. if !expanded { return; } imp.long_label.set_child_visible(expanded); imp.short_desc.set_child_visible(!expanded); } fn set_expanded(&self, expanded: bool) { self.set_expanded_inner(expanded); self.queue_resize(); } pub fn set_label(&self, label: &str) { let imp = self.imp(); let markup = glib::markup_escape_text(label); let lines: Vec<&str> = markup.lines().collect(); if !lines.is_empty() { imp.short_label.set_markup(lines[0]); } imp.long_label.set_markup(&markup); self.update_property(&[gtk::accessible::Property::Label(label)]); } } podcasts-25.2/podcasts-gtk/src/widgets/show.rs000066400000000000000000000130601500126606300214470ustar00rootroot00000000000000// show.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::*; use adw::subclass::prelude::*; use anyhow::Result; use async_channel::Sender; use glib::clone; use gtk::CompositeTemplate; use gtk::gio; use gtk::glib; use std::cell::Cell; use std::sync::Arc; use crate::app::Action; use crate::utils::lazy_load; use crate::widgets::{EmptyShow, EpisodeWidget, ReadMoreLabel, ShowMenu}; use podcasts_data::dbqueries; use podcasts_data::{EpisodeModel, EpisodeWidgetModel, Show, ShowId}; #[derive(Debug, Default, CompositeTemplate)] #[template(resource = "/org/gnome/Podcasts/gtk/show_widget.ui")] pub struct ShowWidgetPriv { #[template_child] pub cover: TemplateChild, #[template_child] pub read_more_label: TemplateChild, #[template_child] pub episodes: TemplateChild, #[template_child] pub(crate) view: TemplateChild, #[template_child] pub(crate) secondary_menu: TemplateChild, pub show_id: Cell>, } impl ShowWidgetPriv { fn init(&self) { self.read_more_label.init(); self.read_more_label .update_property(&[gtk::accessible::Property::Description( "Podcast Description", )]); } /// Set the description text. fn set_description(&self, text: &str) { let markup = html2text::config::plain() .string_from_read(text.as_bytes(), text.len()) .unwrap_or_else(|_| text.to_string()); let markup = markup.trim(); if !markup.is_empty() { self.read_more_label.set_label(markup); } } } #[glib::object_subclass] impl ObjectSubclass for ShowWidgetPriv { const NAME: &'static str = "PdShowWidget"; type Type = super::ShowWidget; type ParentType = gtk::Widget; fn class_init(klass: &mut Self::Class) { Self::bind_template(klass); klass.set_layout_manager_type::(); } fn instance_init(obj: &glib::subclass::InitializingObject) { obj.init_template(); } } impl ObjectImpl for ShowWidgetPriv { fn dispose(&self) { self.view.unparent(); } } impl WidgetImpl for ShowWidgetPriv {} glib::wrapper! { pub struct ShowWidget(ObjectSubclass) @extends gtk::Widget; } impl Default for ShowWidget { fn default() -> Self { glib::Object::new() } } impl ShowWidget { pub(crate) fn new(pd: Arc, sender: &Sender) -> ShowWidget { let widget = ShowWidget::default(); widget.init(&pd); let menu = ShowMenu::new(&pd, &widget.imp().episodes, sender); widget.imp().secondary_menu.set_menu_model(Some(&menu.menu)); let res = populate_listbox(&widget, pd, sender); debug_assert!(res.is_ok()); widget } pub(crate) fn init(&self, pd: &Arc) { let self_ = self.imp(); self_.init(); self_.set_description(pd.description()); self_.show_id.set(Some(pd.id())); self.set_cover(pd); } /// Set the show cover. fn set_cover(&self, pd: &Arc) { crate::download_covers::load_widget_texture( &self.imp().cover.get(), pd.id(), crate::Thumb256, ); } pub(crate) fn show_id(&self) -> Option { self.imp().show_id.get() } } /// Populate the listbox with the shows episodes. fn populate_listbox(show: &ShowWidget, pd: Arc, sender: &Sender) -> Result<()> { let count = dbqueries::get_pd_episodes_count(&pd)?; if count == 0 { let empty = EmptyShow::default(); show.imp().episodes.append(&empty); return Ok(()); } let constructor = clone!( #[strong] sender, move |ep: EpisodeWidgetModel| { let id = ep.id(); let episode_widget = EpisodeWidget::new(&sender, ep, false); let row = gtk::ListBoxRow::new(); row.set_child(Some(&episode_widget)); row.set_action_name(Some("app.go-to-episode")); row.set_action_target_value(Some(&id.0.to_variant())); row.upcast() } ); let listbox = show.imp().episodes.upcast_ref::().downgrade(); crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::DEFAULT_IDLE, async move { let episodes = gio::spawn_blocking(clone!( #[strong] pd, move || dbqueries::get_pd_episodeswidgets(&pd), )); if let Ok(Ok(episodes)) = episodes.await { let results = lazy_load(episodes, listbox, constructor).await; for result in results { if let Err(e) = result { log::error!("Error: {:?}", e); } } } }, ); Ok(()) } podcasts-25.2/podcasts-gtk/src/widgets/show_menu.rs000066400000000000000000000202501500126606300224720ustar00rootroot00000000000000// show_menu.rs // // Copyright 2017 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use anyhow::Result; use async_channel::Sender; use glib::clone; use gtk::prelude::*; use gtk::{gio, glib}; use std::sync::Arc; use crate::app::Action; use crate::i18n::{i18n, i18n_f}; use crate::utils; use podcasts_data::Show; use podcasts_data::dbqueries; use podcasts_data::utils::delete_show; #[derive(Debug, Clone)] pub(crate) struct ShowMenu { pub(crate) menu: gio::MenuModel, website: gio::SimpleAction, played: gio::SimpleAction, unsub: gio::SimpleAction, group: gio::SimpleActionGroup, } impl Default for ShowMenu { fn default() -> Self { let builder = gtk::Builder::from_resource("/org/gnome/Podcasts/gtk/show_menu.ui"); let menu = builder.object("show_menu").unwrap(); let website = gio::SimpleAction::new("open-website", None); let played = gio::SimpleAction::new("mark-played", None); let unsub = gio::SimpleAction::new("unsubscribe", None); let group = gio::SimpleActionGroup::new(); group.add_action(&website); group.add_action(&played); group.add_action(&unsub); ShowMenu { menu, website, played, unsub, group, } } } impl ShowMenu { pub(crate) fn new(pd: &Arc, episodes: >k::ListBox, sender: &Sender) -> Self { let s = Self::default(); s.init(pd, episodes, sender); s } fn init(&self, pd: &Arc, episodes: >k::ListBox, sender: &Sender) { self.connect_website(pd); self.connect_played(pd, episodes, sender); self.connect_unsub(pd, sender); let app = gio::Application::default() .expect("Could not get default application") .downcast::() .unwrap(); let win = app.active_window().expect("No active window"); win.insert_action_group("show", Some(&self.group)); } fn connect_website(&self, pd: &Arc) { // TODO: tooltips for actions? self.website.connect_activate(clone!( #[strong] pd, move |_, _| { let link = pd.link(); info!("Opening link: {}", link); let res = open::that(link); debug_assert!(res.is_ok()); } )); } fn connect_played(&self, pd: &Arc, episodes: >k::ListBox, sender: &Sender) { self.played.connect_activate(clone!( #[strong] pd, #[strong] sender, #[weak] episodes, move |_, _| { let res = dim_titles(&episodes); debug_assert!(res.is_some()); send_blocking!(sender, Action::MarkAllPlayerNotification(pd.clone())); } )); } fn connect_unsub(&self, pd: &Arc, sender: &Sender) { self.unsub.connect_activate(clone!( #[strong] pd, #[strong] sender, move |unsub, _| { unsub.set_enabled(false); send_blocking!(sender, Action::RemoveShow(pd.clone())); // Queue a refresh after the switch to avoid blocking the db. send_blocking!(sender, Action::RefreshShowsView); send_blocking!(sender, Action::RefreshEpisodesView); unsub.set_enabled(true); } )); } } // Ideally if we had a custom widget this would have been as simple as: // `for row in listbox { ep = row.get_episode(); ep.dim_title(); }` // But now I can't think of a better way to do it than hardcoding the title // position relative to the EpisodeWidget container gtk::Box. fn dim_titles(episodes: >k::ListBox) -> Option<()> { // FIXME This api should only be used for widget implementations. let listmodel = episodes.observe_children(); for i in 0..listmodel.n_items() { let obj = listmodel.item(i)?; let row = obj.downcast_ref::()?; dim_row_title(row)?; } Some(()) } fn dim_row_title(row: >k::ListBoxRow) -> Option<()> { // FIXME first_child should only be used for widget implementations. let container = row.first_child().and_downcast::()?; let container_child = container.first_child().and_downcast::()?; let container_gradchild = container_child.first_child().and_downcast::()?; let container_great_gradchild = container_gradchild .first_child() .and_downcast::()?; let title = container_great_gradchild .first_child() .and_downcast::()?; title.add_css_class("dim-label"); // FIXME next_sibling should only be used for widget implementations. let checkmark = title.next_sibling().and_downcast::()?; checkmark.set_visible(true); Some(()) } fn mark_all_watched(pd: &Show, sender: &Sender) -> Result<()> { // TODO: If this fails for whatever reason, it should be impossible, show an error dbqueries::update_none_to_played_now(pd)?; // Not all widgets might have been loaded when the mark_all is hit // So we will need to refresh again after it's done. send_blocking!(sender, Action::RefreshWidgetIfSame(pd.id())); send_blocking!(sender, Action::RefreshEpisodesView); Ok(()) } pub(crate) fn mark_all_notif(pd: Arc, sender: &Sender) -> adw::Toast { let id = pd.id(); let toast = adw::Toast::new(&i18n("Marked all episodes as listened")); toast.set_button_label(Some(&i18n("Undo"))); toast.set_action_target_value(Some(&id.0.to_variant())); toast.set_action_name(Some("app.undo-mark-all")); toast.connect_dismissed(clone!( #[strong] sender, move |_| { let app = gio::Application::default() .expect("Could not get default application") .downcast::() .unwrap(); if app.is_show_marked_mark(&pd) { let res = mark_all_watched(&pd, &sender); debug_assert!(res.is_ok()); } } )); toast } pub(crate) fn remove_show_notif(pd: Arc) -> adw::Toast { let text = i18n_f("Unsubscribed from {}", &[pd.title()]); let id = pd.id(); let toast = adw::Toast::new(&text); toast.set_button_label(Some(&i18n("Undo"))); toast.set_action_target_value(Some(&id.0.to_variant())); toast.set_action_name(Some("app.undo-remove-show")); let res = utils::ignore_show(id); debug_assert!(res.is_ok()); toast.connect_dismissed(move |_args| { let res = utils::unignore_show(id); debug_assert!(res.is_ok()); // Spawn a thread so it won't block the ui. gio::spawn_blocking(clone!( #[strong] pd, move || { let app = gio::Application::default() .expect("Could not get default application") .downcast::() .unwrap(); if app.is_show_marked_delete(&pd) { if let Err(err) = delete_show(&pd) { error!("Error: {}", err); error!("Failed to delete {}", pd.title()); } } // No need to update the UI after remove. // The "unsubscribe" action already updated the UI after ignoring the show. } )); }); toast } podcasts-25.2/podcasts-gtk/src/widgets/shows_view.rs000066400000000000000000000215201500126606300226640ustar00rootroot00000000000000// shows_view.rs // // Copyright 2017 Jordan Petridis // Copyright 2024 nee // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::*; use adw::subclass::prelude::*; use anyhow::{Result, anyhow}; use async_channel::Sender; use glib::clone; use glib::object::Object; use gtk::gio; use gtk::glib; use std::cell::Cell; use std::cell::RefCell; use std::cell::RefMut; use std::sync::Arc; use crate::app::Action; use crate::download_covers::load_widget_texture; use crate::i18n::i18n; use crate::utils::get_ignored_shows; use crate::widgets::BaseView; use podcasts_data::dbqueries; use podcasts_data::{Show, ShowId}; #[derive(Debug, Default)] pub struct ShowsViewPriv { view: BaseView, grid: gtk::GridView, } #[glib::object_subclass] impl ObjectSubclass for ShowsViewPriv { const NAME: &'static str = "PdShowsView"; type Type = super::ShowsView; type ParentType = adw::Bin; } impl ObjectImpl for ShowsViewPriv { fn constructed(&self) { self.parent_constructed(); let missing_icon = load_missing_icon(); let factory = gtk::SignalListItemFactory::new(); factory.connect_setup(clone!( #[strong] missing_icon, move |_factory, item| { let item = item.downcast_ref::().unwrap(); // TODO: Make this a widget with completed/fetch-error info overlays let picture = gtk::Picture::builder() .width_request(150) .height_request(150) .can_focus(false) .build(); picture.set_paintable(missing_icon.as_ref()); picture.add_css_class("flat"); picture.add_css_class("rounded-big"); picture.add_css_class("show-button"); picture.add_css_class("shows-view-cover"); picture.set_content_fit(gtk::ContentFit::ScaleDown); item.set_child(Some(&picture)); } )); factory.connect_bind(move |_factory, item| { let item = item.downcast_ref::().unwrap(); let data = item.item().and_downcast::().unwrap(); let child = item.child().and_downcast::().unwrap(); let id = data.show_id(); let load_handle = load_widget_texture(&child, id, crate::Thumb256); let mut load_handle_store = data.get_mut_load_handle(); *load_handle_store = Some(load_handle); }); factory.connect_unbind(move |_factory, item| { let item = item.downcast_ref::().unwrap(); let data = item.item().and_downcast::().unwrap(); let child = item.child().and_downcast::().unwrap(); // cancel loading the picture if let Some(handle) = data.get_mut_load_handle().take() { handle.abort(); } child.set_paintable(missing_icon.as_ref()); }); self.grid.set_factory(Some(&factory)); self.grid.set_single_click_activate(true); self.grid.set_can_focus(true); self.grid.set_vexpand(true); self.grid.set_hexpand(true); self.grid.set_min_columns(2); self.grid.set_max_columns(7); self.grid.set_valign(gtk::Align::Fill); self.grid.set_halign(gtk::Align::Fill); self.grid.set_height_request(500); // makes tabbing down to the player widget is easier. self.grid.set_tab_behavior(gtk::ListTabBehavior::Item); self.grid.add_css_class("shows-grid"); self.grid.set_vscroll_policy(gtk::ScrollablePolicy::Natural); self.grid // Translators: Shows as a noun, meaning Podcast-Shows. .update_property(&[gtk::accessible::Property::Label(&i18n("Shows"))]); let clamp = adw::ClampScrollable::builder() .child(&self.grid) .valign(gtk::Align::Fill) .halign(gtk::Align::Fill) .vscroll_policy(gtk::ScrollablePolicy::Natural) .orientation(gtk::Orientation::Horizontal) .maximum_size((256 + 6 + 6) * 7) // picture + paddings * max_columns .build(); self.view.set_content(&clamp); self.obj().set_child(Some(&self.view)); } } fn load_missing_icon() -> Option { let display = gtk::gdk::Display::default()?; // get the max scale form any of the monitors let scale = display.monitors().into_iter().fold(1, |acc, m| { let m_scale = (|| Some(m.ok()?.downcast::().ok()?.scale_factor()))() .unwrap_or(acc); std::cmp::max(acc, m_scale) }); let theme = gtk::IconTheme::for_display(&display); if theme.has_icon("image-x-generic-symbolic") { Some(theme.lookup_icon( "image-x-generic-symbolic", &[], 128, // 1/2 size of picture to get padding scale, gtk::TextDirection::Ltr, gtk::IconLookupFlags::FORCE_SYMBOLIC, )) } else { None } } impl WidgetImpl for ShowsViewPriv {} impl BinImpl for ShowsViewPriv {} impl ShowsViewPriv { fn set_data(&self) { let this = self.downgrade(); crate::MAINCONTEXT.spawn_local_with_priority( glib::source::Priority::DEFAULT_IDLE, async move { let data = gio::spawn_blocking(get_episodes).await; if let Ok(Ok(podcasts)) = data { let model = gio::ListStore::new::(); for pod in podcasts { let item = ShowCoverModel::new(pod.id()); model.append(&item); } if let Some(this) = this.upgrade() { let selection_model = gtk::NoSelection::new(Some(model)); this.grid.set_model(Some(&selection_model)); } } }, ); } } fn get_episodes() -> Result> { let ignore = get_ignored_shows()?; let podcasts = dbqueries::get_podcasts_filter(&ignore)?; Ok(podcasts) } glib::wrapper! { pub struct ShowsView(ObjectSubclass) @extends gtk::Widget, adw::Bin; } impl ShowsView { pub(crate) fn new(sender: Sender) -> Self { let this: Self = glib::Object::new(); this.imp().set_data(); this.imp().grid.connect_activate(move |gridview, index| { if let Err(err) = on_child_activate(gridview, index, &sender) { error!("Failed to activated ShowCover {err}"); } }); this } pub fn update_model(&self) { self.imp().set_data(); } } fn on_child_activate(gridview: >k::GridView, index: u32, sender: &Sender) -> Result<()> { let id = gridview .model() .ok_or(anyhow!("no model in gridview"))? .item(index) .ok_or(anyhow!("clicked show not found in gridview model"))? .downcast::() .unwrap() .show_id(); let pd = Arc::new(dbqueries::get_podcast_from_id(id)?); send_blocking!(sender, Action::GoToShow(pd)); Ok(()) } // Model data type // ------------------------------------------------------------------- #[derive(Debug, Default)] pub struct ShowCoverModelPrivate { pub show_id: Cell, pub load_handle: RefCell>>, } #[glib::object_subclass] impl ObjectSubclass for ShowCoverModelPrivate { const NAME: &'static str = "PdShowCoverModel"; type Type = ShowCoverModel; type ParentType = Object; } impl ObjectImpl for ShowCoverModelPrivate {} gtk::glib::wrapper! { pub struct ShowCoverModel(ObjectSubclass); } impl ShowCoverModel { pub(crate) fn new(id: ShowId) -> Self { let self_: Self = glib::Object::new(); self_.imp().show_id.set(id.0); self_ } fn show_id(&self) -> ShowId { ShowId(self.imp().show_id.get()) } fn get_mut_load_handle(&self) -> RefMut>> { self.imp().load_handle.borrow_mut() } } // ------------------------------------------------------------------- podcasts-25.2/podcasts-gtk/src/window.rs000066400000000000000000000361221500126606300203340ustar00rootroot00000000000000// window.rs // // Copyright 2019 Jordan Petridis // // 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 . // // SPDX-License-Identifier: GPL-3.0-or-later use adw::prelude::*; use adw::subclass::prelude::*; use anyhow::Result; use async_channel::Sender; use glib::clone; use gst::ClockTime; use gtk::CompositeTemplate; use gtk::{gio, glib}; use std::cell::{Cell, OnceCell, RefCell}; use std::cell::{Ref, RefMut}; use std::ops::Deref; use std::rc::Rc; use std::sync::Arc; use crate::app::{Action, PdApplication}; use crate::config::APP_ID; use crate::feed_manager::FEED_MANAGER; use crate::settings::{self, WindowGeometry}; use crate::utils; use crate::widgets::about_dialog; use crate::widgets::player; use crate::widgets::player::{PlayerExt, PlayerWidget, SeekDirection, StreamMode}; use crate::widgets::{Content, DiscoveryPage, EpisodeDescription, ShowWidget}; use podcasts_data::dbqueries; use podcasts_data::{EpisodeId, ShowId}; #[derive(Debug, CompositeTemplate, glib::Properties)] #[template(resource = "/org/gnome/Podcasts/gtk/window.ui")] #[properties(wrapper_type = MainWindow)] pub struct MainWindowPriv { pub(crate) content: OnceCell>, pub(crate) player: OnceCell, pub(crate) updating_timeout: RefCell>, pub(crate) settings: gio::Settings, pub(crate) show_widget: RefCell>, pub(crate) sender: OnceCell>, #[template_child] pub(crate) top_switcher: TemplateChild, #[template_child] pub(crate) content_view: TemplateChild, #[template_child] pub(crate) player_toolbar_view: TemplateChild, #[template_child] pub(crate) bottom_sheet: TemplateChild, #[template_child] pub(crate) bottom_switcher: TemplateChild, #[template_child] pub(crate) bottom_switcher_bar: TemplateChild, #[template_child] pub(crate) toolbar_box: TemplateChild, #[template_child] pub(crate) toast_overlay: TemplateChild, #[template_child] pub(crate) navigation_view: TemplateChild, #[template_child] pub(crate) header_breakpoint: TemplateChild, #[template_child] pub(crate) player_breakpoint: TemplateChild, #[template_child] pub(crate) show_page: TemplateChild, #[property(set, get)] pub(crate) updating: Cell, #[property(set, get)] pub(crate) is_root_page: Cell, // for bottom switcher visibility #[property(set, get)] pub(crate) is_mobile_layout: Cell, // for bottom switcher visibility } #[glib::object_subclass] impl ObjectSubclass for MainWindowPriv { const NAME: &'static str = "PdMainWindow"; type Type = MainWindow; type ParentType = adw::ApplicationWindow; fn new() -> Self { let settings = gio::Settings::new(APP_ID); Self { content: OnceCell::new(), player: OnceCell::new(), navigation_view: TemplateChild::default(), toast_overlay: TemplateChild::default(), top_switcher: TemplateChild::default(), content_view: TemplateChild::default(), player_toolbar_view: TemplateChild::default(), bottom_sheet: TemplateChild::default(), bottom_switcher: TemplateChild::default(), bottom_switcher_bar: TemplateChild::default(), toolbar_box: TemplateChild::default(), header_breakpoint: TemplateChild::default(), player_breakpoint: TemplateChild::default(), show_page: TemplateChild::default(), updating: Cell::new(false), updating_timeout: RefCell::new(None), sender: OnceCell::new(), show_widget: RefCell::new(None), is_root_page: Cell::new(true), is_mobile_layout: Cell::new(false), settings, } } fn class_init(klass: &mut Self::Class) { klass.bind_template(); klass.install_action("win.refresh", None, move |win, _, _| { let sender = win.sender(); FEED_MANAGER.schedule_full_refresh(sender); }); klass.install_action_async("win.import", None, |win, _, _| async move { let sender = win.sender(); utils::on_import_clicked(win.upcast_ref(), sender).await; }); klass.install_action_async("win.export", None, |win, _, _| async move { let sender = win.sender(); utils::on_export_clicked(win.upcast_ref(), sender).await; }); klass.install_action("win.about", None, move |win, _, _| { about_dialog(win.upcast_ref()); }); klass.install_action("win.toggle-pause", None, move |win, _, _| { win.player_mut().toggle_pause(); }); klass.install_action("win.seek-forwards", None, move |win, _, _| { win.player() .seek(ClockTime::from_seconds(10), SeekDirection::Forward); }); klass.install_action("win.seek-backwards", None, move |win, _, _| { win.player() .seek(ClockTime::from_seconds(5), SeekDirection::Backwards); }); klass.install_action("win.go-to-home", None, move |win, _, _| { win.pop_to_content(); win.content().go_to_home(); }); klass.install_action("win.go-to-shows", None, move |win, _, _| { win.pop_to_content(); win.content().go_to_shows(); }); klass.install_action("win.go-to-discovery", None, move |win, _, _| { win.go_to_discovery(); }); klass.install_action("win.close-bottom-sheet", None, move |win, _, _| { win.imp().bottom_sheet.set_open(false); }); } fn instance_init(obj: &glib::subclass::InitializingObject) { obj.init_template(); } } #[glib::derived_properties] impl ObjectImpl for MainWindowPriv { fn constructed(&self) { let window = self.obj(); self.parent_constructed(); if APP_ID.ends_with("Devel") { window.add_css_class("devel"); } // Retrieve the previous window position and size. WindowGeometry::from_settings(&self.settings).apply(window.upcast_ref()); } } impl WidgetImpl for MainWindowPriv {} impl WindowImpl for MainWindowPriv { // Save window state on delete event fn close_request(&self) -> glib::Propagation { let obj = self.obj(); info!("Saving window position"); WindowGeometry::from_window(obj.upcast_ref()).write(&self.settings); self.parent_close_request() } } impl ApplicationWindowImpl for MainWindowPriv {} impl AdwApplicationWindowImpl for MainWindowPriv {} glib::wrapper! { pub struct MainWindow(ObjectSubclass) @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionMap, gio::ActionGroup, gtk::Root; } impl MainWindow { pub(crate) fn new(app: &PdApplication, sender: &Sender) -> Self { let window: Self = glib::Object::builder().property("application", app).build(); let imp = window.imp(); let content = Content::new(sender.clone()); imp.sender.set(sender.clone()).unwrap(); imp.content_view.set_content(Some(content.overlay())); let player = player::PlayerWrapper::new(sender); imp.toolbar_box.prepend(&player.borrow().container); imp.bottom_sheet .set_property("sheet", &player.borrow().sheet.sheet); imp.top_switcher.set_stack(Some(content.stack())); imp.bottom_switcher.set_stack(Some(content.stack())); imp.navigation_view.connect_popped(clone!( #[weak] imp, #[weak] window, move |_, _| { if imp .navigation_view .visible_page() .map(|p| p.tag().as_ref().map(|s| s.as_str()) == Some("content")) .unwrap_or(false) { window.set_is_root_page(true); } } )); // Update Bottom switcher visibility let update_bottom_switcher_visible = move |window: &MainWindow| { window .bottom_switcher_bar() .set_visible(window.is_root_page() && window.is_mobile_layout()); }; window.connect_is_root_page_notify(update_bottom_switcher_visible); window.connect_is_mobile_layout_notify(update_bottom_switcher_visible); // Setup breakpoints imp.header_breakpoint .add_setter(&window, "is_mobile_layout", Some(&true.to_value())); let p = player.deref(); imp.player_breakpoint.connect_apply(clone!( #[weak] p, move |_| { p.borrow().set_small(false); } )); imp.player_breakpoint.connect_unapply(clone!( #[weak] p, move |_| { p.borrow().set_small(true); } )); let breakpoint = imp.player_breakpoint.get(); let is_small = window.current_breakpoint().is_none_or(|b| b != breakpoint); p.borrow().set_small(is_small); // Update the feeds right after the Window is initialized. if imp.settings.boolean("refresh-on-startup") { info!("Refresh on startup."); FEED_MANAGER.schedule_full_refresh(sender); } let refresh_interval = settings::get_refresh_interval(&imp.settings).num_seconds() as u32; info!("Auto-refresh every {:?} seconds.", refresh_interval); glib::timeout_add_seconds_local( refresh_interval, clone!( #[strong] sender, move || { FEED_MANAGER.schedule_full_refresh(&sender); glib::ControlFlow::Continue } ), ); imp.content.set(content).unwrap(); imp.player.set(player).unwrap(); window } pub fn push_page>(&self, page: &P) { self.imp().navigation_view.push(page); self.set_is_root_page(false); } fn pop_to_show_widget(&self) { let imp = self.imp(); let is_current_page = imp .navigation_view .visible_page() .and_then(|p| p.tag()) .is_some_and(|t| t != "show"); if !is_current_page { imp.navigation_view.pop_to_tag("show"); } } fn pop_to_content(&self) { self.imp().navigation_view.pop_to_tag("content"); } pub(crate) fn init_episode( &self, id: EpisodeId, second: Option, stream: StreamMode, ) -> Result<()> { self.imp() .bottom_sheet .set_property("can-open", true.to_value()); self.imp() .player .get() .unwrap() .borrow_mut() .initialize_episode(self.sender(), id, stream, second) } pub(crate) fn add_toast(&self, toast: adw::Toast) { self.imp().toast_overlay.add_toast(toast); } pub(crate) fn set_updating_timeout(&self, timeout: Option) { if let Some(old_timeout) = self.imp().updating_timeout.replace(timeout) { old_timeout.remove(); } } pub(crate) fn progress_bar(&self) -> >k::ProgressBar { self.content().progress_bar() } pub(crate) fn player(&self) -> Ref<'_, PlayerWidget> { self.imp().player.get().unwrap().borrow() } pub(crate) fn player_mut(&self) -> RefMut<'_, PlayerWidget> { self.imp().player.get().unwrap().borrow_mut() } pub(crate) fn content(&self) -> &Content { self.imp().content.get().unwrap() } pub(crate) fn top_switcher(&self) -> &adw::ViewSwitcher { &self.imp().top_switcher } pub(crate) fn bottom_switcher_bar(&self) -> >k::CenterBox { &self.imp().bottom_switcher_bar } pub(crate) fn sender(&self) -> &Sender { self.imp().sender.get().unwrap() } pub(crate) fn go_to_discovery(&self) { let widget = DiscoveryPage::new(self.sender()); self.push_page(&widget); } pub(crate) fn replace_show_widget(&self, widget: Option, title: &str) { let imp = self.imp(); let is_current_page = imp .navigation_view .visible_page() .is_some_and(|p| p == *imp.show_page); imp.show_page.set_child(widget.as_ref()); if widget.is_some() { imp.show_page.set_title(title); } else if is_current_page { imp.navigation_view.pop(); } imp.show_widget.replace(widget); } pub(crate) fn update_show_widget(&self, show_id: ShowId) -> Result<()> { let imp = self.imp(); let same = imp.show_widget.borrow().as_ref().and_then(|s| s.show_id()) == Some(show_id); if same { let pd = dbqueries::get_podcast_from_id(show_id)?; let widget = ShowWidget::new(Arc::new(pd), self.sender()); self.replace_show_widget(Some(widget), &imp.show_page.title()); } Ok(()) } pub(crate) fn go_to_show_widget(&self) { let imp = self.imp(); let is_current_page = imp .navigation_view .visible_page() .is_some_and(|p| p == *imp.show_page); if !is_current_page { self.pop_to_show_widget(); imp.navigation_view.push(&*imp.show_page); self.set_is_root_page(false); } } pub(crate) fn pop_show_widget(&self) { let imp = self.imp(); let is_current_page = imp .navigation_view .visible_page() .is_some_and(|p| p == *imp.show_page); if is_current_page { imp.navigation_view.pop(); } } pub(crate) fn pop_episode_description(&self) { let imp = self.imp(); let is_current_page = imp .navigation_view .visible_page() .is_some_and(|p| p.downcast::().is_ok()); if is_current_page { imp.navigation_view.pop(); } } pub(crate) fn episode_description(&self) -> Option { let imp = self.imp(); imp.navigation_view .visible_page() .and_then(|p| p.downcast::().ok()) } } podcasts-25.2/podcasts.doap000066400000000000000000000027471500126606300157600ustar00rootroot00000000000000 Podcasts Podcast app for GNOME Listen to your favorite podcasts, right from your desktop. Rust Jordan Petridis jpetridis Julian Hofer julianhofer Hofer-Julian podcasts-25.2/release_process.md000066400000000000000000000014031500126606300167570ustar00rootroot00000000000000## Podcasts release process - Ensure that the version in meson.build is correct - Update CHANGELOG.md - Edit appdata.xml with the correct version and release notes - Commit and tag in git - In the tag message add the same release notes as in CHANGELOG.md ``` git tag -a '0.4.9' git push --atomic origin main 0.4.9 ``` - Do a post-version release bump - Open an MR in gitlab and once merged, push the tag - Open a PR at [Flathub](https://github.com/flathub/org.gnome.Podcasts) with the new tarball from the gitlab release ### Optional maintenance thingies - Update flatpak modules - Run `cargo update`, build and commit the new lockfile. - Check for [outdated](https://github.com/kbknapp/cargo-outdated) crates `cargo install cargo-outdate && cargo outdated -d 1` podcasts-25.2/rustfmt.toml000066400000000000000000000000211500126606300156530ustar00rootroot00000000000000edition = "2021" podcasts-25.2/screenshots/000077500000000000000000000000001500126606300156215ustar00rootroot00000000000000podcasts-25.2/screenshots/add_podcast.png000066400000000000000000000730711500126606300206040ustar00rootroot00000000000000PNG  IHDRb6%sBIT|dtEXtSoftwaregnome-screenshot>%tEXtCreation TimeFr 19 Apr 2024 20:19:10>^ IDATxypuW;W H[" hP0NVq˕Tppp8$(% $g?6iI*?ϧ5~$W2tm{0;#t[>U[Mn`V $ns ̭N @_ѧRVBX}(+E_9]2}!9t8}}_gP$aƄs@upn3Z Hw'aȄcЕuUV8 +C d-lz+#p-0 %l˜p cp:e" е:@hy>BO0]&mB@˹%tU㯜`ӛ P 6>P# & wLom'z@d]G^ƀ!LgC/m•` 0&ԑ#EtXx:PFg{07 a `||ʧ* P]5O}} dyX1)(. a~ f8Soj3zZ0uztuC+C@@<%@&lØ*:å#OJ @wHnj>tН!L0Lwӕeݺ"tN`>t@Б !{9ӝ! &FO=f:t a{x{p%Oe HzIp cz*XDO/c_BY\cLۃΆ0o9&T})?0b ӕ!&O/*Fi81 czmPa< /r]{=}^< I<,s/υ15 dH_/cЛtUHO#˽O b7LGC<-9p6'Pro=`:^1P$H I_c׋oOu | y-Qwvkwmoc&\5Bteh1 ;C0x橗JDoc[cp>a\(DAK*Tfn)q \D3Jl <3}^1ֶp桧eKyALo΄0%-|ڰaC#<2oM&$v*b)~[xKji e\ھv (z1<=_o nz am?I6lO,&&#𠩩i͛s=wNRc[bmq:Cy}}(/ װ$_OAr_p ab= ]`0XKJJiܸqh cnaÄ#ظ5Ó;0~G aap~eee+l rmI7@60W+S #kKoI梢a>[TTt$wt[ _#3赺{(N -6,伾$/x$OHNNӯ_~iۤ rQzehRzJ ch{:kW[[31/Դ3!!Hw'k9c6WLwLC"q/ӄqdZ~BzFLL}ׯq}Zdnt s9{ln' :ywv3 ``29 qS0 |rRrrtFۄY2t<͊>I\ Oɸk2L?d@'}77yyHm`uKhcB]<]8׋OZjnnVkkVl6""BFQ%aNyOWi_zlk_9.ݡ!455utw_= Yr_k? ~]{ !1BTUUiŊZfM(@PVkz¸>nX᾽'@s Ɠ>W:>\TUU駟_P.kƌ1c9whƌ6=Zt^y]vSHݺclv]t;~G3^71%I`AO?R1B7n>SwuԤ?XO=T+竴_GTw ;)JF81ESO= a]ɡ84]*..ֲeԯ_?>|3]K/iƍ裏4b]xQ!;> !=ܣgyF555ezt=tepx|uM(B@_ 3oUTThԨQZj~(//OǎSee\YFQQQ#Gwկ_?kڰa'IzGe4uQ&tVGgjݺuzd0*--UZZZ`^oF䎖쐥N#~I&鮻< O'ڵK)))9rn*'mqIII$mܸQ7odҌ3T\\k/긎r,fϞfEGGȑ#JKKSNNvnݪw}Wו+WtR͙3GWNN9shժU~'+(55U.\кun:jz;Uvj~o[ޭ¡GL_BЛotŋj2LVDD!Cwޑdү~+}rܹͦS&I'OVEEvܩ#G7L&vڥk˖-8qb@ǽzvܩ4mݺaΝeeeZ| f]pAEwq|IL'Լy`۷OWѣ%>$˧hÆ z眽S _SX簫@(DDaҔ2=󪪪T>SEOnM6)##C/^SO=E+Ν5i$;vNr bF!I*((p do^}U?V^#G:K=|uAAvsP >|sb>}ZMMMԩS$IX,mRRR\۷oף>*ͦ'OJmj(Iuuu=h4\/ҥKJKKӆ aӋ/ة78&}ꩧi&f0gn|ȑJMMUYYJJJ4zhq/b I#IFMF3gP?233(\2fffjҤI:uV\ӧT?4h IҮ]dX_J.˹sK/iԩׯ8<$=Z{믿K+V˗}@Ot#{ߎc[---:q>UUU駟ViiƎ|%E]]hOTT Iu,nm.?vږe{/%' &4JכocdžzFiґ{̰N=b .1yASS뻤8 Gݥg1rBM@o%XfsB.VyBo b@fuxnԗWQdd7::ZfYFDkh4*>>^fYjmmj?SDDDh4*22R})1t/G u/!pb[KXgĄu2wJ1!B"1!B"1!B"1!B"1!B"1!B"1!B"1!B"1!B"1!B"1!B"1!B"1!b LCC~e6\ӦM:͛7OfYw@G l6{9婺ZwqbtG k/є)So`0H R[bX)IunبzKV}}&NkjСV^^;J_ZfaF999:tvڥCߖf{ァO>D5jVZ;Sڪ_]bҥKzi'q)&&FEEEi&?6/o߮L͞=[Znl6u)''Gv][nջۮݻw+//O̔$mܸQ7obٳ ܧVmݦ*ѣ!j/qFok֭3@OVmmvjjjm a$T6M3g$'';?~|c#ˆ`Ќ34c -YDO?N:ŋKMg}>*//דO>!C^Pzz-ZtSsHҍ76h4v#@kk[;ֹd+""B0`i555);;[SNjmW7Ç$k@`b0pq=JIIРoF|r 8PwvءUVi…ԍ7/kРA]vb/$LI&ԩSZrORg?ӄ Bpз$ 顇RbbUVV,+aIk׮O<mٲEgϞlnɓb L&ٳGGVnnn@~WxbjǎRSSS71M(e?F&---:yfHddqmds{mwy-?rY/ev_{6G @A @A @A @A @A @A @A @zw^}77-6l-[PVUv]&S}vU\\X{JNNcXVmڴI?ۭ{뭷xb[~Ƿl IDATϔA)..NΞ;wn;_z_vE۸򷽿6 ܻJMMU\\fϞ-qousЙߣl6kɒiӦ)))IIII7n%?/G F&N =&$$(22^4ղ裏l@2dc666XjnnVMMMl8p6 &;vhԨQz{F6M r.Ԑ!CTUU\櫍 aÆi˖-JOOWVVO\@kkkڮ\߽ޗ]6mHS^g]Cz :tz>/# pu-%IO᧣?ԨQtw*>>^۷of:[odeThhmݦ >|8'&9la0tR]|Y_}vޭaÆi~ۧקYj&Ԃ/;676{౗Zg?]q׵Kb\%$$(**JΝpyW^Uccf͚dEEEڵkhu>11QIIIŋoZ^^^Cvv v]zC PÆ ӂ tܹs>>zv`ܗi`H mhhpNPQQ> .]yӵKb0☬j\\TWWVEDDhʔ):z.]$ͦ|fTRR"} :[cjЍ7tǛ>}k?~\ժVaa{M8~IIIÇjXǵDTTtUTTAՈ#j&9'/-++SddbccOGOLL222tAUUU9 f葃}O0egFmst\m+{d2B/_VSS?eddt.\@KWܿu-6ɓ'=I@9}N>nٰaôlٲ3f.\-[hذaZdM&Ѩ|l6kʔ)σѿ͚5KǎSaaFb(u~ܹh7k0d-[L/T=C~L>]_}N> mΜ9sS/+W*>>~Μ9:zv-ͦ4w}Oss[EIII{CC|Ogu!m߾]VUTJJit}ٙI&i~-Zw{_mґW0nTT233uݸqCC:\]@Kg@v]>Wkf <׮"\8;;^^~ǷyR۶mӄ U2QQQ3$IJK{2e"1!B"zsA @A @A @zبfY, @a2NպB VUuuu0bbY2A:[_=I@ ~EuuuASuzA  aBb1{o` gapK Zoa0_oP!!SOWn%gϞծ]t())IǏג%Kb V6oެ?k׮:x~h͚52=VO݋ BDGGԼy4p@UVVk.v=S=]]݄9b={¼kz5tPL& :T?^{5EGG?ٳg{ A t3p|Pcƌ͘1cH+:1׿J͛s;zf555α:$z1%I>swl!n6aIҁ|n~ICR/Glɒ%>@_m+}G2 O~fYYY577kݺuںu\Vk˖-zܬttCguY2z{kkc~_i޼y8pL&RRR4w\mܸQ7o߮K.iݺujb=bB{Z?.\QF7 h?z #У_R>>zo@[&FبfY,@a2%[sb0pz)VCB  DbB  DbB  DbB  DbB  DbB  DbB  DbB  DbB  DbB  DbBwӟgl6+##C?M#u7ofv=IRDDSj͚5JIIPMMMX,2͊h-Gڶmnܸ=b0`s)//Oպ;?\KjjjzJ---*W^UZZnÇ2L3gҺq_/2e|M IRcc{zA{饗zZJ/^T~~ϟe8y$פI\  TVVJmbQdd$)66ֹMccz->|X8q֮]CZyyy:v*++տj͚5چhСڵk~[6M>UTThԨQZjNIRkk^u8p@EK.ի:8?͛u…K/}I}QF=zT֭ӡC$Iw'I7M2Eݫ kv[;̛7O ,PLL<(ժ_ӯk9sF??hĉ#Gwկ_?k 14 ƍSLLtkӦM:|m^xm߾]={ n:l6EGGȑ#JKKSNNvnݪw}]wV^^bcc)Iڸq6o,ŢٳgAfٹOmm:nMUUUѣG>7*I9b\wNNs9shժU 8Ϝ9SWm<ɤ3fXk׮_|$i׮]:|TUU͛7TCCRRRTZZ_|Qʕ+Zt̙XBG 8P7nooFnݪ\=3TAAO^{Mjv*++SZZv,L˗/Waa֬YX7ovzvܩ4mݺ{ݶm<ԩS5ksl*))Ν;e24y䛶YoG/}i=z$i/GiREEvܩ#G7L&vڥk˖-s>CEFF??;ֆ $I=.\NW^bwܡ'|R&@S귿?={ڷo fϞ-I:qf̘nZIRQQ>>}Zimttt𡴴T6M3g$'';?~|cz:""B/4|pR@8;t" ,oIIIaaYYY[O,]pA5553fuA-\Pzp!ˆ`Ќ34c -YDO?N:ŋKMg}>*//דO>!C^Pzz-ZtSsHҍ7+0m7yd 4HÆ SNNFq@Fe($577`0(&&ݶsu]CO?qu? oرcNNNVzz"""T\\4ӧԤlM:F{JAAZZZ ֫DO vnytIѣGO+**J!M]-%%E?\6m￯'O0 ?~\>RRR4|p544o$-_\}ݧ;vhժUZp*++u 4h6٬bї_~)dffjҤI:uV\ӧT?4a„n9פ$]rEo=zhٳG.Xc`4rH͜9SzǕ|EEEiʕ]~ΝK/S_~:p$iԨQ]~,ყ&a --M=U\\2eeeW^qKZvx EDDh˖-:{fv&O+Vd2iϞ==zrss:ŋV;vPUU\~i%''˗/=ܣrڵKΝZ/e˖A?~O0`?i˖-2 ZvfΜ> =Pke.nMn[ZZu<@'EEE͐T'*.Z.]^.&mB!!!!!!1vqy 2o#^DK;y@ݸqCGѡCTPP3gΨRvL&Si۶m2 4hP?[nUKKRSS{MW;CKjfSD|.ml==nW~~?HÆ lZƿ˿Ijdds߮' (VU;wԠAdƪ^劎j#G/꽡>Suul}s={._GyDA111=ZGtA ^?u+l_̙3{ o4hU@'afbNZ*,,TII,2224{lEFFj@ϟW}}4}t3ƹm4eʕ+{5|pI_|3gΨAC ܹs Ir劊uU%$$hܹl6;vLŲйsl2%&&*//O?я&O۷oדO>|۶m0a*ŋ*,,կ_?=ZO>nwXZڴi&N[ڪ~[>Ӯ:N< (ͦ}ѣ;wFh"L&h߾}\())I S__/ժo]JNNثaĈjnnvŋF$ҥKJOOl4iRSSKJJJnH9111{eZk޼yr…jii*יkL:*55Uqqq={fYp\l6kɒiӦ)))IIII7nG LDEEiʔ)4iΟ?SN>pnG}$*fܿQ*//WssjjjnW!C{_[[VsѸ\HHHPddZ[[mS]]-ͦ:9j3` 6L[lQzzC\EEEnw}]pA?OTYY .(99Y.]{"׀FRSSuu%''.-ufޮujjjd[2d.יkh{xc04tP!U Em?o k, s0`}  3Pzzl٢;VӟTZ[[jԨQ;۷f^;3z=ssͮy纯_ !Z̆׏hq=t-FƏyMmE񼃺zc{>2!XR߽qv}<ģ}nE]c}W%{xo|g#zW6v!Ke7ݷG,/N2EY޸SV=cځtylς)"(""㼓cCsP%΃{?JY7`w 1@Ǻ.Ț0EQDxqKg} _=8"cnow=M'ЪpyW\\pA'?iڱGFFSO[.j#Jw9E _睴r|fL5{@KX/t[n%Ǘ(Jq,|D5a8s`G|Ǚ;lpزhMf@{G㎋>8K;TyZf&*kO: "(?fxGxtMqmGGydşٟM뿢g}6mJg韢Rī^iۏW_/>lyw;/_ё&a"i/Xٴ#JG(|t03b}.(^=_җώ숈;#^L/"ӟt|ߍM65\{LL+bpp0qUW5,M==ڼUs>teжf# 1Ĭ_>7o۷駟>WЇcuŇ>)g޽;nx{f͚KoOSyӏYDQTug"_1ũI,[,N?w'pByn[o5nָ;Xow6kڑ8>oѠ`??N?[.""cz衱bŊ;~^coyy]{#p@kb` N;-8cʕ3[|ywyO|"6o۶mO}S}o\wu ݾ+6lW]uUs=QT'-[$9zxl;ٴ#Ήß1H%ώ{wӒf7N=x^ziY&֮]y`< >"o}k\|qWƙgoyK6YDE{wwŭ>YVȲ8^@k/aRu_rvW7^wA :_~(uGg7<7JҜb֭[?\.O|PC+yED ED%"m5Qs+jGu$oO4ČXnx^6ihn@;Mz=1Y+n?!|{YV}ehKKԷ8묳f?7p&i|"~V(#+#+w'9Ř/~Y+r9^ȲL׵{G38cV]m)JYD6cT#/=[8do9/,J(%GĩD@gF k,%C{1յbJ+#QTF"׿88xN(Xlwl7߽5Rwd]ݑ{%/\Wtym*kЎ1f;v@YD(b|bLDE"*cueT7rWJq)}ype!X28Wz,"+",R9l_h)(ry׾2y^YdRDVT\畫R41FAczcc#q{GCOƎȪ"ʑe剏.((e∃ʼnzc M:T 0" ,B $'_3W+"<<'_ -R; &K0YEQŕjF|a`b>T/Ob ,B <VJh2JiT11111111t-m߾}@1{&$"$"$"$"$"$"$"$"$"$"$"$"$ҵX ;?zt$zz4v |"JYxAu=qqڞf>f`I=lZw]񅻞荋^2څ,}sGt&/EQ&,q{ {`!xC{͟SDQDEDy'iq.#"⩡Jlٳ7~8",nxhX\ƃTd ܦfخTs]Wuru\Uddy>OX%"T*166CCCq/ڥo[3>;\*O{wVw7~/dY'yh֭]]]Q.Tv*˚1^DD^]lGگ5\W= %oXS>#LD!QT(qm?oV&ݻ kEWbDž/Q.|il<|_q׏4q@b]xwwGFFb޽:ŴaÆ[{@"oWsXDEQT⼓V)T]8洛3ccZ3ό?__,R|D5a8s`G|ǙQ䕈"wZ|gXa$wM<~JRQړhhM=(OFD9{}xX~~Ǹko{q79~NO}SqygbxxxbwذaCU׿Ozұ__oo oxCvm|L/-[4ǜnҗo|կ~ua83⢋.-[č7;gqFqfѣ#M+#gg'>ILB ,!6lK.$9眸{}{_C۱cG\}w+ '{btt4""뮻./`\uU3ƍu{]vmiӦx_=twwǺu+|+׾6>?.x;_WOw144ZӍ}2]w]|߈??o1=8#"s\k\~q 7DOOO\z?;v׿8#bÆ 裏__}lݺ5>4{ӣ;Xu1ǭ]ִCv3E<hEB 0իWLJ>8b`` O?>h޽;nx{f͚Koz*FGGUzUpqGq7co>:;Xvml޼yɟItIf͚xx`|[jxLcWTk|f͚8s/FFFn~q1aGcqwsϏytjX~1#G.Ȋ}?E1T_n!VZK+JqW~ظqczꩱiӦ_={-oyKկwq!T*ӎ\.Goo4zvH<ϣ\_FFF"T*ŖU^t@^^M6CV5SCc_t+%fIU___?Kf*]]]aÆꪫ{JO<'=3q뭷ƓO>{WquED@ڵ+!v#O͛7Ν;s\ O,<իW`E1Ƿjժ8묳ⓟd<1<<wyg Ϗ.,l۷o+2||8,k|V̲6x`gӎ|;'bS "&[[q]wś8ShMo;,9xG>[XlY\y@\|qqPۿ[|ӟݻwǑG''T# SvK5~$F&@ ,TULQftu`jLԅZ->E_?ft_j_ 0~h1&n^7U2кhr :O6S;;&X$@k]C|'@}XX ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! N 1bh{߉!%{2L[@!$"$N!f6瀵b@gv1m:ZFN3bښH+F9 V=BB)eɼwo3/iDtV1s/БMClz !fɜGKTǼ3E@[1L id񞢺eYea4y`mhI1Sinmc|ym:[f1:Ŵ{FLOh`[}{XlT]4bzOK*"O~`y={4aF¢ŞӈFJd_b^w^+Ѐi֌vKYI.޾4ɾRv+"_;wP__|,ٳUV},"vϊ^kj'ZϞ'Qf!(2ÌLht=iӦIVeM6}D<;z]_BΈ0)ݾ<ͬ?#x,l޼#/yK{ SvL.LI[̈i |zR׊ax@ߣ:5X&4 MU_b^sݞ:͛7iJ0,l޼#Guԍ3aiޯO޾5bS&9fD5\ط_`ݻo˿?GpMtJ݄S]ׇ:5i6ǝmߞSj׮S{;"}8o>twwEP :&FGG|'~}O|[ 0cSNI=-)&Y7ׇ11MjL˓DRꂾ]5iy-LAvufXB$ajׇ=Uz$eߧ{ֺ}Y>T4!b#;Ez9qjc™@'k$LGkS?%K#=7=Bf9YߎlVEy4E]ęԩzB l&?(yO^Řd\&{6_ l{FtO8>xTI틺Έ)Ęj)j̜.j6Ю Ŕ|Skۓ-һt fs.udj?-~ro/TklOa&[7& ;=NLqzs3buzdwӕT1հR;~fMm\?l,ǘEy03=NLsZ5|jRsT(eSĘ8Xn$1tFTAd2U2qfS˞ ANO~6(E> 4Yf3f !SROqdl_Zh11ŬFOQŕQpoLaWZ|C# U&>L5륑O7" Z4S-<}LN&R}La3m7&(Lo.1fOWq] f12iFS~f}=*43L6[f3} 3d%fX3uSw& ~jS+7U.Lj 3ݱ+,34"42C&<<^dit1i֍`)H7zT017]ʐЬY11uN7F@@'h$LLug )S}zܖ -bo?lĘn7ݘ}1ӅD7ݘq!f.׌3mg{\f6)G&5a""ʩh\3CtLko>6,3o1#U)N3jg3,Yw`abBSĖF?{5B%JtF>b>߮#LQL1fˍ.;cktgm#\3/׈1+ű FKL`=FEhP0S#.fC[gf63hLcHnB[iu,ib[qlǔL+Dacf{~,]P`{hЬ3f*?hG2Sk,ܯiZ)6,ti1R- bqtTEԤzpH@'ol2-Zr:B0ц!b!n? h' J"DƇTcnǟ Tqm"LylX mBH[N Z}|PGoZ :ySk0:`:5\tcLRK9@Rk)E\ttZqb>o;6 &"<<7S† //+Cbōq ON;-2OqIENDB`podcasts-25.2/screenshots/home_view.png000066400000000000000000003321671500126606300203250ustar00rootroot00000000000000PNG  IHDRb6%sBIT|dtEXtSoftwaregnome-screenshot>%tEXtCreation TimeFr 19 Apr 2024 20:20:57fM[ IDATxw|wvPJ% 6e+( sp8#qP9xUDpDpѣGT({#(K.ͼ4mz>o~ y$ *5p6Uwj"„ql(;0N8Ppl @49낙)8+5Yʜ p-ju S[Cz8ԺPZԖࢶPԆCyS3+OQU!Icjr@Pmd5P" F*#fT҆%-:V1*] `J[ h +KuaL4 aPSPSԼD[PnBe9;4a孎0&…0 `|ȳ*E R5NKqW)DSPP5)4ݙPJ5/\LE11A*24$)i8tTD0&BaJp)˙8PzT1zV1 TfSƌmP*"tL2",ALyCD2Le44nTTbjcyÅ0& >iҤV{bbb>0>6mڻG-)xЊda*bf`ЮPũԀ'%wKmcHcd߲em۶}*`ر.a'$7 c6{Re1Y Fa1uG=x 4k(GҴi$eÔ@2~LqaLqA*&\mJQH|=*T cزeBF e˖!!M!b2U]q"=EBEuK 78oq]B+al/RkFP+ԯ_Oll ,82(JمbhTAz%_E c*8exy쐛|n l6fnWwS@N:$eW˜1 +2]*" F}KZ& / (==]ل05Vvvx9 yZ 'B)P 2eQi+-wa39o9 !233 `jۭn ֐ fXQcDsSڃ>p3'կ_2PCBn[ PM!!LQ%#(*SYALyXQCUFd5>@ϯ&P;8O";OQV6h)TVp\TD1aj/[*Q~~\.<cFL&Y,l6Lr0 Wg>ֳ'UwSpJQU2*"*jUi1<rrr$*"W@NNFcY+c)4l).|$42B1ESԹƒ2o[8EJII$XQ)±cǪ ǎ?cǎsխ[7թSGNի'O20II1{?~д/6 ǣ'OV6իW1cVk InI.I^u?\ B_OSpAJQé &&ŝ:Uiĉ:v.b7Nv=0?66V֕W^{N6lЋ/_~ڵk'xмcjڵ3gիWi :#)jZIrrr4{l}w_p8ԺukvmKԯ_?9 ~/"*am3>>XxnVvے1eMJBƇTKj۶mjܸ!L47NǏW iӦ?t-\P6lx^=ZӧOWzz:ux[Su*E)+Z~~\o~3ТEԴiS^z-I6lPVVN/_.IZ~|>z_UO<:믿^}n/O@(?&MhϞ=3fƌ]vaÆ8qbۗ73tz JuT:qN:]jΜ9g,ԩznɓ;詧*4Zzv &HJŋ_~Q={u)..NyyyZzZ㏒ݻHv=0aRRg*8Svvv1B5h@W۷o|?PW\q{Bϛsfi>}֯_/\VZޓlּy /h֬YܹziӦϕ+W*>>^Z| kJz={vSNL7(h;?))I&MѣgI9眣^zI 6,׶Q1U&_yQM6[*mF]vJMM T4ؿX*]uUZxrԢE 5iD[lպuԧOuA6lҏ?ƍX|hٲe Ϙ5HVZEnݺi̙2e(٬oV'O,S~1۷*v%׫ݻHRtVtjƍzZj .]h͚5r\Zv6m-Z .P||.\w7no^c9fVkb)t DݾƓ} ?mTx0ST"YpR(зo_ZJ}zU|}G>}D|PNZsz WVJJOk׮?nMGtRݻW.}ZbF%I#?OZb6mڤ{, (%%E]wx_>&\)]]tQ 'b+WN<1ch߾}b߯'xB&M*:ܶ) PcW-[Ծ}4i$9ΰ륗^/dիT9tkڴiS`z-d4k.%$$s \,$Ɖd6թS 3f̐[R믿^mڴvZ?ϟ_k֬㕜!C=˥[V0ho#jS˖-% c˖-+T]U0N̻+ŢnݺkSN S ))I>>S델 BPƍ 0&MҤIԬY3߿_ƍ+׶Q;*bj}ZFƍGyDK,Ѿ}t뭷[nUVVVZ9sh[z)K[o… YR~}]}/EǕ'|RԮ];խ[W۷oWΝeeպukڵKv]:tRYVM2E<~au!UhӦM駟ɓ'®\R_͛+'''nMZR=|rvmJNNVjjVr:uRBBnݪK.$XJKK {n?>\-8{Aw;@eX=Ѯ]ԺukM81P2i$7Nv*qە rQ61DM{9ٳG/CP֭5n85jԨLs~})))I?>5iD|2 2KK. ףGڵK]veIII1c>s}ׁ۵k{LC hftM7iJKKhTvtM7+M'NԛoKjٲej߾|A5oO:uo]@4"vzy#p}Æ aYp9#Ghٲe ^6Ǐjڴi`ʕ+Xgg1֭$%$$ .6@4kR3 *X~BWZ%Iر9IҊ+ -'K/ڏk ĉjѢ1P駟}ꩧ4hР4͝;W[lÇe24hP:a]s5$ϧ5k9z>3;vL;vP۶m7>ƍ5gرC'NP˖-կ_?|2L^>޽{u+77Wv]?ԪU jW_}۷k:|򗿔+cCiŮ߽{w |Bl߾=0MJJN8>LwO*b %_l1cFmo޼Y7o֒%K4uBaSi1c(77W4~xB zdm۶MK,$}Ǻ[fiӦnVrrrrrw^nbOLLTrrҴo>A IDAT R1@ ջwB;w JG]vZz8뮻N]vU.]4p@%&&FԆ͜9S1=zƍQ_~Zt<V^-Fzꂀ&''GO?ty.Kt2/lڵ/%IFQ< T1@-aJرc/kRjjRSS5uT 8Pcƌb)v:tPlla_^NS /\_|%Iv]:u s:IV wJ|UVz2 _(/,ըQ#:t.\Q+VP^^ϟƍ뮻*vfYݺuӢEEIZhFkذڴi;wj1a.BajUFt!9}7ņA]>]wݥ?8PUK/iĉE >pkܸnfKAo?P$ xz#v7={J=[\rrj駟.jgj$-X@+64RrssթS'5jHIj޼yDѣB{{WrFҥKvӧի_U˗/ן]>T˖-N>yduQw^DJ 8 y<͟?_Ϙ_^=xmQFjѢ+IX,ҥ˵kN JOO$5iD͚5;c֭[GՔ)St:5w\͝;mw+ >\˖-˕cgR* ]#ǫ^z]v^@*!lPiƠi BC;弟 FQ[l ܾ袋z6Hʔ W4_i׋2"JԠAIR UXqI`|>%%%aÆx<1P|>nFTk@!"tM_`0Tw @hB2 >Wс f9|%t`0.$A PM `<\.^^o5hhbd*|%A P +^ .n[NSFQv]EF$1'+˥\jl691@ a^%Iu֕ec0d2d2#ۭLnl6r1@@ a^<rrrdT^=BT(٬zf)''GG^1 jķ> anj1!ѨgςnTU1NSvU"&&Fv]NPU ET sݒjn&?nwBE R1@% Ot*>>W||N<O1@ HA5hb&,dXd4x U1@zr\r8%.vZ 2DC᷋("&!&(t1,2,2YeSW:udh2f}Q1M.F:|f-12md4h+n} 6B֭lp8rajPCcG=C ׃^ .n nt:W~ wl%%%d2ڵkիW/9`` G>G?ALFͧp.~$Zz:$}>Qjdg?|?BucbZ{uڥ 2x<:vbcce2gPPVkLII_J ]]W+h/̴E{TU.^WFc/{.|&cXvx~1̽-HX9UfE19bWm'-uW>W>W99zh t/np6)Ϯ_9RbSr:b)a &_`-*.#(n[vMgd˕ ] !No]}@0  PHp7WôiJmI T6tcG? Rsff(jUNSfI Fo:pH!/9%_i>81@+tM۞f\ThbU|$.$$[ Jӊ)ƨ6Xe6v:v:q;vf1cU\CV%0bWD(@T&5L\B==9t_yb޻Gzb|M \)t h ~bgyMcU8ȒVB]{u,#Gy8^&bb,rOKc0Wj i{%r4unzz&ۭBB=|$)L JEN0FY&O).Ss(NL.r23GTNəT 5!msIN^_3pV jզ8IR~N:%D+l\NYbb+ak[pz鐴%OA]|iDqSLllquH lvN FY,9]_x޸U;Оç:]C,CD}@$MmgΚ@%g!LFJR$kWsd*//_2Ydd1K>\'OVNӕ1B^`#Gw4Ǝ[[n駟MeU}9SRR4b͙3G^R[|V9Nt;vL/ /\]w&Lro;}l%1@X9b%)s22+>&Yve$FYl1)\co3>_IYx'{C5`畼n9x|o;$Iެ ,fy͕nszodj1˗댊y_~)4o՚5kI&Z|A+$INȑ#ս{w=3ƍX6p z M3Lnu|zꩧ}vs=JNNjՁ~zH . HA\XBrssuwVw"Vկ8L뗅߯ /PSLQBB9u)..w*KED6_Cr\Z8wYbbcպSgmZ$&5ſZ f'P8V>) {J\}fj\<. 6h([Dl)/[FIFW>U>O>Y2U~N;YדdIK.ڵk5hРB n6L֭Sjjj 5kڷoz*\-*]VBD_ڹs̙#ޤI]z饁󟫩 ^qqq딙3fԨ _S_ew֭:pfϞkd* z5eӒEsU.{NCk].Y66\[ԠYs׾5lWmKڿu:ꫤʹyby]{MЁ۔ze^dj2ʕi3(kTX:F.颤r<ϻVzfd0h])Qj]dUXwyG3gT~~$_$ofW_}uJg}VO?RSS mhZw?Yfd2)''G7p3~ U]F.{SY։}&AP0F Ptwj߳+6VΟ'׫s?_:uQΩSFIRIrԩK+e ]r5۶h_Ǡ!2LjթV~ou0Pa0H f(?)˥c2ղicu|Ř J۱S+V-_ݛy\|k/mp:2O a.QFiҥ1bq-]TK.UV4w\-]4H?;GmÆ %Iyyy:~l6qG ҇~X;[xx -[L~m[W\k֬Bc׬[N{Zhf͚W^뮻NO?[=O=w}W:u* 222tȑ+kѢE0a̙D5Jyyy?A髯ҫ}wwiҤI5kV^\NOOԩSC>S5f\. 5h@ z:IOO[oG}T}7o#Gj4i>8p@o$bw"Uy׵|rM<;x4gm۶﫡ᑾKTu"iG@mA?~6/[,I8~L':Yl6e?d;~QVzNPoUeC}ꔲNT^vv b25WNș# f4߄*oRJK.aѢEڷo "I:z$iʔ)6lϟGyDӦM_6'իWw}'+\>c;Vj֬ƎX.Sffv)ݻwKV\`>|ZnM;P&MyB䓈#eIJLL9眣{キLi{<7 <>=.rz wx<1c|I]x:s4p^z3f:tp1y]~̂w"Qܱ*붭Vf̘oYgֵ^cjR ]Iǯ(=-Ǝw}WK,#:}_ }/]*{Se~ >Oޠ t6\M۞!ӃMFF`S|r{2[Ը~9:j4ߤb:765;=Cv).>^CtW;Б_*I]sj(A߯! |TBJlo>|X&MҝwީƍKE5=_|Q=%)PYs%(%%Eԯ_? :T~mD'Q 8-_LrNf(++G.sL.rpgUjp"]+ϗ-C?izx o_|~[YYYz뭷;H_^%飏>Ҙ1c4tP_Y/L:ubK.pyi۶mݻW#F(sOt\zzE~I*߳gOL&5o\}'|kFV.]o]=ܣF. m(p8ԤI-^8ajlZ,{FQSNڵk_Wnꫯtj_~ Epw?n;HY_ BYfX ң>ySOJh" 0@%zLhǴH_,TIǨ$n[f>}hZ`|Rk+QAH\R򾯖,~](~u)7TǭtIÇqℲN˙l^e<mNw[NOױq|>N64m\еiӦzںukS>fE)%cUܶ#e2ԠAl6.R)޽{'衇ҤI~iB A PMwl Y;]q bl4}jd(?'[d,&b.R^ _ ^٩XOT*%%&ȕǎxF<0*vjz5a޽[/|>_`0ւcw뭷j޼y;w9^+%Ru:kV/6oެcǎ^+~ƍդI &..N]tє)S$&&*++K~N: HAeE ݻnܹssN/Z`"b믿jZnN8^{M^WSFF͛#G(//Ok׮nWBB^zI[lщ'+(111Е$GѲԩСC:xR YqtԩBr'HqǦYfF_]+WÇe˖2FҴit̊{_ЫW/ڵktuU~M{UNNRSS#6:!!A,5lPׯסCt]ܱ*iE=6mQUw֬dBH0 eQ*jAn*jSX._m?bjU[⎢ bAxN$ȾN{̒F$$ףs.gι3"ʕ+ٱc8q_|C uD19u7vm3rHVZhT3s.sLtV?Ή'l˩ {w{e !DwIBp1OTRr"|VI:j;8"a tzk(ͯ1xz:Yt n P-(i(OdSS0t(*,dU4\USYQnuM޳_a&h[2aV\ /_Wy:7k׮eڵMσ/HOH̙3y饗,Y<{loX)*11{w{iӦ1{#}SU'xwy7 /`6ILL?9 ,hQF1zhz)HMMl6S]]͛ygeС׿on`L<3s.sLtV?kRpOCCC`_yy9x<s*߻=KgOH{J;Ԡ}mSms.kYSf?P]]HoK~C0[2l|*3hjᬫ0b*z1od6c4Bͣ @4gS]^7+w];&bDE`OuE[čN;KY5}યf#hA[o#cot=WYY ,`֭gUӤڟ3gO8q_=6m:߳rmOy3*=ADnn.fL&S Skj^ TifF6Aσ'hʾm/)#y@QUT(-O M?rLEQX2J5T?1H!"6Ŋj2q"bz1xSi2Oqjk͘& 2l').bؗWƂ-+fr /¥hWBo233ξRWQP^sI!B{H!&5b=NuyYyYG1s@П G)(U5LhFY~34i|u∮ jap|0,¬DXC4)baVyԺ4 *jqk.0B!B b8e(-:ֱ6NVvfecN݊ݤ`S>0g}p"P 5‰D58VѼRUvcRT3؊lfxp Ϥ:NfV6uEbv3v8pbASԦS$@#B^&&&W_}!A$#yy6Lv_%I1y1fs2׊J]m-`7m*f#<҄b%2"I;(/%2ĆqD0b{gϷZ +GQ E%B&MtF}ם!D%![J ( @J$񡪁lL&xuk *LjP~lf TH7 uu G݆c5qeهq(a g1 m6.\Ȑ!Crssy7[nذa,^vO8oMJJ W^yj}v222p:re8/##oA3g^{#G2yNk=ݛBvAQQaY mJ^{5ϟE]9}eeer-/..f,Y@UU>|8ӦMlD !BFsϟOtt4۶m?6d:h'|BZZf͢O?7r5״'|}HNIf(A__qaß~o'NHHH+Cf5y/ѣ̞=DNN7^lRSS4h111̜9ر3f0l0?>4:ôi:thמr-=(9s&999hݻwrׯ_mZ 8Z-L&, vCJ^^iY!B?׺Ba4UC}f(0LвՊ;nf*v Ʉ?P 0["ooQ _,!aCChxl8_Oxkgõ^{ٳcb2nӿ ѣGINN_f Gȑ#ٳ߿xZZ$??Epf ґ񆆆RRRxЀ PVVFEEE@dbdgg3f̘mY$rrr(--%**+OUUC aܹ Ț5k7ncǎ }:QQQ"K#::V۬端b֬Y,[h֬Y`ѢEx8nfٶm3f`ٲe 8>ls%??ӱ>F:ԎbaĉMbjjjb„ ^sq;FVV6l ##)S刄B!DO%!z~QM*1wu$o _j66L&o4&`߁\}|y3_^ܙٻg;fٌUfӒ<޽{6lmvd$&&:'Olrܹs'<<3gdjϘ1c?~<;eqÁPb0 ȑ#GڼFQ$0p>B7n$77[on ?Oi-r|v ~ޞH,Kt yf"##9rd`_UU>}:vڳlL0;wt:9x _|qO8￟￟{3gk.j\B!g1B6AAOR+_dZ40P >%vyy/8ye \N,6; :tk:a`Xŗc4f3]$$$Z-ű+Um=eAeee,p88x wyg pB֭[ǎ;+ tt:Nklu r1n&qlڴ͛7o*އ~vmӧv'N?>3jeĉ9r&B!D !z].M?OER5*HXQ|A7on d;A$itxj;ߙw\(BhhStz?4+8#1{i7ǏsM75Yz^x< Bmm- (,,<p7umV" Lׯdffadee1uNGg{㗑W_}ŵ^"3o<͛dߚ5k4i)W e̘1޽nT&!Bq~IBMk( z(ެ@PhrxupEf3aP[[G!CXfUg(-4&w%]0 UUhhhzbi4QLMB***5j);rM0 4(0=l6STTɓ'q:l߾ût۷/QQQ_Bjkk`߾}:10ydnJvv6l޼UU]-;; 60oܤmdvٳlyfjkk8p K.=؝L&:mƺuhhh **K/JQǏ0 nJ]]>'J,lذ0\;eM)Mn˞={سgO~qWB!Tg#)lS5MͶ~F\g9N!:_oCuuu_NYc`bVBR, ۻ0kb0 oLP\1=bA7 tM`X⪫!IRr$]ɋۍjvSZZAzz'S1yy@5yf7Yq5$Bq>%""ٌd KzoBj^ T? @omm׋FbUd4Ϣx) 9@QpXVf3Eh h:nq| A.+T$F!BqA@給5]5_ t-o 7d/EQאe48|4VRL&IEQ4qlj:q{p\Ma@!B!z9 tD_@C!h$0tFՉ{;|)J kн핗Wi& Mp8*,([󥾾@]}Xoo:5h_lg~C]4 Cuu5 !B \nn.f :JSȅ VRO76iV302v[:muɈB!B!8G$#B!BqH F!B!@B!B!9"!B!BsD1B!B!bB!B!sww@ QyyywwA!B!D7@ && B!.puuu!.H25I!B!@B!B!9"!B!BsD1B!B!bB!B! !B!B#B!B!8G!D\ Ne8k1 ӥEFDT,V#B!]F1B@'%'z}0 jpo`cB!LMv]0A`aP(n!B!D@=m. !B!z? ]0~؅B!bB!B!)+yLQӾ0 t]GUqX]ׂ{a-^ϟ&.B!B b8Oiƺ>"*'N2n0xPxƌ4B!B /w֯_Ϯ]HLL$666p_Omm-Zg}w} Ұl6==UU9ri:18S}ir. ۍ,|RUUfCU&4ɂBw\~M+ۭ+ߘ8OF;ɾ M'  Q#|{ FAiR'k|bv߾1 <(/9i Ll ,YǸnrrrX~=<#̛7/\.~1j(VZETTڵ.͙;1珗^z\~wwWB!z< !z DGGʀxǙ:u*s=gCeՁbbbzd0L^C!BHj!z[n0زe g{n~E|յhG4(((8^K2g~Cf͚M7ݻͯ~y.I&Oq饗ns%&MbҤIwhlܸ{wYfcǎVczz:/2?Z#;ҏS]ߖz^|n7O>$^{-]v]wׯori<,Z'xkNwGK/˙5kz+_5k?Ϩh~lٲ뮻Yfq=pĉ۶m#==ӧs5ꫯK/a.{N7B!bjez'//W_}3ϴ8oر >;3W^y|_g…DGGPQQϏcyƎ˃>f̙ܹ3P믿?k.̠A:gaǎe]ŋ_1k,^|E&L8{94_򗤤pw8nX2d+W?wo~3W_O tf;_W~ӟ;@zz:'OO/i~ ƍ#&&e˖QVVɓ'6m=]j۶mG?p ;>}i> ŋGJJ vնXbcǎm1wߜaO~{tR4h444vZVXAJJ C aŊk;ߧKp[G&&&[o;*4nTyQM6;n7ӧO'22dF}T!1BJJ.22SVVeL|1UII 3fh?'']׹;x<jkk[m'44tn&lիYl?՝˩j lB]]\|ܹ.[Oɗ_~IRR;v8>9x ~_qZ?~)S~;~~{aÆ&G{n diZ:@}~/ik aÆyOINNnl63gf͚ҥKٴi_~9'xiYfW_1}tL& \~]+"&NQs}^r%_|q`:ٌ[Lq'Oy7,,\__ϲe˘3g?0`˗/G4t]G&w{}9OjbZ|".2ywfKg +++0 ;<4Ҹ[(..&.λl}pmB!艤F\v14ƍ͸)(,bW;PXz5$' #22ǃӇZ6;ϜA-7b2Jvv6+W{cڴi̞=/x'O~Bdd$̓&vrFѣyꩧ#55Xټy3>, :{,P窫b֭p SOϖB!DOٓ;Ҟv>5h_lg~Ckx<Sڒ{,,_Y'VafnXr-7\="#"xl*6nLH#Rسw5KxX8zKJq\جV.d2_-?Rtb {dW۩w2fH.YR}ͷi$#F^+'R\RÏ\1I\>k:'b˭„Wʦ\IÂS7^QwQRZ?{~x'vfxrRO-#DdBѦurx뭷ذa˗/nbtwׄ=Ԉ9"R.Χ}C;p̝ͥ'cZQ&w߁&lجVqםȯɑ#Y{fM|e+}"#(ZǏ/}YnBrkjjXj_}BHFIUwqP&Ǎf`RV q8\zdݽEWGQUtݻdn#$$ !xMHaWGeU%fNq ѕyYbqqq,Y뮻%A\~JeeǎchV6#S. !Bt]?/`tMݶ{ȿB\feL00xu ON"4$ٌfFn׏B!DvwWb(~c-.;FQOBUΊgL0{i:ݭB!Bb[[whB7|jk0V* cDpL&#R8ٍB!"""O~µ^]Bcڷx99,>f6EL7/~Ehh(Q}pTVvGB!zUUꪫx뭷Q$8$#$7/-d$/0xr1 CQ]]Ço '2sw!Bɓ#<"+& qUmkOOHLL`M*2)Z!=ܹs+B@2b@ɧ0Xƛ1x`B*J[~`7mapm… 2dHXnn.of 6ŋ'xIII+쒾CaÆ/_NxxYtk9ɓ'wJ{ioܺo>222(//GUU Œ3h͏>2nVzj,YdfzWSUpδi0?Bq!{yBL(ͷ47,q(7^=O_ %$' cw𝡰uWwB}qƒY IDAT4,z'MĉlٲMӨkq|РA}-+1mgff2`;_].C ꫯnHNN&..kO{V2NJLL ޽ovKKK&))ű;wz1cQTTƍ1 Yf8B!D%SJKdwd>AeРxn&L&AuKJxg4 ]yyuoaĉ⸢(l&.64G2{lL&999]:rXV,KߙtM6CvZ{ȸ̙CBB$&&pBJJJm]jpddb`:t(uhB!DoBtNg}lTًjehBO>{22 |95yr>ڰ 0˭ ONne9sk=kرc`߿?Æ ѣ$''YѣGsa?p<--ϢEHHH8Q6rݧ=߼Y$rrr(--%**+OUUC aܹ`ך5k7ncǎ Lut܆arO?e޼y Y,&N$+,&L5Ǐرcdeea2222eJS!B^#D/3O ٌ(}v|=3l@Fm^p` /#y5GΕ{2lذ6 ҟҥKHLLDuNcO9-o„ |Ǯ]?<ʪLw}ιC TH$*8FF5h2L}^&jN:$&1$%DAb(;a2Rɺ={[Գȑ#IR{<֭njM7M7 7y7:B!&!ޞ F[9Q57t6l黾8-^L&Z-/&5kְ`{Nk}@++9ضmZc =zeLw#Cj>CoڳaN>d/.=fV\_W = 1cPWWGɄB!A& qp4mmJEUk{t͹o1fvm|}ݲb1ݼyR,Yr%SLey|>=Ccc#}~X-VRjK,s] ЙN;3uβ,#Gdܹ\y=LS#B!^(p;q.Fx䓻 y8c9:ϛ7Ç7kaɒ% j0x`fϞĉIR 63fF޽B圣Yfm6R\veA˹GAgt_|m۶y={K.SNwYzo͛Ǽy:墋.%B!>Nv8m۶ϊ0 _Sv8iiirKvʸݶwT?>?:0M6?~IB! TTTyƘ?pBM-@ĻtmNw~qvo98_v$#Fž׿'$B!G4Y5I!B!C"!B!BLMB+8MB!B|HFQ.ǟd.'Dkk.GXɲe+xW^ %KٲUи0\fy8?@B!!1B|n; S|Yl9#GGur9J}ٸqTqXvלyil+V>RW!BqԒ@ATgƣҋ?ͭv9ߘpCǠXb֮]n ~7lOzձb* BUU7 ҧw/𴦱W_|>_.<<;.8\>u>ӟx k%;-[X`woB!BG& ʖ&?y Ew/jnÏpi,^8N>ikױrjzfɒa^z4S[nc#;[d8vؐ]3sɳ"DQĶm:rTWyI6B!~wާwڷcwlݶ~Va#`%I8>}{yo͚?46o¢wSOa;k-/m'<իװq&X[ϛs籵y5Xk98|3)//aCy{ eee446ҷOZZ[9失,yw)k֊֚eWd(/+X,bKS_Ϩ#;5cH !B <֥_J쯉B]-@ĻtmNw~qvo9hQ@С $#BqI FtGc F& !B!B|H$#B!B!@]ќz4]!Bq@]P:s.|4]!Bq@]PEU23D)EEU !B!8d$#DG&[~TdRdGJ!B!!!,Hޫp7C!B!A$1B!B! !B!B|H$#B!B!@B!B!ćD1B!B! !B!B|H$#B!B!w8555&!B!8 $#aPSSs B!r\p7ALMB!B!H F!B!C"!B!BbB!B!>$B!B!H F!B!C"!B!Bw سy|_[Ec(x>i/I~ NC ȦhFk}1$A+E6h$QJ$ ݪ*"KÈ0p(h2,G$Tv˲iK3-VM0 &%8AJ3zD5W^r,UB!BCF1BtAY_8QJ)`x6v-=tUQByR>:SƐ 㡕"BI) ehH>O*Ȥ|T` 8$I<!#bK"qb4(,M"\9 8EX@bQ#I0:̣/B!d=?{%Ε-84hCP(|`hAPxCc Sx8 ΂ ϘKqNx4])[&b.qF1J|֠(h)!M9t{&LG8gQ RX1X,/iEt)Pc,#xFqx"KS0&Hg|ضW4JcUIKB!Bq@]R mtH21&` 1e||XIi"6T"&IU(@cQaEkU.dLa1=:ZSYYȑ#IRxPÇ?$>kL.o0 Yp!w}p7}g>W]4% vm^]s-BqI F.YWvT*Q#$}iK`h U%]VN@AYƣ,kL澯J9JKdkYz5#FGƏ'?y@n@1j|'ɐd?g?Y{ݬ-w3}B!7$DTVk֖Tķ=RZZ>IS#.)|c|VG6KzS"-2E떘P٭h  ݲկPz5Ux) 2 Ci _9\3Yrh t{$ZxZxٽ_@NEG? /:~SNaʔ)DQw_駟%\SO=Λ7뮻 &0eΝqlԩ<3xL0W_}umӔx.I&qM7vN6uvLw9k|ѢE~黜 /p%0anV^ݩ9w6oܸw<~M:{w/_fbԩq|ӟ'?I1k-կK8q._SN;TTV.r~p?B!>j$#DX1XM:Ԏ%4A+ ^F~p-/|O}S<O߾};yԧ>wVZqϭ[2}t#0j(nf(8[na„ ?OT>x wwyTWWw5δ:3^fynVMFMM _;joJ)jkkg͚7y$#DPxxibaKQّ!y3,3Sܣ#2&,:7'D#3Lc?)_bAkEճ 90Gt& B-IjG*g|b3e('QbH$( bk-ZsD/qF2LǾ{g"N[[>(7gϞ\̜9s5;}cz ՙO2ѣGSWWǭފRg}S}Xʕ+馛-:> IDAT׿ΓO>I>|~UUU//5߳=Cy晻S;s}\.ǭ1B!8H! BһFwLQ |u$+-SĠ |4*6x5)RFk5XlVTVhzWQ(Vis2Cm߆>Z)VQVfġ_˯%Q%Tqa3/+ku1Z& q"2b=bGUW]Ŕ)S/VZ5\RPǴ<̝;޳5T2.6N8էFr\Ǘ=kvjSgtO:3^2`] AѣYb :pgSL(̛xǸ뮻;v,&Lǁg{ȸq93PB!U JK֡U)CIbIPcIWZ:2d|2lC9pV"(t(,EGn>BBbbK#'s^Ř8uO1JЁvę4M&BЭ$I1EIY Ε2֖Ol:^|Ŏ/At|Rݙ ~u֩{y*&Mu]1õ^1dov|Y=PОAٕpvswxώ>}Y6+cΜ9~lgZk~ӟ2gqo2n8.0ڿ>ݾN:+W^ !#S肬MB5{qSShmۃ/JkTJ3rjjCuGe!e(:czyѥ㾢iKB+졇⡇eرc~FKK 3f'? mmmߟ;c7Mo|q)㎩R\p/g;v,?gjwW_}5[կ:UZ!18lOoǎ;6m{O!:eGÎz qB>}y]IIP)?EK뀬途PPMQU(!0UtрQ(/SӐɔ ze+ƔW(kGbց ItL螤)co|[ qBm mGeҖ!,iڸ$ Z98&ז#"8(MX䥿Ӈ !B\'=\]QCC+sa֝G-@ĻtmNw~qvo9"SʲYI`c.s8kQ&R! ΁k@j*+K?SAKs lPKQv*U O)ZZaK0bh"d,gWUC2Y(?z&9%bUv}\~ńBj88ӑAbҔ z>q'oq!oYq%ZS4"QtmyGk:SUoiE7ʨmlH4@V'+"Ͷno : =0"ך%GX,ǂ(@9ΕmԁB!֬Y÷mƍ׾5 v$8$#D(PƠmE58+՜Th1``^}!yT+RXR$PlhM[--Qۣb`҂eTg*¨k1WBM\TKpaBFGH8P,&$IDzm$+B#믿?y;<կRSSs$8 dj]!)B\"$σ|1Kƫf떈} .H%M-7X֮ҽx :V584;*jPE~L:cDO6ace?li%[@c't>a$aDX( D"A8 )FE8"B$!I›$.B!Gk-O>$\r ?J:G U)_ W@i'xC Xޠֱbin&䋖Bh'ce*#,BqFj>jqC˅߻gOH Ak[ŶMe f9m9(BROr9"_l(( yB!c}!ćL& )㡼Z4 e6dmJSD%bGeM0*Ĭ_>FyGc12~e³(؄*cmM (7=5S~9V*Y878B-ɧWB@>QRKX$"RaD!ǥXHB!&!" )q.FTOp[R@LY8" 6z?]ŰQCʻ)QL&URe$e26$)%)ޚټzmIBa SV,AS !0"Q{ 0,bR&-=89#/B񡨨k/>MB|$#DW8 F*jE3tZ u)0"JE;*Xz6DbCOIR83YPTĕ^ށy&>jZZm8UQIQSucsk M XZ&T,[#)MErk(k<3X)Rcp2OZ!G0('!"pJ2.ʓ(Mxp ƟV @4#gY<G: Ew82Ɯ&Vykvá48qX9a-Z;N_ņ9-JK.C:"qL7lK6uA6~GaX% rP@*DIiymB!8$N:$1t!a$!"$xѽR$EKRh/ w\R;W/ iHӶ $᥿ɵ : hjrYYʨ)D 9V-ϓXG3 я\lm[3Z+\b#KSl,1B!8׏n>p7EH F.&1ZiV$q-b)ύ  KĈǰqCUiF /㳗֓lYiq[#oJTd=2͜׊8J*Ǜs8(ǂ!~['̥냵A양6`I)iZr8gIqb\"!B|tA@S/wӄ]bHWcE;M"g#2e%p_ZF<[3{ 2^ifӧXHX:G>N>>}@by,}Euc'gͰic+/P,:2ceRwf@-Ll+Iʤ27\G1oIoza3_fN1e .!I$.ՇUB6yd~i=\:w]blspAXccЀnTX"&KP,W,ywÆvc=X"R(gq^Lpڄ mLܝ\[on =^9//76 .H,[b^yy9+>< Ἳt!*+/?1֭޼W9 Q[G]9ǂ 5kw}}9B_|kJMM z*}W^G Zxp444?aɓHW^ywyB@98.귿-Ç礓N:hܗ8ҥKq1`&Nwy7|f4i{O?͖-[?7n3 }f^t!Cpi-B!.7|37|n (Ddms8aJ=/g,M̘W&)"[ e a` :"-m  TTzQ b(i;XƋ/o`҄M[A"}1_ SN{yf/_ΠAsl{N3$I6l3 BKp$$I$Ip8*+h+82bh W[͹L1d5̜@kёGiOdc q ʔo%JR _b?,bH,Kc8]jij.R# ?4b#5m4?xF|ZM6QYYɤI}lnn&ɐN;QWWNJ+e˖-lݺu1!C|rF{5 1lѣG3wݻ~ !B#b芜xXrD(iOQ C+;;#׿<=Ws< ʀYZr$qkπlLXkY!ǟ^>ӏ AY^}c3ڍy"6(xkaO>>ٛshҫGI+Gu[;w9*++yy'kvNŎiii!ɼ'#khO|g̘1̞=#ʲe˸Yr%ZҥKYlW\qB!8I F.9Eز?(?Zжn4'>WӸS#S׫B[xyZTa'[A&#Q h+lݞ牧QVØ]M|z04-嶻qޝIU{kM@1D5%F'QFb1&љ$fqD%7$n( A MK-w9GUM7l zܽνUUs 'rg_{} ʼg)R ĄqH CYc<.kCe; z-8GmX4550}tC|Izn0 ywZ#FLue?p6 ~tE?qa׆] azs3)f/ HeaaEIH\>=, C 0/Պ#"8 bpn\,Æ +.d,Xn w%Ltz IDATһwo°Xf,\U %c l]n磏>ʀw}}E̛7ќ׿ȩy̜9nɓ'w)(܅ܛL=H&ӕ6}znEx3L :-PN5jg7SOpѣG AyxqGB'bc j!0 R^@)OJM䜥"LQKb\yT'! p> 0"b],|T'1l70"80aǸ8&-6ݒ5СCY`A vk }N>V3r9nF.]ʐ!Chjj_/XlSNY}Ν˨Qm?NՍ!ݬe˖qQGAjjj?~8Xhp@ǫd=W_O_d22@*K"@ba$ "s$qՍ46(w8RΒ=lKU!c&0IbbAdq^ 9<π|@!X\+UC\R'xx$! .que)>/W^ 6>~ .UpB B"`,^~ꫯ;f;f˖-ck۷/zbڴi|԰xb^uƎ۶̝;}ңGVX>_Ø1cx_~.?&cǎewŮ}o-^%Kpn3+bDFq#FL ,.B b|I 8cQ?JL^!&U=!ЄqHS(X+0hN\|>sqlb-vE2S\6[)4Fcp>ZΖ2d_ט5k3f̠O>{~^`Am ٳ9CI >~&ɓ7z;w.[3i$^|Ey zbر9rms>YfEg񭆪s=q1c ,;z=&ܹs;wny~1qD a-""""봩;wx˼ޚ77Omm `FHRw_Σ(/y>@'? ՟*0a )\bO[( Al] l16F\1"[4W\Y+~q`ccKL)+5NklϨX|~)#5؂틳k0*̕8I*v w!7֬*f$pUD6}IVBaM10 +e`L1SD'rb uQ\NO1EpGY~8j`3˙5""""""G Ĉl`C\X<&0g Eò98$A܄ITaRUظ8,-P(QUBx)Dy\.C:^Ķ8Y/Q+Xa0'LP~xQֆMY Ĉl0pI..7>xIV[\\pL.ń8\@o?d%. 1%29<ȮDp~) W 0q+qT*[|<0"LG(Ɩ ;<U^\S Fd+yiLz(q &Z ^~#ׂ*fDk?σt/S,6B^;+[HVᰘ(_ >R!?SݓDu \ Gp/6&1)1QAlI1rⱬ#Q1d"I!+|N)#K"oҸdOp9`ePo6·Oaq; حK ,҈F&)f86*- [mLIҸ800+֗8.jW Qag"?K"""""j "S*x۱#݋\㻸x 5}<,W +2(,Տ/Ԃ\qT#kkqO0zR=#-9lЀ s0ukP#ln%q~5.l\؈|q3cr qGL9[BDDDd+g(HR F4cu ?Le&r?-| b 5C~4~ )E"q6ڨXA[hV7qP ͹ҼŹ6L fqq|1'QU:&B0,a! B$κbץ8"#q8$d*}.I􈈈l8nTD1"Ak= &5 @,K@hi ,&l1 e!kP˯KB1&pxŌk{6΀T%c g(O$Z [ p." R1\#EY*+=6q}R{n{@WD"AE_-[""""2LRUUyL?|2&-( 4a-ds囈HWtiȴNF@H7j/Ӭ90S6lu`Z`hxQF)#cs2$ #""""m5c(Vk~QDDDD6JGE(# o!#""""!(#̎Ao""""sA@7EՅME1"[Y|~H7Q FDDDDDDD(#"""""""M& ĈtbDDDDDDDD1""""""""Dn@H7Q FDDDDDDD(#"""""""M& ĈtbDDDDDDDD1""""""""Dn@H7Q FDDDDDDD(#"""""""M& Ĉ|<;cOvik-gfXk7{?6ĖnȶSNaѢE]~w[oݠzꪫ8׿Aik޼y|[`ذaycֹσ>o~sym|V(#FswiwzSZh>:?Gmm-o馈6H1"[ɓk[n!jjj8SZn6&M[oM7!aV̴iXlf:c#FpM7m戈6F-[k]wETWWo~sn-(-g[ko}ADDDDDdkI"|8ZOt-&2bD>yg _G}4SL>>\y\|L8n{シ,_ӧ#FϦ_~]jKYb-駟/k 3k֬tRn6,X~Ȁ>|8gqCmmy\vm̙3wyrQGq'Gocǎ>z]_@gԩS[/X |r~v̙39&>c..#駟fҥ|vaZ1O=|dN:@̴iӸ+e< ^x!_ﭷb„ -Zk˗swƏyy8;fL&÷mN6> "2LۑH$8ӹywY19n6>h~!կT*π9s&/A0uTg1&L.RUCiSN9|#F}u]A=\_(#use1 .4PBiDI&W=G}4r ˖-oCe,Z8S }o.qp'r9s_*??x'ֲ` ooQFli |F?^{U^|y,Yc9 /{ jO"N(gzqoзorY--jw޻Q]+"""""bD>'zm%pAyL>c=_am8c۷/1Ɣ4 e˖A)fV\٥vTTT}Y+"""""bDQ `ԩTj>㵆-/k;rg1"۸rI'_n|޼ytc=>}O?N6lk֬oӻd2 욶AMzEQW_}5\QF1` =\y;AM\|żkٓ#Gv7 ^}U~_2o< Ƽy6mF'p/| ̟?e˖q50ajjj1"۠8yGihhhqҬwޜx|>>x2e wZ|V˥^#;|g?஻⮻⠃bԩ}l(# ^xErJ 01cp'3`nmӏc: ~-ZD}}=;#G3ΠfϑGI"஻ߧ7ɱEDDDDD:cL'-y-5ooL'_̍W*(#uP Fd k+ڮ怋1|R-O-LDŽakm|xy$I|oi Ȗ@2EAyLd2yz.sa% CrBT*E"X+ ϙ"[1"ݬmZKPgϞ$zYȆ1>SQQAE444Et)#ehjn2c%c,t޽{+#""""T"wޤi,qcUMB-HDI{A|>Ouu5[y""""9VYYy466?{6wWfHRFH7kA@&QFDDDDEEEL ZeňHR F45EQ@UUnlK?FQTuQV Ĉlfm8&jjjXDDDDdUSSCqj1"CnҜ 1L&tDDDDdL&<8[eňHP FYk ÐN3gwc>yKx~yoqKo~O'S$+2dz$ӣɊ hO;IG2 IDAT{x/t3-n^iKx *KT%3T܎㎟+[m]ee%a*#l]9d2Ų~Dk )%퍔D߾}}̙øqrk4c|0^fcS|o0`|D!/Zʴ>4mVA"L霝cV9KVTdxṧ{ѝ_\`qrJ}<[R@Q]KIib-oXOeem;)#F87k-i']cZ~bZla)e$)$TUWRUUI",e8\iWv,Zr,_ڈcusЅ9wm�4g|1ATt*IE*Ml-|@"ᓮHa< 28D -vz>""""l: Ĉt}ˮ K%)jT&O"ӪRa4'xTWULSQ"(T&KE>ՕRrh{: ȖI"1սze˘,}FO"bgީm NhJOXzTg:*R8U+bTg I("uii:Ԉ|n(#>cry./&'"hicɵ%vET |1v92?{O& ˆlC=>8KWձ&OjB~ w|̛/)W9(umψl HTTUPfYR^x~f>*Q ;9_N2 yH&/ixbK@HGCYR$TӯwiT:E*!]Ճt>ivl 6$ pABα}>V&My%"1"""""Q Fd+gx}3.0;~ 8~18t8REHTgd(s$SIiT/޿1śu:]4andZ^'ݔZGbUߗtE|~d?gYlq*\|rMkj @eeȇbk7[+{キmfSf?Ccaڴi]o]7骫K.lZ(#Fd+LHWVAil}}){a͞qpek8˓Sɐ`#\%]q>CcqqP e2xoKWԓJ&p`??|Go_? ⥗^׿5҇3<38i'hC<1^x HR[ʥ^ʜ9s8묳8Ð~-ݴl9_̂ wIR,YTTTlƖo0 9uY :Eq 7pI'qM7ѿVl 9LӓO>/̽KUUUy÷h1|wy{՗A~mnzwuuuy睤iرc93kKoͯ=$I2 L8OO xyvs4=oϜ' C4?87dGؓoς`}믱yw}aތY"PճKٳZݯlc66xǼϧ;Czd]wfܘK{=;bGm.J7x#~;Bw N:$4ӦMgϞY_?g魎yknXrey>n K/DϞ=җĸqx[}>8C9s&g\x^q_O . 7wVA0w?<+V`6G}tWW뮻'xv?<\s ;seڴiۭiW9iѣտls+ƼNn?Nr#zjFŅ^ȠA:={w)+c׿ҧON.wy'?ʁfT}{wy6kcæ>ZbÆ p}Ww_VZŞ{șgɗeWk/fTVVNc;oRI&qEڦs{yw۷/GqsN]SSCCCQquѿveuogŶ7og;},'pguV)nPDdk1"{qjlGUza4j45kx]_*{` 3vqz|h0oO<}v5Y=_>Uѱ] 0 Y/޻JofE4;|*|'_.+os׳smX^D+xznj32e \p3f`ƌN{̘1Tb=_}Uv}|.Wts _~9}O>$GqR՟{9Buuus/ #Fࢋ.*os5/2uTnrZq3a?0j(~߰tR..]Zf?ٳgf/2>l ;C L>gŌ9M& 2+c9|]^]w/W]u_=uuuj^~s9gkڕ11Я_?zVۻr}6w~]ύl뤾+Vzuo䩧+;$JqYgu3k֬cO>]:vGO1bDGs㴱\:/ /siӦ3ˤs;xG/~QGE޽[#c~0| \}ַőGɬY袋ݮs .`ĉL6Srauxp]v&MO>n Xo=Z߈388{n]vYZQsEDf Ĉ|}o<ԯZI}rGd:M4Ԯ*SV}uu4% CҙJ&x {|8455ۿ}uY@ˁHCCL}ߧw}Yf}dsk}1jk 6sg0h ͛붮 :I޽袋9rZ״+[cBd{_6z}6wֵFg6ub V47xc/ ;3/wޙs饗Tt=ۜwyPˏ]{*++ѣG9nS7usY=cxݶs[ngwy䑭39r1kSOR6u'|B|уaÆ/}dY~m(u%/~Q2v+Ŗ:L&K />^xzl)$Ö?]ZJ߮ ЋRwn"1m_|9W\qgy&5>s==ܳ<=f ;8,XON&aw,g5xZ D&!Ͳd8nuujv>k-ַ*h(̞=]vم3fpW3|yvi'f͚\}R/ ~yWihh`ɒ%k(.ҥ]&mk4_Ӯd]z}}љ }4w9Xxq9o~O>JSz|(juL&ȑ#y|uuuL:/:\cٲe꫼kviA@ccc9lc_:t\Zj{.Ҷ;C=ԩSg}9-]l6?kƓO>^fnn1 4SN9N8Kun_w}2e ƍcfo[<~;g}6?0'NZ˿ۿquج+#FőGyǥ^ڥ-""[1"[wew.ϲ Пi8 %( |t76rIҼ;A3wS\cR&(^$}5U|47m YDkpil&jÐ~}2ߘOsp1p Ӫ@O馛;_*(}?Gn뮻oZԅӟE]ĤI .`})/;@MM ˗//`2eJ|m)OR[[K.v7 CXk 6t՝w٪VK&Lॗ^}vqG9~>h/_ѣ[ӫW/K/ᗥ\.7M;02`otEtM醶+#ul5sc}:iOee% /sRTt2kZKǭߖۮ]yޭxl)=={gǠAK qa7u깴o%ƍj7XRee%'tO=sY+9566B~HGg̘1OZb'N?qoc]s<̙3Gy .}ݗSug}_7'"K,a_k}.voDs&NWjFq;ȖI"[B6K>KA>OA-#Xckki\SGϓojYKRЦmKPjιR'md9v<4a6*a0 1/7nG&0o =.{_&}GƎCfu΁-)!0>}:SL /dL>C?ӧӳgO~ӟ//B|̚5pi+>X/<>^sd @XBQQкWֵ{˵߇˯J֭ںQomz.E岉@ȾL֙9sd&$$B罹9sΙ9Yy^m#FH?#9D裏~oʕXŪU]ðaXfw&33ŋw̙3Yf K,:3ٴiӦM7M.\Hiiiݸq#uuư7QF͛)//ڻ;>ьޓZ_w?Gz}=<:9M7/{^k͆ 3fLִl2>}zu칯j.\gO׿nkp8|~?}<ג<-Phsau:k:t(Zk6n幇 ƒ> /X:CڦL ?뮻 NbO~G?yH$徉{[!$ qrӸi+fbfq߷GLN'E  BamE؊AyAkCέd:rT+9!8.?XSUT74a)khut'UI/'Lk͏~#oOS1҂1Z~򓟰zj*++O{9s&E1`}2i$-Z9222KO멪@ ܹsy'X|9P^^Ά }̐!Co~b>azƏ7M^ymƮ]Β%KϧW_}z}H*MI܃R6yw=Is=p?c}ӓ_vm׿̙ôiӸ֭[)++gѢEddd9sx衇ذa555,X|O577rw%[ 7@vv6ַXt)YK0@e|ߥ[nGljFDQXhVs95׏Yf1|innfɒ%ZM>|;G?Zӓ{DYf {鴡_ Z[[Yj6?syٺu+HJEӫߋ5<7x#se<>c]իWsi~|ڵ?ߒ%K[; +G& eg`H0PSEb/TSJ1xʶmY`>ϻ;=9^1d}˞ݟC{#ICC>˲xyxwygr]wqpws]wǙ6mOL>[o%" xqƱ`u?!?0z+Xq1v}FtM|gs=<֞n]]L$\r%,[k)SPOڴ,Z'|&F;̛7oX,'… ?)H$ߋo#^z%ZZZ75\Å^[Fo#3wܔw$4%c2m555YG7ߌeY="/[zap>q6+i[ǏvǁyBH_5xH$Lt%? N 'Qym--Bi67c BX FM: %M 0vpbQbmm<ۉx?Y}/q'M8#`f?Z7G8^x2#ROuuֿV-8]b~ q0 Yl8fϞC=eѾ`_]]Mff!s_to:u*+WW U4TWw xo& Bc,+C $WݶP9- 6+3D]i=MQ=MG"1B!B/0 q4'+hͱ69W,[ 8rۊ4 ƒF E6&zvhknfӎ=l-"84R$B!@GNtIl3HhctףJ;#3v8k9.;@v&VZ:`۲3n$fZsijibOehln#-)EC!Bc-B,GOqXGzB `MPΆ1MKs: ϧ6B@i֚ږ(kvaGe#UMFR&UHF!Ǯh4ʆ m~;7n$ѵ !, q420g0cIY0( 8gl33{jݼTR^DcSV^϶lN| h2!{^!BA?8cƌa۶mm۶QXX/~#6!đ%IB̾OU}b3ҤI0ɯ%3VA#3u$0m{y՟ N 3TAmZ&|RĞF- #LEsk!B>(//_W~lݺB~i'8$#Q-K{WJ䨌ˬXgbXH=]( :wz@]}qhmR[!@tEJ9[ϒB!Uyyy}:#G`ƍ;w뭷~jov ~??˲b̘1̘1WB!PeЎ_$e}1gc@Nks#hP06-EdeҚqgj2V#f(l>ֻvbɒ%8CsswC)L~~>UUU|'|﹋4h;v ?ޡCLx9h+V>x .^[ѣ)((u{/̨Q ٳ h4ʰaøR ]wUU۷ogԨQbŊN8q"guP^^λヒ1>s\B@2[=!㫅8V)}X‰ǽ@ (FY6>1`173EY^Đ a'1܂! t.ŻiD;q™32?H{Kү_?F̈́ND%1\$2tZɓ'3w\;ٳg3|p(,,䢋..8[ne֬YضMqqzR>رc Fl޼ .<e]Ʈ]쵵Θ1#FݽaÆ1k,H~~>&L`>XP(D0L؟N.uuu yͶm p#F0i$JKKڅB!DwYW;L{] 8߄W%g$0Ic^7em,eaD&(C^ &_MVˌ<UPH:OzftqBiitשA%PGSSpW 8#GuVF/s'e***O>e6l@YY^z)Ç?Lr ׻vZN:$l vՑr 2;v0`}Α")..\=\g}FCCÆ /2'|2't|SYYINNgfȐ!qGެ_bXN:$6l@II_~ 3i$>nDdB!1L1B+DfWW`h/AJde@1A& `), ݲU#:Yx1/2&MK/Ûoɲe8<LJ~ȵ^KNN}o&r~6,_t&O̤Ib6mbgq幃 'OfŊ~ m۶qM7uٹs'ÇGk֭[ٶm]w!z!BqI FcmǽJ)S&1t$B%^H,2iv $1g6p7ndMM _<8q (,"`AfVuJ%eJRY`0%@VV~˒b۷o窫5w聆9fΜɎ;(**bĉL0||駌921q^^wfB)Lnn.'==O?2e EEE~~ &nݺ.ג8ǠA8Xnuuu%$5_d ӦM .`׮];o;aN(cƌ9dyݻZ/uڵk9ɡ>iӦu9X1e^{5rss=z4eeelڴ)IE(,,db1VXoܝӧ3f̘n4'JvMII 3f8ȫB!}b8fx;О٢'vk/ErRTfվoYR0" 0xw ugeLΝnCq(֚X4ꍿt%)[[lNࢋ." Yt) +:Ӽy.=z4+V`֬Y1n8-ZDSS檫:ބk2~N+8eee,_x6@cCQJʼnE0$ B!8 lG/Hdx/atAt"aKLQ~֌jjjq۶Q(ǡf1?\)KKKKRfY_B!B$#DtpO>q:ǽnْ"QҤ~J@i7N5D"Bk֚Z}gAw DQoYy*p IDAT^2ɽl:ymB!B>O1BA?{P%N$ OL{ʋa%AgF&Q㿅B)EkkƈFSЀljoEIh{/W~6p"=Ώ~^B!B7I F>hʔ),]9stI;Mѩ xSMbڃ0)@1hMQ'SW_NRr2cikmE'kOٞm \v)K~ڋwR!B!Sғn'ouHxxlwx9;~|)D$Zk!D8҄B!\II wgPuQB.ġ >@wxlӡ!frcL6MqWzmd!B!B&B!B!8L$#B!BqH F!B!0@B!B!a"!B!BD1B!B!IH/@/#!B!Gb8B!\ss^_HR$B!BqH F!B!0@B!B!a"!B!BD1B!B!bB!B! !B!B&#!D֯[/˖fR XB) 1 w;~ʲPXl,7j6J,xەOKܢb֜Ky0hth 0/9Ľǎ1(F5 8  ࠈ(00e;.B!b>t~ ZPJ1D cP(9(1X l` q 1 c, Xe1np'`Xݽ Yhx) q X( 1X E@cN׷3QCB!BCHJ~cG;8Z0 zhc0Fc]Ȑ1nP6fӞEc h~ Hj0/!0A ! OP)(BBʐ !As^6͋^tdnB!B?1pv0 QLڳ[vw1Fuq>2QYb^>"eow] mMH Ҍ"" v0O7{-B!b胚Z[0_A>7^ڸ3r4^n.t̊"qw7 X n ܒ8 QP8^Qļ:އJ !& 6ٖEk4zB!BZ#F>HyZqx=V߈6MX$c(o$0I}8r˓e4;1jk,\R~L"A%s_kϜ1Ijo#B! G 1^#^7Eym]2^P$8Qؖhw*a J"Jki2-CԉihΩ"f Fe!c1xA28͘hL<Aƛ(K%B2B!BqL@}jᮟa,,n ӏ7ŝ b3퓑˓@18qFIo{_&'ż1v˒, ˸2hQXoyB!BqL@}8((IRkcpwDu"Ek $7%^k]wr֚[(7 66 1yC楌mS8 .B^-K28lDa/e`ĺB!X$㫅B y9-Dn oz8q'-OH8F}c/2ڀ':w|'MeXX zmoL>+2>+Rs0so}^v8ꫯ^GQZ[[x?^{mJdEEEL:իW{#9nҥK;}?s :S!B IBA^"81`y+ڛT*0bk7!)75)0 ĤdBv4;ml&/+_OH({ڲ Kԙ:g< - |g Qnɕq3b$q~l^xfΜ}׮]^^ϾPS!BIF}noRnֈc֛kpf)75&EF Y0thޛ27Kz{)!Y\4m \4nO2x0:K,5cF}@k̛ܹ7>˗xǸKWO~lz>./.^|EZpʽw=ٳgswRVV֚+Yf֝snDZP'B!>? y=_~y x$x;HI Jy}ZUYϺ.%fH HA\L-x^)R"CV۲PZ8o~)sJKKYp!Mii)OWgС)Ν;[n .˗sP[[믿οkqI'q:p8_RTQQ|׿} },\7|ࢋ."//gyg?㥗^" |5'YWWSO=żyzt?B!bܿ2"3b:)X *G['ʐ19}`$7MlY55l-.gXƽ;ӈӏB+-K²UJkΜ9\y啌7p8LSS_뮻ϧ|;)d=b :n!C~z*++by0zhN<{~0i$ G^^~yٴiwy>͘1H$֭[7ؾ};˗/3{9[޽cj֭[ǦMx饗gs/}KpN4套^c̘1 :%KZ{礓NB!>#D_@N(1ʀ(Q0폵WdYnCzŲJj cFLLњ¡Yl.ԓ4`Xmb`)" "P2h@m֛h5Mv˓5qĔh[q:='o'D())9SrWsYg1gN?ZKGŲ,&NO6H:~a>y=++SO=+V0vX-[c=FQQK.eԨQ,_ᄏGKB!C򫬬,*^x>|M?vyM]477񔆹|ܹsӯgZOB!8I F>ȝ_QvU^װ/J<7ocpGQ+fXXhA۶S2cCc <_7_ɄA+tw^ mFhq'%` RJ[ 2L!BHi}1^hϋQQ'&&ʏ nF7?=k?8?xM@f aoe0ndi "Z'y3odffx^ƍ1oȝwI{3d~,&Ọ>0d͚5ٳ}=nga᝾ݽJ~5kϧf,YBss3̙3z 6PSS g=|=B!& 'AmQ㫕K1nY7QLiL"(HdѴ7,bE[Kgȁ\CuZO M('@Zn;75 '+ DQx/~;_omJ:o4,X޽{o{\ˤImf?_rkKXl\s SLtԯ_?N=._C~anVbƍcرdddpws]wǙ6mOtF?>GGD;C<'p99(vPI N`)( $1l= ~DzfH3RJQʈQ|iI<#dgfyɏW^xf`ݵA FUQ 1khܻh]o><^x}ENs+dΜ9Gz)B!8SN`ʕGz))JJJ&`۶3Mz) M"?:|6ޜ&1Iϓ?Hzmmwǻv IBAl1^IR|!0 GkȠu5%JLR/>{h qN9Z 3b X~+Dq Ulo=c,P `{ҸoB!BHi}N*7r/C"$J1xAwDBKԝQJacc!T0iS̘ dc()bxEyy7n߿HS_)gAyV-pꨉU"h3F톩amA)8B!Bq4@}6 O[d#[H7M ㎴m7;ƴK%&OMk3_ÇkNٶ"m%[md>~<⽂`6,E9qL(QRw#F$'g$:,$hT{VMGl4Ȥ9n^- v bʢ!n%dc)6&`C1*wnѶ:BFK [ֲrDj fSi e҈ M6XHiB!%!"Ȃ!FxNfI4u3fGeø~iDq!q{8^LvmhN i @CT`w4!Z5vč2F׊A lCV8(ë;(C0h0 d FTc갌B!BbôW6Zl1˅6nFf؄BjZi(棽QZb۲JC2,bb86M8h3dيduQC(lqJG6RHS`, x!'h+&,U'E7b[2M!G[nX,ƣ>oy|UsιK@ MvdGA+.ե2V~Su.ev馶NvLZm*jqCQ@ "[$,! Inqo k@ b=}\-|qw mH FH&1D$UBцLPpcrxsK=Z'fلax'ECGc"%f4Y *mŧ특>i cY ki\&UWF_~5M$#BϿx<κuSnvJ% =QOL?*GAdl|q=27 N(%3bBq㗿%Kݻ7O<]tж !:ճJzə1$Z~PɲӶ}CmϠ\ 2E]Bl9H ^46h,ϠI, (&Jf[ ۲hv5gy"X(4e}2`Yi ,, @&?YLn&X+yNB!GyyyT2L"Y (5[<Y7Naf[$NSL32bohCkH%`V"9Xmya|'3!;yĴ_capɃmK@CbR((8aU,.q7%JB!||ɎnӨ6 IDAT@I4*dN۲ 9Kƴ̊f(yɲ)N(j !Ѱ#qq7VObR)0h< ~`ˎE7(q=ۚ Ʊ"`+aQ$LO'-`cGGj;zB!ۘ1c뺥K!:/  ,1m~aY&y^r PK&#̳i!v4i&͞FD0)ch ZkR 1>eFrx>PqPFZ)"10"<3ytea9 + naߎ{܌1YEq%PRRxuu5}vC^:u*Gy0`^zqd穧bƌ߿ͱ׳|rѣӧO'33SO=ŠA;v 瑬[ymFy|&L@>}_o{n~iZz왺 ˲̤_~L4 ǑkB!'ZS2hH|IER*Q>ڲ̒D\5(,,1 !#/ !8|Aha\B6& 1 Cit;`[ ,xx^"XfW'fԸJ6$2l2>۾}; ,}"Aǵּ曌=SDxy7ꪫxﲲ2uƖ-[<}~76mbڴiضͶmێ}'ˊ+t죏>bӇ\f̘'¤I8묳Nu] @OkK.iӦQTTD~~>C B ձsNAl&9묳9r$;wzh֮]Kee%3gΤW^׳h"/26jjjmTɶm͛2dAkEm6Knn.\pUUU^SRR…^ v=3 6C^m6Cvv6ӧOu݃/ZfM> :kRQQZ~Dr6ȑ#YbQ HB!QɌ!:d T"/\Ӣ%JKi떃=1,CuS< 2mMH- %GYA(z.7fBC>Oa ;4jihl"a xR>lE2 H湉y#DNJtre:eee 0pA3|&MĬY޽;?J)\{l}&(朒<%K0zÞ.7oN*ݻ7Zkv /b2332e m 2ÇSXXxr\{rxSSPpC^ƅ^HQQ驱7n1dȐ#.iGnHOOgرD"ꎻ 2o[o˲LSSS ,`ԨQz뭜wy|lٲ1bUUU޽?!CrjkkyyWXn'~ !BӗbT2ZFRD~De *:M䄚\(eq'F|CQB[!AOn4ؤGk*,(XC'K5\= cJi,MRY>ePܐM-dy4haټy3@<\CٸqaQJѽ{6ݻ]s=jpyyNZZ!.xq~~>$R~}4^Ӟ~t҅B\z3iRJJJBߟ!CaÆ#; 1b,YB4eڵs9=ԨQs=s=qL2?C!B$G5FuK>-m#cNiyb !!lAa06}M0tk&sNU0Zdhw}ccfSv8dm[=X8ʢ5xϠY ;hO'Q`YhƱO~˗en&,貲2"/:vZ˲k1q]zwy3^}U,_2DQh`M{:Y߇b6V-\֭[zQFӯ_v FbƍTTT#$B!Dk,t"{d qm v2ٮR$_jYƴeZAJq>&&J# 70hX*SiLTu-MYFLM4|BUFEl7c+M< 7˲։?M(|"_ŋsW,c֭xm3777_;wRRRBSS|I*Y-@uuq QJqM7..;gy1cƤQcʢ,Cyy9'N<涝Lcطo_*𡔢wlذL}{3dVXcnOZZa2 !Bb1M dctČŧ h([ݚR)c aMQw#DQUB8 n44}{!аDѸG]Lk+ޮB!`'?~bmX(\4FYh_p, .'7S͛7o]v2eavs7nHfff /=z`ƍ8vbǎIhR;>, eY޶wޡw.K,AkJ 0zh^|Erss)--s 7=&L@~;yZTbuٱcL43R!B$#D'J7J&UbUƘq,1wmxsM9guEs)*Ơ659m[0p g .qqd&X:P ||,P~"ʲpMlQXhҝK]ꫯ0p@.͛1s綹vܸq}yްaC/dMF(b;455ѽ{w;acXp!H={vc ,ZzB=zkS3gGpB+Xi۔>+Wr5\qRZ!Bщ^ߞlgwO-BKKo=ϣ_՟O6T?l`2:Uֺ%/@fhr{V5l!n:`hr3w|ՒB)  RpK),K[he23$sҟ͉30lذ6KB!>***qĬeB*`pwoCvImjV[8C;ppN}#D'TSaPn&D9jR2QM(I-P$D(Q|f@Glk0YSYQﮦGr() MTkEݮ{7Qp5l(kshcX`>Pmkqu' OVW !B!8I FNh=9ļFTX!B!K1BtBRĐٌ蕆XeiIoHNA :I)n}Y=,^jxiMryoeH,.n}xi%j ټZSԭ;Я}(u569Hm\/ 7LهlKomKmV?+7+J,bH$­kL)ȶݍ` YiA҃6AGw5MCԣ&ND ۳מ5S_$3ښ7tU lJX>.͂^k[? A*v%AS0Y))f6yD]Œ*&}B!B+  9*QMؗusk{b66nfObe 0ݳ)BL-M㎍o1²mdh<_S#4 %jhO=8aY䰿6}ۤlt8L.^4dv 5՟{ctKwmb5M=FmtV ߏw !B!I#!:!K%Q+Ef=[Bе(1.d-ovs ۫#l⺚)qRx|\Wԛ*9&=hiv-b4֒րimه x&1>` d]Ѓ.%dwEf^ ;VڪL޸ 拃in5sVR1B!Bq,$#DH}`L.GiI.;js~1#@Lǹ#J@y1俖Ji@8 _\?:-hkg|, \Omca x&-`X1l¶j2džp[v;EX[+\ΠvZʫc9F& !B!N_kJ& F{tͶ de``Ʋ~ 9u/R (pl+5J)lBXJt,|PeI*1o |#q҃6!!î16l^kf}ؖr!B!8$#D'FSS*9%SIlY KBf \6c( ll쩋a%0  yY!FݍyؖCs$F,]|~Ͼdl ORl5l;YII,_{7h~1ə6MXȱ\H7:$I%37&?!s˘?>FB!BDmQ[Z;xmmYxO!ڥeɏ։B@Ϟ=;iB! WQQAVV`veB*`p${m}iM׭huV!}8'̈B!B!8E$#B!BqH F!B!@B!B!)"!B!BSD1B!B!bB!B!N ęh߾}!B!@1BtnB!8E"ng$Y$B!BqH F!B!@B!B!)"!B!BSD1B!B!bB!B!N !B!B"NG7@qhXMc:9'RpZY]!B!8i$#D'ES0i$Da^B!BdiPC]iCC]MG7C!B!N  E: LB!'!:3q6L3B!BӟbB!B!NI+R꘯1ƠƲZ VZiycA2{P6"B!8I F)ye}{ÆamQL*v$;;{Oތ>g$ =#۲پcǍgߖqVvVdꊙdee+˲+KqcYv=Eݺ2n^7gϝ \}ضC'B!F&T) IDAT 9+f}Gcnj&-- F9#G86J)ƍM.]Q\̐gS_ֱ| [m#33Fc\  Xv-]^FkCAa3۴ך0] Yj57mKA>9xǐgs3}K!B!: 9X~=R=h{eۜlWIvV=п\<`0}ٳw/y!=-={ҳgL>?"׉H ګy{{ڵXj5 'yTB!s@S[p٥|3Zk0uyڽՙ뚣DQ EcDQ\e萳)۰ 6l(x!ѿʪTc`}Խ]%33/^uyj،1.]hnn>%c#B!bo`¸lۺH#//W@)ΦgPXP@ĬP}gӦr\ \vضͲWp)URBFz:&Mϔo,x96D)ڕ[Fb8kٶm;iii'm<̙O6>&OOx7:(_׹1cD߾};ƍcGw{B!ā$YS]ѥ6ի^%=|zy#G kz킂|d'op޹['ڵk3g5jTp~_SQQ> :k;}-[2gɦ旿%w}wcg}^J޽Yj7nAO䘜,'N;,Z?ӧ/]>iB!1BNee%sK;`„ }}0$33F׿u,YBSS m牴xb|gԩ:Q0;ØL8F6lؐڷe8W\q~.]ĉB!8^2#FqZٿ?OUW]_֚w+2boQ\\ ɥ SNelܸ^z|UVK/QUUŘ1cg'|_[|_oNuyy7b{、k+L>)++G|[bȑp8L   .>(>w令ٳgs饗}+sy7ܹsYr%?O?~|{[\x(駟_Çy+W^cm՞u<ɑt1袋x'hjj"##vhϼ=ϸ=գGzŲeRw/^̹<-caҥ\kB!N#8mDQ^ wq_W'?=`n u㗿%w/"zb֬Yر'w>lvʌ3Rɣ6lXp@{Ƥ=m:Ԙ)ѣG1<3oOyM8?5hUV1~xBǏO-OZd cǎŶm8@9B!8di={kXc֗mtA;wte\x̙3jxΝx&YiZZÆ c֭}H$B~r sPpmۆ֚[o5 oi݆b^{-wWn /$ 믳b HU 6?Ç?=(tڵyΝD"|hߡgcr6n̊hll !BL4y6oނ :aCSUE?B)WQڷYR999Dœ?e2n۶)۸y'~_L|[?Xbֺ ?0[n ==+G]}CK|rJ8fG壏>}vծǣ뮻Gyϧ?[oAr) q9s&?Yf {377;wb9aׯUUUǜMƏcmF$a^ztuILryK/ۣ}n9sٞUWS,XٳO{饗;<+B3,M4F]YYC?zy'?2T /fM&0]`Y/ #G #ywbw#<}] bAA?0gϦo߾\|?97<裏e(GsepB/1zh~9կ~P(C=Duu5|_>bb̝;?Op u]<=ݟ@hԩ?owGz/]{5_C[MAA4p+W}BCc] Ȥ9={㄂A&e?9%3w'ײp47G2x\Ϝ2gtyff͚믿I"},wtS\|vmut,_꿤g`08h|;ۦ6^qZ3wᜰĘ$@ ]W?M?={ ٳQ#XBN9u7SԵ+YY*濷IS_W //9#s?Yz+K M!q(7۷/~{G7Sn瞣#gI&d|K_:D'bDgp&b:~4?v]Y.Ƅc (]ֶUio׮]B!B ]r*oqc9p]\uݍ` q8{/Æ ftZP Bt }<_}*Bbf!NSeqv]Ç _i_@a\c {:&y euPنUBM%4q;pͬD+..nCեKOBq;v6cƌKB$#i`MX~׬c\>,xwöm|k>[n!\}N+??8}B!|( TAN\Y\!B3֚W_}{ٳgs7vBSG |3 Ȩ1KЯ/i8eY8zP] ٽ{OG7]!В?楗^!N1 qQ<쟈.-YR~bYܜYYYZQ#b'&z!B!<@gM`b0gŗuA6%##a`^Z[!LŽW_MBb 쟞۶1x>=aCyb͡#-BqZ,.n TG7IqI F3HJ|M1S]c ֗Qk 7Rgdgx.By7vX?_*& qIBA sT_Ib ݻ#mZqcFV !B|Ջ; /"dFg]vx13H=z(,lQ@ !B( [<BH F3ē#xCRƦ/577YwFeeIoB!͕W^Iff&^{-/7t@%Ddig͆QJqˬxy޳'o` esQ\L(|Vw@oÚ5kXh\r %%%~z/_N]]=z`dff۷o^`\z'&SYYIcc#L0>}9zA1vv#Yn;hٳ=c_o{n~iZz왺,QA̲,233ׯ&MqZLvswt3iQ3_x,nΛ4o{of| 5TVV=HA.ӛ(ϝ!9۷`|'򜲲2}]f̘A^^-c֬YG\VVFnزe u/ڑHgy}rJU3Xҵkr㔔p;\2l(*P*Zd7)żz[޼jijVX.TZh l aVax|9ۼsF3uh;1T\\ltԩ޼G]G0`T*nܸ={@ 99Qu$""""ƮID.?M7}cyB OMH$TJYjE7ob;+RVOq9+52.##HIID"1̑#G0`DGGÇJҵl0FR ???>|?NZ8lB . ;;b۷GRRڶmk1 8z.DVVLb4z*"##Vqedeeaĉ#61D. bN}b7莺-wRݏܼzf Э]T#<==JB@vv6ƏZFnn.t=ĕ+W=ztUHKKC~0|p\v wF6mݠJ$WךW^D߾}u&˦F}ӧQVVf"qqq/n߾~ [nɓmվ1gϞ8vǏ#..~[naO]O""""r]365Q#B <_ PYYB[F^/~U/t1eo>v2h!mtl! !!!t AYYnZHwp1xzz-t"""ၮ]"../^lp} 4ז:жm[xxx]v9r$t5GL&ùsp=\>113g̙31}t 8ǏGVVzQ g#>ngxjdpss̍o݁E7Cݑ@&Af0^Nyy9gff+W=rssqYj% tW"FrTVV]N:ED+ @4-,Mk,kmH$B۶mQQQXoILLիW믿K.VVHLLDhh(&#""""bCjhPzAAwZ9kܼ|$&ԍ[^~6oiݒR>>C& IDAT3]" ,,LF,ƍͅL&o\ntԫW/:u NBee%.^ .gϞ oKOii)ZPVKj* * 2 .]B^^]DDDD\8X/Qk FMܺUKkil"sQYRR'AXjJr"nnnZvݓ~:cl۶ p]wᡇ騮Fxx8Əon>/^sqs8z(  55UUU 1M{Qp#00cƌRߖ@Byy9<<<׹;5OO:6Ehi׮F[XYoOM3|hk_k YO"hrT*QQQa"/JӞ2y".k/<¤qFt. M X HQ)Ƅ#2ݽ? *Jj{|Mf\E Qka;5vN""""""ri#0!"""""""j&DD'Ntta̹(}[ ]3'Ude]1Zod^R\E^~n\'q48Je#HDDDDD:"JeXR<=y>{lvC~A!㍐˻D&<<b1<=%(..FP` n@{qZ.<%P)U]QPlf  <,Q w;Ǹż ҡ?2p]]!): /d:^7lz ߸0JNbvxyIg 777Hkd Ǒc!JD5Fb#7lBu?kyx|]y3KJpy,^>>{DDDDDDîID-PeU%JJK1a8qdD!4=1xl(l-2/Рo/(՜k ŋ!+p00!&M­[QT TOOv %%HpWL:.bפlDDDDdj5^~f+9'1h }ݎ.F=Y###ӦM̙3P(p47'|rlٲ7ovڙ]3_nݺw߅?pqgP(0}td2L6 QQQªU0qD|gvtU]n%҃"^´EPㅝKB1$ "P`dOTW&@fTc[ZЮE}zxxãδݻwc+P(믿bʕÇ#992 {/x /.V5}tG*ݻq1lڴ ޺gdd駟F۶mbѢE(..6Ĵsݔ#//6lH$ 6oތ2_^oRXXg}+WV""jzDJd\Ai8q. ŞC1;! x ^p)#xaCp*].O>ARR6oތK")) IIIĉzeff5jˤP(|r(h0yd>|mڴAll, zlذǏǐ!Cte]v->s޽ŋÇ[HJJ[Kйsgks}rJ<#9r$.]Zg{-,=X`Aik֬Yt0vX$''cvn^JJ ك3f 99GMbiX1ni?&c8l1j*uBTY UVVb„ O[|}}QQQR+V 88]v5[nkusǐY:L 9T*jjjPXXhrcʔ)~pwwK/;wDDDA d9GT|QO}6"trG 0n7DHf^xc„ ={6ӑN:aӦMHOOG6mꭷuV 6 ~~~v/"""tRl޼<x )++G}_~~-"##\,Y֭C^^V\[F~~>֮] oٳ1zhܹ˖-Ð!Cl.'|_~K,iӦAPbƌo_|>˗?FYYV ]v,w<vٿ}P.X`6mڄzHIIԩS1|p9r׿,5%~Ν;Z+p!,[ VT*3ܾ!T*7l0>]t?5k֠JܼyFOLLF{Zu 3p5^/,c)=]=[ 4V"A"Z#1BQ1^o}u ?^4#7iԳ v¸qǏΝ;#,, SLAhh(Ξ=s"663K^3gb֬Y!Jm6֕ؼy}giZ:>j˖-0`@$HRݾ*--Eff&~V&MBBBBCC1o<ݻW1c`ܸqZqka}ÆSd!Icf9hd݀񻥺.\j,Xfm\c}_C7www^'NĆ 0j(̝;W7 ~~~(..DD.%sGvӑ* PR{3~NШܛJ<싇z}l%̾aͶ(++O?'OyyyX#w ^DGG뺖@Vcԩz)J]6ѻwoL0 1cзo_ʜRYg<DݻիF DjCRY_Y[A" 22RKtϞ=w^w}Guu\X3i$ؾ};-[^zY"//쾳o- _'}{=q#-- uE rnZ=̖c<::ڦt6j۶-P[okqKuٱc޽[7X--.d2֬Yӧcǎ=z4j5^y̞=!!!uֱt14rF9޼I&!-- 6l3 k׮ڱO }I[nœO>it{9990a{o#J3`Ȑ!xѡC3-///L8 222,1 쾳o-M~^z ǰa.o2diX1n1l8l1@>| Rݬ9g郫W믿 Jlw˾}xbƬYT*![ҹnzϐM.k'V'1d$''cػw/O6mĉqsBCC1|(J$$$@("22e'""ג&r^WCD8yƊ5msR "ۋ+ĥ|*sgg=0a̙3iiiHKKCTTkչ0=~8_#F^ǎqSjrrrYVV3fSNŋqƍ3$$ؿz7xWQ`NDDĉijΝC.],^oÇn:;vLػw/B!222Zϛ܎m C}f킠m7Yڷ^+Wĉu.oݺ,kF3gZYivq[asacz k׮E~~]f9E/uGԲE۪o߾X|9-Z W^jXscT̝wK}m05H$BvtN0k׮յV ƪUw^O}+֭[Q^^ #44%>GJ7p9v;v2 H$&ƕd(//Ę1c[oܹs(--ҥK[,F7|gϞEqq1{:̙3k׮5Tkݺu3 HMME6m0x`,^999FZZeTq }v"?? .|f79R\.\.۷ :[w[:>L^x0`~ms= 06l؀Ǐj5l9 5}=&MO=6oތ,\v v3fv}///uthӦMüy󐛛kwСC˨͛7uRSS ֜p Y*Δopi,]GAAA]O?ϟ׍3ydgpMSƎcKAQ+Ԣ5Ic>P.ʋJ?%ErOIsiՈMswNn:v-b͚5vޞ={}vxzz"%%ET=** 3fUӔg}Xt)_@<ӈEEERSSb TUUcǎXx| :Ӵ}+P*ׯ>3g/_Ã>voP׍`T 4k֬9scٲexP(] ?0OW^xwO<0 >٤Ih"dgg͛yf=&&K.ՍmaGyE} ۺ\5l0̚5ޭuX;GBBUw1dij1ni?2u6裏_Ƈ~PN:aرF׷T7s猾)S̙33g.\hwˌ3㥗^B@ݱvZ7DEEE֞!K};Sl=P{Н֭>ct y֬YUV?Fnn.0h ;~Fdžcs{g! g9(Dά\{\k'0?]BeDE__\[#Hd5/>J令i(yU{R [>-3R|0ØڳRٜx&<X6bWo]GB %gPV®#xwkrD#ȭm!|Qs)))#nӘ d=!C[o<1׮]Ä w:-bZ:ԜJJJI.F;c]: X H } jRF#"go;,|FdNFf 4>쇈vwN-;`㱹wʰ|K~U? #ycGf gdxqi`9ECۊ[8yCidff"<<M  v)Ͼ}зo_ aJZHBy{ xBO{8T/vG' C!S_TU254~! DKyr1իW^z+Vɓ],`}+H{-`سgƍgm9a"""\!ц/ ajRm!Ģ_Y^l~=ͻGC)# &=m\AE2>Bjح~uTTT`՘;w.ƳƱ @@@]ʖ/ZMwKc)2Ę2-J]l<<TԽ;C0 j\3V{v r}Ű gDDD.U\;e1!E?ٓ>TJR|+vn`kױ]&Fs%vܛJco*3Q-ks!ySwc`6L}X#$-n`وZvb!{Z֕b>xr-{=1zRjt[Ř31kv#`/,Z W!UV am-𫐈@{mn冶qDNa[46QO3]BqɥG{Rƥ|E/ʐ#njԎm~` zRjUR T;-hGcA4Q8$:۾pM\hUhԩHIIAiinZii)&O{Ρe#rz%#0]qTcҦvKPkvZboFu!!C}y/O'W vJ]WaĿ CoO!۝闌j H.ܹsx饗t^z%?rܡe#r*ԻhX1f  d"$]ƞk?Dtի4QYDz{ hG*T w<^P5h<~=^u?W۶ĸRgkk%F;o^FDDDK.MBTTyY^k/,W.j3c) R:-b,X'J;|DCL} N\R#WxP_ğ hgdP^Oj~X5\ٹ"DDDD-S@@>#tE7-** VBPPCF R Yh[x1e˖ݓȕ=K{ļեPmۈQT[tϐF,`dz˩@q A~w>j`%7K\+"66O-iٲekԘn7dl[:3\I7k>W.Xڙ3g%j" >#êУr (*A); 7Bpݞ˻DA=:e#K%A (DDDDvkbڵ ttq™3go( Ս]k5v!vtѢk4I_q_+!qbP~9!?/+O |%5\޽ޱc&&&@V{}6"T ssb KY53/ڇv4::zӉ'_h4";yh7+ <e0_[ޜj yg1~^!¼Rqeh4DEEa]v | bbb0rf+9֥֭ѵkWcݺuѧOmJ~ .\L&CPPQg9L D]o?JJJSO}Ϣ"lذ?8udffB!|||ХKw}'""""2N Μ9HTb@֖5vrZ(z]C#ϵ2Twg̙3{Bd<l~={#fէ}z%~1W0DVƠ*50P(5l/ƨ'O{3ϐ7nd2ۇ=233ѡC\rJ).m? ʍԹsg}oƈ# Hpر< ظq#:u#F ?Vqq1ѩSz=jt=z`PTqFd;֘\ [WUUcɒ%|F{mnl8/“9> 4U k[ Oxm]̽^x?OuŋV*cbb "S H\d,r0m |}}/y)mQXջj獳9(cP̯/Uz+%)gDWV>>EΝqaDEEHWTHKKÈ#p" 7[riaaav-DD=K>t;vĥK+ѣG1͜d2[nu敗pssCLLɁ' P<6Þ ""jH$P(@pI!J T*' ]lٲQFG1[QsiL@amhq! (hL%s[㻰 Q] C̍3cK kTf@^sZc)=56u=Zf AC1.kn:,7U+Ʈg0+eyβoZ"[CS 1ٍ3 MX5/5\v a&jL ]+klbz iM"8[xT岕 shHcCkC""""""՘4.{ثDDDDDDD={(NɃ{͖#!""""""jM+g8|fJH* """""""Zt 0\>DDDDDDDZ/""""""eX: _5ՕqFDDDDDDDΤU/H@DDDDDDZ]bi7DDDDDDD Cc64f Z\^IENDB`podcasts-25.2/screenshots/show_widget.png000066400000000000000000002175751500126606300206730ustar00rootroot00000000000000PNG  IHDRb6%sBIT|dtEXtSoftwaregnome-screenshot>%tEXtCreation TimeFr 19 Apr 2024 20:19:02Qn3 IDATxy\e={wV$ (̠(.#.8#ut!6 ?ppTPTdQDQCFBtz꽺x]9N]\]uyGa;"„qn<8L>_uPM`f $ @/B!^#ŬdfkH1[_Y̶b0\̖FVl0*pTJ$5r@0>m9LGxRL- <ѶUj1ZPRZ q#mdAJ̈́1>LGZzDS+Adh @y;@>`a&rj?\x_2+k;O53*mX/V1*L&L:Z]o2JՅ10L5JcP$`FØj Lh2ӑI oNbS0…0 `F|)Tj:W*es{M1LX&ŒT2Lti mFTL a'lcHx c!Tt3Vd&%Uy`M&bYt0!DSm0R'DYt@0 f!Lvf:CjDJCS}7)l.W*m5TȌUIZ cf*XL>URU3k˜jb” `Fڮ#mo;ڎuHh_1)gS*qƨxq ƳܨUZ4Zud˜t`Pƪ)HQwfZT*\ KVYTk3%I0cUxHDIBTL95YĔf!SK'_TR*`T]UWLx$}-K1g- K\(=}׶Eׅ0#&n7ϫ bZw8% RU*Nͅ0a [*QBT00ZT*) \T8"T03lV*&]|I\b nKTʄ W%JTҤJc'DWZs=eΜ9UԔW\B4QUZ&MIok0&70ek,yd2yOss$ r#=cT+f:Ҥr<(w7yW_}!0̌x<櫯\Dq[d3VR r&ZVX9S8:uS8^0EѢ* f4^1=d9)N߸Is}$A[Œ԰w2Dn bJuE.nҫތRd yb aOXKJ 5-7ZU+|Sa*A(J)(a8r]WHDXL[!JO忿E)4AHJ+#UhkvvP3 P"P**y PLFDBXLS dr͇}g/YIw[t-z\W]Z*pטZ0I2TooJJبx<>C(^\ SdUCމZ֓Z/~+HL8)۫D"1C;{qR+MG30Ǎ co|7鷿mL&H0'LN}O/dў7~{|e51cO6>*tttӅ^Xj5\={hݺk+ T SWAPWwSXQRJe*fR&;|txҴ_k*NWbLSN9Egu֌[VyWx@gu~_Mz?u))?E9*aʼeH#}ǟlfP51DK47lB:;;ukÆ :CtuMy}_y:䓵xb_^ڰaä}N~(+Ђ pB]~o~DuRVŌ;h-JjlTboJgg>0_4wJY'$]~:c%ILF't<?WJw_;Vsg裏֧>)HuUW駟?rx5LD-13@ga0 o}KvڥK .Ы^ayk.va:b vmzǵgNӅ^h4:l_+WԯkI?AIWU]V7xV\>[rN=Txto֣>o]6mRCC.]ZVsI{{ltg(롇RzަSN9E_h…?c9f &Ɍz!իWkʕkeW\ 6Y!:ֶ`"@UIcuD&dia뮻N7t<+_J=쳺KSO {zk_D"zb1=ZxN=TYkuwF}q?x≺ tA|cvm˵sN]~ڱc>l|ɪSSSN=ԁ|:'O</^tMz;ޡD"yiÆ +(`Ic->EiZrV\ 6h…ꪫq*W7Ӫ*b%\ 6hٲe6Ӈ@ڵKs=Pqg}V't]wڪ~:s5g=:f͚3uq@)355qF:::a/^ŋk۶m1mE3ibUkknF-[L?>f|tR)=NK~Z;gɒ%O+LUz?xJ|/7o>( Á)H!/~1РJOM{Wb JmٲE/֪Uj*-^X[nՕW^9mcv13fVuttoO}ӟEQ$-_\{o߮W~zg`)K_I*EH$&}7nԹ瞫믿^r~_I;0I/[nQ:֖-[O|B+WΝ;p~%#A]j֦UV ʶ`3 ALU 6_|ΝztUW-oy~/~nJI?7 ~utt(Le/{;mܸQ .Ԋ+tQG1P1355)z߯oq06x㍺m?I\R/nx3 UxݔNkUGG.bmذAGyc97S8MMRnn**6c&h4JI=I~uXp%5ݦlѮ?>nLT3hJt 7#tGiƥY9DRE)m7s+b7kdW\QцL&[m566LR1*blAeX1*|ڊj bJVĘP IJ$Sj\%da_T"QQ$PYjllel1`D"J$nT__/ufbjuԤzR)e2A0̮qG*(ͦƔsxP\ RE ! ƿ_j: f 10`}ǟA @U" ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !or1=OW )d>$e<)~l+dl13}DA `VpFf]3}Rf2;= LS$@sV3! 0˙wY3}LA 9?#L 0Y> PLST.f04@2C0oPb5t=1Ӈ`>jW>35 P?G`&>TA @TA @TA @TA @TA @TA @TA @TA @TA @TA @TA @x3}aM(de7dgƅ Tl"+)TCxOc.yKPjF^`$G{ץ_mO'$#'}sh+K5 5aoէncIZ+ZXvugfƅ T/]ԛuӽl6VɴףԧJf{ɰF T)Pou=O?V™:Xq U+~ݧޡތ e c/t3xc#U)ck۝ \o}oѩu[24U TcƤ:֔4‚|kdB=&Knأws=c@U!Y#wt)##Іz^lxCPMb@1uǏ'> TZ0 Aoޟө_cA H uzG &kL^}r*TƲT 0o'Z)GףD*UfJX'OwEtQq5;aF ch A +f0hfSZOM3ڴ3n 돹ix }V/z詤kJ]}Vo%V.=zUsctܲ/tɓaV1llAaeCySJ}sJlJjG{}S|Xm(Jgu|W:vy\yILB)YʄE&.ء[I@ھV'?L艵)u#vFd3Zm=DnN: zaQ[?LA 0f՛ѣI>ޒR&cs yeF-) }o:0Oo9EgNKp%9SF&J )z@w2=Rpp*RLn\1F#5yjKwԝzn{F֗0eoeqycoFVUZvTDJzlu?߫~g5W7ydde@g`Q.ҺmyO_AB;e`H@Va@i;<#Fu1^/pfs.C/L]|͟#m|jהVoNSj vcs!1fPkftMt766^n9^q*\lόsmNmŗ?E?{EMt)N@p=zab0YEzBpB_a6Q*m@#YHhN94Ҩc<+w~>]=1LdY8r/+׽^GuuڼS>諳ϗ؁P.%ˢ;Zuqujs:b*.x>@DWJɭr? ?S .*_<', 3R_wПJaI>ȇ&㨱q+:eqvPLg"Hw<ЭOڡa _̗$v>DIL`̆~cfSdZA}cOuPozu>rv[k ^TA c$0d{ {t=za_2eq(7qM:-h1rOԕ=Q[v;bF d+W>F\gӮ$|yYV5~ԥ?Wo"Lip o~Лu jnp\C  A LƗXgCHa?Ey^N=6.+oZzn{w=>9l$&}淚=a&*Cƕԟ Kwu}ӡT_ުҨM|=b`v{Čp?LxH/_ۧܣd:,(:5xwߨKHɺZ06 :8(+ߢNY?8O]sOk-yפXVMݝԭ~VDAŨ,*b,Wt͝ㆌȨ.עW)bmb>^%Js<}3 uQu2( l( V٥?KT6 Q2Oq\@O e̤Y U.$=~Z6H+pWs^פx,J<*u81 g jK2fpZTwS뒺^* ٦|+7}vmٛ`QGo~u\:O%KF^]o~bN:6.q\Z) خWՖFjA h7<=lR}6lKI1Fܨ/tV+#Je$s!cK;,.3 eџKwi hf [Ѓ-Q@aYRPq";Q_hͩdڶਾ/W7*F81 Pau >mܝRh$k{4t'MLܲ"#+YLhF*JgB%}tFJCeFɴdQ_?M 2Qr.]7?ϟ*j1L KmS*'SJÂF?ٯץ7ܢHU<(66TOᬭhOp-L}a*J>֧Js<בedG<"-хШ3n65 Zck uɗPV]SH,WB|5Pm1Yf8xvsnϻF?|WL~vwj7U-VWKYcΡyz1u: 2>c3Q:W%4mwta$;Xj@F:[5J sMeB#5K/ر/ iYcUmSkk|u.25ޞz1z1ds2+Y}uouw(}ΜOKd.*E@YEmͨ;Q9c, k@ _i-)%SC𢡊Z$녽 1zсښ,%B@r$;:r;zao:[SQLJޖL[ݒ7 Sb6+ F* :p+ķ mбVV-Sԉ)@@HvΎ@/GyV_~8“0!VR`tֲ#lɎ3FrtvJ?jձ+"}bm@BѨѲF_7CC(;j΢0VU[7kjD _/mϨSE{XݧFW-M]Ku3SfOSjA FK_R k1z٤>*-0[ĚlAy1O:"B@E}P?]vw*'ϟHI&lي+8OQ`i$&Ply]a{CY _vYDF~w}[VavD)[nz_~8™ B-{>ȕ2&*b%.It6 gt qt5(q`Z_ݓ _wuSo2=b})CD' GE J@s)~OCHk8߳3vwdov_'$(4!8PRƷ^}nuQqyN1 dkl Yf;Rzz}.):jnp)f#QCGOTn %oP*%:E=+W;ȏܶ?C@W"(n%t]=MFnmt+>DD*TƗҁ΄FLU*=N/PKKU 5 g%ݖ=/-zius7j{j;D\U }6W_ ٞgٮxqjl.YZ[cU؛ ! e)To ɕnn}{dvѦK*XL}^"a՛zRRO_[ԧxZmj@R @#Y+=W{Цc3uꆖX3KHtyshŻԗHljPWԫt?֠hԨprk՟ oHZ[w/!ד&4QcQC4:Oh8n[ק̰<}VfE<+#)zx2+=i=_m25 ZfSzڐT/r@kDG,uF,`R/?/auӏ2KN c_Z]=d䳜E}gCZ9lN-6H;}&çB=tξWS-t☎xQT/9$Wք2EMn&43v/;3)?6-.y[wf\cs;ߋ]ėDqp*?%es(ѤZF@ RԺԐI<ֺȸcl6E.wbV_AzF =ЕvUDo}]Tш'xx ko+Sڤ*sPPNdԝO%HzO-p#ttQiqgk4gout]ɇ0s]}V{z?1F-]hCNY߯T+A 5 f={l\ W.(FW+=G{rgluʄzs)#8< ?GRZ|F~fz3PqTD*ץtz҈zJN=^s))(+)t՛s!Dig ~48q-!뷚0MD@ 3}!TIF-(2j22\ѥ&/j՗VYю}FS1/6&/a-&IdY_|WOel~δ֪?艵ڐEuk3bIDu^qXxF2UZVoVYO=}0>y^;38m*,*! |Owvhɼ PVo򇭨yқ.)j2ժ ~ءD]|]vuCol5[gBSϻ7iA$/%_%bTEV䟓Hw{7ݯoҒRCwYuӑP[wڵ/Ԧi`8ZtF 3ڹ7s"Gq'~u[:PmbaAhiGjH9͎l|OghP&J`-hq+r`hbMz{igfH B1RCCGuԡ~sa{zziMk햴>kP~_!`BJ;.w:UwԝՓHJ$w3eGN|Zɕ#-멥UW`sPFe$fkq>10ߗ+ _3ڌqLA[O" t{?/J{WJ_{kC';( }nMZWѫWeb+JZ#Z GFu1-1VN/{W4^ߤXܑwkz~&|u UdPk6f4BB=a>{~?EVLedin- Pm(4qՇ Z[ӝ 7'2krTj3+5DmIN2vH@W}SzÉ },Gk6gdm0(`8rd]WSu̡1-^):6)3]~ovk-i٢vYַ~֣<ӯD2Thly m\Z<]zu IKb\cI2A$jA ޮ@vXhX< p˔7;5JGIuٻtJdVc8FGY'oms"rlR&fd5W`gtĒV,qu) Z-K>Ԗ a΋1:~yT}[",G-&GFf }VI)V_@@ $CCͮ\ L:*XտYo[ɌXk3٥rGYpx㨹ћ^[Ӣ)%3bۓNÑyPT>ЪcWDulw6͎v~$P;2am#cT*c1R`6n( C(u-#J@m"lG JO쾶9XUb1FMbQ }pRW0ZJMS Ect};"q$ϣZF@VGCEKlzz촔jX|?*3$,X0'k/j?I1xv uɍ=C:LWlTJO=vJ#5ƭJd`eP݉"בQO6VPB.* s?|Y˔zӬ}>ګTzh5:&Z6fǽ!.ٞѫ=[6MyWgw50~mL'먷ijldžWA@22j7F.:W@WOvg#|ņtLDF-luuZtۛ+ ttOd6>1FH;vgxe^mho/[ẈzzPbQJuE ;Pz~oW'ȟemv1Qf&ZT d{ڗUos#51} Vv˨Vhxa^xRl;X%Ҿz!ՌŢ6{j(&PHMCHҶ=R2Sx@nQvwtt17_S.7jmnլ;l{;}˝wqvu_9MLIL#tȂ`EWqXV]U[uw]{aWݵkEDBQi -@@*L~{gIH&紐13S|r4Ծw1!Pk)U6P`U2)A!Gp KU{V-_wQ,o*<3၎I #\DDd?LnIEhkO1=>1k¨Rio|I `t 2.kcs| *cRKǽWM IDATpŒ 6mlxӆe8b~YiN0=ђp2>P4t.\sp*UzJ D#5j P"""7)ُ-̝bf[fcW`%^ÁThK[>Ƀn'˷5Y>ڛ Q(D!Jc y:(N6m%lV19g駦S31g71}RP7 9a] ֟Q̙MDDdOr7#1#{jC<>C|XՅa`F CS$75|Mf2P)H5ٸnٙv0F=?͙v8w<<)RSYKݔ63 +}!Y[W@R*y]1HTލz  13s¡)]Ry퍆fo\&jxwoo?JcQM;o945\|rJp"Z!O9xdYiԌNiox\DDd?pUqJO]l]M]<^ZG/LsĂɮ4e VfrC) SZ9BbRPIuq鯫+>3&ϞǞ?/-'~6ܥPrܿ~zeh%"""{ ؅Q=ĖGēk"Vn(o1\tjL5+Zy-4dB5u16F|} ?<>ˊ|]n.fnX3Ir sB[S1KWq<0]}1:˔\v}%,⠶FpkՆ2}1: oOeSޥ\6,եOs!pI9h`|1څ(Ƴ~KB0{Z@0΃)e9eYǣJ㺏Ut B6u6B nob}jʅJDDD FDDd=x8 gO9~<V4v4ۨTilvFfO gHH E>iCq25䖀 ˓ 5qB9Xfmǝoi]eIs%^ilȄ FDDd@{s/j" aHt9he 4f W}c{`džԾ{Y wy22ke3rƆOL6u;QkW~5 oƴe@ARut 6;54% R w,)pǒW{lg]~N;=2Tf GO g%6#tc1MvT̈›d_O+ z p!Y8:a^0"""'C^ssq/zY5z{gj%ևl1{ IYϛ/l!{5 &F;X3 aZsװ8<ƯIxA#dFDDdR#""2ژ7~_wc*}Y0#5jr 7y`诎H3o1SW̉ 3s5R<69\\ٌ᠎ӥc G-pḯL( bDDD&5I%{K_YO-v3&[sB,zIxE+GOc0&S[iɅcŃ1O`̪Me\"3'As=ܵpoW4YM$""2A)Ӛ8,cZk{Imw4LY@O_ew i Wux^:{!<˗=9S+9c`pŬZ_ƏX!R&zֈ/M[cȋV.T#""2)|L hm 1fc?b~~[-ඓp9̈;ou{$*0&I-N="5fJ#~@ki̤xY|S8|N",{VnY%fNqy+aw@2XڛR&ZRɐ$0""" x-<;tWQ>&mO3{(06I3gp4͹Th CKkeќo8~b ua=/OTm!/<,;.şt18b thyǥtxv4A}G|Y6n{S1 k7G|]|]ښ&i4,:w]Ԉ%0f8yM,Pfs ,Z SBNOӒIش+Iň;BfM *ɔ{+֗[ZN\u/Nfx_DDD&61"""x~r']I59m-!_|$RJPdY)N92ucL%p"O<hV05#cbho6לTCeȕtCe+_ҟLJ'gcNn{8*'7`f%6GBE'|,XYp!Yҩ71 pO~[<Kp{(Tf6mNZ&oP߹$vW/n}Д+9LDT*c1V!$႓a-SOA&.>0+cgK+[} =Zr:1xWw[b?/f?o~R,/qC÷0sr;_D&k0ҡ7s4em̷:PĈO.NupMnues1 09=~9A~} 1Ґ5,amgVuFU3Ɛ^uN~KSB<6v[!IA.f|v^vz#0;8O{5ݬ\-7pf,_0~b*laKOTS SsY̻wn!_ҩ+/jNsCŗO"""$ g|Fr |ouuz] #(yy2.'&yscy^_\YSB>n+?_OcfڛR|H: ICk៯l/m%ŚRZGyϗDѯsbx?GϬ+pMϘxlU~W哌Kc.g5𷶰jc9i[S c9Ň?0Mz+9Ў<ٚF~U\>}8Rtn;wab1IxpxOY~ C`yϖ2TBھ1ksBA2M>F|t|/L1Mk e}gc5C|Sxٙ PHGy ""2@_!ш}{_=#ٌߖq4WE 2$ ta3_{$ʘjuLח]>oᾥeN|XJb xqt_RKVncڰ-Ebc8yqT^zZnDZ6Dߑ ٔտNh̙c'#Èy6_9ղ>LÆO}7|z+O*HcyHj:'-ʐfh3j_hO\idv[{Cx de^/7@1z¥=(e7Ҕ3C#X#FDD[T}qa햘kwb] 0cX^}n3/>9Zrjʶ_u۬<WOU*:'k_ c=Cd()q f/NopQb^Yۿw8ّ#fOMqShiИMa0VV =^@`#Kܿ*:QJTfL yչM&&D9^ FDdbPw֧ FDDƅR,zY_LJJ\L+xiKKej{9b^Cg RS ἣTrlxr1^c,|ޡ FDdbPw֧ FDDƅ}Cuj;[U'U%$Gr=COTdӆ\ڒI%=f#G)2 =91?zfmm{ha b#/?3c.?fߠ FDdb8M_-"""vXN:;?9)F_ZŢ+| J D8g #fS^5> TLRӐ5LmOqڑY.;fb C; EDD䀦 FDDD]&̘3\|jO)q}y{OYS**ƘYk52i^xHSJs19JA?1"""GX<7i/p"7e(bXt bюM'1XkCCCڐ9NX2̛fj{@c.::EDDDv59J 1hiiCf7q>fƈ<|6Glrl1K>i 1z'e!kiobf35ͼ)Yb 3'1xc>4o=AAY~D7o+?{Bc?=i.89GyO?ӗw  [{"z11%А4X,CrYCs %: >)t灂'Xoɦ VJ wTQV>AAsZLwzBA[Febc-#"""?DDDDDDDD1""""""""{DA^ FDDDDDDDd/Q#"""""""(KĈ% bDDDDDDDD1""""""""{DA^ FDDDDDDDd/Q#"""""""(KĈ% bDDDDDDDD1""""""""{DA^ FDDDDDDDd/Q#"""""""(KĈd*_svmcZ>/"" xct x5o ݨTXOʛ{a X3|uxgpc5ə{ = S+"""1"""cxxY,M8\rJ/L?߻o(=?øB[q ' IDATC.<9K&To3O+ wLjzD` 8yru=K#>25<_Wy2+7`ɑI{:Z^ufȳ#""OS#""2V|uxS<3 ́[~1 }^n"Ss={AwCWqx< W]13S3e~\9Oc|-5ZVTmj]{ܹƞrd{1c8eqNA>HA3_j$2<1ޏh=g d'y5}1G Ol=@!|< `ÛHWoZ9 x n%W}g7#GDDD5 bDDDid5(fWUu8GmWc͖h,{6&p_+0h)NB"aśtvf^|jk'FDDDP#""t_|=ݞg-cOrCВ$$1 0Lnq&}Es?m;|!xU3쮭A^ۤfy!"""uĈ^S(ýq~Z)pfiYN>{|Y1je6ODKnTp␙S=֤ڦPDDd_ FDDDp,c7=igTk!0Iҕ6 ,+O`<~ a cוӐ\N?:M&hP1""""1"""WJq}N=2dTK*m15` М5&.?|6KsVLbl]rA)yaT`0fp(m:zx)Ǟ8N! _/#0T@9r,XC*V<cC9N*4mFa)4<L`C3Lf3&a9a쾷8ƴѕ9S1+7BϠ#.{pPa'/0Th/g{MfS7q&T1oWWVBkX]f{&5Ñ j>p>ffKc[SS*YG-9iQz ]&@ǣ~ߠ%2zfM$߅X1fSc햘RTUs<1`JӘ<וq>e a̐<ƭ1ZM5?YGik Cl vWwiIJgC:94'%L(WއczqEAnr~o)[ybMLS*~1CCSg񬐿sii 0S2UTγ|S̗rl(9'7fO 4o)ͦ* W)<ȷg2҈u!S`mM|"?eBZՔcOw?2J_~#XaJ"""#VTDDdWUҍb>AC(?\G1`P7Y5!p3%},YQ -ʨ5;^Kӗ/ 0Ե11˞TǷ䓊]{?T"u<2;c-ƞ/];*TA` a`0$7b,/?`ņ9~; dc5zMz-Ě.9N3ʽwDP<ŲToJRr{Vor|:21( d,P&T2݀WBRw/l7ΈO7Fx*I1oн.e_vb9yDZ+,kۡu7:6qi^xHH:5 x}C6G;u]}FW*%=Fjc"~{_6δ4IRIGɕ%~}Oxh9*lg#a Gv9{`a{a{sI*ADDI"""oGbTnhcN^ȃ-;,??QX#9HsLur$n}(=K1X35Xq}%6ubG")s4xzuQ]j`fii޳_?7:O?}g.!"=$MO15M;#S_1tUĆ- E 8lPɄPyxr7?|d3sc.p뻆_W*{X]E̙p 3Щ-+U!> T:B88Czri1)x*!- [1=QXr6ђ}xֱpvݟgs(8j~KQt1w>\bٺxW8ii̋ߡFDD䀢 FDDd x~xKWw3m wH3 MUmaRexLbcOwn.9i_+a*L^h)i뱼cmG?w-՘\ΥGMct*v*7_ 9˻.rKVD|F08!V^vV0Y5 MsZÑSGOќ:8 ;q!mX,y+x|p#gx)w1o+UߏEB~&r>B>~uQɷ$[/Jb璫>:zh;!S%˳iկkhJϊ |?C*^G@67o\4@ߧ8g vIDDdR#"" ]1KVDnvS}}cҬt9z L;oxlgKw̩!D<6`V?w^q&ςY!ow>R x*GJV/QrdҞ :z \er17 <xr7k7zgSIJ|qP05$ oGbc2|d˞ =yqg rxX?09'I;y,ۨI2V>i%cMy< 4xÜi3 ._ÂY)8/0XbM 9?˟&W*DDD4 bDDDv7˱nKR2Ǧ8Ԙrɍ|ex'!+7ČGG%_Sp)fMNo Z MT4]<3͡3Ϭl,z%( Q@{}[vvIe;7և>9K%``pxTbM n 爃fLJU Y sb<%b\>BA.SFWw6&C!  G=/a [R3) <3:RaM˘5dB 23Tj*=}7?XD'iNujXA42sM· gYN?27$-#"M1k6y&{HU8U)yښ*rFyCCƐ Ovu9cV!XĈ"璐c=x:e{>K&5Ճ!<O)q#l;䆷~ֶS7C>;@{gtJf}3 xڷlgB ̲같_ |c ~"{jSmbp8`v W]u p1)%_q+#]Sf` iK<J%J)"CCc9iW3DDDA bDDDv6'x_@GkxXOGq4Sl ͖J}o ?T \W9jzO/U?004H0P0=wY7AOgZlp<18=$sR1'˫Έ{y'M>9EaΕSE~zgâنKNm`ތF1"""ۦ FDDdWxb94--S(1tG̺2o Ǽ Wd3pq)Zԣ!20yǾ!>~[7@l8f%ХZWf)GBc<łylEӶSF|>~Xs|]-4e<[rÂ Ú (rmny7_g# wOoyS FDDDMA.J?="2?R&J+Gf'2T`xQ{+vpdzfKO}~'`5iMrxq&]粖8Y)3?ghktGIu,}1iŒGc%2>NYzw~p B3%`Z_RJӗ|>ʆr\<%DOu]d05\" W/'rڞ0Xi!VZzʊ _RytUGS(1&1`aFe-4eӡ篎L%*;2.Ȥ 'OvO>>knpǒ2IeXvwy~q!<"';LY^t\-NI`mxY0e^x=/s^ϮchD"""z|!}G^IpCN|ef&X9r^ϿS34d ,_BS֎{NznjXI1;Ws[30sr,mq/ zt;<:,GOX0ѐe?R9ugdI6$Ӏ;KIP̂Kfܶl`Iny5@*匋dPX7ġ\p| O` Irɗ1 Hf=,_3LcI6Ռ02ΒM|uY0YƐh>"Gu3-#W4pF@h1Y ThjcE1kFmEasTM*Û.֔,>^y3|M-̜l*_ xmySO`f5m.Y'nl2[I6H*Ǔ||^*h 76t#ɦk7mJe"y{\KŌ#Ɯ4Y6Švæ[} 4  ҦUX-v e1U˟ˬ?ұE A9jjlq"C#Ӟ`e0U57I&Ӧ 6A%JAE 2XǶse}Z0b;U6,6^c-'[Fcdi5`H;'KC6*0?ۚ-n2MlMac2|!㏁[6G];Y y[y`ik' 8__&o-{y@xdT7G 2`KQP fXi!E쏄ۀQ'ut۟C}J1Dtc?l@na*/LQe$2l> #ݎI?֌ه8l^/Vۧ՗^o}ce NIJ0t?7[vu( kHcT{mJM?@?S2L-fӐ<8c˶Y蘼-r@ 9dL rJ?1[tmyO1kZnLCt䴭qUog箿'7>nFgU+ȩγc=ps.;a.1B Cx!!b<@ 1- IDATB Cx!!b<@ 1B C^ܽ-( \]VQag@!Z2Ov}݌@ e+VW ;lag-G 7ȷ]vDQ5k͛7En۷o_=ڸq;+jȐ!Q6cɒ%ou5=2dʕ+'IX,3g֮]+ժz(1/_+Wի] %Kj̙ڶm._2eʨo߾j׮]r̞=[NRɒ%#hZd,X Iڴi|I :Աlv$m߾]K.Օ+WTfM :T*Tp܏?y5lPt_QQQ.WO=cܸqZr$WʕdW]vUgѷ~~I5js<ܶm,ǻy,JiZZh=zhРAdiF~V\>s٬YfiƍuZhA) K}n[o?ðE7n\Nsu۷Zh]vPYVՠA9RAAA.gO3""BwѣG!CN:yp@ ֭[GyD{OfY>>>n?qD͞=[Zt^{5Sb'hǎziӦW^.͜9Sӄ sfH{ɒ%Znys=:x𠂂{WǏ6lؠ{O5jPŊaÆiРAjӦ._wJJJqh%)66VٳUT)͞=[ԗ_~)vUxyGw*Ro |:WuU/996lؠI&f`0[:1PXX*VV}fI>^fҼy9shȑZre/ah _}vjJԸqcf۷/}F:uTRzl6kٲe>|T0?Qbbm&ɤ/Bo~{1b#]ŢEi^J*m:ݥKU\YaaazhI˗zHŊSʕw%)55U={T:u]i&.hz:uΝ;Ε} <4iD :q$iΝz'OJ٣p\Zj)$$DKVu,quݸJiӦX,%I.]ҩS|䦎sGHRPPF5j($$ӧʗ/ *CG{5kTpp"##ٳnwE@l۶MP%I>6mڤMh4ٳgTU^ݱ> @jRLL*U$ժ^^^ΝSRRxlkTBBbcc!*UgϞj֬:ugiӿ}||Tn]:ue oZjesl߂:EUzw^UZU;wԇ~cǎiǎ\٣Kng:8s挬V^|E )-ؗ%?NWiVTTի[yÇG^8=~,I~/vҧY\9dHB /ׯ׵kԲe,| ?rb4'$*JMMj0D")mMNNV~ԲeKOe˖K/$ôit}z7U~}?e]{\:^~13٬~8ò?X>`sl߂<9L5k&////_^-ZW_}vŋ[nNbŤZJ(t[W׍;imVƍ믿[{·h{9#>ۍI,,̱}fGQ||^yU\Y:~.^as***J/_vKMMͶIڵka)|GGGUn8}t/exQv>>>ڱcGW~0\9םk޼־}tu}'JLLT˖-;j񊎎իW*88X7V``q}֕+W4}tG%J?'*&&FIIIھ}7o믿׵qF)m$ݸqC|c( 2eСCpB Җ/j:x^iӦb(""e6kպpl6;w?99wl63vϹr;!;lٲ ռy>1?;ooo[3fО={dXtE>|8˶w2jݺƌ͛;&ugܴ=GEn*'/r@ ֯_7mݺ5Eu֚0a:w_~EմiS >\}wޚ>>2|qw,&*٬dݺuK;  2aVnݺ%I*Q`T%$$(55U~~~2_ OaV,`0`)))IEVZvA-ZT=(Ѩ7o:؇\3@^#@1&Ia_d22  W_<_BRSS%IE)>:?F!J@ eaXd2TXB+VL&I%r1r@ \X,2),v>>>2X,z@n'VUfY l& pK'%_E5u􈱿$@ @aZe4rF1L;} /(ybb0 ;+c0@ktg|bYA|?~/_ܭ[7?~4hرc>ɓ5jԨ| Fɓ ;nٳg4h#Gj?ɤKddVZQF9SƍթS'M2E&)3LjРbccN _~q+gLnݴp;]+7ܩ7_Wg}V_d^ܑ~[-[Tǎw߹k ˙W^W\رcթS'cǎzu 6VU ,P߾}={jĉJLL̐tFޅpfС4hPeW_|jժZZji2LnOr0b-ZHv|RΝ̙3uZn^{5EDDhݎ釣wj9a2ٵٳg륗^orkf7oTn4w\)bŊ)!!A>}J.iݹ֝!gsu,nݺҥKշo,/.0&G Bѿ_ǏWŊս{w)mcǪjժ_M6*^xG˗ĉuwQ͚5UbEIR||>c-^Xe˖դIݻkRp3f護ޒ$M0AZxRSS;hڴi>|nܸC7P۶mueݺu+y3go߮+ @SNՀW_G9s.\P͚5KÇ7|#M6M{ɓ9s8~ *UJAAAneڵjݺ6d2҂ }]s=:x𠂃\ q眹sN5jHE6mҖ-[xbӧkϞ=4isVr>R'J $'';ݦM67n\mݺٳv 9sﯕ+W_4rH1B*Tq63˩Mٳ]v-gXoZjzs}oiڴƌ nݺ={Mtu;kC&)_^[ZTjUѣڵka͛7ueժU+ի'ͦ\{=bQM~^]tmڵT0W_냂4j(ըQC!!!ݻ$^P TBu$11Q+WԐ!CҥKkڲe$2j֬/*UFʳdҥK5rHUZUaaa=z!ߵjRHH]gϞխ[_hԨQVʗ/eɓ'wdEEE9~5mTfY{~,Ο?_#F<ҥKGU@@@ʜsӧ5h mCCC$bd?UP#N:ڰaz!))))?q9ӫW/ļIIIZj&M뻜"66iݹ[W#җ) 8Eڶmjժ(5n1RJhnݺ:ucك>4]}ݗjymS%KnwU6wիW맟~ڵk2ehѢE8pV^N:jjȐ!:tʕ+aW׺\ޗ姽*22RzRTT-[>}h…1.\Pb$IO?HL&ݼySaaa?@ B'hŎ}n$u]ZfJ(/T=M/&&Fݺux/R=:INNV>}#襗^Rٲeկ_?Y,\-} }ҥbg45sL߿_k֬СCՠAM4L&YVY, ?:|}}>$*՚ڵkuUGF7oJisb'̏:gvٳ6nܨ Ęfun]ۭuSnj IDAT;w^~emݺUmڴq6q66M)$$D?ہWesiذN>?1)qn-[l|5kj„ j/WA2sL&SasQ:u#(""B]vՆ 4p@(QBt̛ѣG+55Uu֕hqx14 @ׯԭ[7 6LQQQRJ+***… zDzMbŊڽ{w{&kʕ++00Pǎŋ\rʩH"ںuׯwyG֭[u%Q|yIˬVsk'{7y~AK,Ѿ} 6h4j jÇsLl6gʧtΔ6ZgϮsUuRPSN~T_vM'Np|l_~N66M=Zx l\3;v.\hޫQF#oJHH˗6mڔ%ε.mU]w9K{Y'Nݻu99sFsÇs[ŊS>}m6]|YKSٵcW$C]M6iѢE޺uj*+22URJz4{l͜9S=P~t矗&N .($$D=jԨmڴIӧOWbb*V> _ ?# 2DjܸM|; 6L'Ooŋm۶Rժ ON'dkѢE6lkҤIׯfU_zǵcu]ׇ~_]GVG}TgϞH/fj׮ŋXbz׺6InۛҮ{Ik_2ce_^{gdڕr;2HMMUBBcHZ~w=cڱcGkQ<ƍMsuM6l#nG'*RÓ<>} *Vm")AERjmҽW_J^ٲY}N-dqQ{ [ΝlVݺu $?[lQFRAцaC 1>}Z[n͛7ӧwޅۀM: ֭+!x6 (!!AרQTti=ӎ,?] .={Vǎsm-#[#)#;$!!b^xEFFիeW^U޽կ_Bw<ɤhˎe/>,Ty Gjժ:qcى'TR%}ᇅ7 1xUjUDzJ*iٺ{ 5ob5sLըQC5jМ9sxv]!!!Zxqag@ ;R ߾} ǒ%KTzu5lذtbh׮]:rRRRt=(<<\˗ϰ]JJmۦs͛ Q&Mt}eIg.]ҲeO{usQIhTѢEUjU=C.I|;Ph֯_/ɤ.](%%E[lњ5kSO9ѣ*[N:;vn˲~zx>TҥKx9ٵkN<{L_zjGE$%%%>SʕcGϟW```^rE'OTʕۻwo`+WN:uҙ3gtX,ԲeKyyy)&&ƣNnr!L&*UCbŊnNNS||:t蠞={*>>^;vpΝ;ռysGʕ7|iӦ:|֮]YԬY3IisܺuKFQW̙3`իW/C7oĉSN>}ZN҉'vZ9rD5*r@ B%KjJLLTRRl٢ dlɓ'VTIVUgϞͰ]֭EE tL*I5kԃ>RJ2ԕ+WVjݺʔ)@կ__ԨQ#)88X5kt:ƞFٲe *))I.$aÆ:|6n(GL%IQQQW^x5kL۷oשS]N?^.]$8p@5k֔__vMVҚ5ktafRN04 @0 С6nܨ *00ʜ#'Oe6%IʕURS\ rʹoFe˖uLwr m߾]0}e?RxqǝrKҺutM[FQv… յkWs=VR$_:vXOMS:uw^jJݻw׫W11dү[*,,,50PhJ(Ν;gX_ѣJJJҌ3kժ;<|l~-r[Weٵkf6lؠM6Ii×[F=s*Qc 嗻厏Wtt^|Eڵk5khj߾Hb.R^=-X@7oVժUqWK w 1pڵkeݭ[ti+㙓5gũ|JLL?올V.\B y)K۶mնm |gjРc՝r *Uc`PJtر =^Gƙ@լYSs=8 ( ㏊SBBN8J7v<(ǏhѢ0JǏKiC.^g*%%EvdRժUU,YRAAAt%&&ȑ#3S~}uM;vLGq̚4i~ZNZX,X,JIIq]{G BsiJMMUҥբE4;v,ÏT{e˖Sjմi&%&&\rڵUxyysڹs֬Y[n)((HM4Qڵە/_^:tݻc'pX؃=}J*N:kQ zrw3x~1ݲ/zg{L3[d2g9&u~ {g}ڵkgPbccUX1y{{1O혻 m")AERjmҽW_J^ٲY}N- M1B C_JϞ= ; #zx!!b<@ 1B Cx!!b<@ իW ; p  _BBB ; TYpch1B Cx!!b<@ 1B C(TǏuǏg.!!AcǎO?͛gՖ-[uQFAjР6l]t${Q 4r|e2ԠAH$B&Mԭ[7}gZm"##jժ9u릅 :>L&+W4vXuIر~mݸqíV&LVZK.:ydPXV,X}*""B={ĉX|{ΜI>5jzJÆ ^L~4j(C6-GU t<#\O=>{1Y}3k3e;wag\~"##ոqc=ZEO?|OW_bѩS4a5J3g,^je2[YcBrrvڥ'*99Y?zSllƌr_ɤW^yE}2*^Νv lRJ:qfϞ={O>Qҥo˱saofY111ѣGm۶nQyߴm6h"˺ 9]OOAe˖iϞ=2ec}```!ιlb.]ZjeQbInݺX-[L^^^ AWΜ95jdɒyGaXbtRIԤI=1cF}[s`խ[We˖Մ ԴiS+V߮XbZpa@̙3gt⾙>'///-kn^I Ŝ9s+Vhĉ WxxN<={*<<\ׯ_$^Z]tH#???fM}c]dd͛~)""B{Vtt/_=z(""B0G՞={>p?%JFj޼6mڔa}ddϟ+""BzҡC-f̘;}8qbfN:M63f2n:kݻ]:Ν;e˖4hΝ;Vyl6zɓ'X_խ[Wʔƈ#fgѢEi-nݺ縍;v,Y5k֨qիWyx뭷2GC0qRR4iRNU;Koҥ۷#c뫗_~Y}cxk͝k?3W̝6[>l6\9<奜O=#蒹,=z宮g2=ҙڣ;nYAG Bѿ_ǏWŊս{w)mcǪjժСCg(QBݻw3>^/^ejҤIT5~xIСC5c [.x]V[҆M=ZfY>>>͜9S .Thhf͚ÇoO={hҤI М9s2?akJMM;CiӦimF#FW_U U̙۷k ԩS5`}WqYRJ)((6q,Y2a >>>NF6m4n8l6 I֭[յkWIٳg*3sL_+WC622Rn)UV_|Q=zPv qV#GT"E?}2aݺu0a$mٲE׿$Iнޫ+:/En3Iy._Zje[/ՓfSLLj׮]_W;g6mr&WUVULL<Ԏr[NK ,?OIҥKea{WmAgr{t%s{t]}ٳge2b 5JժUS5j(ǾZr .]Z 2O<.]ZjwZgL&.]#Gjժ ѣ(呤ɓ'5bѣ1ceYWgΣYQt .l YF(G8&n'2oyDEcDL\L F h  ?z)i4~]uso.釪[uur s455a̙򂓓{=:;wČ3o>#667nݗb=UUU_7QQQ!L|g+keenݺLTD[9881e(\z+"""ZMk<Кsǣjo{w?"t{okk >|8F  ԩS#44]6my\v .ĸqT&ɓ'qeTUUPxH߾}단(bҤISn7772{ebKKKuuuC=z`iiZIe^d7op[3$$$_b鰲±c`kk 333x{{l*5VQQQ`WDÆ CHH>#Iuuu3gBCC`111hjjm6dffرcX|9|}}qF< Mkrm&qOCCd2d2 ݽ{ ɲA:T L&xOoDDyuBqaǎx7kS~[Fdd$N>cǶ/ ŋcƌÇ/ޢK֌3;;;"++ 'V^FxyyXIƫKkDŽ.R+̙Ӧ3f`XnBCCDX5}gqNFW=m=5@DD[S %%QQQXbRRRWWW}Gm)o.]$\~uTTT`pssnܸ|||cΝHNNFiiiҲUǬ_UU.^/ӧallLqԮٳ'v6R{ԭSL&CNN0P{)$ "ܺu YYY*I"??_x/q5)u6888K.1!++ )))U&555Yj,** ؾ};̟?ӧOn1Ykk5/mc-ggg1Bt1'NDbb"Ֆ5:>e^8w;pu7}.={ ;v1͛a:ggφ ̙s(,,/WzhWy|Ç/1e$%%|Qٳg1k,;| ֭[׮KӧODze0n8a S~ZRscƌ?*uVX___Xp!U͝;0f̟?bik-_GƲe+ [nuL&CQQQ"´ipl߾]xZWs/<==1}tя=3<ӢJ8L>իW駟 W/ŀWKXQFo%_fYYYaϞ= §~_~7oƘ1cp\pǏt|ī<Z_Ƅpab pA_tcjq㱦xr&Z{3ǡ#W{ӞBGÓbܹ0DY]xXiYz{kfMk#Jlg;tUܿ؈*իC#zbݹsQQQ8}?pƒеk7$McǓu|}}B&&&¼>Ignn>@&5{-Wz ?PZer5˚DLQ8Y/QIJJޓ0hhhP tvʼn|Ԧϟǯ \WWWm]~-GY[ɓ'0665<<<0zhXoD)d2{ 888s N:ɓ' jkk~矇ݻ"Y 7oބ[uֺ֦sRRRԄZe򐔔0… 8tajj ###>>Aqq1ѻw6tx!',311n޼AݮFH¨eeeرcq=\v = ƍ'$ߏ!C`{www֭BCCԎ***`ii׮][nxѕxi5cC*>ڌW<0F}}=~wѣضmvڥ˲U|F~~>R8q"fϞԩSxJ'N zI6QUUKKWiۣJe\.T*Eqq1Μ9 &Dmuuux"1gc@xx8f̘ j.\@`` ̙={*W3 d2P]]-,khh@nn.mۆx9reeeZnP}6nݺ|$&&"77~~~n'9"z"466"==G)nIII#;w 11j'U:t(222PZZ GGG\t  B.]ԖU>|8]~ AS-֭FmD<|wkkk%YZZOyyy4h^=zիh"޽;{?#P__/4h>>aggwww#773f#GCs+++HR߿?޽ <զa8o/"ꫯ_ /<==!ˑZ B8w*++ajj GGGL:UxӦHKKCcc#r\.5.\JtΈl)Mt +Wʕ+6#M= tV^fbIצJ?D:rP:UUU0119ֈGٿrfJ^J롴LfYך)# !"""""""2&b """"""""a"@!"""""""2&b """"""""a"@!"""""""2&b """"""""a"@!"""""""2&b """"""""a"@!"""""""2&b Ĵ [yyyg@DDDDD`"E"tvDDDDU[[!S&1DDDDDDDDD 0CDDDDDDDd L1DDDDDDDDD 0CDDDDDDDd L1DDDDDDDDD 0CDDDDDDDd LQZ~=8 /RfڵUh~ccci&eϟG`` tn#Gy߯ ryyyťKD)JQ__ %66V8#F@DD6o Tjd26l؀c_͛7}vdddeoG__{nG걟G۹ʐ1sh<ѓŴ "ҥs믿.,322>rrrիW[u믿ܹs jn׮]?Gaa!֮] '22/T*ENNV^ Lwy ?r222pA8}4,-- tpۿ3 [}E9-81Dī-,--;b,Yo&BCC;d`-1wKеkW$033%,--1j(L>gΜ1ܹ{²ÇweѢEzZb?t9~5:E||_|;#F`̙BǏڿܜL&î]0uT`ɒ%(..ֺeaaa駟ׯ_^[oWMMM^z 7nɓQXX1ٜu}t Cdd$BCCUyCh:tH^CNNNz),^HKKzc jokNJ.b18Ĝ3І~v~56t'""a":󑚚(,_HMM8T'>@yyyco!Cqyy\1n8umذEEEHHHoa֭maa3g܆TZZ$L6M\\\CaĉxQPP OOO,ZUUU^jc :T86u5cE ]h5 1뉈0!' l.]/'O{gk׮aҥdWvv6\QWWl@yy9RSSo˖-D"#.\P'** W^Enn.`޽P;GHdd$_NNNBlcʕ2d$ ̙ݻ:c=z4믿%o&***d֭1p@!M6$V}6,YKoE]]+}kggX |8֯_ɓ'ƍ ƍCll,vލ޽{ t邠 ;wFJJ Fvd2̛7O555ZommH޽VqAe+**pI\|UUU(,,t sssᵓ,--Q[[3fkkk 6 ׯRSSeܹspssCZZ/_}QQQ ĤI363g ȑ#ظq#||| owִĶ*C ۷CkPԤrĎIm1iVYc@_>}Is1<==QPP ns*t#cXS{юWsb5&Xg1įil]ODDD u> /kkk1}tXYYرcIÔ܂ ^똃/ IDAT#??K,={`cc#7nv؁7x?~ںݻWm̘1'Oƺu e0gbѣbbbԪ}Ap11ŋ z1cxQRRoooc۶mıcǰ|rbƍ 3f3gHqm!R2 MMM*_TUd6_e2d2DCkƤ~Κ[]] <3Dc:bL¢uk;FmÊ+mcqHR,СC[B{>mGhz""5:ELL RRR+V %%)))puuDii)ЧOaY>}Gۭ1lllrJ_jQTT[n!++KmIгgOtɭD'"11j\~Xx1`ee7nDb1,_H+L(ij|}ܹ(--OsnWߢǦ9u...,aL&CNN<<b66Ĭ'""a"8ʗN?|k׮ťKp}Xbꪗcƍ}66lؠ<006la`mm-CQQr9LMM["-- MMM())4#>>^(e/QYYӧO/Wxg{N>ܳgO899aǎB"ؼy3Ǔ=zGff&,-- zWX466B*B*ѣG8{,2331vvm{Wh޿ڬ`eeI&?DNND#GСCqܻwEEEXveMlbXYY!<<?2lٲEmY1cRAW>g"$$֭CAAjkkC13||j@vv6QYYx"44mv;ŌistŢmL7}Yv!f?sΝ;B)))b| -Z'"?ޚDDO4\333Y߇#BBBt7nDLL ܄ۜƏKx̙3ܹ͛sѥK~D"k&j"F[[[ 6LzWWW,^۷oǶm0zhy饗ӧ}9((9e!!!1j(@UUΞ=?555ӧ֭[sssd2Qc¡C߿?ℿҶo=e4˗'-[F1[nm1&߰zjL> &Amb lڴ o6u &jD3&ĨsۆJ"x=zgyF֘0a>C۷oY۪g +ϧEӘhoįklZ_UU"9***P\\]XV^^b466\ihs΅1>NtV^fbIצJ?D:*ollDUUp՝;wӧOK=Ń#55U/sh}:_BCC5::/2&MM___@FFF쿰666055d'277 @fȚ+{(2e_kK&ykIII{*[^^zաIc̞= /زeK'p"" ޽7n 88C!2*ܹpttĴi0u93d޾C&""'!"Qsѓsqp""""""""0L1DDDDDě7oQ^^.,+//ٳө1DDDDDēJo!,{7puHN5!""""'?Oxxx ??_XWWW|GQk0CDDDDDO<{{{|'bxg:56"`" ضm">>I"zvvDDDDDDbI$$$$tvDDmD =|}}۴]FFc!"&bSrdgg… x"dz*rssQ^^ccc 00666j;y$}:_MDO<\Tb9s&LfffV:tJYbĉ={6SNÇ*N8777DFF5!/ zb]}}=~wѣضmvڥ5PWW/"88ś=ߏ cƌ@jj:.\@̙3={dv+tqW}6nݺ|$&&"77~~~lf"xyyyǷfС@ii)q% 4]tQ[Vav~7  OOO \2]hgJJ F0ܹsU;Y%|||???a^AիRԡH >W^EEE^n>C9rDxoddW"((""""""z 0 @uu5rrrkܦK.:t(1vX`j!77ŨGeee d{):ǏGZ#?oQ,www&կ_?ƍOAe{]u333 }[W*";;pvvֺ""""b"qM_k.P;kCC8~aȑC \.Gee%ЀӧOٳ6066knݺ@wqmQZ^l6q1777~2CDDDD"zꘘhD ˗/^S[uuuMi}IJ„ 0ae/',suuō7T)//](n:D1rHxxx R?3woy믿Q\\\̘1C08 2LwEaaa=қ!N_~W/x`nn ;w055#N QPyqsx".\>}@&5{-Wz ?PZer5˚DLQE!""""""""=`"@!"""""""2&b """"""""a"@!"""""""2&b """"""""a"@!"""""""2&b """"""""a"@!"""""""2&b """"""""a"@L;;"zwvDDDDDDO &b]$Ig@DDDdP=xk0CDDDDDDDd L1DDDDDDDDD 0CDDDDDDDd L1DDDDDDDDD 0CDDDDDDDd L1DDDDDDDDD u(/aشiSW^yr\<ҥKޗT*E}}ʲ(޽Mƶ#00YYYh9rQ99s/"88fq?ÇNj/Çaʔ)Xb82ePFOAAAO?Vj'u iIP[[{!!!8q"N89fĈ͛!J2<=]L;;"裏P\\~W;wAAA-ڵKoQXXk ƌbKWFff&.\%KL6 op-lذضm[2 ((('իWc„ Lطo~'l޼Yʪm0x`?R梷U7>sўX񨬬Ç{Çm"##xbHR`Րdxw ;1DHMMEff&"""V v"s\t ]v۾[h^)..ƒ%Ko"44T/u$&&"##P9W)gnn.$ftR,X555ve=z`Æ 5jllltdO{544~/[x"V?4$kݻc„ PVV5cffKKKXZZbԨQ>}:Μ9cиHxkuxСC?qM̘1WVVb͚5Xlǔ)S={W^6m”)SS<::Nŋ4$$$?Gbb"Fu e/%Ν;1|c̙|=zDFF"44P4}]YF={tRQж֞toWZբvڅ˗xkcc*466?#맱:pww׸^g:1D)ϟTDEEaHMMEjj*pV(vZxzzbԨQz3gT III6mJY333 ..ĉ﷘cժUƎ;0tPDGGc޼y CZZV\6 l۶ /_cʕhhhP[o6 "/6l@QQEEEغuJcѢE8|0<==h"TUUZ_QQO>kѸ{.֯_/׿%?uFFFpppھ{. r9//*577_=z˗#""'NƍuSbb"ƍSTTT|iS&[mkKHHDZf8p/UhGǏUJNNƍvٿ>b־ CJJp $%%a„ mQFaڵ:u*߿۷<\pϟGrr2'NܹsnCDDO.&b艢n'N 55իW ػw/"""Э[e###gggprrBvvJI&_FUꐒ"|5j.j{uR)݋UVXz5jjj+|Z{Ŏ柟Fll, ֫>6暚sNx舰0XZZ6QWW'|ˑ'ܖb/OtoG&a\֭[ ls 8;;c͚5H$j*}6,YKoE]]+ѓsQ322ׯܹsѳgO@C=ݻwcժU8~8u۷/|}}@L4 ~~~KKKuuuC=z:+**Bcc2dn߾-,k;~Ԥ~֫[lkY[[sjG]tAPPΝ;#%%#FP5OS;_Lsikߚ!$$6l 䴩|~7oތzٳ-ѣGLe˖aufΜ)1GƍBLQϐ |ISO_[[ӧ ǎQ]]O>rJL:xyR,XR3f`XnBCCNXWW9s 44 ,@=#'{ב%K`Ϟ=**S,߻w-_hG'_% .^˥91l0࣏>Ryb&R+̙1mۆL;v ˗//6nܨsD}Wll,LLLt֡.vL&/ɊKXu 666B&A&z;nEj=ڎq4n8رo1~xQ1usiO>Xv-~m1cЎ՚oRR֭[AaÆ Xt)ajjB<:gee3f̙3d")[S %%QQQXbRRRWWW>>xsN$''Tk*\x_|9ӧallL,nRVy 4...,999ebƠ:zj>3d2_.q"ܺu YYYjwؿ6\uiOqF x*\æMJN)51y""OYf9[nі-[twkҤIzꩧ/nFѡҥKf! ^ukĈ0aV^xxUeey577+ԓO>y@<wqՒ̾o_#<G[C э7ahϟgZr~ӟkԩPPw#<>}DV UKFݩf_}>ƌѣG(O#%q~bsדȽ7oV^;SkʕZl|>ƏW^yEzz-nkzפW3F˗/H4ϼU]]]oΝ;zFT__>Iz!n n -I}enC~13,nkkۖuNzn~<-XSۑ:gOOnJt;zhSw6ڰaL1!=  VXX~[?=zT7nTSS;gyF-rtlHhݺuI`Y=@*--Mw3_/~ 8P .:QH?ӾqwXa0G @toɒ%z)khhТEtҴ A 㵵ZlYlٲeOֶ֖@<bd+WjԨQ:|p6lzꩴ A 㕖ӨQ"eÆ ?~m\ bQZZg}Vcǎرc?}9SeeezW lbB\n|Ѻ[biժU?~^KK>#׫Ieee6m~ɾzWu7jIkëJ^{mO?ڵk/)_tz-Iڼy:P(aÆi֬Y˳W{ァ/BӧO.\/_^~?9rziС[;Gޯ_^mmm;Ң 6wmf[ٳgUSS#F\mǎ\s5t)[NPH3gLG @Znr]XRR3f7T '555*//א!C$I3fСCt)ˇo.=z«qnΝ:tf͚)+++O>Q[[ vL>=uFTPP!x1pႪ@={$zzt{УGر߅sΩNx|]y*//bɦ &`;&HPHPCٙ3g"[[[b^TUUj~9R3g9rDvRss ٳgS3ۿa߮?06ǎٳgURR9sĉڷoΟ?CnPaaaUViȑ:vΜ9>}h6ˊ)`fޘt1$,׫_~׿|#͞=[jN:KzRUUx = !ҢVn۪Uaa***T^^a_.처^xQ[nսޫbmܸQVRyyϟP(5kh˖-l먪=ܣ>}h֭Zf,Ya]>OTMM4h ]wu߿$gϞxZ[[#ꢆ$Ў;"ALSS>|29z A:tHֽޛ5 +uyM=b0߯;v\2dfkUUUO?TÆ Ә1cֵgٳC… 5p@۷OK,2}tY&AM81dܹzU]]c&t͒M6iԩuq]V}Lֻ{n={VwToaaLAI&M>LSL\qwޘukV{չs[dܸq;vuym۶Mo-Z^zT5k,\.m۶M%%%jiiZ sN>}ZԿo7vx_[.K{Ռ3N R}UW]ա|ݺujjjҢEvm6K;m ˉWë3^:2,* -OYYYaOGC QCCCYF]vYe6nFYiѪUuuk͚9sfa:+^BG1qrݒ:|W 7߬^zIr4|}z饗TTT3VO=zh„ ڱc̙hѢE?ڴ~mܸQC ȑ# HT&1N,1 A9On;R~98p@"+$tMzw}v|q'C+xâ_rZxgzE/n֮]Yhʔ)>}<nvUUUwQkkJJJ4m4?S:uay\$n AiƍjllԐ!CpC1tP͟?_۷oז-[TVV $myL ԻwoUUUQ=zА!Ctw@j^۷o5cƌ‘={vXN8 3`z,a y:lљ@' pZXlm6_+𳸭m[tKЗ½ ~^_\R[[bDM*dr]AA4I^I~I+RϐgT.C11V'B%-֡IFfTy@ ?Gb|OF`Woڤ+hY;[լɁӧOd ڟ+%:Wl!-L{8$zrߋ/Q h64/Em>#b{uL?.ܦW$O+wcc/ $x@ }, */OwTmm'#+W@nx[}^1A{&h!IPlcjҞhŘ_+VxՓZ.+bŊ%Fyn7*7gY![#J/*=?cih/־};,1#nDƇk~6TTTWWW_TWW$X =LomXÒ.ALcuS쾜ilYPvqۿ@j\5|Ihz6(JZQq8FMl'G}添io?TTT+L=?+yRToVNz/q[ؼysO>3fL}QQt\YXI  T\\t&#bRx$VMrO8+V?7o>>91 /ϲ dddhR" FQ1nriP~ ?݆e 8.rQø=^(Ǵ,qWLpL g:3?Lڇ&%8ËPB cAC2|F9 GQc˜1VyV$6VÓIU`ŮN^2' zЄL!NSf1\y0)չWRؤGL4 v́G8812k61&de9-1rI-L1 2 GcO6rKepOݝWĪm(w<2 7nZ.1oXG6&z;%=b5d&J2 aACO=oN$+<ɸFY*$}N4(#CeQXI HneS(T&( T7$AIV0ڜLUHք0!]m{@+ɪ&,BLLoFpdzʕ WF9``r5;ə&;9u;]+&Nt tň@KR/f7t^]6tНrd+IENDB`podcasts-25.2/screenshots/shows_view.png000066400000000000000000014151351500126606300205360ustar00rootroot00000000000000PNG  IHDRb6%sBIT|dtEXtSoftwaregnome-screenshot>%tEXtCreation TimeFr 19 Apr 2024 20:18:46EK IDATxydU}7NU۷(D#4q1F|'aF""dŌ YyEA jFB+tUucg׹Uw;¾]éSNݮmhU+kJZ0a4""""""cP3ǁh@ϛh5)؟+Z_2C8?YmþNZHrEDDDDDDDvUƬpa_B03U,Jbi3U5Ƭ`>fT|3cFDDDDDDڌnXb1 b25K/`f[vz>U/s dlBafpYJI8-U4Ø2OT=csDDDDDDDKm1Bo .˜k\z,eHCDDDDDDgѨeX̾1 b4+[FQۚͺ bf!r}a/9/F |T̾1*F/ǃ\$ |F ]Xs>0!"""""LӃ?G1cvڮgbD`2roX f1CB9O9d `V"ß*0"|i6Evaa )h, f$ aY""TÂ<""""""lXU Ur;Rf7{Ғ1qj\K# Kk˖-Gmoك>xX:Ԑ'""""""xq 7|%<B|nx1le٪aE1F-[<77Wy# vĵ\r  5+b^2vHE 4m^?,iNɒ<)iZL 5/o/e#Q|~~JV->!/zы)1! } a̰&j'-u00Eeӑ6?7)y cf cf bbl0h>yUʪa$|M adz|7oU"i2Uk FMK֜w)IJ5\sO0h]8C^aÆm_Wv5bSkU4]-SۤwX5'C)3>`O'Ƅ%,=cM{Ԥyw4˛W_}C"";wb޽ aְw^ܹƘ""ZBNMW_}QpV$ZH06 d+aZBlsʙ|}_"""Zevf w^ "%Ƙ`s;+Cf51=8¥9SDDDLMM1YJ-06WfTޅd|,UOlXWf^x1﫵*Vzh%"Z\X'm*Ұ}Fw$KyXy[^E=tVVì_|m1^eYk}o[)%R(vJ-"̇)4l1V>ZՓV:eX2J#үQ R/MDDDDDDfc0999Z k-ʲ$6) cicgdIk\˭媛VkP9l&n{}zz&&&tucf%̪ Tj1s5j>ٰbDDD9[o7t駟ƦMp=N?E,qu_*3;h>bjrr ={`B71Ә}$̡fZO s :rn3SÞ#3!"eO+}7p39|+_7ވa]tz꩕evM7ݴ"BDD4ƘyOGɞ={`̰•9KaE ;,X7еgZƺ9]hY8O<N=T|(ݝ[s9p]wzw3<m~җ>xɞO?.>k_~all {KKxK_;Ngiωh-Ko}e)qƅ=NCߛ0.vWմYa-,CDD+[n<?|Z=\s5x{o6tM뮻`iXkqWb֭xgp '`Æ |3-Dbz }%kjYLkE!"__x;0>>,񖷼\s ;8{Ş@nw@ZUU'?_i`g>:;<ŝwޙ-}xZoF7`8sp%o}/;O0p!v9AO}Sx'!1o/| Wv.RXkW RsbϞ=sssС;cn /ħ>)?vݻw`Νغu+t__뮃R /{˰}v|HK\=޽{s7oO<я~s=/t011LGlӇ>}.v؁K/^z)oߎg?ٸꪫis4jl\/_R}jҒҨWS)-8O=vڅM67oތ/~nX<=UW]??{o`?# T^|_?WU'>77bjj v71AƏc699Yw!+ĵ^۷㳟,nFկ;΁soonuVlݺ?6oތ'|_qQGϡƶm~:>1ַ011n;oxypꩧCUUx___KS߈haVS^kzزe .رpa~vmZHf/ƼDDj{<>D*;nΏs?s8|wy@ELtwNi~@r?Gq~Xkqw3K_Ru](w}7?puQzOO⡇qӮ^x衇|N:$_*EQ[Vڴi6Rs/{R`W裏}.|AXko} ~񒗼{/ʲ}݇<98#[񖷼W_}5o߾Oǎ@(w\OfQcjcуQc""> Lr .=g>~G;7|3?o}[׫׾'>>p3vu]x衇wkBO': ]vY^iRJ r~!:NN?y}xGwO<zxG @8w>Op뭷k_8^'J)g\yq饗?A c.Ö-[|m{Ьh:묳;v]?A'?xc//G=ܓ.?!?>GuT( H}bZEO>|+wa{8я~w.Bp p{5\c9sά۫ w]P3CwܑαX??.롵Ɖ'H? N=tC9]to}[KӗhjMW\qE alق-[#c=+bM"fݧeDDvI)я~]tnfر\pN?tLLL裏bӦMG>o."tMi/$ݛɜx8蠃CSNp8N:E=.kYUW]|+pǧB> ck&wu>C4O{p);{/m݆(v'p<@|I'^c9۷o^a5\N: ׿ֶ(f\h8cpUW/lق+biso n[CCDDy䑸kq嗧ly̱:M1}e]6p>vaضmn݊#8/zыRJT10e͛C՚r!8/o?y y׻ޅs9g#y睇{!ı__LIsqӟ4o;q.~x7oM7݄SN9%]~cc*lڴ 7|3&''qa|hVSvɑo۶me6m__i4Ů&Ö/Cn/G=3Bؔ/>bbb)$kkG -N(09991vPJ-7+Ĭr """""""Rظqcj[%1ַZRB)(nSM~YCDDDkUUnКURS{њnWzh%"A icccX[ ""Zt bh۸q#ØuDk7n-J)tABcϘ5JkvJ""Ͳ7]Lk=!"""J8'""Um=NMb 1!"""""""Z-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDD˄A 2aCDDDDDDDL-1DDDDDDDDDEw=_=߹;:!! aJ)!s 8 _BZ k-ZCʰMRJ9;ŸB@I )$1 l/ZOPRA* )sCh엀OE@JBJBk[YX HsT(1WJ)lGU; p?/)%~:ưXkFLiqZ pZRTE߇s{BJHBU@Bݴ4OJ\CUUBkn#p9X&7j2&VXX׽+asRgH?+ t:GR\ҟ/UU¥s9W9Hˌ1(kL7ʔl ;)9bme `*k88sIQXg2' 9O8 g(^_o_=n<#j$"B3~sg8QwLʷ# MKYEBtY[ߌ36<^x5se 1n?Y[.3z7hK3 nbȝ7o;B3B);)^pފ䷟-dhE1X eA> Κ 84Pw)5 ;,i2d8#oeW!Ȩnvb)`~h 4c2 a?! 0V{Њ-H 1iFq " h#aB])C4M)T' @@tx[M(V; i޿,K%*ABcM V11ik3U6Z)!aLVڅiVjWl(?TI߄ko[~P1đJIZ*|5J&) LCn6ֆF i;~KjZhZa*TBzp*k( A pL}qA8 tP'yQoNjndpB4>n츈e[䯳r{J9lJ8UO;7/TvR\96D59 @L&/Bo+x{gȱ7 aBVЊqR*T" 0&HAj= >AeVqVWl8?E%VE (F۫+viK>V)^!J)8u! Z7mj„,T U [()!&1qJYleBEB#'~JKjx>lӫM ],cB8A8s*lڭV]>uЅ5jZV.BhјʇVY ?gT!OIcUG9)鴱vس®g,g)BYTUz@ L?wrsh(~fX[?xsA*S x 6e mydzךYVZ _}oxp g߾<ӑF{3|5G(ZdU٨R 5UY"BV&<VR) D])32h23vY: N4B7*RA?NKPOIݶɍDFTưe>f/&d:6M7d08X*Td@=&0%\^joӋ3&$h?!B)bh0x?s*#B)6l{4n*bҷuX -jJ??}ꗰ^({l49}5)ҥALL@ٯ`)h.Hg,(B21B}B1:=g I(RLRRB(Ј翈͐)S"HYHf !2MRN=xB"45>0iP`bjz}q*FQ ΥU6n[9돡Rqp0K΢ZclL%:{TbOqu0 Oǒo!URJ>~hmlrBMgD L.P⬟8SӛPbS䛾1_lgb.L0JX Tu3XyYH\ |6=WJP%fWKcΐ]su,COH֡kF؊!9 iԞBؕaӢpa捦czw]OQJ!Jsاɚ /&4ޢ5u8(TU8;\g/a C_JD+A ݵ#Mk+5/@ooz@$f a]& EvS9eED2 u%l)dݘR%WwBgW^P? )@RXbb>ݥ P@iYQU6LuBE]0͆T=N;q&cƯǝ&4U^DƄ "-RU\x.uaJ#\&S83O)t&1+\W(aL?l7`SS|lX0ą@X _c#ΰ?a%$V n/[k Z@ˤp^%O]؆^WʞGӱ=~71Aq\lJiCàQ3kmİ2w ArB 1k-exJ*1j[BB7ғ98gRSb5E,ɨKR0PJYT3bZQ7+5:_UMP:N+*,phIksUiRb?*Pn`䪲`jMOXS Ȣn+|`ꛪ͐[-raB?JSb4 aJN\',u Z_/~?&L!Z tJ'$PWXZ͘ 691ԋNR0z&U;,^C~!W0MHB@ ~uw^&VpF)@Ui6Tz-~ڬ,K))6’ Wha-kd"d =/|5L\<$43)Sɗ΂0&22jPjҼClԋ,p4KS8"{4пlqvB4C44s/mRdvJ+U㫋h !W!k gbB>!gyٱaެӭ"4%Jeu~i,9ϝ ""Z]ЊqbUKlڛOqoJ>%dꪚT_OC)~ =.o1 A4痂犵U`|2#4ߍ}ZWim/EB}ʢ8}IKUVDK釈uPq+,@M#:m(%Q9}ءU:9 VaSg8Me}^p~:F݂&&D _IcCeTYNjJh* @aThDvOEX VXW܄`{&orUPz Fs߆@%N'*ekg Į{Ċ0r9Ŋ'_e [/UZ?'U& 8솋S RdδPuy ,k F1θzH~\«v t89o|HP[22 ȯwCOܐcXVAz#B?yG* }˜!kWk_aBCk%w0na9 gmvYЌ粟mHͭɦ" ǓU N )"""ZЊS7qQnS@.ŹdfL"^`цf"[YI% \h[( оC`߄t]oBK m>!1~ L)d :ao*#eX:a<Ȱ5 BuH~+ZPҥ{Ī )zau$+nT|BS!Pia/w`bC{sPӷVYf|X^8n*4kv2F0@Te2NB~6|:C\v~dU !~ HMxB5`!#\|/Ud]!C3>ߌũ+Z΢osk_^)PW"> M{X֚yx1HnN%J?@:0=ɶbLpo~dȈC7M-HwqJ:Tܰi?FرϘshwZNuӱl)GE}ic66n@_a|vBe|]h]hܹ&vm4Ml0́k02aXbU+bM bh fpŝzE ]l:Ѽ2@ҡn8ץolИpV|K_b"{"Ut᧊eB:zHH$uxD)\ +Q1Uʠsuvb+맒ˬ 0X猏MKY@"YƙPQTk7N9<RRcY1-T^wao :Ä`& "CQ϶s+tbW7p!OlE 6nz|dJuT1غLߛ13U3/U޿_DUUhZ+LM`xR*{p(cc-z>8AB첪q3{ p`K. Dׇ\L 4V)lk1fE@V<2&N˖`s""5A |P%3y|E܆J^g |e塕}_8cSɽWBa0ĸZW_*U`oK%k,u]}#CY]v… DgӴسg*4ܬ1@U)D$ V (eb!!HSv@boU\6)X oV>4PVbUj.QU+*T!%R2 }O_>ύ*Lgq7>} WBu;ٸwx͌100~}6K*s·u0|9ֺ?Υ O~ ~S NI*N{3kƔH[[6{_I:L鈱–|0;t8h0p`V`6B4Lstig{LWpgZ{>ʢO:I~_/:;aeNɩ~:t:-UY(4NOJ{*Bck۵p0֡*PhДQJ2 s١ZR5KUUSnQZhyhޤp IDATCE`kЊ|y2j3a4IՃ+-V* m&S7#BBK&_)e}U +Z/-bR! 5&1sBK"/uР&6[0BUiR[UI!R KX#2u8z#*lֺЗUYLPucSBEJo>Ǚ!. _揰tU54lL!!+}cb+I9nCA8q+Rk'aY}\ljYrO&B*}su"=>4t…~?uW#dK~n ̺? Ff~蜃p6qŠFϓt<ݴ;&waY&m%xr\- yN]jfJCcxaM!V\Y'}hiAYU@VJe@o*p (NǫzzxF!НU޴ƆVBp֠wiʭu%/ KiKHAkeXh^[x VLz>e.GRV+,{CYEHLrkֹt}>B¥ XyJ:J_)a _{9 Ƹ$UX92ֈKʴ U=2LQRƯqGRji_=+JLl `hn9ʪ3~-%܅P>/RcVw¯ ~7i@ÅPjq}c~.I?J>*Y0J֏mKM1>GuCYm@!1ס+}@wL^6@cM W/k)M0Y5Ϻ CXJeK/ꍁiLfEL~封˖#mg {? U2<עF{A;۬ jnX 9.s}ԀoJ.JILMZc|R ww~AoЀsؽg J)_nBnؽg7,w@Ղ$LuQH!LO!WӓkphX61Mg|ݚ+w!筘T^~A )dj@ԍ\E JhA Zr<%%m 4C Pai0PeYfoeik*(LYaL!C念 M!NU|9MTW8q@{!+RHh%ጟdu+Qa1*!|vҠ߫j"(@mPSW!CUc]ehz,SSB@Ư%!/U Uv@UYtMUiUX*"SҬB R Ȭ B@2q[TӯpV.-c_|eDjX.`|$vD/!055ေN3{&wj n3EtU韺#T~=:uL}f F^=7 ^җy'~Y^~A >+q̧Pz=@UU~h,QwD2N-*5pt4T|76ց ~`9XՀM)e(1ROXqCB0KB  E˔ʠ +fϭ-VFXFe04΢߯ VVi8/<7p*GZ+.O~0iLM**KO0 (c~_htR@`-|’B[epL Dމ͈ua_&0"b+.I%F)WYQ Q2L1J*i?p.G~jS!A0)xzʕa1??1PaUk-Y@WHBdOܶlrP"ku~zTjq~=<;, ꁣĺ0Yoh;|w` Y0&O a{\.otFS4/cH5мnAk>.NV=;QXSv*4:`{n+IظeUB) ; m|/n.[wq`]Hӆ o/F3CϬ!"A _ef$0͊B!%T! ä|*E7x Vq|cmj !CI!T׈РJTWXj~3$~7U=%VԉUU8>׉ =[T~m,h ca*IW<>+<>2!l*}zUU`INqίPCӫ,FJ*E 4X- )/+_B.c|PW|X%^H?qB8@Nd#9TMu]H)_1e;qzuR"*sk/bS礟r&!|OMVo4B{J+_7\ymWJfȱN(VNn=qbzېB`Q߷ ػgRqo:^ZlܰV^R)e 6}kcr$v^"`1!dZaS/ULr (l6&""ZЊuun;0@XӞ>K-|?h 7a*JQ(TV@/鳡`**7I(%l˪ۇH" OLdZ*86 -=?%E+֡E+tGXH4U/|CG%%$$Zm Ҧȅ}xbՈDelo-اd|!*H! Sdht>-2 v/ AA aGk.L l#j,8,BŎauIozTŃFps!Ջd'ݐQl2 L4IǪGJ)DU_I4,lɃ4v<6MdM B& Sjp|XBR1C]L?_7T4+`UdY5De:m!bS5tQѬIɹ;mw=7WD}2 8x? LzxV0e]pgLb8ޛGM}g޽$,v"c  sȁ9@-8&8l0X,2 $lp!Ib>Yzz{Yy{A&n~鷪n޺]}24W!p @;m|?32g9ք7X.;Lh#1p}V7T 9 -;9; y=؏70`4c5hSNT pdG+do$a /*hl/! Es,D(C E:{u]_Y1mbJԳ0s$>,ŐZk⫒jp&M& εd(7F5sʓ}f#D^* PIils4GnU%THT{[q3:&)lHjy^$U5Mʹkʼn[nn$?y]rG\eaU?&^|\o;ᜥz]A$Yܐ89s׮@)EuOl&Y[3]/Xwn䕧!ƀktb .w$GG LN b"S}W@uJҳ1qoy34׆MmjSklꮕRRܸƀ4Yv3d侀%5%lAX.{¶0G#,6=VZ"IbBGJv 蜶Yd` !7#EBBīl sƠYv>UUI"-H#QeT &]׀8ߘ6}\Yʞ.Ir1ArU8SM41<NĬDŲıV3mp>r%La!(HDE!'r D?`L`9/N*]mָ]Dϣ0g/} O,G@RV=o,k"S)Rd9l0 G15 "T^fc@@ȗN/RDAu}^߶o-^_﯃{n2|X?4EV sܽW9V 5LC$Daԅp~a&a}i4D͙n@OeqY=gY̗(Ksgw 11M8::̙]޳\tc{{&\X~1XPWMA[֞˚>b6MmjSpmMuRat2;fl:[|XbStuMb1M1W|hRNFy]F>K "Yfrb+洅E3(Hy13-JwxK<2T "@CT{β'#J$KJk>@(Te@UjJ$uIR(K*uvHB% K)*˕c> g5VN,:'B FSs`@cKG0,*SM2*d_Q64 Rf< %;iȀ( OĔ"C)IEG,F!xd]b|iiRahuL&m "3h(M#eMnb̧ugȉ F5ZG$(0.`N 6 [̶1r [= `1wJvc*%wJ+ g8kljSԦb6u*洗dzCnbh!91ix>KRkINƹ,e>`\&%WLmq7UC$ P{3Ne7h%̞bW_H;e%Mr=Zig1Hk,m=} 4lXfv UG  yUU+Pϊ4޳+| ueBM^R+ a`!g$wc:ޣf<}b>_f.]Sh8K g4'''tG9 8#cbkk"w 1|߳\,;OÈVJL;KǡCsi&hsjurev6MmjS*b6uwK _Y VX XR"} xq :F2)7j$ Zc%ŔST6Uy_@m:{BHLZ1_%I3bz5]̒8"j]^YSlt^i묀8yVdŢrJRQ<**|^vGjƈg"`"*jZS߉tG@, GxIl "x\#M(ZpC_[1'a1,s41ʰJRDSHDV ڨ ?N/FJ)Ig)!%bĴQ9^{`S%Ub۵r9'"<)z._ϟ{WL&+-ȿ~wt2K|H?F3|w?>~+>—Z<atnZjVLCur &'ݲmlNP舣ﯘ}+L>ھ(U+MfbuYJ;&McX&2+J/j9^,wG7o!, gWdXbaNZwX=1D}`1h[}= g6k9:Kzէœ&yegԦ6Mm 6@̦vUC* 2*M/}HI殤uQ\V:{ &neGC(]B+Hڔ61Fv4}1^.keQS qQ$QQFG酵Q3DT6 A;3yu0٨ZcIA1`jCR k Κ̶(f? k*#T #Mxl44cu=¤ڦ@%SLhs躜Y4&L0)mx3U? >?мM(/"W#z,{B||$b'0r$6Db"}3Z;W%fyko{;qrF^h*0q;̸Aޫ[ft'$L2GtR1g,>1Zٌ2S$I+/9cVPuu F@8'e`IXٚ8G6(dn4w̶&lfp|2'k-Ut5FL-]sgwz㈗^:Ã\ͿyB)NlM' $zLaT,=JkGh'٬D<Ҍ{ecaa$5W'T;ZC=mMmjSs6@̦Z0R<.kj4SibaT6Õ Ťʹ x]: IDATdύn=gS۲QC$o ^>cjdhjf6m >X}Eb<ƀ@Ln=oEDTt2I=&F| #_ҬfJ=1!(ЩP^\9IEPcnrJktJh :ft'r@WRKϙbA=q([DKI+K)3#Y6^km[zr?q*Mڿz}&_Қo'z*|:^3^W/J~[uwWe!ZmnVxu noټsyuY5Y A\9*8+Yfkؚ8k5bɥݖ3h% {SRLXӠ!p,rz9gljs;xD6WC9b:"mM1fYU*9))YX1d)xmE$%ڜlLfj A>d)! "$F@'D #fӉ@Оheh5d=EIX%HS(;Fal &ޮkyhaiU1dSaY?Ȍ1;:nB Rm$_@S|YdKř(*F7࡭,!N Lpq ⸇Nsr֓,u\\,88\6F #$Fa#FrT:A)GFKS.UiS:*|aؔ8[%Lmn5MQSjBbĔ)滌"%,f`3R4Jpr}O[cէ׿7׿kdͿE3&w|LW~?Ӿpo OgS 0*34]7̞ Q'>fȜֈߦ1~b:'W6߰?*U--I7& gv9ù-gm%J&-۳)hdB9nc񜣅g&ݝ-:9YvLIåm #R]$svg G'꛵5j1zn;8kvݝ)[S'_.#ݢ o[J WUwvLnjSԦ6ܩ Vݒ7Ed2i+Kf.P1|lmtSlJSuT'B"GaȔhLmWg=R ʌ8+H E(n@_7(5LW&#Q+n(oW]|%Gc7:~DgB:f4!L 6K[_'zu<[r1%:R=@Sfd9)jD$4 MJEf3'̱t]`b42Fkk&^^d8ZVMYJ":kI1X,V#Z=[YNc>{7,?U4S>ww/v6KswpxuiF35a¨UɑEݕRhҤ"e`㩳.ɴ$`oSvf<`XSMQKٰ6ֈlٵpΠtaȈrl%NZRRנƈ_b)~!"ઠ#yGo^Iqqg>$*QCCNI*w^  ?w&/<>F֍A S1:MfHDQ$e2 ÐT`I fcNm@1f# &yA~LM)ӉZGch$j|?Spya$q7~|) <=Ǿ3Od^?S or THB(_\UsVS?Һ  2Vuiv@KyZ>F^ʜdJXł|!Xf}4m-r.xgZ)t%t6hN&t(HJL}qtx.#ѱd{}?Ӽ}XDqdNwjyӇCgw@8}*/8˓7yCyso?ߝSyKǓWorvwFLw_*14\zD2{4&v12i废;[d+`̳6Y91tMn@MmjSs6@̦jcqΐrYLp !I{mbIP9]ʘPZ bIQլ4! z%ӷSfl[dN%5T)'τ UX7(< %q'Ĉ p2 KrE7)Xɑ4N:s6{^2%AU`ۘ/Ũ4!Ea M; XfM 8&Y2}}%< CSM50|yҳs&pq3SMc"}յ 3uM(YcTnR^{.=pg@(e Pq0-4>H4%5K$fJ%BHuH|׈l$Uޤ 1./*UH2lr_ , ͒@jv6ԧY~Ljt{'GY/~.7<Ɨj2fԾv HwG'}ms PzĈ30h "+Zc/7+>9./(m p 2Z4 /sv> g'\:]Rj'u"t^µ 8m+1d3rh5Sڦu8:9_7nae`yĘz8s` k {;۴M#,GlOx5>7'F]Hu_l;ăWQSTM?GMTω{fX3FLի^}1}rʕ7 㴾U4ĩe7֌z_rJj0tz* k6샴[%/JiPv-gᡋߞ/2kb9M~Z/hXE2hkҨ̤QJc&G7 ^ pCyqs}gQFǮ|9&ַzh>g]@YγX,fҹq!Oqۙؕk;)&2_ZK!*N;XWAӀu7MmjS7klUa&=))CT2ڞYMvdR")i2c(jO^^,q1M d)> 5PeE5v"hkRƊD`uC1EG*iI?*m9WN2L69{i\մ6.ogHe2{Hf "fr x9gKKr?^vu9:/&Z˹B b[Q| ǫ.'4l"\FsV2 v쪧I/]cL9E< /k O!e4}1Z[(TR֢/#̈29S̫uU*琢F$€R4*7'+rC#ʌR6j0H8:-sMȧ/g,LiUM:o% _ڭ:2fb qخgZ+8r&VvEqr>F&lOؙ4lh]V;^h쵈V6G˵K (4;ktʤ?^Kpl͓]>3.'xϵze쇰ӗ/E[r9 xً_'MΌL^p,ɼ+oP#v6 ^t)M*M;dYӴh벥u wljSԦb6ujE:cɣ+>TsdRXA;G=^{o=%nmaXkP)tf6 "8Z0D&לHd \!>~Ѩ]ډg2 v2!uK3gŔ[/ڢaҡ!Fc~{ehdҲ=.7} [ӖG RR, :UVB9Ѷuҝj)3~v:MmjSԦ7uVJFID5F5}9j:?`gPd2LLNj&r7I+۩6GY,<x,=]t( uUla'N#^@ yEnĤy:y˫x*' kx؀DOpVfzFk8s<29di#F}}>.翮Rj X@)Qh6`e͠|&)/߱a `hɔ탃g?!{|$ NWx[_SbNӆ/!g_?/n~?~kx2:UKV5j@SVU-pkFkIJwhĝgCR)Wju"Wc$x/mI:}\`,I#BQ:uc :6H}5 Ek]k0m6$k֢&S{>[cϟåMlN4[Lvhwi3t 4Lv= mܤs//^%Jq#dY& {\p Iåٙ5eZZgh8kpήzVi)O)'ueljSԦ`xxhXAdFK#RԢE0m#`S~3$bH7:>^d@Ъ{yNX1!d!7''K'}oJR%:7IX6{5dRlun^+ bn{iک\4O$Cfch'-":ARޗpU%LC,"=2Z5b(l&D n)m(!iډDek5$0(!EOt]`9^&a %B;jP39:/z_y2){~UvdGyF CF#Jhy҄x@cNټ|*I)JIJQRpmӥ@=-}k '#hm0F|a ro IDAT^$_b;HuTQ"A(,I ƌ$U%ޝg*_C#_U|Ɨ}ճ_z G7D lf{\|E|ÿw]ɍ\kZ>67z!?k?/p| l|뿐oߢܿy:oKu]Nk]YV5>))/; h|_ >-5&Td qg<'i9LL0lk*fgԤ;´`,ﱦ5rFQ=6(0)= Kk4Ԧ6M}pֆZF.ibca{JTOF+f9"w=:z,}KlHX߇i u:=ct/'A l[%JSOXY4}99WmSmBH>fatFB U@&iЃOb '!fDdZ1P*l֛㭍.1L˥(<:NdSZ0J6~zI8i 6Kf,ē$56<\Ku&\]W|/杚,}"HDKIq#^9_h93띢AWft.jNd]eߕ:buXuw-92Ddi5 R hyfVhWv$U_"/FvVSo敧{\x *?cO(J_~/"n>P?-_=?mʸɈF3Yxhʚʒʢ>s0V_ Vڿkf:K++Bl"16mzh[:_J|s%`m v2ŴmjhVRkSLӈuɔvf vk[~7'jkY 4-~G JgD=hYl4β=iٞbZe{ڰ5q4VW#q͈P 0vLiǟAxԦ6M[alUtyҶ-:{|xR`3ė%Hw7$*$[ZTCFs1GIȣE$6U⡵B%bDriҌgy֩eDDntNQ-"M֊D,Oq&a t(NnE&iF>FlB;xHL/ V+O$W)l#-(eŋAY#">4ZQ<9x5sW߈bJJeuS21Yrdl,ψx/[V[ͼ#>O/#O|tƵ'mf~o_\0>G~m4o7ƋM ̏*@O]%297[X6Fk15F{`锕f4|3b$, !-`%0iʼtissCuQJ^*A3҄k]#]'%#)N_Ot 7Yf#xh'6ogzTLq"MF$$4Fu&d/$`0"E" .J-DT^R$34-\ H*6173tx}C6%Y NR\e D\`YOMZP ble J IWh ̕5ِhaOt=q攁ޫ"1Zc=aPO$Kh$`>oSXWGoNʅ(͏۷-#[gc[nM"R+7S7 Mf30Trm4bl-L(Q/N,_$U%vi :L9)DVK {km}˗t ˓9ij۶$0J[DYV.F_vh*^CHFm}G^l:_xL1xL kȊOLI}G LhXB牋9-14-ZZh1;^egoA8cX(/`$uN9?o܉9sMmjSԦ1e2: pE`9HS"}jLJzAZy?IQCc DT/Y࣌+aӗCeh'IA)4≓5dwF 38#l1U>`*H$LL*{jJX#>UK)t,GBnc$mIY dB@eyDB6M}$96}*(s^l b"'~]|f;>a;ZOpvRF9%!{Ƴr6`JE#0'Z+#ġ,u=>|HxmVd& 2pGrb[WB B5<(zq HqZ$c/Z1H72ף֙UVƯSNRJaRʲ{N3GLeyld敯5|$ 2:soQwS?iSYO9Ƚq|pη 2ףzbN ݒvf5amhOؙNv mCۡ48G[ rx"E < iP6ĮGOgLY7Sba=i F''zO"f2ɿuwYq-.AR"mbCӶL ?~vf-.{Q4َJj`8N0\KIMmjSԽS fSwb3j,J ,-!LkS5!HR(ޱw5]Hkr 1Ň h-RYSF]a #D+Y;L 1D'H.%JP Gg1&&=sD0a&!ʉ%"RP5'1Y&V"/J*)ykŲ'D0:AIu&1e8:JHP}'F>#{wo9p ˨0Spq^q>45'pfjx`;}S0qp13Mu QJߋr0cbԣfʿR(q,|>^qh (h,1Q&ˉBe4f_굖|q$[*ˀL)bM + `F3%bRfWa)ߑ??= ((Iസ[jX[,eַ3k54Hͷ@f `XPlﲻMwrL &3 5YP>x9[;;"'b(oۖ(O 9l6zKF#OKcIR )FXO =*LĐW+E"ӖwDh%Z ޓ4!=1"]:G ht(7LjE>z"),5['bJ뜀SVqZGoJk?eSԦ61kB~%f7J wAƯHTʉJ4`DmEzdQ#)egq,18Kd8&BV? .8>2G>DMX,WXJW k?3k;4ݦStx;Dx}|/u u!̋^yǠ4]x">n ᝿Kz}ʼn,_)Yl1/+?sdzMmjS1kU:ZgN+A1:JÞRk_ P)jB[DUI>%ZPI)$2%3G΍1!<pIABcmK:s=7ʙ@  Zh8أcQ0̈3*ݟ bk+"bFPEUQ9 7wXkax޵'T*.siTd>8+PID_ِJ҆ %Z u>('dFVe4]5pP7Ji-HU$J@AUF$j'Fmt6D'̦EBb'-kٚa!#}%{aó>'OɓT";ƽs|_v]"H_syő >qJӋßޯF^xHaTdsJQUc]U3YhRB!Ƙt E8Ni9%p,W Mzxq4o-iNӬ3Xdf@iTQ0z&U 2QhB$&:Xm<'-u Ǜvt#nGiRj@aVI.n8͍o^߶9[8KbP5.RVaQg"Vk21ױ$ieUIn7F׾(`!0bR(m1`CJGsUAԊ0@<:INA)D_51݁@)\UȨD3IRFM´r #E1$D(u@k(*'r`YX!FwЌ`a9cNÅ rEňTN'cOǩW˾:sOn>_71[m.*kao|kYjVϘ1zʪNDQUU4_߹xY(,P %E\@a,Ȩ^HJ@:aI[qbn"8IiK1RWNQZk"V ALL)cu; #ߴEJLe*%}pf֎`%Ud:dz r(񘉚?'Dk5e4[U"^Ҵ;2] ,;Ѐ.3,!,>a]RP9#:6SriBt9|0+++( ︃mݽ=kqײ岅ȗ^k9#_yIC{$M;,жN==ZAR%CLc 4}2? pRo2H"$0%%ƈ5;.y-3Z"P%>zФmeM׫Ndlϼ  3gA5 Ī eGFvK[bc|'7>e{na">bUP}op[̵77}^άf5gǹY=eՌ(z!lOko4!x>ѳk1 UԴzՀ"z0l2퍉0F5Lz9L_lba4~UNX">7J<]#G`kV Z1 ;G+ݤ#-rcP:0㰍lKL*1HC5dF}Lҗ$cS* \}s0d+V* qVΪX\$EyJ)ٽ9_eganz _c"Ώeir:?*B cYXXbqq|յUl|.\zܡEv+T[s?u<6IED]2f4؉4*ĊV19z,FtyT fِ<0q*h'h}ށ0*6-02c&o u j,P*k9Ncn#KY܅rkܝ06se5f-bYC^ N"+hO6m *~ʑ`fWG#,RZhMOe!i~^UL㪊H,}?ZTZKFtsZg~ma( h#Dy+cccHU91`ksmwMlf`3"{![e3o3>rŅs#?uqw} 7}7zC9YQ3 fVOi5Աc^/#cGZ"׼(/MG¦a N7TwطnI@#iJvr*&uFD=(@HVtՔhbt"d#$"I`wi^J 'L & 0Rd2 H#Yhc2\Y7c I N*Ϭp.e̪ԤKM(P 7> ]Nژ,Ie!h0FNו})[,pcuu?G9q=x#ص@S\i7|զ{1oQ*KMHoe TU,{ 11:I/1Po|X""ﳚ 3)aT)ZKӑkIbK̛z+50D}-@$4JϼY>1z0&Y$cxM$Cɨi63ɁWl5 0۲&{cbYfÊhc>{Gݦ댊ʊak6Y\ IDAT\Vm :j6KJ_U&gHرJ:Z%&AE"hJ?#jI$z'c:#æ1!U61? 0D(66(*Dƚ;a`:ߤgFT"QY b̒9Ũ*jm{^vfj yVx~Foa;?ˍuw"7M_m53(_EEW~Ζ=YY3 fVOYdDR4G:wsŽIƶL$&ԻZK|4J4zդ?!HRbH) )'oC xHd2.n=$9zA Dl6+!&*x-2c@jl&G2nyVfYb1%LQJ|ijtQ FʓVRH*jP`3arTe`ma^?qkɺ(*w߻Noa ψ|EM\|g5VEC갸kg8u̞=y,\q zg"ZiBQVҴ/%sfF@Z.'i R$*X1TF2r5fZw>Ux(w.@9'yCT^7ҩKf2/jO'UQ(D"{ uMJfjB 5e63G'S&&9NOcsl`>ae1ToqUXVVP*2݇0כg-g.sk[1),$ɒh5:.ЙI,N2ڌXQHyW%jZbL&2*!U9jPY@pƈDW%{F;_]% QTTH{g4\`P?8CҌ GYT*Y\UM_UizEo+Vr;X^]_Ztxu*L# I2DR5V<4*ݻ*s`$2N ,a SG/R(|)_ϕ 5% l'Ml 9qvvrl{zl-hhI1jy(65ccyj;`deۿ?FSLL7tE=t|_yVXYaNCE8?Ojʊ^kmcHmqhta\|lbhPTd W &%i!~^&v_B$8Gtt/~0J GL%k}b,x ҳ> b0?XbLgäYN .mY=c}/GQJa/Aޟ! A0 *;^`zaHRA@ΟO ׅč5?/~f5gV̀Y=Xz!&ylLqIl$PI!f>Q5#͚f$eurҍFӼk&6XwUSKtQKBD5dP GL;i-LCSW6K D+Ѣ1IBh5ḋ5J_O4~3yKӾZ-iErMꁘj)  5Y۱6x󍟳O0f;-gh nOwf^|_4lˍuv__O׿!Zrk,cBܲ4?pc^GA$HIf׀.UIYZ a:yeV_Īf{wa6O%$&ZtNmD[U佅>!X`Yj–)Kt2_ak3Ò~!7mr+_ HY tz;O j!W|+p X~e4Ϭf5gV̀Y=ƒF#ҝCD]Ҁb /%$éO[GTOҡ$ZǞ4_7$u. 5ϫ1ȀP"[$k68nN Aޢ Dt,Z(m#Ǝ;*hfzaRrV 2^5(\<6flr윀7Z~Z+ǪN㮊eGuOfT;A݋#a#Zskk'DOU%fV5l4Z1nSE:ѺEؗ#Ɗ~p8B6S*PbT5l*cz!)rH!nmk&;ɍBbeVqы9&b\)&L]%Ulψ<Ƭ-ykO.󸵝KToibR$XvT8?%qj]өI[g_;2ohz2;mn''ߧ@{-Vg=\y ]t^G9CUe!`ۆ3hk PڐuD-J*xl=6GwPĐ1R j %85+g!*hCCGM pȳÇNZCT5Fn7G% Ӵ(K f/cRvLɔft/27]0\Etvzb+{,`ej%§MeR~] }?zADW17-bnxZ߬f5̀Y=eqBD)b ƈj)8e)ƈ#I#Ҡ94nB(\3h>fǑ4"$[4EbfZ)LV<5H|UG$%jJ=PG =Ȭ@*$L("Z)Hm1ZǸcdDh(溵LZ6$I2ȳ$Ee)3dV5TAU)@CfYCƐ"Dr{! nx螳 v]UΞ^ ],:NsE ȋH$$35Hm"&8(JmL+`mn7c~~&3tҔ:V\, /}z|bs=O2ja4wB'ylQfH}tmaC)b!ЊI S)eP 0A҂"6KM- l9k%qʫ<5ǦV35)sy{ɥ#wy豩K}y;˧NNH&=OD:l9MaG[Y6scv俓mfd`}VM$11R%FEZ1WsiK+oKKi:lĈ+ #l2҆IFHwwU% 2"Cx$oHarab3P eA(}&P$d9 PS(ˊNo=*hwšQJRFC4rTasoʊ1 3ƨD>cq>6RKi:FS|4v2Y=+8-Hta̖ZiYWMOzt~?1-[Cԡ˞g5Y=j)-IxI2XNZDQ:O {A@a7$6j,0$#אФ똤16"80ڦĨ1I}N F"pd8S"1@FC(DՌFgG($U(A;DGg4 b2Nb4d8UVCZچbX9 5naۄEbDo|aie|iVOˊ %:?+=s$7u1;(?w| ouWk k/l{6VGO~bլ9 SV<7zSFܺߵƎWh )1H$UDq(q"1bm|YYRKr|^VIrIRhh̅uhn8IMrP$C&%QZQ@UFt`N1ȢK3ZiJͅ,Xԁ/*DIZ FfF5>tJ7 $ q(#o(={\pPѡ9:Lۑ8 Ә9{|{xK&j)-`QY8JGBY*Yf0*!TVcS"x՘& 0DKQ{-Ka1(ADJ !MתMQL:y2H~676JuRbϤJcRHriߦgFm717M2Tl*cL1ۀq`se{ƍ$Of˦&h~u>ٙX['r wm@'5D5"}j8=7G@3Qh0i[\}wv2X0JwfGpt`,~09*x7Z%"1!T(#@ʬHkMH3lF,K[{^J́Q3h@Pn_ &ƫiu穆#Z6U ,p*)Kd`428s~O/CdnG9JE!}eP)6fYi54cژ[6 (پCJRJL:]CʑOJtXajM}"$yHRVj%!xJѧFTKTU\z<6ȧ>~ "W^u>) GmZ,Ec)`CGݼYbC&@`) }DB=0G@y97J&_`'LcDPU XaVL ahuEdlk@AuL@ZDYK0jߖIKCc Dֆ ^/Y5,^{t8Ats=|q;xLO{n@ǎM06&eMvUxVB J ˡȘAi e1n e% ,#(ZPCFGX}hpGi9 NNY9rksE%)vR%;@^?َ*[44lY=?zBK᯼pAWA9(}s˾ 翅?zS1?1zʪi ;tNrTB9SdYz7TIF#b ku;+~5uY 81b0 ,fVҊ<ƈaDjM$cDd6JCYx d xNiH6 KOL=F461rbT8Po9)IjU*fJm^,_|F#\EmpFc>6҉Ye!tɹMeZ k. i!:LY@t\b췏m>f3(chIk L%݀ :W&L%!7ѨJ 0| LDjlƂCbtݙVLE1Zی*$sg$7~)t>mȘEj,Xy4o5:92ΥT҄ƈ c5;/ʛ&&N9n9*=_a>`ׁ=9')ˊ b 28G.)c2yzQ. ]q9]P>۽6z2pVzZT\_OgȽ>Rdh'3([TĪU .M{Ǐ2x^z6Ծ#O|ڐuȽw~ܗLof5gT̀Y=e5~8@DdN~,MTnPo VVyQgD/DJ潠(e]4fTv=>`כ Ĩ(Fc>CĄWXF#E!l5 VSUA^F%#F%Su*=mE! aHbQD+XhTɂ"ŶGN?z+~l~#_u IDATt{h JcfB-FcTlyl$q桉rDjRN2x|Hi y16Et;yҖi-7j;Θy7,ic6R|#-눛~x4:o;5>Oѳ|,VF5*;bnk$T `m,DN稬E@v,+I|G{7Qqd>.LL1䙘v:;@+(lmOj;P9P hvH"c *#bwkቑbAo1-"+ Ò,g0`aQ -UYR%QIQ%?{sKŢY^6(f_TZetLZĄykfV? #A qKu{d/yM%0*|i2f!KW;p^B񍱲.=hbR(e H,5d>>N&-dI"8p 1FaVMs#=ͫqՍ/to[ປ^CiN]q%{\ {ӠLxhX>~hFL]K\~sxۙ[u/z1KRFS/d~^FuWĥC6A<ѠPvk_"@1lp'?A}]O~2] Ȳc_>)7Eg6y;GKYm2掯\=QTEn)|}s}GY, n;zn8uauX{20ٳ+ U̵\~p?J݋Ѱky.XbdYPU(#̶dDWBtI*E`{\Y\`4SF\[*"ƍF !;B'}PZ%3Jhevn)+G9hzmK+OVV ۜ͵,Zahв}Ɏ1܇ދWC1^&KiȯmrQ_c3٘Gp}wGdcA-FsË^۰fZC⦆n _A:?[koejVz SVy:%7xIIlbL&E|cZ˔Ui1Hd dXI|ZF*YH#YA5ZH1I/U^эI1u1WH̰VubbYJJAI(Vd6, Zi,pn,qCGY(kࡓte}K<+m8z=PtZD #r<+isuW!5:Kyg0yw>I.cy'|YF#uJΟ_a8&r>6o6k6򙇎3KqѾ]ݷŌ X`}cHTP,}PaͿok(3p {@'\Jan|?o_򁆉؋QKÿ>|әkO/.;n~?y/D_/` fŐݟ=clf5_̀Y=eUKBP"!IJ<ıa̎ kYa 1d4F [GI|0nZj3WE* lFb^֘d\&EXWQMn@5c4\d5ELhQE4&$[%'OL!דXѴZeRš$5R*VJqYX=]{%T,qr<$JR4WyB8r$kĀtAX@$ W$Tv֙;2Q T _n 8Þހz F&$t' U Zb? e@W5JBd=k`Gǹ=RyȡH)j6IZ:*Y9a#OO2tSTzHBTldQ6ĨbQA%I]TA7ҪgűGT=|w?Ye[}֪ gi 2 nOqSTx]h+We/^%Ϻ7׿'e_o7m;@n7| UYrLJ>8!͚ӛkz!~'b0:SLMǾ: 6m#)0vG~(F#l>];̥v0צ zt(7a0, \QQr̲7Y}Or%T;×%*F{䭶|1&4( a.AgϺ%I>gsgٵ_sɳ=ָ)Ui\{~}ϻ9wڽ{ū9װv,y|$W_5/">fMVAu˟<>oeFo.^_ͳXF_sG?H6Q݅F>ԃ`k^Bnxx!s^r>3~#/[cdq#Ŕ4TEWdYΙ's.;VfiiZV\ڽg!iel >hz-S}ـ d0ߛ诳o/6ˉ!`3"-VW8}~N{b?d<>ϙQ\nsdRG޻}nxhy*i8B;'oe(藁`ϮgG>$o,wNtWצmg;َnD|`K8g( e_|R!7.|~ȿɿH pX9?6%'v K(2{uoS=t[og5Y=j+dM1M2 DZcmE+!h@%*yӯkH3&MJ; !x_cހ2 yBA})y4ZX-Zh&Q*ٮVC5IF|j|W&%$c$zZE+WT>RM"H(ʱAIL4^3ɹ\k#E^Pi2TZ2`Z冲w(,ʀQ5\g=r#4U5 #ׇ:T1p%Eƕ^F2<&3k!ric5NSe/|ɋY[!zϼ.٣KV;9 Rc[_=h+2&1ĘQ914V}dÀo{asw?}Wgo&{_ā.ȁ.mپko{n>~)G#@>WyO4Y\^~E"x' 鶩ҳ*KI,q W:˝>y:kȹ3g\z`sYxY m1*vus.;5:],0ǹ!-alZ9!zw?{'Oc']q3 KEZU @Y9\aE+tk#V\~]bq#0un`Xn#ehe4hzZ$m_-lvb]-o߁L=.5oQ'bȿP{(~g/Q\}.UަgHpWBQEYLEEZQ8G9v(ȱc*ljDFZ" @Q$$Ve0Kݪxy^꽮~8ڦ7sk3_4w(R<'լf5bf)=YAO^ IGo/͌VC"x1%yRi ^%@KtjǕ$MGZOD$Tʫ$J)k ^D֧EXa%ZٗN@m`Ad:hi̎UmRRw5BF1!T16&Cd*E]G\%Pi2? SסkIDPc>w_ GZ"/bB5q=?RL!j.Cg5}5L8#G#=I\p=>H*Ks!,a |Љ]Ҳ$yOl| z erƃ1n>NRdu RO:ET$( "0)JX5R1$"̷ـn1Nr(djWz|H rctK?OkS$M&u#'&K9ȗYp~=E\]q>۾2#2{qs.uS_o  %n6 h0 / l'8~ÍuzBWuw'5pimk,Xg 6W\_+ַ1F._Ha EKs]z*"С$mF՘:8%=y7IY{Uōu㓖r1J9(2nynlFrhi-㊭a6}{nla $Mx5n`efb-ǿN_~p,ٯu3dzٻ*tzUqz#Gs˫~g5YK̀Yk5->ݭڣm_|u4Ic&)r"F[a$_RԵ!aF^& c"I {$3I5)b}jW7WWJ 1)m+@>(Lb{Aa V c;7k8ԵK)I9M/LB0,$?=Eǐ< `c }::#K %-Y|S日mdjD,k !/]uMJu&Z)V\IkZ#^'et5jܐ .N_#ԵC'O="$JfeoY9h[bijwn:="`kK.$v7IF8a(- =v-;{F3HP!Ubt-YfqεgYs(9/ejSnʫ| Sdyc>{U=O*3PW2$رt\ h F؍W9=,3C&,bfr:"F) -A"e\w4_\!gPRJSO?' LS; ! dJWѿы_`iQ9Gai(K-cJ+EϬI6Vt{n}/B%acD4%DY jD*ڥQ%Hx%>ã_>  IDAT.ƒs_+% @U^y!svX6vyU]W :v= E?V\,)\0 ųIadq'[C^=q=FǷAc,LeY H=;s <ⵣ&LϷ&~"/~O?3=^ zSn%\0_yk3qh}5L9+v6P-N@X<;/sW\zƵ]e;0&g/G>˽\sӡejeʠ[^s1|q[P9Ǹ]}b'@jۥ9,cnnK2(8<ÔNtZZ7y/6X>dZg<FC,KXY 9tQz tНsgy샏ڐAY5p39Ї1:'5Ǹ暣7YјJؾVP4ѵ#+%r"'in"u@%FT)1JUCM>5b+tf, p$"Ļ @kȌ"jӦX[gZdn%M-F֚*ILJB{Jb 'a{΁TՉрA)-"u+ٮdqb8@WkX\,(K1ҍ0cH8#0'!NJ4l#11bM+UBYn}p1>{ݧ,vˉ\ 4L9ٟdڠD W&@U֔G6P56=ۿsЛGp*C(T D($CG\0Ϳ/kӒK,$C Ăjє,1n^N1wK=x-h+SWlidسݫݽ?c>TnO@Kbۡl?˯G-o/{5wU7\ N Ǘ#6 (9s~M7G'2Eh ''Vx9L "\9s~n?':YΣmFp=n0ܓ JտƊkYn_研"Fy#{_']=G|Qk|gfT֌FTgF+Q)!)Ee\P'`H+V #>)>s;[үr) ?/~䯑ۚ%kUt؃ /;BDƫ͉;O]{ #-ˈMV/y]@|R/կ`eiaoV|57衃r8̡Ez&phqnf-`}03U5[!1zfm)b&O3|w\b(Je]ӂOg!}V/)40&}x1?}VϱozW=YU3 fVVj+U6I*vU_)%8IC0Hܮ014&1ϕ$Q2ɔ+)iIhQZ0sBPmRJ]J<1!(rNWVcHIj@ܴML"m$}mJNNfbh5X1xGK|st$Əȉzr۲R2sI'Uk,l3@UI;nGS u`\.Kmґx8 ,<:0"UL /S _sC/+F$SR7W@i(Wy[nOS:Ox%yQ3(F6Iul_NdRe~^L8ÅH4!8>/QzX?wW,1/!Ѥ1UF7j45̗ĥ%3jiAoP12S+-ZB>ڗ2Wz|` f\+퍒vi?ÕJөq5uc&^O毠L0{7"1D~7v36 \O%ј9:Fn|nۗt(Gjx'o_sǛOrg?Gb9ybo~[x׿~wO2W8p]pwٔ\GzJ*^g ?Ov{ D#GXX\A+MWBdNAY\YXXd96-`ߛE|y1ƣ-Vz? Wpw;8~ O~VEiNphZ淓CNX`73^9z8ZkƮƍİN7vyLj wxow>ӫ{,?[\: KH| G{|x^p}P;zERNsÚ.e\a5IX5i8<յ\y&YGS2ݟП~S?Uo}_G_w3zVՋ֖%g*4zM-D 8' Jkb#{bH[V;y'R ^wSX3jjMkcdFpLebxX KQf!1h$~y4U$3EvDJZj4LXh0s#+MVTO]Xy/t1;H )n;b onW37g2\[wɫHYcy1Z< \'ˌt:*TJ]J@JOZah1ыPE"&J_yiWMܹ2& ]B]KG[I."r, "Yft2:]+V%Qv[dڡ?1|2_,y:1k}L=X[lfRv3b,e4FZ L5=k!w33@Rկ6O}IRGeVt5o¾?Yꫯ#fVVMc'f"!Q/idfN|W@c0*bVB^5 ELz O?2fN+:xa 4J2JoLM[1Hcä>$omD~Dk/"sV) "E7ωxȍNҦp19 #u%:ICݲ)Lfūh<@$NqoS9b rn1{8h4HySH98[)N@K&RJQ$OBUHPHwh+#^v^#7`2h{̹/_`nPœ2ȼBP:%G%R2w0қ#s` 9690 @Kv%jAd=e*yLwfK# Ti* ɜU 8|ӑ`uO1Kx S$.S_8*ʴtq>6~u$tRvOG_;3Y%V3 fVVKmRtcg0$$ht>S֨'iv 1k߂ICR5ɤ eu2JZiCdl%TM`jc/R@$% PV&U;omMfK!7iUJ$ZStUhcah>d(Fw!ſFN3-9n EZrlRRSH4yր0H7&9u6)nVau"T ΡB_ddyQJtlBd#iM@huIs5l0s:y}ʆ$ bbrKO= J|]c˗X8!ϬvxUfӒ-@-`ba'Oshq}J3UmF:=b%%q(ǎ<#8 F ZD%"U]6 ,7/0H2&UEPFS#Ê~8mkR^~+]s.rcIJde3wZ:䋏e4O+x++K\^ݤ?ߣ8`āEϥkc,FKoΒqwh=c'N5R|XW2R 7ӕ}wϝ2~+?Yꫫ3+5Ȳ"˅-"!%hL_cR9ZLLLɬd6.yRyW00LbUhmpZoZeZV$9  Bb{ůYܨXVfRdL dDqk5Ɍ(!Ϝq$J`HԐ@itWB7 IudXZ@(*#y E^@I"=Q#$QdV l=>Hn3 6KHb,}:n/LC1fu{xq^lj?}2'8&O=[~\wd@2dRD:1 \DM *I58ɺ}^sATUǩe) (KYX H Q}2ThMD5 3BF&HJ"t0i3DJ=c* aʰu{'c_ً{~…]I:W[s޻> ڹ3A6.]k8W[X`<ݟ37wP*,W/{'z.>e.T>>ĭo|++]GwnʾC7_8?Ha{}Sa =/nw7C{wc&IT>Bޟ'+Xv/.w :&ӛ9y|M TNoGj<\[(,ĈrkÌO2ZdC< HՁqs8a FC{@*6V+&}Fȱ7pkDv겕0EC m蓳x O<lT՘.00r1*x/[F+nVĜ6dN>0֘U^>G3F̾V O#fiΏfw%OE}B!MIdg5Ykj*x/7dF+uZɆ1F@Jd5DY~rcToǵFn"J34<.T;eP:f?%Ą!z}AnNmd6iXMR1O\Iej3E]Sս8e>r0w ;L_ O|/};Ku̓|WW|O^|q>?Ez=}!0bB.>eb ;Rd/s%k!pszqZMɱ22b&*)=c,G{]sVJeB{DơGEGnEn! g.*u5s ֩ 89s^˩gh<@nG[%TSo2X9!{`e>" s}ooRA_|^א+taNѱTe]Rz=GVpMz}橕K:oZn5 &B5}/5%sSW?{ʞKcY= O8sj_VE-?Rȯԁt~` >~<(}_=Yꫫ3}+=Zb 4ƭ%5(l@)ŧ1Un(bِ4L0@TcQWMLt}$4Bcʆ7!Da`}/q:On:V7KI[Bg9t0[[Cj\_%~8߂,Y1065$DRT\{b"Vk9lѣp6Uk<09DHia^̪o+t\u]- &@!ɦ) 湉1dF^GːyK}Ji+ӤIs LjMַULlo$ {[jRH18},y> n uN?( *?1puR.+N'Ia6CeXZDڐ6+{;B]ĨQ6ɩ}=B /'3N#z*?Yte)u:1R %ֵc~S,R/R@kE5Ī•%Vy);z^yq}9~=DE1yV&a:wu.h'Ls~t=O^^Yw{`])E}ü/OW5o#KпxMg@̬f3}+DS5f8Ԣ胴)'Hcܦ>AhNQUbXHNHv$qO40 82JpQ؂/1**$TmP1d-K[@d+ERBJ>0 m5ƈtJzEaڔ(QUH9"hlmZ1HjEYG^{"W] nO"C( x!)-We*#eQȽJL5[djrb d%ۘr3_6d9$qPsdqu,% jҜ(׳5Ue@j,?[/\b}cĹYZ>FirFcr1Z$1EuJ1AyNZϜFΧ=7D$m[#^WOJ%aZЬl|h-P<~e|?oۨ#<*򶷽Oe8CIR m- T!Rt=6z-`J\]GYj q:xDtQ8NqE>Fџv{w0$sF# a~ҏt ૊:jTbɮ@G VON(D~)ԩ=>/_+/ >R*V}?y>xɬf5f@̬$GԵK A *HZrkyobDX1mP#֊Q9YF& 8Ҕ"d-xx8*IWj EuDV sɀbZ jAՄ2YpVRU"DzFauu%>4^:dR"? .鸇]_\c@XDJ;2RV"k`\ 1愵SΉZSfyj b#H!KU#ȱ#sz퀵kȏgZ8,2/,[,ԔdlnY%V]lfX9ͬxx(뀫+B*L(rwvI!bٱƈOCB]h\UE>.E ཀ< 0PW>oiI A$F٪IQğF>>6` Sj O\Nm.T3۰ڗav3^';WkM8ZFc?~۾UP-nRW#\5(lѹ&Ě<T zDu1YFp5T1R%D[\ӟ/X \p@Npd袏+D稇[U˃!\"ekr scehC>%;J17ѳ}qevJQt:R%u*k,䙥"8jﳐ|b2o*ySn3p3-}tZiC')ß!CK/9լf վUUM4) (åmJRԥoh/vGl*-,AUd(I1ύɯVPUye M75_ ,UmH LcL<Zr5^$hvVHVHBQeңLbD26ɶBrS\'^AOw$'EZPtSt:Z$6QƌZI48zdh*2rBrk|tzyC|ĕG֑j9EYtwhad ij W%[8+∸=c=|};Ojj8&-8%v;a$`9~ *%+uM / ԵyY=($ a\ugc$!v'$Z3dCTVJxaJ2ή(?Be 1X#2pc6D(<#tHt’q 5t=TTxJQ&1em`|DwEs5#pn{H5)u(,{1pHNv͵0Y!Zm4)\ KBub+ȍqY8?_T2(-qNMK`Bcx[X@Ac6LQbhcEZETX% cAd=1P5E5FYMR̪VwJ$pd4HUNJ.J w2´VqNHiE7\^"Z\^ hΩ@'*ZƉ[$HYKT1S5ELTi0`bܸ5@HNk;LN>ɏT;y|m^@̞|c ;kwnFGݓ s&-d,3ewjSr f y.Nns^ؙPu0M5G\s=?:ӛ)CqeI=.1V|sg+3"u;UͰY?yo`11C@XU(J&NCKFK?ޖjr\2 %q+ъd$/,_=)I=E83C#OtQ}~zJ?㿉?>>;Fkj_K'OiT`(Mn3ʇtӫ 1֙5t K#i䆺+/}VOaVTmj1֢4̴!(rĞ1_"' XODd;U۔7 "6AiLc3Z@i52ݞd)?4Ʋ9.Ul=EqhQe؉1`$_Ab QH 8?H@ fH-ŐD1FK"en8C93=zY-޺U]=Y(}/u_jENj s` Եe^L&S9(S约p4Fw dG1 Md%j0G3+l%SUbLۗXgmP{;gB:*tI(*^8>=1k"Ǎ{ (.[m4FI9!ѱRW4&tzJc Rfh$b #LLjըdir)RD ȘRZ-Fb\bP*)LHeD UO5{BiYS(U%bZɪdՌ75 lݥ',YСjRQ9%jʁZ6'1\-ׯ|x_Uu䳗>?rbT1w]סihbn 27@iM^X0?o6񩉑\Q$hCUXkIu"꺌iWՒ`YT`FL)a2mad 9+J'R!flVL Kkge^lJQU%f:B vLL0ƕj"Ea2ZQ~ak$PYPb+P7Ҋ#%*g䥷 fF#Y,өȷN1xE̹$L%H8ց,҇ RUUb>yeDov-E4!FvMecS}Sϼ}<;Z%|e3#è@)ߞQMY&&)IoQH+Xc1IR⹳hsa ,:U桊|.L'Y\; V.$VXKK^c^tw[xF90)%J<3\9$2V392CuB '0*ȫ1D8懒W0CX )/ύ[V~0:yp;;o:ώ, X#̟~x豇xۥs|JAcݐI{Q)3%K\A6HAX)dtġ#Z l=Bi @֚U]1UŔq!0 ĀopbjET1Fm5MI -=A)ap =>BD|'tx?iN8(}MQZ~?KMiF?5f/cw]Z7DKQˇK EV$)j:/"x"ƍ PhLFZvO%9*h*hdk4VS,N@kB!a I=xdV{du rdSvqg#/x<CΌ/><uoě |- yyUެo4?S޼_4sh]cuݵJQId^"tS1T]hm Bua0 Q(Jtr$x+$d2s?$.Ҷ ;v}"&JXm–C3]#"L׋ɬ֊IJ9?骥a7N⡙npL**CaHFdNu["J)7I QX-1zL6U#86C{l6`+Qւ1ݍY40Z̛O晾/i-I1 ,u0 K)uT*Xph!65ڒbmS-Ӧ"D&KCbzMX6&c2M(ҥ#Z+gڰ1S֦ekjn&c֦ck0j&aB(FRVIKJ*>x$ k2'5b@*G:(8CmT>ӷ }u-dvnf0;)rQ- iG 5VرȔeCXXܽʘ/ 8!?cOhMȴ4+Km5_|E؂QK~Ls(W}}*FSYt:5A'@I*IP׶zIvᄋs=gFZ#SFQ9ypAE%DiD^$6y/G꺦k. ]WEXk 0$UH7j9ʉe[FXeYneF}qU0\*WUf-|t[5)̫8"MR:7\ r1%TeWU~K|hCFEjP*LjR T(SŎ P1a ߶(ch5,^?iߟSIɆȒnlՐ0Ⱥ~'fmO!D:O "rjCR H =ZeR"{2 E |RUu>Dt +3D%;tTty5VIu_1n>;ohJ)g<7I^~]I SH-㴭t֐k['e?Ќژ , bWΡ:,X0?P .>.VRg9[ף!H ?d Ee+bz.&^Kǘrr^Gb&EWxԱ}xp)ɷx0}\V0/;{TơA[W$G..nTr$U#MБَhg6ET,iE䈟d%L;CiCvr e`~svf! *C`@b1ľnzs EaI'Q,NUMhgKs~>A[ >1gZVR02%A c'\9I{VɯS<~?ƼN4= SٿG i>wWt2=Gp? @fox]`Sf?g?cr,// ׺ֵoMuݵFX #M5V@-qC+軀!4!"Z|c.Mǎ`"D航72Oi:QPEJ~+%9 [$%vdJT֠Jc FafJO"\LJRRaP8')ʨe [[\U QeEdMJBvBh\%l0TZҌe&˰VlƘ=n$ɺ'iyXW;at)L̴Ob |^hRFS(bNd2HPiKFQi]\uN5!>o=c{ ݜT|ݞǜ>Gq<䔱J$vum0VuI]f飣~g uSx  S}?}?@3UE N*Oﮁu-Tk f]w3(tAbުȦ$hڒ3݀1gŻ%xi0LD.vc.ͭD:k ږp(qP; YFi3b"\5! hC(b,Fg7GcN ) dz&9A)h"#7F&?$g\&%a S`][JSU)dyU%m IFd:ӵ5&bN)K #alϼժRߋ&/#j41e|y7F΍f&ٌ+MF_Ψc\-֙ +<]c'4ov<\b[oЍr}y?l)g9gJS9GW7k^g~6x>a "&u c؜NT.%S[$ޣ)2zBwXm$!zN|bG C"]7 ):\J,}aĈRt3I1L6 F٢S^zX(Ā%C֓RuhH)ac>3|6o{$nȁK_-fÝ-;lB}}vs7/Obw.} O} -#\'\?[ ᳿K?:Z~Il k}LֵuݕZ1k5njRʄЃRTvq:C3k4;Qs6XA+62V3hͨ6 QX+ZTY!WGSYEr9)>жyLJ"˨~D"(p8aXg)&bʪ4F1%P2ҌjH1Vͩ tx4DZcLeU ej29ex^1@ȣpbx 0(T.iHY'D$J1,gg40=PYf/3;H ^@UVTP#`bPҬ}e`oWd/Ҋt)2MfHn\;C(1;Mb{4WPs4ƧEc ) GBUͅGezZ(|\HKsL'5'0:'#߰9UKaKa+nʱŊr9[(|Jh>P5Suc"=NWD#i2VdCI~'I]R$r.YZh+fY^/E)Y7TZsDs]CׯrscICg؎9R ]Kʉh[񅙷-0!L#mPIyyUyr&d0L r;`[_7')?u_ᓿEo1b/>7Hï@% ~x/2ݐVSYiZqVN"]E ֈIF S9Y?}N"pH%gK25]ךfY!CLeiRYMUIڌRLL"5"pVZ%cDĤ)OdE?gw][ЕB0Cq1K9$5Gacbp,IPP5`k /LeEꫳ*H’5N>eyx}&xЦ] /Nx$y QL-_ +xfA_(\mq) }sRWc 2#j$\+$<Y*f9gO30qLhSUfM44eD˹(1cH$f1RvOb1Va ,Ed`a6Œ# $5h4*fS{OqiR+^~6ϝGs/gL>TG#_k:W?WǦ?QO|wKjropUV*옅Ĝ?[:;1f-=h|=>xA8w1)Қ9pME29jPy/`":(Tye\ѣ <"c&nIfv<%ƈ'[6ϞtBdMq ܕ^x\|").)c9쐍&EO&㇁0Yy|B$l>g"}"tkaLL!`9Ǐ@u߯n:[V+O9 Oޔ~J;o]ߔy7o1Y׺5VC1ͻb:2he5rT~\[z Me{G6Lƕhء=))J ,,㑦gSi$Һr ł)2fQZIr6hNҌAL~'!3`Pc(T6SŐjD?pV UxI-ZKZYt2!t&/U.Pיf$~&9ʃ7p9e1m [eLeT-ds%ޭh,,c;Ѷ2V)p;=bG1DRepJBe4!C?,ֈ1JD7b#f㳄SrnA/kD/T<1|Q*N~}-lUp]/U#F)%K òJ/ `u"M5sUƧ PG!2 %-L,aɨ}$aFx*ĥ/>w fܹd8GW?O+c1\ZysEs;b|I{֑_H1:dn*ҷێ=ʬ^]q}= QpwZlBհ?".q880"e%v1tMZj%~>T5}C}E?)zZ]lR| 3o[ڮ#̼ځ%I~4J4(t" s;[}wˈymH joR2tmc1/_)i׵uݝZ1kVNX[%HSUb:ȨsT뼰EYQY3y'#UhA(lř͚݁2Fig8w2K3%"D|D*!$%5(LbTLN8HZTΞ1 J>h& D:VcD?O"E+1UZdSTzjHH]o #%Uo^,5HHrn.+t8rz/3쟑$!㜘3˙ԕf=u2[W4Wb9̢ Ȥ"Xȇƽ(Պ.'B1*E5~ ݷxѡ\iU1R5_lSu<44-@/#/Kc뺲YLx JYb%=ԗ]+y ,$EZ1,R(z#fGF)sRf6ԉrSCSǪnV{ׯw-^2 `z4s_ĺ>OSU?dH)x2EcvLEz' )Fr$1֡ɤ)cĤHfXmg$kXL=F[jIޣ0ksh3FZWIkf^cfٹy{J=R${Б"a "/DvsahCaE 3_-d iUw'Lq' IDAT;vyYPa5Vc<}Mu?c>yI̓Mzw0շo"O41 ￶ =x}+P5i׵uݕZ1k4Yib K_RRgB+ 3y c]im/qHQFcaBZ+6'GtɆv+7{68'&$i##)N0(l*+zQ C׃Rg6Ψ"UfElDS)6'4dRt}f:;E{؜("DEm` L Xt6 JkE'd%[R^tI[γ췘-7eD#u}kԑ4!V{LUxwg2ݹ/Г~WNdȜ"Gyy/plU~{wg/W?YbK*{{9}}LwsҋrլaT#qҗ _[n}o{ֹ&[l9^[%]RUgYۭrieo{b )C^1ENU$WߺZ5cįOrߢ^#c$O7?~+x}'#lЌ_״Z׺NuݵBi-Z0 S54UE=>YQW~/ ESibzH[xd%D6Gyњ͉#';`T8V"yHjVV\YC]yV1.T$̖Y#!YSQ=N4өn$Z)5["pN[IC Ui7L7(F QӪ0l%)4@bR.rE!m=RK|D+wbWT&0WrS$p$DƄ{AbacWMH*YRB @s-. ~85a(m}G#}H$0t<ޓJj"9K`;L$A:p@$Fy+y} QBm}=~§5Ow7<<2X+&>TѴGiŨXҐnMEf:6 $rİ`h8H18qƹ$+)v^2ghttVz>t]G5sŇG3^x钐$ksÏ_auIa3^n==̕N(2)aٌ|3loCJsy_gO|7^ĥ/>+Ϸl-_uNw$g +|2Ek&i*1J€ԷHT (C/tN1qʵb^b֧ᙗor Xi͓7y\,:&~:|MB+1;]! x:io)˼'$Y9&bg(fG-q_˒<5OgguHTܭz]5?5Mcu-Uk f]wwLT2tsr6`ewsR/txd-)D؜8h=$.ުӋIe8sMndv#f9N Gp`4УFԖqcqV"%Xa >DU[H'HQ3)&!eY1d&cEx$>*UFM2|0ZȜbAf3(W-$' c^Sbta(^/)ʼua(dS.Dh ] ShC!&*4UqUJTB3BilbLpk%CaY[4QkOu,vvN+X?`bcda#L)_5" R<_ٻL?;`>1ols՞ƒS7c@iBHbT1%iLkE -:I%64AU\Y1 ֺFLN@R G״_\|RB|`W5~;ƙږhB2 ;rJK9| Ѡ8IY Rqĭ*CV7jCSӇHGgk.^~ٌgy0ߑ? ~ ;6٘LP!gUPz 0~ +y^׺-5V_Wwh p'1$EJr49E|H(ck4d:v{a q}3\8;+-̤v?pP4rz2j, .k;Ɖ5Fڲ1UFaV+x6v[8wJ3[TY&XJal&e>!3 vȒ٘JދYl׊IDa0kFZ1Nzu%^2yKyHa$IA 0NNHrP93S_HȊǶ/>ϖ)(a _m&}DRU,)/LdK֊" }(MQSHrΤ~cPx?@x8ʃ`(~isFi҈%zQ X284}ofm;9r櫟 `iR0q*y=8` 6ϝ`uezR39o]vQ(&[_9毲v\Y2UH"~1]Kcl|yC|XVbܲWܴ^}/\#ČxD7xa $/Jc.@ʑƀE( WmELB@+ŬhێL $RO<9 5{=p:N|w,{_k]Zݭ5Vi5y i8y#ڰ?mP91ٞk+v=JKVY0F3 ]|(͸qi!RqcMm85{nl8g$7wl}9N_m.2PEI$K- Rْ,RٲieYDM%2D[e0CD"2v /n:ǹ3o^v Uowsc|F1atK2)=uISr/am-0fI)x tȄ !h+6 bJՊ<940G&d(i3$Ц ALJeL@(7b- -kB^4U)e|q!&7%1ֻn$В$V'x#h5SSNAݱÏѭ 4D?45sJ?s4ƲWC1T.p/g|bxbukdu p}s+; 2KQLX/(QAgG}Za75&(DiRjDǷf&L21Z$t;] ]++{wܳ+Ij2H,'WHrMʼn #ovZ讯._G̉(M=uPNh1hGiӒYʚCYͣo͖=*Q:Nt܍W{ԝ1J|=_ۃHswWa=wwo+=߷E-jQZ-EjG dieȬPu  {LAY"cZIEE/Wda8lPJȳGiEj=ˤ'KV,n5bxNR JȬʨC=(](zVVcS0!`]/a.S K_*8&|BhDmcFje.]Ӛ{1./s55ŧh>o&l^ՓoQ7&_>sUqc%y,jQzmYԫVJ<5qZK]vb6$Y0#kA$h eMy !6Ji4)Mf5ynqƯD6MQƤ^CV 0JwoXmF#hZG1.]t 5/XR֑ 4ѬʪC@cD\1)"Hۉ̒軡t5U<5ԍDv.PcFQV vrqQ@T(iЗ: hC 0+Qh}ZMQ@BSWMyR1^;25*fU6cKXilnpaJUDyPR7' &97e(҈n0G}C}dƷF\I(x دy;_X&קu{Vб~ǾqtSa[;F#M ~0g?ʩStz|uy7b+Yȍ=`vy{{?r게VS5!xkyƠu"BSS{*YZƓTEA19Ŀ&lp&hMdpS XะOhLJiSb$̀3wr9 /V6W!kG#5/'O>qُ%|cɷP$8 IDATBUʪM`"ZLwWj#Ϙ1"xPSQ$y4(.5}%1>|xd=-(}o@ I)"ԥA[&eIT9u0Jb={:ς҈WT= feּ)kʑ>;!>9`/\Hh"#pDfˈH  !F1pxJ7t)UFtJ%3fPۑAol*op??B2a=VO0]̧&*76{.<4_S `$y*gz$+'Ou-Oױ;'Bѷ#k,zNE&ӡUG`TAZIxP*J@ $YDʺyb5YKS$_5tV{Ϝb<H UYU5lT^_' e D.vsº, )c&C8×Oa(7CJnw H긐&J9Gկ#1_ٟ毐ſ:q~/=K8+E)E+p$wPyþLBؾ״ʟ[c}$#K?S?qWJ)`#ោEj,ULpx'x$m"` 4uc5ۥ5J9lk*:*+b0,c(̠074.X lf(zy ᥋67 z6zsS<.7k;Ǖ vaZ K*ЦD7 6$18M{,`4 hRʷѼ#iAiL6Qh# 64FyNSjj=Yj 34tȎj ¾3l#52P64" *h !%yҰh%=>ju[Jtw뜼i/QZڸkfׄ֠5:Pe̴<2FͤaSQVVsM{!qx.!DhV!T9"s@q0}=><6E4$A2j:;f`aq<iqfQhD\isO|h{y`2׼vK.>h&kKjA$Af(pu*ѐk@ס^\q^fnA2E,@m4eQ`%%< \,Q(PU S1I5G)dU*|2,K&{d; 3A7c_\ET,e_'-&3Gۥ? uE/R6\fi@QUA"2ڻmSEC,fݮbu][O񇺎=4U.GvHmd {tu UdBl e-@Vj>b籄soHX>Laz=@̬vݪɬ΄4qͳލ MP-V^}OPD)NS{Ba|yF.P)D$<"kD+cOv^/z4ٴ?GĻȀGp55UUMRBB DVg g4bk ę$ 3eRVN6<@kpzk6B xows  XZ[:۝UuBVOa'N٨UF;<\<'Mgj:3]gzGD8=i}8&|9&!'/WHS?qbxl&e4?@C5MjWWT YZ]D65>V4X X;Nzhg￟uN:ɠ:Kٍ;)kYw ij it19][eRESw ̗gڙ}7WדV2A}gꅻ^wQZkEj$ʢLFi%{t2KYPu&U|3E&WkP6&'@UF;mh9/]cj~,pi S6)JWnYY(/&e I-+;)yEQ6V4A+%(&Tel8f, >D'*Ei yz1]I2$&R$,YvUddeC"+&͑w[ =0GbjUP{ķ&uE?5v1ޑSFx<lAij2PˁFNĽqؼx +'Oqc?;3,ou5| 0gtuIתjz`RTcɲc!vmr$(I&!Kκ$ ^j(BSM9'i?CmKKCTSʂrofO˝"9N`er4nbrSYV#' ᤡk1uMƅ ԍ÷8?|.|ӓ͑5s+3Vu /Ei{|7J~ǏR ;[_XYZ[E /xQZk@̢^JӔqi$#fL ִK_b5BiVml$ZW5, r(Nv>"mFǸhh $V8' ETW\V#exٞe2>KRPAXhE(k˚Ǥ>Tׂ:YؘVӉ얦̍N'Z+,S #EM0FG&LUB|MM#Lv KYF_4U-hur3fsJ (M8b,wj {ao2:jaH=M h4qnVZP aAłc4&D,!JheiXTVt@㛙W̼yGV׿ΝCJqϣqϣݰ\\ ?Fq/r)IyČ&E/ss,mlp< Ky^_ǠMOc-'x=Xk {MDey#?<g|(yA0H=csP>ctwSG|>0xwW3mZ> s(='z<uTJrME*la4 c!2,AMY+\Y`F3RO&YJQBhCu謜)+Dk]\YLH]wHUQ;n!ͻ UIe &Q%_8Ge]S56ͨ'x~QP}mҒ_GHwtz͔W̏u+^;o媴}ӻl>l^N'|bԢW@̢^j8래P~NYz*, R $$V&ɪaKtuB(xP-e\D@kE!@?הq1ZEY:R  `=;OEW 4]^|g\$) `gOdB]+4h-X8ƥ̱AT,hRE_[C˴5E\޹c(atTU1i) _ ^bOQDQVa3 /e̓`ZfC0nPm"Q ˇ[Mkx+F0M$DFEJoZ( Q:5 VFpzBY 4g.pO+뜺tX"&Zym|rHdx֤jbfv*ήkX&r$夕?aDd<(ϝ)+9Gt^vwqk^RO{ULxS᷽}&噦I|S䑷( |X;suq|U|H>LcȤ I{Mz4)CԨy1H)Dy0MlR=5 ]Pl?q[rv*\>CR"רV%$׋x*FfCl .W;`6%EP]q1*hX| L IM}-ҦM%CqZt5mJDL LZM=t-ӎa3G)5 jβc:&vv ߼}%4uSw,md)^M*lu"77i< ۗ/-N2Jᐫ/1z+>ӲY,is2 N{׮gJedˈԋf e8iaIKˤY 4{ׯ1ڼik,o`_n̓,"H \~1bmyh4x,=t8Έ&NYAImkdj g:YR5l"9h$} Fqz#A\y)mAKN9Ig)I"'Q"<`4hҸ&~3K2)HTKK\1&"lD*?of{\I\˖1-EbA\w&vcp0x7QT n߁5%)6yG;X8('^zc/"s zKwj-Eje>!9jys2϶PAcK}ꋼM\ʺbRN 4M]UaBK}.Q^zG_a-2kp:@wȳfl_.eEֆ`@2XO@ӝш ^+^Ƹ( Ξ9`E4UMO%$G!2CMQ{1.jӔleLt]huwQJ㇡.4_핪;R{ #[ppTٿB?UW@̢^Wu@)/h5a9di5!z\t2w5 B7M (0M,zUdAX#X{¤MaRKѬ/VP Y+P IDAToHjظwb̰pv<oZ-X oYfDzswOzZ劢v#`4j X'0B'Y K}QZW] Uy7>65RbRR(aMHF#&M[c^#3&& qD }]P>߽6Zm4ȍWJ`Y"5J^Niu5NFhL}䠹) McPo|η+58^r5~k&m6HT" lJ6r]J/1$iusi3!ΡEi D9,âHŦ~bm:r7 ¬IR*Mbb︸6j@L#1Igpq5b#4u5ۄC wWNg'ٴI3РÆLXIyYC@iz̙Ǯsf@M}ji =t7H}$U j Wh?yFs,qTsGzZ]3I֊< u X,(VŤ$ HhT*iz,f&sL012eM=dM:hBM_fn[8a89Ҡ*}=ݷ)4!G@2B2r eJ9e2{Ę4wLr8T L 7Х5~ΞcS&uEYTQ59<(]M @mcH$1 !u4(cI9vu4 pjbьFhS|rzF\EM. xT%Ҭ+i҆Pq NMV*&*6Gv__\a xʻ2jyW,qG/GG;ȿ!|ʂRfĿ'Ow.#~o:)}on?CնD`2DR z}`E-+WC6-MJ!m"IIID! Ռ5 5UyQ4Z)VRvwǸ"RBRۡj6U9qmBbR:|tKziUխp֍=G:\Ӡ_;䘭+ok|`tb+΄nzIXL Պql& pLnWqcp~MMeE($ԍ0)Ii!Gi4l Isj NSWpk Q(-8c&mWRvM'đC[Mda]i fۈh% =mړ66Py[בPއ:Eׁs5ۣt1:\l{5ALjeD/~Pj&D)Q44Z#j5ZPqKxQZc 4QAIΕ1q*fmtL$2"K3M?|ܪg= @#e"DONAu+Yy"ȎfySwǮ8zG%wEg.ΡmBdgwerAqqtzv|ME&h«Lbc6 Wrsbg.UQ.nd9'$$3"eRW-Yt;B]V$Zd)hn"NX쎣?ZxGc*N*t$ƕ'3G ur+( y5;,Sh~B. Z^ӫ'QgPgPk'Q~t2JGC^ du:瑽-Wa_@kpnN߇Z9A{) H3Tw>ܣ'ށ:}L* } ٺBؼl]&l_C[7 {~? [>vE-jQwW fQZk2)k 6H*&`VH@k Nր1F UG rEŽ}4Z!M !K ANfScTE$PՁq(*9b9V4Q-yj)yqW o{'|*o aAUiRwB eo{eat'&cESGp@GuWBnyϲqS5~4Q`t ^{Nt-Y&~6-p?˳2B@ZcPE'*F{Ń^J4@V^XOY(IT,,1:->6Mh hJ@΁ =wZ<5Qh?M |qM2u[Ys=E1ι7$S BLViM1SJDnDMQ9QtC9;f>Vh6d;!Qt^9d ̣%5=z?ԑ)t39q9 ‡?AH,)M8k%Z+RԓEiKSԈ|SbM1J \}̂@$LF4.)Moel} vʫW(XRYu{ j*D=b{8n(£)DiEf[Mλ,qңcm5ٸY/1MAi*d2l^2ʘγ:u?ܣ}YI>Gr PSM߮Lke wh~W&\UO_Tz=Oj}:\#TxVSN[hvBhj.]'?w?7㯟4<|awb&PT%P6WL&P7 wO)'4饧{t‰קt=l 9_׋bs60-==cp ˖ac߃lєTqJgXn$&7} Nw`4e3<"qSJv؄M|ؓqXkIM(I!1 ijPZ#%Fs ֢+Es5(pyaRlKI Ѥ?Pkn.-⊌nkť09vm}|>yAoɳte唔䅍T#}(q~]< `z_I&ݿi!W\L\OΟ&%V U2 c3;2'B(?>V"?Y_^7Sw(D~C콿w mHwjtKx>,P|O~~[D,C={%r~bv1]7iLcǘ1xѢ(ZPRj?RDBhI@J#.ZI?KrPkrz ZL,o >$V[DSBDHT$()Hή/oāX.k)gV<`'In~Lw'['S5X+ Ak)E (}t[Ez;fgx2HmJi,eA-RFKAPz,P%e)Dt2m]v`M[;xMwS|]s'v|6PA|'GoE]}sY~ 11xBҀL*,=T&%nKڳJOg,N!ں"uJ{#^LXYϑJ K4 (-ցI%:UdrO5]F* juYY< <{rn=Y wzQI]|c%zlB \dqƞ;xFZEǩc'<ܟRklnadO6 XCqYէqT*/I+s/m Z#Y+hGfNl=\};@wK|QtG'AWoXS1Q8P|j:E^;,F *Āo~*Dj2f+dd3EUXܓQɑ2b K?u[rYn>R3gaF 8휭I/B13,%YƢ@+gtƀ)I "lC|%o_=+GQjܐ0D2m.ͽ> XWdlXs`>|--7`1K%콂Ks|ii s:Z g"LzchBYETsT%.$ȪIpaC"1<"H(]|m, bz^@SK RXQճn$+#"lk4q.i!ԛB|_<}~?K'd-gOArְ_~F.`^fҁۑҤװW'~gNB;\^~/0wy/I&tw\W~ sOn~K>41mLiha}^/5z~@%%Kiw8ίf4k=!B՚5`X:ŅN @I=QoX"];TsSKẒ^Stv LJ@prCH+snqfEk^dlmuχ߽/znJ288s3۔n/M^ז/~@%͆$Zkh9xWn$),헜~ΑUIЄ~vIu`C,1yz˱'-.i#h juAM6!570|7JuvEWb N0l,#: ʀD TQtSZ[`KҔEALR`(V`rź*~#Ia.{FX1Tw~)R(:0:v?3tcwfsQ @o*bKc.#6i`ҥ Wm{=?K۟k$ZaKr!Ϭ-KԒD*Ep$xkQiRgH"MQQvmIH3 }aY"RCVX@ xOQd)RptjYO 6ۤ -Y)6\/+Si u=;:'C?s|"-6t)P~Яy3[y֋:{Aף ?F_OCD?A-[EmoB*/~rYhPz}#;l g74S f/ZtT *9a iB"Dv>5Tָx A$5[fh%!2.4y^e IDATYYULXSogi! *QJ Te J {mc)maT<[c#P1F@ vlp?vEaB7hVʲ(McnvOyk=:&gYM-@0&kU3i]Dp$,v-gNt3FSa5!Fsvw'RlgɮG!QqJ|Fv|#O^Yrˮ(}R `@wgS& {{X%=ήm!g-޹hD!Β ֱ,(!]YPRF+]!Ŷ|sњ*5,bSA@+ WZ1Quc"I*f`Xұywۢ(#l<u MKoIė \,7.ڂ'?4gȒcR^[yC$:PI,+HD֘XkBTd%(rp"&!L>6* ,Yє#eTLWUzk9 E[Zjtz=ѷ186t(l,SmIFraͅ/?131o>DƝxj⪡,"3@r歿.{=mAb~υw ;_ @X=?8o3- Öد{;[jw*uo}^Z=Ә41}R񢅳6rB [i A(MP*ү3_`fj'9 uZȮikb /VBhR(U?QùH˖2p^e3mu@8#\%=G@Vz7p^ɉv.y:W_WM.Q_X\=X&֛[s'IMbJ| =k'KlɘK]6 R'SE 29' P1+&%HnQOm^a}C~'KvLpuyWWu$Fn_ls{Y-v7i;͌Q, 4! y.[9JDxk0_]Q»SY/t=:bD};(? Ue'}t=RRLk) Рdid_Fq -Xka9EUJF::1 1$=`4KF" &ٌ @1#B`&Sn_yBp~λY= eY"a4v~R)XY]gqqiD&)&aD̈́ VJҤM g-eQYm\Q%eB&ETM(ʍ͓}I05ONF//^b}( B%ys ) s'ѻ.Tv4i/Z(Я{  N=~ GXڋ~[0y-3-)>x]w !ȍl1n']>^7yKpǞQmug!{S܇+g."!}ǯ_)3ibNE"Z#EZ=Q\VV[fffYoU è#2A8tZWFŅ/$J"D9Z01FkW-1:ЍZ3uX*Sֲ툃|1cr ,Kہk2aiɁC'-Z N1GӜQ]YI[ bG 3C^صo CiVNB7t#C~SdQwҸ[J5t+9qeyB~ρ*3:.񈼑Ϝ=skir ,g}JSӚsYŤNȨewap1Z<)U,9(p>Vi@l2zs*;,A)l|bXvdp6r2"2}>CkȺ LAP;%cZhȌԎ.#n:i-$'3{Bl$z5g q[~:ڐgD9ıcHH ,,,R3I@*BلQhBY"amL>?0B*_\f&2A!NX[ (}!AuV:]έ6 KoYiiu3d-Σe 8/WfunME_.Ƿ g()?+F&oD^s zŽ_q/I?ה}t>D$_}[&Dc?{1z{ $?e(Q}۫I+|C&_7k焀~.(-@ʀ4^1}r kSzyRSn܂2hUp,ac`KIs"̈́AYzh%!M:IH8MWQk4! ; Ct YcF例YYw%H@VzfΝ+=^sևhNŜm :=#0iu/3\sKfq]7!ŀAf\LHE5* =):e.;y6+K:HZxi,SOCAW5Fy ?w ^B2R=2YR(%- (.2 Đ4*itxPdqE~ʜ"$F[g6GN95}8y62nJҁcKnXT2 ;l!fa^QWD2!ҧg}?, xb(7x1WףpO}yZ僑v07aS]Bp/}F*Nm^6CIVyw~!5[Yq5Y$E  *Ɇ $^,Ct|JL$ tx `p"HIe\gOg#Ҳh].P&`NY <~ 'QclW~t1a~by[q_G4S{3#׽ s{@,޴P_^03Fk(?6|zb060oz'MQnU®!N?Gc?Aɧ/zZż_# 4S f/ZG 0"0D R22^#B*m5z#,K(`R1^^fd'"`׏3R-FyszdX;C@h QX)1V*:\ 65@(@h$k@ ymw4[.V+Y>i '(`ffren Q9p(wR G`0 QRD`aCs9U ^R/%ɏX@ QzGM!ʨS7h&yqbrU P-C ,zl UbC nL9jthx-?d Jzgf71g+yI[u஖# =*۳ ] .To#36-ll@(h 9,.8x{%:3;7\і:jZ}-@xdKdRC(gKIY Wew"sdE֊FI7- \ڬw3J][=[9}}Gi]{1lY5ɳ+#, ]qBvI i@T;~}3/~ ŏ*&nAq(?n|%R~mZO,F} q1@#'=wvS>g=o=Ηw}a{iRԍw_F~|=3iLS f/ZF$2&) ]{Q2~P\Y Ja(4@KѱE8DԩPҡD*V_/cZ J)*<:kUI,۞늼VĻR %Z 49,W0h yhssNȷb+ ?9"` ?S}(ä]@ݴAhÄvm(AoVJW./2Ѽ8ung,kqV4?O-MYZ\,)[2kQƠVkeY,*aL EY{}.AQ3]ֻ]cH+')˂vzK4M8&+Ξ\;wݶ &ˑl8AAqBn?081k|,:V\qdR+ηs\/a_}DJ2(J .I-yvunY2$H!!(~8ĝ28`@}q(ܫ*ׁTE亡(`R˄G΅V>*Oյ>: D $l✣n(,.Y_Y_~DČ9 A Qח<3lD13a)Isfvpy}!W}U2-~"ưX)wDtF*j*^GZ$5cьjN0Bi QUbyt@UJGSȫ $2)ISzKݦp"`E%)ݢD' kgNqZ>=IM"k$ihAYgm3~]Liv̘IoԌo=)C=Uu%G_+*N9nΣu}}{aw{쫸'&t[;s]ogѯ{eo2!1< !9:zWnz%mP~7 !֗zv/ȡDuWb>iLc/|Lih#Qԡ@H+QQ*9BHLՃ+8J+;ZI4,ZZэMaIa#(c@V8R$#; kBP "sU(%h>Zg5O>&im2˥є4Ѿ:@YΟs}k.y@?5<PLw ފ`vQ33'9Q|LR 8_ɈEm)~@V7㱶R\d~!4ohn{E}M=0,n{[ni1AAnm8?` ц#K{9m)`}, !jWix xRǜJSRVj~:j@0k*!^6 |+=JT.#Bs(D,jH)UYaOuOȊ#"PwiEҏߏ8&`LHLj.Pҳ2oQi6#- [텍.L $7Z!Rͽw5B@0iT FGcXڝy{4jv7CE(سNFun"uZ֨ٶ/-cYqRedGε:ıӬ2Nt6>@?wiu2FioRK ssM#$J֖^\(`&w}>N(+}s×juba7b~kQE^s撚zuQ:/s'p2 g?jkϯd"|诩?%{K؇0|!\'gPGnBt'еﶉr#cO?AX>MX;Oh>2ɇ)H~W~4iLㅏ)3-TZCAlNAZM)R+ -9(,^Ik%*(H)BXod,K`Jl -b,=>Z=O&YYH%('p6/"xѨEWUK{ts8 AIғxG^h(lt8Ip^I}0\4K"|iu5Z۷}|Acn8F&dg.MY\\jJX}{=J}jAnw*F+C'/XiwiiJLAӣ_m9ڡ{ c]o eiIo@fnQ4M`W-$$fv̘ii s/?E  ZOn^A>/C]r/G՘ƤknA^s vC &/i_JG+~_n1wDt7wwp}q7˧_]P攟0~~;Cy{ӘƋ@!|YQA' MLULT`D)F A$yK W)- S@ePd9u-c)RrXSK 1B|6IIE[R%*88yל8mYY4 >Y\|[āl^ZJ6wuN4j46:{DG.=ZJ82)LXE! p6օJpyr?ʘIgo~|ƒZfWnÂR}G;<{3uV%!ъh#82,%R/R2RH38xqRL39aһ!h1G/ o1Bh`x*9AF},Y ? vYpἭP3"VkKF)0,'|i8J&yd`e Y$bcS1lǀy$mi"*MOlwL؜2rX> IDAT¤uz1FGBP7] l֘4ȑC'?GѨHYtL/`j 5MI! nj);l${J3\t N@Iř;"%Oakr'~Q|O"yYYmǖ }Om0ْZ%Eǟ?]ܓO<Y.7Dť߅Oz7o]y:?zP׿|4i|c L Ң"Zw4=QIF)^DJ(ԠSL2*q ޡ(( W8%Y1,f켤Jrc'L`~PX_쬩K:"4IX ~pAam95 Ǐ^mwTb"f1 ?r0Tul%=a/0ZCvx_T?וfLgT"H]_Q1ӴF]** &aߞE[|s]^V>d6Pb^?!PO *KRc<~ ۋzM;`ӏn%6 dե+X-C7A%6 s3L]\\s1zcOY0/Hد}G|e[2&y0N?';?{YZ+pO|죄G ! YI$t7o;ރh.\vW?6zO+OyՍnY_~5BBY@}II}koG>_B43Ƚ{ ۜSw)?>B{-93y9E&+j41c LE  cyJ]$!Q2ؕG%'XHپ+݌,$ ze%QZ Q3ynn$Pbµ48{ e.Ȁ0xBHtiZ$`%3O>؍Ň]d֔x83g8TFÉg˞H-heqQ@`nAwd!EY?zMMוXZ$!ınBudwDL!9_ f?{w_5$W̱^޿Ӵ 2kq&AyaR0gP2468xWYȡ$!)]IYFݖ~@Β*G('BJmip*mpiB"!M1y9tޣPUY#2^/ZF]KeAӡ,-A$f֕B!RÖ~R*N[V8[L_2!}òI @Kmnwlg"dn9<#:8& q\w;GI^zOM5R< Z^xo)Gj''Emѧ4?j7mV}vb#t{cXkHC=IXkiq"c(Y|{.YY/2zeI=4d)9pvy[#-(* m*GuqpՕgXngh-iRL(85iN ]Qdq \jKJGuȫǼF̖$\ \scx=|^ CU#`~Kc\jاmyCgç{~gG\6 I!__Q~Oq>imQ>)Ӝż͘ȫNUӘc LE 9A愦,6R5)DgBU-2(BБRTgY,QR>8+@^8ȂA@A \}ɏ߻]`q1a64})g՞$UؾIt(ѵ9:0oJZ&xʡ2YDQ`-x?ɡtQeT-Ro){Ξ==Cׯ 6A%:CA`|lǠlj)FkJk+1Djpwȑahy҄Rt1 3,uci;¨H#6ntQxߎ0GhT׽X|U'`kyש0%B(ŵe tۙ>G$-of;ps~y[_%(%tZH9auM=N[ˬ2=Fہ2 UZE<#%RI=|$!>S`>ߩ$i6qOcK7}dfh_cx'.Xk#Bk,dc,;:NnŮԒ%R+926`׌ ޝ% 03v<3``cc 8Ȓ%IRwK+ty{V얥v[='o3jDͨM+!Ho`v?G8`gJނ~UD!/P|1_>_m[S}kIOӻukXڰ@̰.[l1*G-1 lIH3&ؒthՕ 'VF4ɨ"V Y`ˆUR%$ш)OUH_+I$v$ڑp55ṁ\H*yQ>ݔfy!kS4I*7NB#Aa‚i'?=1۶G66g VOKVJ!ΗI9}>ܳY!WяؕJ0sGó|REt(˖.Su&v8DRQYV $*&@!t0e=|'_=Blߙcޓxdoj|G"tR/#2A$Eyc0F]!y| %!Rg9Yn抏 <'g=);:TH)JsQOSJFY'x7ߒ\ۜnPa"(b&tZ+9ڨvX7ƶf[~-|hU |UM #A,S^ nx |cR֩lԗUP!yARH7FG*|/uH*5f:3C5i&]X$R% %PKbut]n1V8,sdfsy'4iW+<#_^Dn *21྽{v:j5xX;97r k2~I{Dz+95֯xj0EE)wY7$9or(޴bl*Ho|8/iנ; sG!VR#8D')ʴounO(/W=ְe+ 2GUFlP!,^TBKIJ J)HGFԪD9GK feQTBaC/ج,Q_O=;Ӳ$؞5 _yM RH8(G;QA:4LLz乧>8uws S:fVe&΋MM{, QdyzYKb*Ss6ewN`+MxI**az8(7i:R3;R^q>yB(4+Ol{:Gt5ʽY[S1oi~a{>rq8ﱅ[Lv7{]Ǥ[zxV T1=ȣ>K_=k*4EYY8gyHW"6Ù !cL0ieSaWb @d|=Jx+13c|oؾ#D7;vU1>9cuԙTo]a`ַMA=Rrg @W#̷XPw0IیRR8@M$B}%LҪZN&8p&z[>E/Xj市@}¹UWo]ιU@LO}`DQ7ygoj-25 Ũ=.fm^]9l6_a.b둤o_,s1%>YρHZCIW{^Jf猎H^1:N u%kuDRw$=5/wd9KMrcPkyQP$!I+07_SX3Jj,;ENV !Z)xoI$XW^ `j÷sաѼ9( ߟ Ɋ:ENQ A U?kqً{5r^?s~/ ,M/CKa> '֡@#⦅Ց#fϦt;^{!̗>;(GqOAQ1sRC fXx&Ϛ,T o@%;&;O2R4M1 EGq5ÀC,|),% ӈ=;UWr$yrQ*^v'f>Hr(·(}z a Ba*Μc W^`tDP:,OiX:8Tc' O?cFJg=Q₧Z슬b'̝@GQƾ{\!+ BI "g=Fm#|O!Ncfv4 i2JYf$ (B 3 K=gW~fd"n*JYLt#? QJL_*ԋ"dV4?㷔cZó,S5a=1ú|eеz9NXpQ"q J 8yN39ŀEͥ4қD㫡J֛ٱcdqi*74Bmҗ oQS qc=@ ʲT5cӚOsM6q"V%: Zp %OZPLoOI^r|  DҘ}9_L bty>=fbf Y'C@=JKfo9)/hיf{$%ttAjz^'\~,]%֫^m\MTtu,x(%MN,%dꖰ kBנt#֒/\t J)K0JY/B y ȌzL) y"ȳJRoݹsE" p2/] *O0umdH 8@jO `^uVyF+Wג$.kFߖb++H@g4 xJ\ VJ!WrtDq-rxX/˾5x@Ha}NI,N!px̲9&2%h<0ՙ֫ԨSK*b:j=)uSWhLVpNM<~N,G"B; 7ǥ/ ͽt\AjIdpI z\)2۹H֖cY`Ly k,!uy]4%J`H1V-{y<clhoWHNu0})Ze/Lj_6K+F$!n1}[7݌Ne>bK`r)>\+5? IDAT옒8!kH5P aJWk8&՘;yZF}D#TJIZ&痚1LYt:yy͋P%c4+r jLiP2Hb(AK4ӘwKYhv5J̸ g73d\(>QCNZع%G]0~t4%s˕+go~'K_OQ'<瀔4[G[҈o#ը^vY3ְ] a]U) hT1Y(:2J閒Pt4RaNX!YnTbI1HpΣ!mFH5TRnuQCnsqGKVGrr/Ȳ/ ,/jJifEdi1ji\`"U<8Gζ3EARG4 Ҝ]XZڣhd3ߟq`^߇*󹏐f})@LDjy cV}UoF~1_%Vw*'Cs=;oi_0~%O?;4߇K㵗lְ] a]RqhD`8q("ep8cqքb)Z: 6cQDIWB 9f|NX#f\pK_}JzS᳟=—xho6E$q$E3d4x(Gʘ=Mx)򞬰t3K%7䖲3)Ӎ$&Pd4TRI cA%0px",xVUX!5LCE |,Tw;VQq$J‘%JYPOr0( -}>6h%ώ0-e4aZO7 ^HT}9NqNiPϨJQy;s?1z0!X$kIF˗/q / Vb,U6x7XkDJ*/DB@z!;з܍{զC滨*ڡ×Q#w##>e n_aWHr$?.ӏabd?f?~I_ ְj Uނ(G$! :<k22*8J Qq>oBw p[XG31937tfFf~=O<ƙ vņzYwW}WS[.ޔ;_tCd:w5޳މ:(GiR\q8$Y2ιđĴZI@L^8}3@XaXzFל X1ҧ'*y30_ٗba!R>a 9!3Ww &0+ *Ai0 Z1D)z]-<֫VL ,Qi̒@crsey>)SL+d9y#=q<2yˏbbbs E)xwL{r:(xC1G(&i6gȓmdb,SJ{WDc\mJ36q,P=LjzN6>Upxr0}DZbX1q"VLM+kFZ:E\ӆ,etl= SLLJv31Q)Hy 'OuyxStczpַmvZDxYq,Oqrn+0:JڀNXl*4Zسk+ggZ8'v$Y{<+GYhYX*T5=h؃WL?x}c_:}8J!\$DJIR80y('oaK#'zpS]Zon!W#{7O^ 46M&1E,ڽ*PzL@z_R Y)DOPz$ U1RlEk@ 'KНwɘ?$ %:*t-x%i{+rz}jBQtmSՈ!5λ2ɣ™<'I-ie9xh(lE IQYFZKdֵt;]T*R7ƈfEآ(|)jg_&钹f|}6= %ߢ[W0o }ǯ>8l!!t]Ʃ݂رNE]w S_-D&:'0_bf/ᰐ;(K Q Y+JAuY¢ `c?AWJ=d kX"5bu;r㨥2pZ*x!dOz4oIU1JzY4XZ:,,6-y.) GQ!VE;g>{O$>[Z9Ny)Zvpfv=}{];iQ Wv/ صWΗE=wǞc>_ź4HλlyL1ʮ^?v_xi Q‹z42VVSz`&J+UJ\ޗT"aLqT8kΣ_Z+$d#"IFodqFF_ g{ QY>A U2c B-P o{M 8Y<$"}J`y ԻvD:ƻ^ßarfk JG(FjXbeR<LC1H}N@"/@ 1%l.#t'jB^CXv$IH Ց2s_(t<+PRD  Fha&f@u;pwv9ca\ْ!3缚t~mا&_[%B_ Tɏ(_}+{P_o0a [C fXC]d#E'XiR/0EN9*iB%|cZ%Eg VdJ%Z+Z{yѡ- zs_=Dm|vB[Ӝ_ R%/CS<BPQ{vzy!ȕ^ح,b8|~AoR S8t$rg!g]I|O;KApH"x|e´6xY!F h3 ?6lb킶0^f#Y gre\y4]V,ƻb] %%[D\6DR4ŗ Ik}RXP5Ҍ7/.5>W\ů{cN=v<>atw.MZF'v GGIi| ZG(c' LŘ"$1(H+l?୥R 4ReHy$qc 2f0dRi(IIdK%x".δwPN `P%&p6T+φÃ2z֕uwb}As٤] $F' K:B?A>ץcҟ pgO`}οi*? |5\Ϭ3ְR(]!#P1X")jGkvNI8_lhKbFZ+"ڎ(BDvVX'?',_IkjՈёѺyT;>sx3\y6FGA>NF1oxU}znfɋ` ׋++WJbjiz4NX\\$3z+`+Mxϯٌv>Vku6*~^OLrE!kYhNiz  >j c|aJgz$JGp!$WJC(SP\a8 })Ts-hIj#RY(E(6qFiYM IDAT4 7S6S8Z\X[GΒ)iJ///2JRށёԛޖAK'DD,V|k}Cgd{OmS$LM'y{X h-س'桇ZdEx":{xk㋴9I*ٱ]wew^5ppyfY _y/i3f￉CAeeſPhT~:'x-cyȉ̛U.}RDyCni5t]ʒ<%~ *,%eH*eڊ3hAY_]w ̍f/mKXzk0,^մRPLb+5kq°6g4#o X"fǔ0YSX50<;GArW8|OrWʔ< d_wzqL[z~2 ^2R%EH+{Tu8GjqG ("NƐR(jD%[!i3\2nbvMQ,/-c CRA8JžoX_VU1cHryք+r2z ϟa}oR|=ނ~/І{|C\?@g/55bu4ynW4zt2K5rDh-eTfF*{G7 MpZ%S8gOJlbnHM@Lemx (Xpp~1AZp`oSg2(-A,,,I"Qfq-@6c-;$C7u_y!( HJI8iyv7Ze`u.0bb XgOvDYwhG\q3Y4S"}bf&ff0j\/?[8bdt׺u?qCbiQ2 O*V`|ٸOd e!.}WY/uoC3_jEAAl"Ҥ.?*: g{ nx A_hJҗ*K)C4{B))D"oL %x|(+XX\XRк*Je*Xl{;ٳs}T)nCY u${9 N:MD2t\ 4";dOf`m}<bia c-:yPU]5iL*ViǺpDi{4w\3d<'UYTTn!w]q 빯Gޅ;/@ -Dc~roX;WC fX4^iwȂN@ZJMLD-V]b qz$b⽤yΜm<"/,K9q"JrJ_\Z25{Yfxd]TAiAWKҨ$:Z%X7r|Oc*/m|ռ;wı'+OZá׏3,{~Ď1j\z2wnYZjzgwL~}<}"cUPFIT,gOGZ}u0Hv~ӛ3̴x ;ښ sɟ>nö J%)a햣^{{hL^)x:,LtiIľW5۷UWp hёFUvQ`t"-UR>.R$F|3-8R)DeY{) SVdY 4ޗӷӓAffhdg} .Yo`3}[%1G\ ^-kGVj@>$ u a]ޗ GH+*YʔD/R-4P@w$}V<f@$@bDRV\IhVY{$vWG֑}mY2-K+cJHQ("E "09ysWVu C`w=Uu0qMȑq&层l)J"2 Y59c$XRID[ı:Vx.o*V%#x7M\`TɰFbXƔ'3 (m/E 1Yjۿ$I^^h4h.E,iQV!3/њk!=!brBL̎lF5?>P s^j$/@Jd x\oyqV?<籧seV[.]]cm}K[oJS\Uςޫ>ϝǏR\?QsG!l\$/sɬլf E._khs>.\1m8~x^?k[}n=~A?̐JPWHRB<q힎y^r=Cxɥʋ*ՈAb8}Gsx,-\pg8unOjp2dX9q^G e1BN޼eΟ~^ِ\Z1t 6fc/q>I* 4F]YzHm/xjT/|+syzy͘QL e(VBjm bI Cf{:fQ%r~:b k<:wmBCܓY}l.S2IpE뛴9lԗ[S4Sm.LEg鈵LW 0t̘x25K;{j9G5  Š[.J0=y6yNFۺqg+%䌈51ARIIAz &rl>6NKqs9fl{b5/|1HKH1vaͤ%H% d-RtP۾c=#FU*@a-/e0Y #e GxYJ1(6Jo)zky^ ެ9_(YaZ$ ,,.Q>?OomC'n%bdD$ZgX3ȁ0z ,ެ[3FJA3$Ag9VHdi$)N^C\{5P;0kQLk~a;Ԕsڂ`L*.M>il+%/M* nlY}9e/aI|0c?X\}ILӌW^p6Jynk{zk) }Bpa*O[[$]׭ת/{N4aJ~?;ѿ'o~#,f5YrzF %$2oufuɳOFiL[}B}q}%b( ?0UJ7c!a,-JNJ} Xb:K ȉ41`ϵ.x?/G!Yj19扇 }I8@&1FHCg+H HRHV'+U<*+oF0XCgCz>NнhcDqDV##0VoPטjeJ 3i19P^1hǠ1Yc4,IrIj|&ckR[6ګjܱ*`~qџc02|ĥ ,/DШJ;z׈h4jsEkK"j| ́1q,xxmcDk@"Q>n?*Ijr@t6GѮP.kG He?} 7?Ea<NDjWw_S'0 F)BOе72 ?UofnP(eIFRH0,[XEz)cC&HW^m#֔Mٜzkti0(J^b:9aiC}ݽ-09 i&xO0Qm׏s'4Ԡ̔hxU8&c妔g@Xz <$IrIua@'^n&lrNyɓP 5QR ĆR֧0-~h)y?Bb}e{DII?q?Cˉ737?G*.dqh*b3:#rEVkNGek}<˩wNo`?!:z+YjV/T3 fV7,!OzT#FZQtz)eZ,FX+ s#M%llf-;t>% ÔZܙkY>osl7>V ;W6Dk]-M#'݈B%3{[ x\B B4]/)縲!$,4V 63zMڜ%f0*/@!ѹD!Z{%Ơ/ĻO}Iqi'ڈ^6Me E=j .F3Yx}skdiJcm.k-Y;cߑ\1 TLXlyE~dܲ<b<Ҥɍ:r#$_V׺d$m FkNѾ7G+Qօ?1la'kPb< $e;fM6c{Q&^zsbr?;T.3ov?1?3q6Ƈ,YBRVd-'QbܰBk (*cMHoS{@$<4 0"u~b,M*p2:#dE(gL>X+Vk}ӷ#|-W9q{B VYF`T%L֘?NTzlFק  $IJ1J70ZKSVPQH91$f+KN89w-&`23`&bw8?k3O ?E sC`s8oU߭ZT\]* sU&b^F 8^y.wN!̶̓g5Y` ꫽,y* *1ʡ+0B`\dJ+#EC 1B5ءyicZ+ έY'ؿTސdpgsC{y/tbV@o0ܥ73m;wpAk r@0L7}f<7 GI%jblusH1?'ݟw:9q&Hr(׀tٞpO̐eQ)]DA*SVQ{=/[t$WNn34ϡQj) ƱCʧԮ?iG,컍`?m7$IUcw+ƒ9ei-AmE_ydxQº erꡈ;Pc/! "[ %V]$a9'&젢GAEZkXJlaDT{o:>rqCy(r2^:4Yխ3VZ!K-"-D(MFz乤ӍYrymR[lgyFʼnC.{1p0gH)c^x}&$jB([#~"E'e;%,Jx\0_9F%wFGy]-9?{'zٳb@̫Qq(krR;B&ng[o`u=6+[VS*۞լff@̬n\ޣ$@ B"FkXkU5 ֖z<{<ךs%'.rڈ$͹5B -B81QҳhiD_֕cw Ƨ)P>J &G1Ve.05rl3l./<x(Hm(0iεg񆛏A&-6(vE!6R]H@a2"MSIJ:06Ks0DCVZCH;P>(s!%Y8??N*@=;1$rƃxt#f)F>!5y֋3'e1T򬾼 ~ocotI!V+dގe硔Q?Kg-/Zg$v/+1zkWe۳լv Ս+UÊp`G*sKlθpc&.kV\R,\Z˱6YZqm+!OsR\J_>=8?W|5W\6Wֆ\\qpz/w9|h w޶}ͬO.2780a;Sb%DA`(DA@e1%!lmؿ/ZV 8t(Cr7whVҧΧBJ 59Ħ{)Ÿ H3 vOTKZLˬ7׆X.ƠC$ĒC7koU645:rc{ً@g.ʰ#4v p0 MReB-G颧z0ouiPR~ߙlگ8aǬR4v4^&]Sc?Ǟ>f PF$BI哐O)dCkU0Ѽ;ť”@ϸwRY7Kb=vƾŵ JS"LA)uhO%B[s1 / AIEXZj7~~ˡǙ[Zqg^T]RG,D%s'2!>F \S+XR IHi6q¸كs֎ehBޏ*0n:F0.|"|Ԍ+xݐ%y,l-aڭvwv<^JU cVիS3 fV7j?MXlUYGc43F{B#0𞃼26ը0:\רu H&c Ma8ʉ9y-1$!p n})JM _1Zx ?;o|\[0|&ڧ62lbro>@*T3=},) ׺|Z0 ,ԸH\wޞ: ZJy‰y߻[ʁC{SQwvw<\l)'!RJ i9JI@*I(tWNH@2'eҨw2YK'kh۟;m3L0XC0zkNw/1fAS/1)~ar1OE}1Q9zJcnJ[C}~$YR0 TbTx_ª @Hb{B9ЪV5N/3~)ΩWr,kIHU8)?<g,XTBhr15ogZVN{bcV\s׽Rk tT^-Z,{ xYjV1al =9cBC O27$c%~F5̷lvF]Ph8|[nV6U&< \Q)M;N =}+=hn;̷}ӝm+<%0qZw{CLO%9̵tG$Q)RgÄ_k. IQ|NʓTaJIG!*^+?}MK&1nL VI۲ح&rAP6ɟ4QL5%7'٢їcqc6OcBmwz%/H#3h.-.G٦+?<`Y i+KO)w4bO-L|=NF7 U^94mwo~u5D"ÈVR&<&yZA"pOT8OSM*Ijj fUy9n @_;2*=ݭbJ09YcѫcVN90 =w۫Ǟ~Ǟ~%6 =oߕr=ϖIg7NW>ظntug5YjU2תn&-.c7Jfuk9VM^'G9ϝNąMK#.\M82c+Ej4a3g6YYq۱g. KfuWo̷bN:qrW^-ijA&2BѨfCtNXYV'%^?/;k8_uͧyۏ@ߺ߻sg: Ԫ+Lc:<&xjU߰m9DGINi5T+ GU9yRL.^MHgc-O>uwZ\KlS(T:P;ȱLJ5fi_Tc͡Յr@I~7'/?G3"5R@rzƴKkpˤYc?Ǜ:1o?~kا(ɡ\c֤}/-:vhRJ^<& C<'Mҋ7Pfk1S>NlI*RJjϬ 0Rjӆs{5a[0 O=`gy%KeDT9gϜ߱lm}^2>^u":2ўȵ1j^N\f5V̀YݰJ=*Q`HSAF`ie?L26YnTeX۬,ϑFihR5\^:kC1\M ͐_wq㴱ZNc~ CG2.^]U*dHh(2%d 45Ơ; q| onqK;7[!wś 3O}9c6G?Z'mNR[!Mٓ=塇s`SE=oϻfIbH3M)* F:i}<͸N{ŝTӐf҈w¤WJ8"$dLPe_f˔]%12e `l $q(4zrK"Ja 1H3C=u.&U Rjd6e_ cyY TȱI`Yu ?ZFpFJ'2*}],= p bV7P#p@GR4Ne9~~#7œێO\bUj:71q#Q4'c,I؍3 cYłuc<'hrImՓ{d.[q](~@{͠u-[&J?kV_uӡ9dMUb KZwb</׫v@3Ynj6kVzKkjT1X˰# j&O;}jr5FmVĕ*VZse|]`{emOph_[ ϵ9"/5|?C_z&4磿YZ?/_?{%/P턵.yL^ `J,ה= Ir7G$F%ϝao*ֻԓ/8:sˆޓF{?% '՞ 643~7v]_Ah5=A~q>'O"y ?WwyӴɮk5cN޼['WYZ \]?OP><ӧ8XZӨ8uثbA'~B$ 3To[CeF#Wmȹ|xRi\kn?f)M)򨂹r؄˄ =kң¿y vҔ"<;)<< b (d8)QhDa*xQ0V<(;Lܠdi;uaPұF&rjR12͆LY{a O1iGHN?⦅{yS>gϑlG~oV]8N` 뤇Q9ƋDqX^W#Lʍ5 rJI^ݷ:N*G1Vd9U1ψ)Ii' FcӰzUڿW]5ZBj{ȓ Pe?LQmShRj%u٬f5^̀YݸdȀ0`diՋ穈&`u0Ѭot8rpE0gcuN9Qs& ,TK*VϙkYm'h6Kpqy#kc| OWI1/]K<|?s?y{db\Jr#X?rM AI\k76F{&Y0|EdOwȿy{>; b4ɵ\Zxӗ,%IҔ4ϑaڦrKy_O)<|c6G>8og__vV0<AYAWٜ0۳|Ә6eyQ碫Ah_)i->6: pY5)m0qO1U&aj\ԏ̖1dG"&Sʁ%..ڌlBf#x@-^/RC?k=ȨcxJQSJKFyiv(J 5_aXr(@Hci,V8yo mO?re)8;LCCpno1z X`3R~?@xT鱁\(@*)KS( 6c5Ο"|ҏh< 'hrN/bu>;QlZȌof k9 L> ˭^@Ubڝ7[ZyլfT3iҬn\} 6?|k=5ސ+G[jvեg =6Uf;UauFQ IyX׶R$#M.]3Y$Z$5<3<Kz~}s;L\_a熭ǟo#{y2-s4,FR T8mnm{./\-/w<" %*pO:GFk S-LU' D["ig\:*Z@R a`wG r]^/p x'h4cHzKfkVk-$J Dzgj IDATP[\w>N]Ỿvn9D{_<3 8'I6FZk4Ws;q3KM*pWnwąEsic "'_ ~௿.[Z66;|'1y~Q¸ s¨57?6]~~[OZynlk[R 8~zbYpYJI@L@WM]D^NdmK ljve6VvO1Pc/,bB&S2E<@S&_mRG^Pcۙ21_GF~θ/y_0|q0FVklB ){s4Ay ά@;sdu4!G h)b Sk'ɉs?|7~ k5:OV)1!(}~W6!??Q IRG 6A⢪g e` b8j<˦3V9#d)iA91rIұ : 13d*.s.\YcG^<~ŅΚzHK%J=C^Edj`4i4YjUPYt4a~^gq0Tbh5k9ATlnbvwĠ7@K.4bդ+јlH'9u:geBH}F*B61m+9u?| | n:L^ cY_ܙ zj/QFj7ZBZR*Au@qW֞Bڧh4CjJ5 aD dĩQEWBkkNFIL4ZA5Vcǥ]J~rc:Eg#eGZЈ mO;s=o-YZhRUPoG [O>uxSO%ӊMDy7TCk,R}|Nv1ZD@se4ƚ̌h)c :e0CHyHxf@6ݘnuc6`|Y`/I*\݈"]G%OF̎BT$rXBbT9A+:[rK(Iy r):et\8.B`>E!֛;C9FyT.R:,""& ziJڎdR^IZkgL. y )<&0g'yԣRjJ9v %:]@D(R9F{n4ZD͢0W2~l_d.~=g> `l)™"<3jGs ֚3/q>k s+]cf՚Xa@)j:?k-D(F͆Ψ6lS2XRs9>SdYQplnZT rK5 3;\Wn„" $c<]/蟲Z AҨ,bF!Yq€vF@*i[mVΉbTZ-V^{=Ir:!I#"TXZ t4BSL!TXS?M%h5T1R ьvGG"aܘ0ʹvLVh|Fanw@#n!N4\5_!#,CFM<=4Ly/'⫳&; Lf2T05^hWO! SwC t$! $ ű۝$zt",Acnjk-a<,,9vd[n}sıZ1!NkC.^pf4z)IfA}>T@FH eSe PfQttlZQz T:]( @u(2$bdady@+ʸ/.GXhW2m0GPRj["H4QPZEфuA Җdq!fyug"hځ>$(BfZ]݄,_AyjKUj0 5Wҝʼ4$jk ը.ᐭn4{`[ҳ~_w۾˜͌%A6܌-rBc21N(%IUbS.;NWaSBF&!!i}4s׭Kx>̌hU9{իW>')r{>D bHc;O|??k1¨O_g<=¹m}bɭsBpH[0kՌa)Hdž$ rt@Yhg,ǜ? DQL&& 2HH`6дs:b hSq¸QXad y&oJ~s80)}&28$W)u{c9RcPǚBߘNxWCuŠcN1=ט> /PKbjOdEINą؋](-7x-TcyH䴣C,FҶ[$^3% ޺WG_{K łt˗Գ9aTAB^a6o=?G;?Qr~EɟpED]BvwwdѬxܑ#,HE%ɉ3/ԿW>[ws<3l>zXyNN7m"{] };ggbOֶҬV:S+5%!X}n#`cgsG jcӉ &:_*@t&5eY* Dߪ쒉 Fߺ;|TRw>R ~vEsIt|K,+55 ĐEg {1Ī2|]ѝ胲`6WTAâ.$WFX:|':cҲShIJiRWزƔ mTdIΟvCfM.9m<O_NǛGОqQ.W.1Xk$轗JS}3oo*]n_$ucK>^ aO6jJW(=bKLUV_ZĐ@tFۤ?Rֶ^ڵ˥nS͏t.k Qby[wzb:3_,mmk[/ 1z`}z]3qڶuJjWsr̝[CÝ5A\:8+xs>:rKn>CBS q\)3}[7}X{jՋE #R1TjF1zO=fAF3bBL|vKD>xzzIh^>&rl`z]IYyt&|61ϨLQ[ 3'4 R ׸&uhf׆iU;?a4,LΞUбbfzx1jҳ%{v`<Cç l$VLd2'"4dov樀w[$muYkxY0KMQdžSc>ދ"L$8ƈSu45'E $$%3mҨP]7҄=;&ybHt1) =WtKnrco}߀o[r ީ&!I!ȲiEB'T)k4$ELq;T 1A} _yo#ж4uTܻnܘ+zcKMxZn?$DKnAon;&Ц" tPMLTE.Ch'DSoqDޫB7^;J܌:۲soKeP+ 7l;7߬/IbBY1L.i:1!{C4Y Qyh ~,*vcHFAH+_ReľN. 5Du$ &HpYmLrD&UϷV5 cvc-{laٝPMvhoV˘aê1=o_/oa}`>1_k=/1󞢬3rm=c xt2⫾- >ժdBݴmKUwbWK㋺GyL'|/8QZǨ)[:##DLrpĥ8'}8HK9 ;l6[/qtǓO?_T.uDeji 7o!qf]㵇;>;SmGXm5VD!<h8Kl|G_PXpM<'˽-ß /'(t|'ц1F*97\.C]3H6J9KrLnb#HAG$42]IFQJuֈ1*%%'byszIѦq' Ŧ(tedʂ\HB2nL٥zpJ;0B9F#f㉺#F꙽EC}$E}[w(E?|ɗa?ZS(xB6 ,0eXLzkJ+؝V|k_[UPN&/5Oݐ1T鿷3$uO X(H){PW?v_}> =O=w󷼜׿L~b.GX &&mk[/ 1z`%ȺeބeCcN9_ִ͊6%\|+ go-߱\:aՌ1Eax]:niBE !8uú 0EIhvF¥0q8#%z(5"A'xw:d+oR^ *O&7o# *KaX(|ԣ[M39uNa^JxH72C;%!b9R~I2Et˟h5o}{.FW̗}JvZDFb@LkH~X h;Y4UA$'K{M)SeK 09|m\3g=$֔lA&k\ c{MLnP;PI, s t9A0F#14MC4_6ͩΈHa0&QsI}3t\lVb}P z]1O~,d`mw , EhNE)K6cS R^QخPCN<;ģ:MgE[lylڎyo؃z:{OLlڒdDYx0IDEApAEgƸ1OHӴ|?n~~7^._<ijB]K9D1z @Q;. ]1 *e\u[eXLEU#Z.W49ҾS!Q il[/rٔS>CDp޿v{wp ČMQ IDATG#u3 1˭B̶X^jqFlXXexS\7 +v'8؂5,G{%c1pzgN}ZstP1[Ӌ.j<ʵWg'4=dUb18k{|6vNHM_CMbhD0-訇-kG"Jj7V/@d zWzy|TMb+!5֛;l{y$| #ĨwOVb4zSK5[>[T&.\pmaik `mrȅ#Dϝs$O%02i:NJnX Lu+a5}RChx'MPxZIKX'QK0#!n$4)c@zhͯNQ<5dH8X9VY}i&ktie87;oz4Yб!1(66w7fUx4FGgi2;q$n 238m0v k: NxRYB:"@֪ڴB " 6qH)\T9J:GSHFIb9 ?x󗿙cU^:q3U|7񍿗?;_IqўdKn*]$2 __Ɨ+N&FE6XVq-`n|>MLKcJH<]\1V.)(2\7&S Yhx~1i٦sC?ρR G>)w|*k"6~vj}8lGamm=Z.2MX3b ,֎YUrpgs9]w<;_ 3zNOA,P̳쎙\8ngY:mC`1_3nmJmO@J*E{ƯSr@po`lgk)#MrcEDmݦ(RyGQݛ:Ǽ^qr2'Ws|ղ!!y@+&$ѮiV\!xzkĀ6KsabrT(3pPX01g7#S}W2ߴhvQjB9#`u`z0$`B(TɢϬT}RSELj2e#n3Ʈnx7 [૎! Cq#Y숹 ?Zf \{UxŹ2 )F$-)T_I!7;'$sr:Q> :t +YkƢ6)x8MۋN)&M4fU$RIUc1RDŽ "XkԁSAx^c28}Ÿ5OLf39$o( 싿[~^Tirsi4*~kԟakM/rH1BUuCUF8hږ"eY Fmۖ,(˪gt瑋x\8.= mqTp8:e(+zյ+XzU>*e<3޵sbmmS[!f[B`qݝ iXԆrkv5#1*5h:+49l%e|QӴ-49bמ3R>LL "D+Obs ޥ4 cߴ k-eY`umjU qekbX*&l`KҞoRS+kMf#%1b8oxn_>a<)L $e =?Y7-E5az!Q6]d(D|$ 1~~׽7A._-UY1W//\뚪(<}52ٙq3͟(uIB3XCSUUZ4M-,URm:Ayׁ5>XGH.r<ҖS^SKz#>U-l:}^!h=f2&&mk[/ 1z`]\9s?uBΝ;NWCE9tK;,V:Z`\p.-p5wX,LF%6X4ѤJ7"}B:6Ս/%q%9&4Z(rṛ--6!#F92Ha-֖ըoƤq^e?w&~W._o`cMR !(%%cԅc#c,^SVĵwc  R."0ƥW:s^]iHG{t^ȟ( r6Ҷ-uP7 6M$ިp[.0tyLJGJzGW' mAw}k:'zc }ck\:ʥEz%|zW=)[mmk[bjbNY>:"Svvv3gZc`2ٙNY Cl-وx\NNWT;3fE$;w4ZFԍٛg唝^.<YωʁMr4]v;.)&bԥ%I7Eht'&XeTt[+FB|b2"u EQ:G cH3hs 7HXټ2 d.  )! ]9xXFRrQaz1g#ɤ&beQf(W64//4PN^ ^!J.GUF" ^e"TYȁ. )Iu蒷$NS狭+>7P:!؇I䔯U쬐`xP: 3rl|R64jRU`5y c6% ՑXޖz_%Л0QvN:(Uc F,BYTiOֲ;+cڙĖW^_|~ұk x1ʜ8%&ge( cEiTPJʲ@뼞:j(LJuΙ1lJQ||t<7LȎֶmmY[!f[HX5'-vg%f2aRj<.,ߝR*ΗՒY04t#FciF9VFTr^GѦ(tYϽ1Z^s'KyFRӵ$DIh҈T`;'Π" $#,౺7&>9( l:N$ b >̬>b2=~^39ƨb:q!_,E׻4(&w$$ء&1$#QE0TU$* s8$E{gFT4sn{immk[KB̶X5N/ըer:w\bwX皀;po|ĵk<źbuKNלRN Mc9_͙Lr`0VӵbDpBfvn#H6g&%R%$B!ژ (R"(P OYt#II*Hy1"!v\Ciޖ 0ف)R>a4uBx 7Qn Ph;1Ƙ^#f8΄W$92@ b'YClSRQ>?br䴦'u4S"F^'LL`5[4 #Dt+ 9 *ʅ'xzшnEf~J>x'?|W.kPwHΏ༞[[ yyRO?P)d (K~[Xn̗Xk~i׿yUc OeɎ4R$!9ƘU9̈k*SIxNo~)ݹ,|ePCȩC[D̶mm%][!f[LE`+&Ւ&D9Ӊew6f\ ےvha]xl3F,ٙZFvs2*j'oP=xGh\cFT__#~ƓSvTm$SDZDžbl0@hKB"S4}ɯCx1v4+e$mE5!4.nHR7%b !(HoomL O[Y łQ#JY-&zoJ0. $ܔPNhɍWɡd3KL4>EFC.{$ .l4ʝtc>`w]*` Xl7 4M$.brtČP.ey+t 2{b脑 .#&ڧUVCЦ6}gqŕMa('ܫ^ғI@7slpnGyg08rPHd811tc{X#֪/& c+'ϸ.cT ƀMBN -!E#Ťt6mӋBw6=_9N4& iOO_E1GLrxf($e|/Zaf.ضvw)F#O;Jt5ׯx`4똨\IXb0^cW>LvBu, UJ.|;[,~|R`-nrԫOֶmm%P[!f[,"M1@Mk 8V?34?~cYQ7+faooU#4nE-krᡥ]ϙ/iiB͓ƶLd:e11]\S"Q*FNq /%D(-B:K4VAn =%ÄD|%*X7lc@N";d Rh­GE .FBvggDSaB"L4)DzHr$6"I*C3(o4 lw4VBr#8AEa)JC 4!!c/p[T,t,˥q7f!# R(w!|7ộy#>YBPJK/6?i=2H$΃QeQnr]b%vt$Rr.t 1ǝlnHkf8-StN =/ fvaO+*>Sq&ETM}2WT4P6KT#uu;HQ!eAl]nє< NU`?A[C4}n4>1j3֘>B<-K0,舖uwk-_4}q7Ŧcчyoɭ~,q~`qrBX.p^]G1V^EiRFh߀ cTR8*o0 IDAT+)sf<&5(i`q3+n,=XLbĐ^`U<~#kD xh4aXY圓[Og~0FGzB'3\{upmSO?aNo^/!FƓ>?M /~| ׮?~OY^q/?x'yszrrzl+׮s0Nn=j9U&Lxw?ʵZލgĵKw[1=ֶ>j+ll:fhaE[/!0Yn!)usv/3*9;9&5JބB}c'eRE]٧up|2ׯX/Ϩ5b+HƆ`2nݺ䘱VA lP k m@1e5bgwkǬτ,k!RUA/F,K'\TB1U5:Ojx0 +%Y(Ҭb*C]T.M- :Š!2s )uk$]R{ߤ=R *phQLq`3SEzaҋ*NRC|6;V)E₎ H.yU P7NT#5!1GH욋b]̗9A=m|˲eyM"imdhqc *X.2mDdrWK xEzyPn'_ : ƤT$14s4HQ g329>hOlljJqϝ( z ~m 3h[*5bA7ٛSV#ڦe4tQ,ƐҙB:PF[ mꆲ,)7 gu)]:j/0?dY1yb$Ljtx 4NY7|¯ˣb. 9_Mk?}gsKs-+ꞏ.?ƿ]9Mم4>>tBO(?s- ?Qf;{w=o;^p狾~Oηӓ+ZEQ}6_7K)~'~^S;Kϻ} w]w\_xQy[߿_KD[GY֋VӪ`՜\FagZ3^ec+r3cynhkдp|ghvirX=N*Yt29ڵOrX\\C|]o1~4,.s1b"bF`5K "):^Z[ KU#xcqSRb-Jɡ6Sb bU9Gm x$zpF>w `4R7^zUUQ-_709-) .}#DW_*AQ^S\c^"D[mwY}i( 1L";F1騉YB }1ۥ5tic9GҒfcӘTRk|  dlATԝ=). & 'y]dpG^Aq0Ha8"9G;ڦk!Nнc X7ү "Frm@L  *d ȣ6YԈb@ӍMVCLnrx,H#Mv=fT뒠ZtMbz1i]t#+9-~,p)ϐIl.ġADA"*8c br~c9f↦$lsO"'miQUa'^r. a-( a|BdO^lwG5x3P"Wsx;QPfxLŝ9i_\R)90M4Ko1& }y;Ow?|3?=}/ε/S?;~g8r/yQ߉z߻wb\]_ȋVն OWxݿ«^zƋr'=zE_j}}ʵemk[rK>eR҂)i(Mӥg'2ϘQTc0S"qH!3טPLİ&s\2~M֧7-2MIlVz:q*#`vkbh%gjmb0nKs!*$Z[Bhd# PMRj$[}3cPLjBTgHN M,msĔ$ÆaVئ2US#sQEihꆺi[s-m DߎZ-w}~^bw~ً.gycǿu'Zmzږx۾y_yC-?3-ֶ>k+lnZRevBjdxd0iۚ USB ɔf{:.b㈓֩R]!s&C._{Kt8p)J~MtK/zOD)ħxXo D7'5:'֧8/OAL;5]@3'FG.?U  nE\ωF;{WSOyjb"64&x@lk| !RnWİ(@\8ij!FHb s9tOTDN :ԴN3Ac䝧nmZ}!91b z,6i'بȾ ň4j=v:6i?hd_X4bìNRf<3@Ptc #d,FW`r4du "&Wy -U<%aH!R1.;X2mg֮sP\M9UVzO61+;ޘ,JUm+SXu_yQ>`:]M@{0]u^~$q|y9"q#c{g`ill:󱞃nr=o@ma3U Iil=)gJ=Sd2A=_#.XM[~9l=k²[ w0b܂wcGb&Ɂ%%+@<ǼSBcl&kp+),âP֤ /(_ZØ~>و ;l+p' jPZmX$kMuH!$b]r$9BrT!DN < &$,tSݦȒ8"ۈ'@)G b@3Q:P9윹F#Bu>1J羲tR4C66l0kk2Uv!H٤d QLU9F$bN蓱R(>8&Aڴ $'ݗ 0)$[@bu>` shB@ie:3W,kjQ*cF kC6>ՀЛ^۶EDxAԎ`dg;)2#`uDp* ʃ-1!K,"JW@END40_pR#U]'!FU* @&=HA(r "GƠN1D@E&̠ iF,j1.2"Bf>GA( XZ\I^#fĔB_o>̘:6h&M8j]}&_ycAջ+ʖ{!AHK|WO(= fc:r3ѵ_r22鵘1ee`Id1|1\#^z wf|k?pxwΝ9uĘoU}[p;f'>7um{߆#/E{|?C<y^8Ky}?ᙵrW\'}X̷/t&3ڦq>?+^].:b#²BILZ _F#Zd7×#4 B'QL&Ę\,>mu~LQDtMD&G Y?md"P8e4MiNY=>@ 35(i6{d!(3HEah/6gf0rRVJOjcH$5ʬb y{mΉ9Rh] j;0gH0Q`TRd52ye' (7:O@ ȐEaJR/&Nϝ|Gm:; (畦"!f}u_1M)E ;X*kTrfkT1b2` 1nƈIym S1-`KY_Au]Hqeey)`(ֻsX_ǤٳXW="S,ƟiF:ƕ^0&^JxY7\WdTk$V4kbR,)nXU6 j\#/箲jc` )l1+zM8jĩ$ ^80BS&'7faD@L!T2Ph<91*P+O>4 P MkNqW}Vpcz({6`tg2|9w+(`]\9=_% b,hUFW#1#;]s{O|{)E<{`+W[nwڡ#k=8r=wn'Sw/@7׿yu̷g Q]{_'Oqo6ϝ#=s"=/sRKp???+ux[g܎9|x/ _xa|+|suozV-7c1}"ꠞu9m[#"x@>l࡚gA!.ŕW\Pss̗= m;8t7@` NY?QI,Fq a:_56'*{"f~J9 P Hmn/ q[ (Ey NK]$vq;U9i6NvWEla[-Hso)@)=Frsp NR@V&:B,. WCMK6"$gH\~[A3]tKtWIdQ>Y sD'"'A( F#؛B@ӶR%R,T# )UGfқ HA&5}g3I-e#:Ta2,e\$#(ݨWmSb|u=x[T=OcfMYo1_`k{ 1Nר!\Ճ L8;2;r2`SUic`iLFhH2"g 5=Dvb+N*'lKMc Q*IJ22|Py`UF7%:+ = F&**킚f$3&SQCsPx,zA*@G`jf Ro~{ T"ԯ&4-BC<8DU -*yKU<X-WOWVzŏy S\1ؔY3_:K+ ~~{Khɾl9n~_wk00=o};o)\y˾E[;s횶ow68wݽoxˏwx_wsY6]ng'f1­gmyuP[1uJGJ]/h'Ѯ肮oFd^+[,fX[f3cL`X.LXecN:!h W IDATCۛ^DlwIb6us^G0%mC&=L5&Z#n 3$Hρ< ^<[`? H{lewJ`0 pu8qf(IA܃sDlqQ_ҝW߀\@Q^Nȋ3p%i$X{fzp KH~N9pA5[C%u`I續!DŽ\"use, *cN5eLPeAģRL1GɃ!oQY ppl\_br9뾙$J&Y1\EjKfpxH922_rJ 1 #`h;i< O#f `-1χ*Y%s):m'eח"(`CT?1JzN p(5)Twf &AYm`+&-"gvH}Rr@|@RDb{NlrG5-ʒ*[&CRX;/ŕw@K1f2R2do IisV)X֔lgǕVnpЏ#b`\{w) TScITp RYY& `!LۉUr_].=X1DsLf*ݕe8QsvxꠞVHa3G^Cq%>G5hrB-v[sWp֦ 6֦/E$] Ϛz3XMʹ&;jq;JT`H}AqL@RA0fX$Y`3Ց32s>ȞMLkuK!"ԑKՈ*EFc^.9L}t:/Ͽ*` dZ[d v~k*"WJz~@W1h8I=ٗaƤBp45H'KLLN&dIP`HJƲ0Ke㝯F!+ Ę;1F<};ǯ ZVG H}Rr 0O~װ6) ?I\4K3C3V0$? HA5@+3&`RaoL{Y=KU=Oy~JJ]l}d;i[r'yoO_U9?bǿN<\sÍ{Ny49{Hk'a>z vv:d:d2d6dlmp@%"X׽;AeW sθ/{.g~#xWÇ/W .X1MĄ@(dS`6bd0;9QۃOh"@n }~@4 &`2Q3u^,9m 9D"KxIh&-\a萙@ exe^8X GM ,)i배DRo}%(L\5s - T1˵W[ `R#TD@5J\oxwQb)+E!3e8g\3[K@DDۇYmYkPBʔIѼ dCUl$fdV6Lhc%+{F{pq HI50a(oM+ 2{QY K9#noИ)o' o0ejJO c/Ұ˨v|G-Eeo^5^v+2H0GcVBZ䡁-yBH*Щ+^!"1&S]'c`a0 xZA @I 4i>j}#-&*!,ۦ^{`}t8ŻȐ2*FR'c P#!RYF%9`t!կkR eQ@I3@-) 0̼z4M P*s޷ܤlA* 즺b-.>g 9$Rr/>Sȥ2&kJdswb/;Tݸ}Fo?i2z8{Nޒsx+^n}m<# _<˃37x/VC2ꠞa]OσӣQDBːLJ0LEw'L>0g?4A;;bѡOrÑ0'9H" A Ҳsg$eCyM @-Qz1qЮc̨YSvas^N)\ QwF) BNY+ҫD@ƤTFN P >;M@Ԙ) )G!@Rw@2C H2 vD*թAp>1q^ ̳ o^7ll^%PػأBLr9K-\FYSx”yû:T]J H9UH*F 3k5ZEqĸS*Fd2ZNa!)0m|_R+!cQ9tܫNO٩3j q=& )+"0^S8 mi TQ+h a0N0 W@ʶ29S4' I)kua$*y/N2?Pٽ0.:7(8W95VDwd>=6. vHR˩I1%pf, Wn;<^~4 V5=(?coUox^ޜRU[VMΞ M$Cb?۳z1^0*JUYm9d'ö=ScKzNԫ^}R)EO/|DGz-nx]ol>m~0W\ti'3\/ݱk/g"Y.EέI_{{1yM|>A!-??|~ģۆHP80k|I)/N<6ְ薚 ?=IL&-gG@KqUϜĝAa1_xi+84mУyrҗ29mh3w .u Ax5m[Pl`iu]6ҚRB $' RЋ)! r`^jjZEҖU<_m'f{lTr$v:NSDT*NL[:[)HY2&1GERR)32 &HӂqTGH IpT/ 4$<Mڨf$)A(Qe ͛%+!6ƾ/VߝmSib dѭߑ8_`Y],;RJF(mf|HA2S2d8 k1YsJ+&ÞʣF<zN Vc%1x,c/5 b%MTv2BŤn-c\C0)RiU sz\#`Qv%y@ȬFY0#^XSo,2x(3Je@Ӝy`u;$Hc&".gw9#'M9k<կHɰ c1R&9*+~>zI y9ge9Je|S5ȇrz9^A_d)Hd׃Kjec成bt2%/S-y5G)F|{yϞM. Ο?5'O%>č{~]^oRq_sZat SI>:v2\KN:w}x5?um_|wwy_\AӪ .Xy7 e'ǑRo&FYfj ES];I2tOs0$XnK0]X1SA~ 8Mja )$aWh53fN62 ";5|z}1m::՘Všʀd%&6r ڈpo {mʋgkܫ5pgL-H(qsWS$}y&gݾDY1$jnI+CΎ2QR ۄ`)GzX EH8nN#9FݖG7ilvs$k8d%%Aּ)g͹*pށ)4$ `/TĩWN<,n%D iGANخg &-yĦ!cz~l˓d7Frh~> Fi𐄃A!`QsefDJ;K2Tc+5%kANs@ ƽhk?v91jT65 VCT EMPI8Yuaǰ> IށsAD!nF2Ys 6AWE]x>0\3Jamc~o8IUy Oo8(yyW@F1Fݿ&T&+ C^ٜ ߍq)d>>$+0@\.f\ .ˆ@Бc%=[_z )-7o/|zr;}f977<|;3㾻>1^r ."># ^}w|~-10bS쿃zAP*8yHx= j GǑ(k0a2]djy,'7ƙ'C\ncc0k,ঐpp bcYl9'Xn#8UKH<IK z1Q 3 gqpuG) =9gm&ZH%ڬA@j۬f!~l/"*G~ ҝ-B< П8-ui,VZVϝbиٹ&R%j̵$@/NKMŞHMׁvLf[#fj.&%+Ƚ%%8m"yZd=&c] XRJΩm؎?9"NȤwt"VF,w8*-'dRC5oP ֘h tvHR/PoB^17F Hf7Q C@ M?1ڊLb&Hb\}P;1* B0I!W oݐR0Y\Z:0Sz|G.AMH! a-\̱XX,XFeX F)YHJ?@!PIцu}}hh'Lj/L?O'A$T:.Dhң4W"4I Yb!箾Cab<O ӟwg@uo!BD/,/,HF.ꩳ<NC^vC\I=rf0ywpjz=bL#1EiR4V20$GH\BRgd®m Α9dNn>E[[_ʰ&646ݭQ-+&2J'ZS|Am`2?hXqҸKiW ,QU۶h;SQXVT\/QFo51p윓hCCQF$g:IuŁ3avLG9Auqhs`78@Nk ޷ < i!G\dmi \O %i3oC!p 'j2+Z&`^H#  B 2B2biVMm,; X%"2Z'P\hL0z!)Ox)0rZPHD# `q%y;zg~D xkm_hTiV{nPVrU'\}W-Kؘ8Ud2zh!MQXgTy**|9z5E9.EӐʲk*.cS^?1_ Jfǯ$# Jo)Y: #^+y`ҜE=5ΚU~̙ N@ 8\"') 2LvJq 3>TI JJ=$0”~j,jyΤF1>3TI<攰\.\v r'?=oR#b)4X`[ <?]ƒ:Gf*ʽ[[ƌrDWiHFب7py$)1$|l|x'P\"_ 7.e_g8}>/~f<$(OV~m;GiW8G^rQ}%]lNO{X@:~\=%rxk6򉏬|~=_>A]u+L7-ʌqYHqebк,,R\?iжk*" 0m-dL֎K&mHvD xB䠠kM8Kq-.ze"@۶9ckk 9 B`N躨3<-kѵi4~"Fo/ypԤ#P,!kld5Ldxj,<#^LB ,ڨ;RZIAx0մ[H栴Hk2Zڠ@9` :B#뱨#Gq"I3p3†&MAGw,{,F)+Å! Kcڌ cּ펙)(rCY"yGH)j,j&Z.%Z.:"H) &pDX,I=:y4pijgFMJF^2;18 ȐIF,#+2X|Xq9i}*2Q&y8TKt"Fd9VDzo,2T!@ 93cY7*m(K ҠtfSl{*(SXk@Bgznȹjx]Ln^DH)a2"-072&Ǝ OeOW̿,E&GlpFq$U QRdI弻"_>ʧ 8L1xt7w Л]zS?cq}w~}omKw|{M6F~Z̷wFpإLg2-<{N;{ ?Ri-_;[n0^k2.:bꂕof;؏ rTR&d&4Es@$O[_UsREF䄔 I[ ^7-S<\8M>̪Y 53 T A%8g{N3*OF00-TX)^0%"1MPcՓ$w9'9@fkBNMˡ+,>F]bޯun8=b'>N me_HYTR**UҿpY$ve( UCJ֘d`<HCE~lLQXT;.:z2>zM|l3|E Po=Mxݛn2PEp⑇)>?:W9bM_&<@L7}Ngߡ0a'e|{nC_=pqw1nG?+A]:bꂕHDplgIԃ1i[5%@=-4`11/t/ C; EhFH1By53%Ge54 vrY#3X[_Mlo/\tiBe3|3A3H J] 8h7@9 jt+pT|E|H4 gt2[`@9)(xMJ cxB"3Qm`ˊ~ TbdrJ<02k  $/Ϋ(9N28'6zA2NAZ1 I ,5 B +Ģr-Km2I)+2pUƾ,)VQ6'澂~Ō,&9#Ό8yұ3+1/둢ƞ3E51;j^x5.suю C\IcMq]Y@ bhkDY(.(P㽱21p ox e ,TU*ҸnS-M(C`Cʩ.P#YnjRR.0 np,x1eWZac\1jH7(xP?6I)ה>>_{drVmpr劯̈Bari1 탤'y5K&s~2~~?ӿͳ1sM7&QO5-]ߌ 1?W/z~LK?s)/W̩Gw ?KimU϶9׿e98i9yw <ȞquPǺx_9ew@^$4}5L𾱗wePu=2֦д8`36`k{ :&X_a2… \h|З05Tޓ &k똭ͰCѶL&-4 f666b4MΝ;ͳ8w,qloo4I9 -75Kp܃[魟X B ȥ!u`{Mɝ IܝsܩoksXDI pBf 1IDz))(&dqZ)kb#oȖ$& Ԥ]55S;djIӝXe @ i =RSl挾;e+ G='HC%KR0CUZ"+bR |4x}WpxQ_1qaLn0٢}bg\Re+$R!m[k8Eϐ5k|  |ESaF޹ q)b4Cs[✃I5.z xa,Fצ~XGa Jm;D!ـB @)4` $64Be8jh처# ƌAU6ƑC;7`"Y@>դ7 U6\Vpe̸ۍ%GvApdP9hDWqP&q|pjIM,`F!cHۊ'bCG1=蟼wO&;9{}moU3\S׾)YwHģxV{k>:ǟ䙀V= 3;~*o{.s賰uuP.b-YD@7pU$":o!4pHe c>(if98} [l-zp2G t8:+c7kꗒpi*CItDj#"9s=v[64E1={_u;߂2-CSі%ۘNZߡ1nj,a:6-b߫Lߴb;C*o}f&X}c+1IRm `&<+Dmٍg0XJzwJL6ZQ\g.{W6BθLΟˮw_yﴇ|yI;}]lm9:w>wq_:O3"t6φч8ǃ}uأ?fbꥸ|ӻF.:&ԅ+73JsD%ށaI%bnR4aQ&`a/w58M9%!&kC@>>/᥇ 3զ jwEtiH)b@1Ee:Ξ9 @k"BӶ:r%69@@"W1C#Fӣqa&= #rV3B j$}-3 k:^ N>Lʱ̙65$u]WFCюMbTdb3@=- Sep2KR" J1kZD5ߡ`W8Y F+ ?MElDr k tjj'@ T3!-2ifx/$f;^<\1he^PJ0kɘb̕mCT ԨCeC2b! " b Lz4fKy ME*Ef|kqeb,nHfwȤ1)@RcGҫYe|ȐT1X$b@HV5n5~tjynw33|{=y~^^?Okx{f|]+rWoF̵5>|w}u˸3{nKf>_~^<x+^}o?q@Dx+_{#Z"uw.vݟ8\zl~Yv}Bvq)[v9 !AkH{Ίp@X9ԫBE 2q}n78F 2</Tˢ@/ {f8!xt]DA~ (%!Pʨ"_wElH'^O^yuuB]hSұN0ՅbUAK*AEd/#E @E}4:TadH1 =uS (@\rQ[+eh>7%ЉQc$hp Qfn{Bf=껡<ujq991sXbŮԅlqzi^W[9bcc#0/6Ć \cYK5](Dq;Iie5?w'G?W},}f:׾:=%gկsc\{ڎ /~+{?0:m69\׽KO?/ѧxg)0};~bvs3V6exwpN( 8Fe^cL_1[܂ k0։ؽ B?$(pǎ?AG5E E gPy{[sjF Hۑ =Gb ==0>δc`<@Y%Ie5^2W `;^Q.@ RF;]|A(T1.aWCޝZ*S&Ai5AN?t%DROu5\(@m :YŮcsD]Ԇ(EH? ]0Iȁs%G0e>h܊DT"%ڂG;syd&VU&ԨlEP놰X,c xnu7]]G!;KL'ZT"PجnC-Ȓ6bQ7LKS'tRK2c&Tg&QL* 'B#QX3=Ir,]4$=qU$X,EOZp#i؎Om^&LJʩ]g*X ڔL|*S_^﷈L;LqTA3{~ IDATq657aZ ]9<|h"uu'AEuM mP-xf( ~Ao1!P6\xWb-rʓgn7&Xo}d]1"{GVB kNd9|/ R2~^񽷽ew\¯5ݟ8{~vR2>;ħ ",w]@GѯW׽o_}{]|#G{gO\%scmĿ7YvynF"O 5$*>5NxD G!1%R  :A!x':>H2 #G`=dW' )7v\B\!t m1*dD48Y#)%di}?TI!t ̣޲߁ӿsbp[F9_b1C8,btgNG.tښ fZ՝CP |xYx:Upd_a4-,8,2海)`瓛BrF\*dW0fqD\l.qմ0S1H,*1ś5v+gbnmL‹kg5N}(cBtjV"EC_ū~5ox A_;|xl\Ŷx(X 3 )'%B:h20+XH|b\+8`G I0$Fʌ8 Á&Zepf2%vwC;p>"%y0h@ K]'ƋQmBu <VcB2-Q2Jv4B~~\zpVF]+ 2@p2ӪYڔzgcmLXx&7U^?ĥ:XTĎVq3Mb.=:\\VӜZd[X Ck̘9U\ sANGHӈJέΜF9F)*TYC %d|#rb.9^^QJASC=/k>F(g\S1}w+^3r/nt昹bNL] czL0Ѡ-kkTjĎ/K_u0o`u4٪{'O^reHUI8%XLʛRc6­Lpv6crb㵺R&WseTa"09R#`ZUmKjOT\)l4&3;X/YgQsq00rAp|t~A[1΂TGTkBT=j :e9U]mT'^]AEaY3$it}?=_'{6imZ~#kEOrfbGo='>#W,"s1Dww90XNuńNbo{!"|'ƆK9bm7t|Y|]Ѣ׽魸EAb-GYb"Cf(y /aƯ-.x#\2I#=m!0K>.$C>?!'s1C<x;s;=wu5 rۿ_=>| zkxc3.̢6{}?q-#f;N r!80"giY] ++Pg\[zk9HZ> BrD IS99Gɑڛ)e8Ak"-'׈OB $w1TVzR{mJ*sVs6#neR@0b `ӯZI22-@$*5AE3\[6_Cz9BU^kUmHbIm/΃IA!0L}brKt; 6e:^@lM:A|m `,e66' r)&z@- isNk9A 'vLd& - rζ'V;q`қ``j M%h0+XՄ*0)F)kurq\%x񍬁F?f TRVb]T! ;5z @>@RB A k/a Q_/E݀(E#F*xmHkܡ0`<,>`[[ϴ& T.*H'PE? 99b8 i&f\4Ԟ,WA'eAr+ b!.c̽s`G=~##"qÍFۓT5w]xd^`7'L֞8/Ej,oz@еo&t__\=+o_g?O"+ rhG >[~qӷ|+OiUFpK^3.1z>Ҿݍ񫿌W78DЏUy7sC?ukjz*c37|۟|އ_3Bvtr y0 Uvqnx@)muOQ*pB\wWZ]]=uˬuz¤Zw |G~Gy"sv\]+A8Yt~ #~ЩS?3QA`/C)'W-`,0BH߷GABiԉ\',+.g> N )Q2D(w/ kH>VScgtY#0q.AҡN ʔ@8i ( ;#e4w7[땊b{]H'vP1BSF(''A-lQ1WK]9g-Dv׋iYd| K^ےCfEwME1| g` }-NN#x@XٳgC!qcesIcs}ϑ랓 1k<>U@m#؂OUiZ搨 a~ф31E1@gb@m)EŗfydR 4LLV ;qPqe9E"lXgLoI&dI ;_?SЅhuꈙvRXXX] >bCjK,ww̌989<0 aD9>;fL6Xv\ ;9 굯o0bH&4Tq@$#B_u͟&s{k &hNTl5CYM:xTװm{blg;7~Ul]?m}M2 4R*И" [I Z_]kMo4SJQHvWM\x5Jk*;PGI!Fq z 3ҁPMĪiLxwqz `TZKY= 0nx~&̝Lȷ;wNʣroFWEboWwlg;vs3V6i\6@E;4p:Qx  +*F@ c'>$TFzTFiF^+Wɑl{$,u#I^y:a Gp ꨑtq"4IHYp۱f@uAŇFulY! p$Y?`0 )P͡!<>>tʀI ?E8*WMIL-ཐZAĈLc+$M-"Q4iPb!Fz#Lx,ԮSd'+Cv7Z"^3gjƙل5JMk;Sq;@$ 2< TFx4\˻]HGdutg!zT!/Gػ)e+`~]҉ 7urP9 L3DUrԍMR~j-h6vٙ T[\(vL@@VM^29 pb kcl` ĪJ׏٢SWwq bsń~@Y Try"]-42U`Y7 B o9[9e/sF-hmH\a;߄&u &v}xCatWL-[^l Paf]`9B8o9Z^lRz{8/aDՊwܠ9j35'2Wq3"@PIY* NsgU _L"EȄNY!Å2v[4F A.g.ޜ5>HD(\Zch1xcr2so 3p^ {/:=v*s{-xpDx_6O4{,;uY|gպE60^իBkgg>ql.~szlg;V9GNayPd{%ftvWY *bD# u| $.뉝*䕵q0a/ߡ @>RXxq `+aOIre @Qv>VE*^pIN4'HA\e⊊Qd\dHڢ\DZოZƞgt{'P iF%s8$ BE/~T4k AC}-D" "BH.ƇMby#i:v\2N@ K/S9Rt Y[T#6 -&*ES21V棠_jK*7xs!W\B@a#!qiY[Ve)#gv{ #!訁-SɫN~uI#T \ZS 렙0C* OȴؙF&ǖ9nR5;(m%+ Z\[mXLxIIF4{hL5wDa .M}1 IA0 M gULR8g8b9N9fXL&1= #Ih, к!F. 3Y4&+HQ |*ƨܔCsB ̕)Gk\z\TP >a*w"s٤"r)k^&$s}YܨaV}FV$֨gk=og;vsV6$hD-(C0bA/G'bVWQʇrIyA5ATQBiK!@8 y)Z)]D_ G/E:|̽3H#%r x$]O6<<ɽ k;vV(CYXvosQbqz*@OЉ2gB4ID!NlQ6>ML{rLYzp1ȮqX ]Իb\ ksRwp~)]HeSylWgAFbb [Nu D4V`A&pdKYc>f䴆*Ɲ.|ӎsp.$2UmFRJi#ʈ<&Է %zۅYMz!Xl`ܘ2фO&LX䕬\*LƉ k ,s ԅS wQ`2qO kAXD&pHesn72Ug+<8MrV-gUĩ"csBmj>jdS#@Os?ܬef.R8s(39V\uu9r'jauTi!iRQRnSn:2W yҊr Dz#[ ӘK%-tA((\Z X#qQ*hk%~~Ow>ŢCBE teTXvq)eLXq1j/9=V\CU9!z\ T%xqVTVMBVFs; :W\![޶lg;۹f+lyb#CA?wG`'<JwR7+L{= "aHOC :.Q ޢїj.=z tIDお%ηzYqK;ijPKeŮ64^䕞 D-,F 쁠P] "Y#8--Z{a!G|l %肚{lǵ(o{v*o1$IkuU`. Oc<I%n>i|âebc=a%S0>@$:lSom1 㽇@m7r0[E(&,Η Ԝ9Lv&=CQfպPV(X%S8 aiwFA:}glb!q9ON siq3n9/?db5c.*pc\g}^&ŕr0C62Ay-*ߣZYV bNiQFdn`5סJ((-?䩱nZu)Nʜ0w4g1+W)|B_5[g.u?YZ QTmi&|EtRJX-u,!s:Ŭjڙ`ǡ5,Վ >u~n*WpŋHi"vM\s 7jYC#GgiN;NeѦ{E3)B% "&tlg;g+l^C\q .@I'z @|N x=N H-*"N E+""#0> qU$ [MtuC.ed @w| K<&욫EE# .L /23ı}sDl<۲.[JʤK!9E*Lى0r_ (]J2QFGZ kw-z4X,kN@~9E ;l V tgdS4Ŏ'$MV9-J$+TR8-ds$P2,؄*8Щ (= "XV2\,T- 3 %0[5-0s1\fh~;bGdQVvVq'aDo&)F*Mi.U(/O#{%*ڄIUDU+l'4DD%@tk!N-65F%v]Gb&NHxa&X;| +ON I5R〙..uѯWΣhQ@6≭_J19*"P{F1w}a@X.8:9 9I(.Rf$GV1p&"a{U3qhRbT#LXl wLLvlfm킺W-= A; 8҃[(=uEWbgg!cȉ92R۵R7 qb̙ Gꬩ dɘQP ͝ gl]dM9.720ZkIطN9*ԓ{ d8T+~q` ޚ*Rq+/c{pSqv@@bu؋3*^w vVS+Ls@/Jo+`[[eҐ06L)9o8Pl0 j`ugShTL[bʅ۪lX4f PrX ^Foj5f[b \&8,25;M5r@VFN\e`5 -\xj/QiC`:-´#Ѹ Ra-uk@piIQ~ UϚf"T.]s|hirSEiZ ոva.6҄ [D:@B0[tƵcT6F!4&".j})57~ψ5vƒR.zk}6.{6M~<-f'Mmp*Dit{/ϑlg;Ο 1y @Y|`؁qρycBBRkx>w.8 |vc ,i:@Ep _4KQ'|%$+o%]V.XŽֵvg}@ ~6Kƙ`\) [yYvG8"Nc:g^ڻSqbu̬p=$Քrص@}⤻ƊaMK`q!ms &; 6-l7ʾqZS- @bDEJAlfh'KU zEMD*eQ |J9bDY*3 L:qk."E$X,GGG7SIaƇ䓵AR2,lq4w('/|p>^?d:VN6_?]N0˵WO$lT럧HLf* qd*$.VvM(M9[+ 6>yo@ t+b@SR&.Xހ&C,Z$^>ebqUv]2q4rI!{y~Hх3amq,$6{:LueJE_zaÀrH)[.@ƌAET#zO5 .JAN>͑dv$ ΎWq◼9Ohi"7Զ*є MI_gk1v;$MT`.Uj alg;/ 1yFz`"kpp;(nh> O;nv"[6 PwTa?*U]eu[dͭN* ρ|D) %[c~/Ml(z_wt`Q"VLP+ڈ2_P] E1vMA{K۰ j)S%sxTbrp2Q$MqwB5)[EH'N'ȫ0V@rKx!y{Z"@v_*&]qzl0˴K+uQf!hIx/,KsZ؄R jIY]Ź/ڂv+h^3ن6ase.]W37Fu4h]u~4G$G8V}jU`e(mY\栱 e{BAELΥ}Oa16 z?rAs' c H.H)#ì"sUyZbh{x.^^Xsi9c\d>F"a@)%ZI1̡8qՃO 7 }ìl\!g5"pPJ6.4L0Z2byXYDj̩]f,lg;M:[!f;ۈ$@4c䋀2…]CaJzD_ Ķ>b=>Ң>AE|D  \Bv(4> auVP!7Ɗ֏cBOԉ"ũ]+F*<uނwK_g@ Up; |Diw!ab T 'ƔaH6{{fVh됚/#<ȩƍъgZ|*7</T(2:NXF;&gպv\OK[61&NRA:JzgkVnUԡia[NQor*HAO(7*-8b-F%`&60vHMu'٦ۂs #K-j]o#]SJnI-E9rI [=Zdv6nv9}* jye'1p8f[q(5Os)L6 Ų?-z5f ԩ䂱X E2׃(UTkݵ(PLS@pD#g5ZݍRyL zV=~! Q Gs{J@^SۊbqI m WL+GƵ:L,`҈Ƽ/_V$& ǵqO2s V'hVkq^A7<樚\W Qs.3p=w|Mlg;M7[!f;,SwſBy- "#ak( |qxuW8#`Aa)g`lB kJZ}tQ5:rlZRd@y 8K= .n!@:-oQ1* a{ku:QJ=!%jNKV˭mB$AotIד/` ƆT2v<!&HO5j1"y 'RGOM%8A1 *ۈ艽HxdNx J' םx`Te,oW1p:g!6I 1SFJSUs{ZIɓbR=f@ܮRF߯+Kc-+1'WW Z=.X`2  xvDHՅ8incj7VEΥ*67%B)E&vN]a|-\!bZKemձi]\pd%f*C6L$ IDATM)6V>[V'Kx D&Sl\F]^5i92P1d 20V]PYX?7ch]2yuD V( N%V? {2'T,I=B }VJÏ<yc~#Q׎#\hD'ZÓy0䤮9eux";' 3.q!Xl9GamP/f `'tsZqHIM eƐ!kg~ 1)í&{}O"vlg;7lvtõwI|mq! dng]|ߏ>^G~ @ yݫ̈́!םGdzq1 &46- )uY|uYMix-zFf!jMJBX ' ڶ#t|H@RO΀,6lBeB$ضj MVW@8Us]aknh/1ovԢR+4БBnଜh=CjեmFuN/*_ZFC\7@c:hoN"]^RqwIϖHЄ:m&~_}Kl=7Ghm; :LxK!!32 BBl,a[8cEDYsw|'!"oFwU;bT 6H!oUn>xRus3wφE6>[7pl|$}Tt{;P}8ƅLU60DnN F]&kZqbx>2! Ж5(!jb16BJ<B 0Ⱥ0ѱL.7JAqQץ˜⑝s%9:18?!ɠť.zq\׌xjTJTVL)5ź<V4,n8]Ρ/޲%lr^Aoʘ*Dr2a̹@ns6v'BkALaM}k_څ}}ebA`qR!Lx Eh@ƀ@șkLG ⮕{kj}c_B4'Ny2>L3&Nޠ(V])qk>~bWf~_'ڀ0C B,oކѢF#Ӑ%׮! Gړ=tD:P8HkzDndh9lD5Xje, sPfH]bv1'$zRpgGRnT֐ x*p"*B,ᒁZ[P#_nP!厃 Vi-6k&#D wXv j=CU=k+c L4I@jtcL|Bhj`0^ #Yvg[c07 gVƭȥRV"8U N?x):wY)qS/hS[yWqfd*[~,G|MP(dbx޿,6\A>@N|*wA"F*@9hSHh\[M\vQSw'"i0Xx7gXQZ] \DA&r@3J)՚h}fmk$r7oX.}ۑQemԟ0x.U+aP>Z4smb@FxGu./b`к=u[UDMHyd/Fm6Q"{>AP+Ԣ-F1J6M,U31_%M-;@Ҫl2bݜ|[}k_Gv!f_p52P"뎝Q9NZxP6{RŇж6h?/{D'^B"{ ȭ94ln7ygx{Htrq(>"2P`Z/ru10Ҭ偖Н4h^W+=esh1q5X1"P*4]NS5!# ů⍽upMzD.tɷzRu Qbք_|ߝlHB2P] m1q*`Zvnl.+a^oQQ?c 7i7ِ Æ59qlԎNSo˱mo. W0^0/ڈAW6W:zxV| D4ާ-e@{e669HjA5S.[dtڶ%Ƥreu<\Jkd ^+l{(#S`T&YẂV52"')WDB l|Zl(L MOb0`Uʭ4sBkPCAJJڊxTyZ (M2L[Lj{Jxdj&&m?~3^yR$"xk~nl-1׾wB̾>w3g ~aWЋ rc?'+ݘ_LZZ!^d2 y$ȓ9&J^h<  QB M!͈qB-ujQZ/&"H ((:򉑘 ޖ$#])]uwEC&P8"]?҅Uo$B1 Dy%A ߋ rfkER@Ӎ^oގ2XGPXiHgԣ[vq{c(%2gWkaT@2 MK)Ĉ58AbDkJw=aUQZ4әCDTZ@0jqBA wz]523Wd!Bxt:| S)R,{1a3Kw'#~G=Z0#%Hk,d}H?ب$a>UWnd58xMEtdžgXc HGu5g{+|I06rp'8:$h RdL,jBE5#DkAwZZMu ēP߾5p@ɹ;O|<"/ Re&ڏMAi-8(4z(@ĢEdN9r6rWHNbᆬ\Xf& n1cc W#|jz}k_څ}}%pp4y`?hR8S=:f/~y om犷lph^ɷHF ?=.b9H ( V4+Rl߄Qp 1e{O%#EIt@lK:SX"ڈ\uRt J)Ŷoy.0Gjw@q8PJ1F&:BO e3MDxo߾}8;h>Jŵ/66Q~LDW&6~]$QP+v*̸{D D2[ tdl(ݙʈ8z$cmtZ+ɛQUv"ĈpjL` +9Srdp#8M KofH~[ho6ŒklR. j-Xڶ蕛Nk_׾~.íqfL^.,@=@@t5' 1. tOLTP]UvXMzJev.US)c@l"C)B谩i r66Jt +LqMEw?=)/b;3O1؝HcPP@ T&b8QcΗʁ@bՐXC>Y"e HK`el{qq0o(8yC(I0_bI+iUw_C0Acb Z11H lfb wFm8tШAAb<"TK#EhEɶ\:ƄFدroz զ=u um  .]0ScJ^{t~W\c"Pjg< m9:e:!tU0Rv% :pOPxl,q'"XW]|{Λn”:p:smt>QE8Pp:\tm*jonSe5&>)4:ӓ2oSw耆㙘L:$}k_..O_OJ(u J~$Z~if7@hpZ}qH֌ 雐vA#%%y Ic ^OØ +lb<(EnHѤD ) \:R7@^Pg222= _K:h Zq@XX1jn{~ &EFogF% \ȿZVWn ΀ګ[uK~4rZϣvg\ MPDF!sGxBϰB2~Nr ֆ%c`{zu+ȕ 2z@ѭFUsF`4 =V95:bL{T,H#8ܹߍ1:PcjMA7Ǽ6= $X4Z ?vXolP?.w Y*]Pڝoy2ѝi[nZ5j.}H9" B=TP]$cCvK8 L2j4Xϗ K2M`{H`'T>< IDAT uY橷~J7B :ic Jaxa!$PjAcǣE#R8ΈBL(96nи_&8"\L| 0!&E׻^@ * ?O5_*cM^lP8jwB`I0X7Na}k_Ƿv!f_n w*jS-Lg( 7h8$ƔV@Gsx.tؼoCҾ4Rx)GQ 9+dΕï |{6&^,ZT@5^jr;h[DwE\1Tģ7ZɄ Vpk]X=Sة=`řCA!]qp؅G]Bbcjk:B[8$`%uwe˒G&st _`H›>ܪo&.8\8T4  !!1AdZABWza 1L@hu%g:SfhڈPB80zQ!㱠Z*Z{"L>tB.6Ϋ*b}(ERțjkmF{ow@<>T8 }8x|kwun 6EtH"q][ҼU`2:d@.df\VW8n\d \⎔hqqFe6RXY&zOo}JV|&(6m<Қ1d 4#q Rp)۫ ai0j/߼Oqqۦ6< |b^戒K?NRJXgs*r8ӚM)v\%ޒdJYr9ھomTv.(pzxsb@ XW'+`DJc_b{ ;׮xlYd׾}}kbVx7ȗ6bM/sF)- VdP=.BLHmbs Tv6J5Ahrg  ˁ.|<8(do&[|;"c%lw7 ~kɜ/-JtDжߊYԵvK6t21=JD.<#Ɇ1fCkR˨N%Pdu/V'5Q+y2\RxP1&̍W/+H OTCtlM<j!;! ~AlD@g #wݫv&Ӹx6?+)d]3?aֆ]+c30\6bdiaϕJ]H_tD`t/]\G m &;.mMDvN"қf޳< 2欁W,+}sosx.csN57C fVFQۙcCo(좌 ՏR)Qk-!޼~Mg97~u/&Vixn{2+Ĉ\H 0f\ZZE`5kp1 K)t:ƯZ+R|?Nx:DdDD bM\)dU:u{} @[⮰.(%}k_/څ}} oBq@TxkChQje2@v Dznqh7&д/kfC߰-wY~^:0b՚#t Q(ݘ&; B](k@~D2 y* |nmdi+*td0AbbMT+4X1T&`~a@~ !ڀr6g) P4%;RNLɏ`bnPFÊ[e%7>](M-O.O[bbm  X$g$0we!7/F2RA@&XF,1Hh!d΅\CgM_1c5R܌|׾1MH<ن@t]']T؈V  u 'W>v$TstțǫE1޾7A=4=H9c]-`vp|WDQEEC&RЪ )Yij"MʘWZ+ZդuY)dlPnL0)O l5 aA0l֫aqfh;Cj0o.#SJ2"h# HE-d.4!b) 73 ^l,lj|OE*< XjeTK)]PuV4 酉.EJ*D ʪO0 9#'d4(2D3g(+%7 12L% 4Dcq@q!/" a&wdB.icHto~iAc{ONT "")9*q>I$锡%D_A^11OƮ6 hC+ҩVFO"3Ȭwr6G\1!nfP:sB# m5x,&28тA~`8ńx:Y(<N G`3S7 1΍ B ~4p%. ņdRJXl#H0m ϋ,`%~g} \| c{X1[&;C|)??Oez?*y, 5෸SrN>:cj6!Ҍ88UCt:HLLGR@dۼKu( _K(,)#-;$zۇ3n_^h5iÇY׵ 9V+nnoUDaT.~0MLeABU0yN%ds֊d̰k n{tؿRK wn"d 5on4B{ ^]gaԷN.`#^d׾}}kbV@k ~Op~ل&y 7cmv!v - hC4Z,.bX]!'xtʛMVߴz̟f|o8 -ol5 i;~An/0:,FhŽJqaEpCݕx .9sHYN_YJmQ!$3Eh XhnBhC*]Op%G4H@ t +<ҵDׁє5Ԫ 1+C /F@UI@gKVeùA7sֆ3d IbjC׉ PPawʓ !:0zZO]sG,Y),@D孹D$' Vy~8{ԇOoɵ)hekR0 -tСa y/ Dk6MV(~D*(3 ad'p*\[ayt sH;{D&k.^)nrϭ@#nl%@ kxx A)]() !訂9$rb 2"LQkȭΜ/*u1OVj ` ;+Qm*ؠt<hňg ckή'\x&k:DC(w>k E<;7icCVʡU7+,wIa5cJp쬐(rTÈ.ҷ;LMB-LL Je.&/^^L,1+ L#zͪc u)8?< G6nF1ۣ=CQpw#Jky]dhVD+`h4M%w׉` dVӄBqUf3Vh/r?»~bm{~Wm-'Mur }s*vE:k_׾~Y.냭f} /vg!3hƄ ˰1y\`%h-J qzl DVh Ս} ZPmZA ӥΑ#,"5.de@6vʹQm`'^(+f)2:.@oJ)]Yn={ ;e(rM0Ӎp#˺vw@y`<榾q{V} p-Re۷zLM $:)@k1J~\fwoo,;H$Qi?tYx?\.|O܁boZ( 0 ABZ lK kΈ!PaFl/*bg{Z="h\&I(ȵ!GHT$HyM)~ ޏ oG "Zxvu5xy?`׾}ڻo|o{=rD"s\x\h9WJ2Mtu| Li6RtoNlj`޶a$ctt9 (41e t|.-%#Z |8 D;MD6Oy2Pwq81^ 5Qh{F wLxQg&)WVq?G8KSwϽX׾}Xȡ ]$>Enmpw63pj3Xy."bQh]A ~@!ze@Ѡs1bLorWhG[9lC{]!b"A=cn'$n-oƄD.E,5ݚ2ݲdsYr.GAn\r ~CBR1 Mjk4J > *֘2[+&ɶQ4 jFX.*UTU~)_YۑsZ!ȈęB 0}1+vDaynczAS%Bq<~ 8|!99*ZYnbGyM&cҵ%Q BD 1FbJ!UmEejP T |~:a].ԱވAT(wjEab|"R\ђMqu6r6gZ9;=o 8{ЁaCďGIN8Ol&:9X{heRdiuxZ[*"r!~~L#ʷ5]j_^#cZ_"/!c킳 IDATHta}T_;lqgbM젶_~{}_j}ت}k_kbo~@Dv []c:Z7fkil9;kJP:?ycj@$mrÄ >3GVh~=Jv `\Ɵ7I/q/l&]P6h9ey ]϶TT{zЅ! 34B ǢՆ d^~3nu%EA N('H=36`wL{xA[ZՁȓJ`(|Ʀ^7N[7 u b&G|ؔr9.1@.}V;wG r F󇰭,huEMkkm5xy>l/wͭJJ(p x-Χ3M2x^BJְºf1@PRи,'zH@ S ; &U[ o?o~Cg ڴ ~N7w.CKCZNqёm㓻f{F6涄`U[9`!:V#.N8s\+ (,bb Ɛm#B4:Zcc %-m9U挲^!qRiTB'.ZC-ŜlZ-R4%5wq&ńbQV4,=Z۔MQ"ƟiJ&F>Zۺ,K]wq$w #g+Q^:ʝR:6~Z,?ׄھ[? }/c_r팘}}w _ O Z;z /o0炨 v(&GI AlCx|5 m`t!pɨ .3o'lb;A^+9ZT ^:@Zae_]N Eگi%? 9YbժRTQYG%*,&x5\bn)#DI E_l ^T =r ՜)koMH&T-wD^88 K]#Go!L/Q{ICyA-3]Z)cҌ t}"&q8P;%V j^PU'b v΀F0%њ9hZl!Y. g vvŋ!XX`B'P,]4\Ъ{nf_׾/#w_~~nB}_wxOx&~l&HBC$ f-"90pQaTh|mti5zKtmnbҙ0'tE!!{ )m:k(Ζް=0\ 3FDQ8kbJPRZ+RL BEw2gkYXEOCJt@sMCJE)4sEVsH1`]LdZHs._+BJPΗ xT1%;pR%!/brjnGO9ng,E3Yu/3|=_nu3_}zYo_sB̾>G?ZSRT^,X. apf#.BP 0H P5ݡVM@@Xԅah+] BdbI( 4X)b<%0C+̣~`쪷TTjݝ^̪Qt`f X )L~iZHx+mϐr`iB&^@]i"YhU?0:BAúc Lb߶BZF G3T͹ D09Nb V@n?gZq^2A9BTafCL2A%~)V{ޡ?TT+/ *ZE2T)0\ t5늵5db9$\ X]]B$"OJYu%g&*~4{*~w%]LzA5Vcg\&t.k_̚ hҾ>_w@ P_Cڃ]X mΠex1k/ -9o${[yZf32ht M/g&H\P 4Bό-n6Ζ)t-[zƚnMl#+㎺?Z2f"`C^ 0npN(/.ܜu5(ڦ tТᔜ 9l*LAU`k*]zw= ([on13Oq~_Jk]7'%&@a[~Ќ0}9d e{1 dab R"Q@dDX}}\ T/(eG;9 q,R qMK.Z+ G5F@H.X_}w/#>⋿&+V;:'xnj8\00`;Kx~|wCnM}{cJP[tPR4$o?O^{]@( :lDaƊy:"  ;w`KW_}EwP@JSg$us1:ƈ <M!Z=7=+)jSmS +u]Fc7.UqohΘR ~叇7Vy 1[n ؗt_|r<嗱~__0vG̾Z9?/?ڄ6g2 an.=r=C"=8YkcuM -̀*B޵Zek$BY*g- <*aDZ #NX,&7St SZl{BI](Vxcb1R fX Y=+m4Dm먱ɀ0͵|; <Qln(D D%Újk(O11'`MLG-_uOЖ' {.AUv 4ݛjq)U5TD! &,{@)QWޛveo1ާOrw!F $HʥP˲C5eUZE-z[%Ue)v("HH0M I9'{9ߘs͵@8˳Zs1\MxtajN,3OIܰ4aOA9 ]:0"d|eX窀jyJ8.P :=Qs86' 57܋OʫK^ .{C-C^W󲗽׾_ hm&wzL+uդ`[߿Jd/ΫzN`];=VJܼFear.]IP&n4"W˔vwLAدILO7q$ƩuJSVIBeNLDPYYY2fCHӕH씮}tiGT2^z=g9+Ⱥ'H1>x/fz1)5![7fyJɵQuwS/FuN9 KC~?!&msbzo9>u%Mڑwm;ޘw;#߹BppUWrȪ}QuT*TBs9}ÿoݿado<+JRT*JR|F' 7i~gE5 j_WK*JRT*JRTNUT*JRT*JR9AT!RT*JRT*JQJRT*JRT*Db*JRT*JRTNUT*JRT*JR9AT!RT*JRT*JQJRT*JRT*Db*JRT*JRTNUT*JRT*JR9AT!RT*JRT*JQJRT*JRT*Db*JRT*JRTNUT*JRT*JR9AT!RT*JRT*JQJRT*JRT*Db*JRT*JRTNa'Pt\O7>1nI)iMA1RJ8Ta[F>ae#I#)f;OL-acyGh 0 m;a4 DURl KLb8wFhF#rJhMb2vɄ&RJx@DM`gu2D9É#F3@Ιm !9;{T9hqAgJuBdAIqϩgs3w7t[Ojn1 " 8-/sƩq2>)<<{tCr9Ⱥh"P:׎!&mj`s':S͎#ӎH?@7E5ɪd4v}~.NZ2]ruwi'cVb2^%8|RCe-a} [@ R|碻.Cxћ=JSN?Mo34+;R8~R6e'ԋ9g)%VV#8!c5Mh\G;iq㽧m[s$@#8 Gj lbFșNJJ=QMPQETl{͹X.˃s6@5㼐SFp"z-Ei:)Kt nIRvZCwlZ2sHTQ rx!>J>y ~EdgabEOB.( R9F"߶q"h6j"a0ޝJZs>A`N7R$NJs-Zȶ)"!Fsԙ s7|?{<{ ܿ#cT**1O턣eNv|uHW":FR9h߈#͞URK*'xß ""Ftc@sY ["t"LL"y%8GPaU)2-#Αs"# RĕCs&IƓT EqCׇpb&Iiģ@3nBMq $9#u" 8CJŮTxW}!*fTz. u8f9eކБq(w4р6IIQ#q#0: ?w @IE(#9@q8"^:mdxA 2DPn a +~s\9*%e{psMBsw}k'd=UT9{mw70}_ӨTS\^K̵ 4wNa/49AҌ$6!sEb qXS|ޠ yBs5IsD2yآML =&bH"Lږ<Dgu :CstSD-A#oGW楪x 7P$q|qdp$U!1׃=Gn" Gƌ<Z#{Asa=DB/ {!$7&#,Ҁ\/ȌP!4i*~l/cN9Ӌs]l=7LIٯd(s3Yqis'ȄjŒQĸRࢳ_S4pҙq\NqSSRwV1gqЩsHqyrX)w" ?8.NċАg"t2n0/סuZdc( ʨ21玙ޤd2y+bL1-nףAq>{v2NJ~ʑ{xrS Sϐ߈fOr? 1M-f""t/+kʈJ"1ɉRfz[2ceR s3ocLh !DLmE(s@mS2(!D$SGJJ'ќIjY9cE\"(JeəH2 vץstninBiٸYhY6Lg"t1V9`:f7^;w"d3ez@_8#~CG!ӕ-R4]zcJIc 3V׎UT{X9r^;q;*w>_LS4>яY R!ݲȊfsDz׌dB["Bp=9F_-6HE5BY۶/)( dax$w!JR%Fˢ]652#8obKY47Sq,I PArQebN;((eY8%yRӼ̇롦FM˳TDj'X숑A" 16@>&>l•m}w.DXrO \,}NK)SCs> ƭ* wBSSahfEP^/0 2Wμum0qG< NNg%x*f } 7*?xRa2kǪT*=~e3BLeӸ喛-{E\*b¸5dw/bE]dbwI1P*޻u+*5!8C.I $Es$xaKNx!qVRc%JB(.BcNB09kO< ę#YN%E1EԕZ Ő.CkW=,Ov( Eԕ^EZeȢHCQf~Y&d|q?999sd^Nr3<O}Sь׈s]>AF[e :d\28czWnffG$;u犐9b:U{ᄃWr]Rme+#{{XJ廇ޯl"UlP8mr+-Իsk(m)OR͖Ѕbⅹ,o939eBhL-9E(]Ty<%w%D0UXԍ2ɩ+Д.G%F'uW8pbܽ*ddaP+^\<樱<뚔[Cf,ic WV*#fMrxóBT\ܷ^<T*aVAR_RM 1M#FFqʢb4A&w)e߮R!Ҷ$)w\YKX"3DMʙdعx"*JRT*.Ul޻ީn).ץ(_bd[O(%˴Kĕ:c2IxXt℩E~\-mSNhđ[+\ Nˑd%NQ _hGS (C{3[{wʱoM/*hF84GکMa#%x1G6qfX+ kΓcz1'}-v~B{XqHDdJ3a+ӻR@X&g57pt4\áx-RϞ[*JRT*Ul~K/Dddͨ2eRf4hqux[do' {2؎d[ƶŋyjrdv)dqNHXK'q"Θ-= EY #Ccb{i4ee:-.# |;GizCU%Xt.AkӒ!XNLj-LŖyeǺqu9BmB(N(5wۙy.*dO4:~XwA@et%FRVÃꬁEJx,(s0F%nKj;RT*JRPʦȦtALC5{P4Җ:L.eIAlY;$HJ}nǰƝ5Kòy53FwMCcf{K>j9^aRT*JR/Sʦ1u), 2AثUؑQھZ: -m#R"Rń-qngс0jh*; mvh|;+MRg…G"Z9[iV~*-/{׷TΚA(u ,22t +u4Cr=2v'@!k\YMyncΨF4 hRu,FuN[C[r7{" ,LT!fH2^ Z_P1BɐjaP$Qf7B+JRT*UlQ˂V,<7wlAxuZ[wn_RqhrfUM; c#["Jx4pT>R6+9k@lt˚P&d]rEӼ8Q[4# Ǒ5]~ss@Mf%9LkDQ ŖPBu89]!$FR`ܶx<+E qKڵ)T=NAex힋҉Ky͚竌mޅUpD/$5B.\W$ѢY]3YP4=rsb CI'rtmEm.(H{&9Mx"(֣e\=>oN;uDiKMzB\ BTC-uЩ,ƕiq\Ү;JRT*Jr_ 1ͣ;q҉RUb b9*]J`N)+gJ)gB"4+xXDN(6qvR" жJd(R9r<℘!REL[NR[T%Bn;F32(e+@݆0󎗎yӰ|A=3\RzC29J[,1]@(HkF] x(c PlMe?]kL'R ަi̡Rm/PlNP,/iY/w}3t6{f;ADrr Uxq#ƌL:Y0EX` )L`}5R^tcev)"{$ ΙctC5JC[To@;'s]%K%qT*JRBLehK.J2bMPuʉbߑŋC5Ӧ %\6BJyc;Ofd:2AlFLH'[x6>XR&;J(K͈q&,#NZ%CV4)gHjaN NPU&j}HJ^w ˋLΗuǘmc: p`۹gZYK)>}iTq fvH ^$Hq] mBl3 1F$ʈ:yQ3Aumgwܧй:<);wHX$^C'TT*JRǨBLe&mKdMPh!~QS+5rJz1& sΊŤSql2rp$ 'FkEM"QFlO&(.6E{Ʊ # *&5!DJB FS}9_2SB5(obέ2쪴&l4͹LPșg*uɢ u9bEbꄔ;ƠSc/yurNA~HoWASvrcp[bѼZQ/اw'4_Tu)XC+JR|kT!i䜙h og%8Ӡ$ʹIQ;ɉn=)'_J|la gIf4 98b"@&4$6'&1[FL;DIRbV*mΈO"ۖFIN%8P R@y˶(9&ˎQs ߶{{OԍL3,SZ;0plK'7GRP9gfk`aX. #23{"++vѼtCgL8RoDh\thPgᎱP2"2ȴ"}rw-µ\;by09M •seLovUT*Je; "I* N>X& ;9Ĝ-^E"x'IIͱ#yuvXؖlLv±?c:L^{uX.J nN-Y%1\.!/B3,ʊ8&}t8:㶉N|cݎ"čSfo`ʑGH3je%%̾*C\x;EU*zw98̽g۷hyN} gqWoi9]I7ZŔxh+W>я~G+w=پ};7| x81%n6;xg?ʘ˯G<@gN7Ν;9sApƙg)_7| F:# /8[,x o .ÇgK[Dx%?ڶ_JH e xNY('ШPLP)B߶|uQqbL!t.TCʂruqKEߢ0innZrn$s}]AQx׻?{)O3`eD/};[n/_xǻ>prK-KY]p׾~*O9oέ;Kpoе[ﻂ%o~o߸NN:d9l/sqm{ Xr-{ȩg+xӞ?|m۶q>u +./+}nqͶ>m8ir뭼=eܵfӮra0x]w\+9ܶg?y~s>Jӌnl߾xC [4 Gw5<e/} yBhfmJFFOɚ!{޵}bRʼ~/;ś6n6?<:ȯ_\Fu4KKMoڏ|;?<|;O'lݺDY¶ࡣٳ++(1g˽RT*S&bGXjHۺI' <,$?cZ%kɛVO,a,O9srPKYɚxBf-{LH2~EE6gu/UKΉ Tq3n[]]a1mos&8ScU(JmBR*wL7xaax/ đCLCce=4seO]yΰ Hcͤ|NMb:}}nrbmu|jͫڑtE oڶ\EiԾr 6a YNJ)]ǁPPPB{"`YFA-W22bI;og=y<OfiA~_d24] IDATxw~.g4s:G䧮窫[z5?O~.)^?-Wxoڗulٺ#Gp޹q7rgrQ:/BÙg?A/ÇWg?î]k>q73N-zeLkMo<<_vۭwy4Oy pՕWz \xE o^&1{>x-|wg~BRT*5Ԍʦc&+ٖt!WDIYx+r6ėŤ+ ,1'bYDRYلxbdHeGi<ڵV[ d%KmGZ M?)}y`,Y( I-[eMȈٮGW}ר2naR|Ĝ24t E.v9#I~)k`0<(43ל3J--L3Wt4b- d *]n]5]te)- v딗7*}9.Wfa'ix/u2Wfr?0Ĩ =~7~?9{9lݺ_—y>UW>]8,oaVBWy ,//sq/?/$6605фѨA#(??ɿ?_d\} xS_{%;o?yg"qnbǎhN|.: ۶nO;wg vƸcN칝w /Zv}>\r gv*/miM–m^Cr)9g^g?9 9t ~{?}iu{?8S93.m4;wu| H鞷׋xyKۮ+pM=e.p9}v.}Ѓ9"'HOFܹ/7~h>/}--JOkXRR9AT!y"7B,D՛'qP0;Ҕ3CRJxUtR;@p xT I$M0j1+mBTirLx&m"އ>T'%%6ὝC,IY4%ƕ.9c(K"hsשhhqms}k)t2Ah:iDfȺoGJ7_J-)ƙ5S7+Ae"dkm׎qph(,u>V;w,>}M1Qh^ وn0|f.]ב4,8v w~ũZ^ؤ?~}a)'g=sz=~!xt-*pWo=<.rYg?[9z+BD馯qԧ>]v1L xFӌ;ݴmˑ#G9s-0عk=2Tv2扗_ӟ>ζ5@Q~+|QPF<9=w.qСx9FK˴?\??᳟b9`,ܺueZSO[~ǯ}Ԏ6iMӰ}{<|]|>~ qJ廈AnV"sW2?}̫=-/*JNb*FRJ[O)Lb"&sA$ECR(Z> 3Ar"8GpeQJsD`5* ڜY'Q-RmOlANKHE( mBQ3 V1g2jYR9 HUi۶8#Jbk鐽+ѵN54|KݱǙq>|C i`Bl#mH&G6vWrn]"_w%d 8 ׉+x{lݏZ<-;sa׿ $_@k\D]C)NN-2.Ls^d֔jy5vm\r%|S/y1':zӟOYwN=xUO)svg§o _t8l=lGpͷ]{|`.\J(e۷_…x{]([J;{IsY}Y1Fv/|v܉wCk Oycۿm}cM'-7Zuo{O,u||xUżֽStcxkw| O}S}Rn"9xKg:/؛ZJrBLeބQDB0S #CFK#oSėRq&4b+!*)G yRT`, %-BOK&4޺M4(}|=*b"4#K#AˆLkZNLV3k";\z:]MR>$wAyW8 H&+5ZX&;~p6d"_GnZJȮG{)]РĘ%`Ye5u(-s>bъ++d꿹GK%8Jgv:~RE!Kc˖-s}79g'^xG5)^ȑPϻw8s%az*Ν;iP:nV˜*nN;4x2́8t0[l9OL}G?QrI'qAVVWٹcg|0Ky=>t-}ϣyԣ~LAwą\-{{M)߁|.~ NСCklF~Gxȥ_ _|nu2uSA^W)gVVWؽ{'dރʯ\GڃvWT'T!ixgKnBlKsf-ҕ6M)9l䒈*GV}6IMF2ۖh.POǍWm"&[p;Qڑ&)Ҕ}lB(b!}g+E&ͬN&8L"B$[ޮLҟ)g̻ E8,:ak4t-8:cH̸o ZwN`bCgd:1LU1ocQpN Jp4hp"4HLg0X(L/gteP'c|$TQR?i͒naJڹI_/5cсGyݧB;>^/1ѹ}ΒK4;v'&tMݻs:m[99ФݻKr)OJGk.KyUOy22 }>dGq 7r gxg֞஽y#g?y?3:;q'u67r wQ9g/-W_͕W~{kX-; FW󗳼4k_'z7r:gyq>|7طoRd/>/[?tw{&11VT]T!i8 DV!pZ\p¨eH)Kjh .@} Rjc,$Z$ku=u|84%|<-,1eZiI8LTA J:_mr\s-Z77~?p>?N z#9x f۶lٺ?\{Gx 'yC/e2Y% }_8t\u哸‹ǣHn񳜲N?4fDFVWW9صkgv;N:$>IN:9oۺd-?p`{(?͟_=:/lB=ñm6~~n]ȯoq׾Q9Ҟp5|_>süJ -eC;ن(mT~MkҦEJrQʦj}e$e&"Lxo^lEbT5&hlF">g;fbά+3)YCSʚxbYPM0(%&)CDhp{ $ ӦD.g-9:yι5ٺHBH0 $#ʠ" 33?FgpEPqDG@wwNtzn휪y?Ω{7C>yuz9uSS"1dw\ \@cfQe&֐1f >)zARLI*^"tI6j)$ 3Ȟ#8\aͭx1.U.mWd%w$7*_ɦJ!#}u96/;c[e^"Xk1{- +WW[nO:׽Ha^;゚no|&n6? O@Z0K\;Jfɒ:݂.5\R=җnƛnFGF+_yr祗\+9nJ@<X2<2KsDYf6[TIHRgT/|g?|3~ŷW%b 힫Vk_s#LOO ]|[ƍ7r7s=sn֟rr X<Ǐdlͳ*Ts'u9mР7h@U b U.sci[IUѪD"֗JQngtKO RB$L4j+du 18#t@J$J_ưJ HPT}^XU#ZyT$Ouؚ3^CYR}Ze@n4QBR4`mג!i:* aَՃCV'RqATُZK϶"U1Ĝ^xmtt} a)ceP!˕n F!ٗfR#MBeYƦ"?$I!H<^LǑZ9&Q!6^XX|Tپ};z|麏.Ygwhhwr NMP/|\{qxN9nqE)˒/|Z.}%lڼ=ΊU8] ફ^3ҾN>dn.=tͼ5vrmZj:E댏3228 WbomAhTMyح!@+zdNCt+)}TUwa2Iv ⽧eH@bĊW+Ct>rШIR=jw>e׉:B~TW[zKe1dpcMuڦ"dB)6ͭ}\@ 1ռh`3..^yhJgg [pݏa6 n{oM_wR>bI8> &mlOY2ϴ4U% 8UЀ 'bf"P-Do!D`|0^{啼R IJFYY0`Sp x֬>G۱"\{r #wSOYJH$CPF._@3{8뜳cLE]$/xi|+ŗu֢A8x+V,g۷xt,[_ˈQ!MŸ54Į];K?xr8eG ]%]t̻~O~K|/29mDiO?p(]k\(=A|;oFV}nT?c{̘ U4 h50X T A*qa[:Y|Rc vEQd֢#r2Q 'X,e64v} hP׸U`ݡSx2IjP ,nËD(-nTS+\SĦٕ{/UT䏟\^TUgWcR%e;s_sء"bz1c>C!?S8 *Or`^=({wme|b#Zt89{WRDI~Y,?i̐es[LȪh1"|y!GxR*k30͒& I#kMGR~oSNZÇ)ʂ۰{I~u)tM3<#\C[x/Ĺ.\"m[lS֦c {.[}{uvlfyroy睇+q9]ٹs''t2kVrsyȖGWvfl{1^=ík^ZVGy_s/?1_'kVcpb{?L[ԳŠ+XbEQC6hg>vtEo A߼1=VT6b 48Zh1 B"Ude@lCTw+ ubߊa8S36x< !ڏI|R_KfQ )JJclF>hј g9$(LnHh JP29~`ՊU)HOwyEQ<-EPM,j Q׹­'w59}EQ?{J A~.|7roxkٶm_M7ߚ> |A7stzKQ>/NwS\AO4DLc=)#CPûޓ[Kf We[V!0d ݢ lT5;cJT#8(ZmKfRފD{ %N,7X{h#T~)KOGueْ!_;Xl]vBOnә],"ǣ<ҥKqLNNl2FFFY ox=N9coyg83ᦛ|!;s‰'o/q헯cݺudyIh Ć ױfͪScazzzm ޟ~7v<šU4j4vB%??q1_&P܏*:?\?!HmJ 4xh m jLf6K"e%c6z"(x%#ъ3-hZ=u,jPʲe8=Ijr]wNV{(+fD$|`2CcYjXJq`&TuHt%cE13f2t OU,yB%3;HPoWau`6M8AhM,R"y#/*ҨPȔ¡ u'X-1t-_uQKr3!ňX5N݉qNŤ,4/*+b֘ZE .Rg#{<} =(E 'bk7w֯?{^$5˯[ c*Ε.J$bQz/}hE|~ueҥw~^U*P{'==U:{ӓqRナ%1>s4:^gZe_ 4h0ۇ aPĜ1.`IH!t" emL$E )@'بѠXj mcʮkc eYш^hHj,A(0||KBV)r$Ҫ,kCRho +e+\-&{oLŽ~lx֔Wq<4+(24*#HHbJZsB&1S( Q%ӟIö0'^c*a/75yg bPc{y5՛:`MH>TDؿןܳo>8c:=n`ǎy]lb=btKu_:?611߸͛ h`brGʝdl|YuT_ynOo'L֚s=ݽ7S|嫜٩5g&ڲի#7nKGYz5==,λ0rͷrYgjbHP8]KF/K.[ogŊ9n_8eݩ\}Օ+OC|cϣn7y/fxd;vpnwp+^1gs|_?Wt-u٥}2~]v)?S _ǽ|~֯[4x}Zl~an&;ӬXe*;w{6359tǶ=Jp߽yiq߽xыΟc{r毿cOl纯~b>/ě㞻GRϛp K-ir YsoH$jzQBeT/ S'S*5 Neij5`!HuE) z*@նФ<>_*!K^ɪ`LSXTD=e󫐚HhEbj$B'2gV"!0ej1u߹}s_HTSC!)N.+.wsw A^p)zWN=e-5WszQ7mFUyAxP s}\ogc۞z:nvT/_N+oqXljo]pAYbvK'6˗/gjjvEe:xe˖͛o[EɁxX22‰'>C':2Ks?n~GʝwɃ?7݆WO.^|E} SJ.K7y'ADWWe˖׿ёDF[D`ӦM<[x`3O3OCRmK;|Y/ Kn-_ʕ+&nƿy¦͛1'į/OqxՕWH`Ba/{ee WJd7hpP/A+n8asIt87w>*;_la2/s9`Y>4h`>4DLc(UhP 9^\YƻbJLzv"qݧ*]1"BuLc,>*bZɧSد5a,SS##|K$LCH<3t]H$PTڹ;옸OSݶXI.B]ʼJynAWF/tETJ=ۅ)SWe+~Uʘ%ڙ\= ;K3j cDQq/$Wea5yDUJLsׂiܒύjHRoUJ"3-%sm[G8 MHsTNDiɦi!f-@5sp9p9g-nwR.x~~х /c@m֯h֛`˙֯cofۥ[w8p5kVsK^ }t+b޽144OR/-o-Wnh jaW׼*fS~Sv!^ ^{U ǹN1+?.E\x v^?k&~^r^8\?M}ٚ8s?Q~ԃ ž5-b ;2O#zX3ޔQ31Ѱ0 )=cc~rh[?҇!~(^')m?]Eg8ie ;8Mi~4x^!bC(yf):e̛E%Rӎ|x\tU6 4HU%k Yc-PW<7UA ,-S[w+ Jf-R8E)CIT:[2,K5Γg5]c pMNKTJ.JY/i*Ha ,h>kgf:*xF/X¤xH 3ۚTNC jHLw(?DbWLqCPH1YRHx:}9!gHm*ƒI hTPU Ő2U,d5bHVT-o{"}.vC#X8ZG29oaf=)Owv nsf C,ci-w%h~0jQD秴ϣ0.Z9) a~c){Bc9ON,X>bp>05Lt`C-v `R 5e@lE`2{Uv6S}ߘqCY#I,I֠A T7 c~8;:e9`gjep!8quC*1Df`羒ie BjS]q-e\%_P}F 4hGC48fp(6ey6ZIwͅX,PNY;#ɦ!w6h畮)>C|i8k*.fLZ+k32c>6U~h+HB%&šHZx$$RKFQKbRlvn]TTɭ!G&f S'5*+Res ]Js0sX*BnhQOh1Vkp!J+"A}@ga&P5"Îv{cܿC(#jQJIiXK{ūE"6chaz`RгXE}lM)e;wb$sPDWeɯNT֩؊5ԛA Ȭ;:Ҏ[S,H5hРA<|??c|%s)iG- +p6ȯ֙>Ε|/o"ĻWOkw i>?搱 2sys#i6m LtC8pݛ_>Ͽy%UͥA4"1HHK, 0*)DC9#`))0.V u>Vqض9 hpozjkl $8b>P_LTh ,C%=f z .) hfF;#), (Ӿ*k `|>a27s4U1}Bhv o\}@wbm<ѕG+m(Oay@PDQw,?C/"LF)ʲi|[n ëDVccn=?9KRXou,b 5݉vE6,U&nH_2b2R sT$[+5287w4h 1eG>3Ǿ8=n5epŅC.pr]IZQO93`{M*at{/3`Kgusw<4T57grZuS?f ˖X&?A."1k?t{%CVĒx%&.a<(\YRHലeX% WL)4tҲ,c0&Z jt ! qk3\YBdڎmhEfCNYa|K%ɳ, ΓamN`'F(6"jUo݆d+YHsߔ$LzĚI9@9q4hРsmO2ceTXtcJ;E@e eJ/x2C A RW\f5zH*ډTB|g>Ewrǝwr8}͊AFh ,rZ'[[cH]>nGQKkg %,[f(,#V-eftuJTCH.Ypnfj9Șn8jm h۬+k$Ov{ۓLVf2٠A h"SK֝>'gC-᪋+fbdBKG,+ytgwǜH@0!Q|az.ʄ.]%go4~-)[|q6 [h  X!FCգ! MD>Pz( d&C(AY mI$eyZ&m,b A3/y9㒚Q,CCCmTc-^ȐhBW%Vtwh( ׳t ʅɎG61D5QlsZJ\)@g'!Su ݝTQkbjq|6ZsH?)sٹn+W^+>fըpf3nZbR9QD)t؎`'QڦC>lӅ~MtQQdlQu+$&q`ݘsko5W}f"A PJן[lE|˹!,>Vp?5[:fp/9JT>1h i_:ʕߘqsqZ^{n_z Q[5$LAbSuHx!.*T.>xrDbPo2AA <%ؾ>G?7N=a和_oҶGAHr 9'W<{7or֬0GuT jQ>k/݊Ja!>8<o|  <1 DBjN2(gL S ۖڭ6FWШ(JY.(SʢĹ2@XgQBoqTqXP Il:9!TY %w L!t,ckD"jllQ0V{+Ƙ:fVK&uDkA :*kjfcmjߙJ7Mg 7= 7̓ƞ+J nz'leΉT mZ" l&#y pA"d*1Ge9ɑ*zSUIF &mELgʡs/Xck[%L3lРA4ջ&y7u/ =ܶv<.ەi~+PFj] њUq\vp41Le \tF+OfvQ)ǿ0nnD 34hip`ڭ T1PZ#Gb p+Ղڲ(,c[C+o!b. A+BaF6#cQ!ok H&9&H&X2kWAEq:fxJ<721PLrQ1(9#3gKUi֣AL92e: ƫ&e}L@<w|  `A6mn~* ώǷ{ٵu3wfr2Qъf k?_I$DL$Frjљ$ uEr0GүH\lH23RO]k碖ӠA { ߼o6* 4hb$->{$} `Z-|}8(ݤWP!7›^>k W\<U[; ,lР AS#Ru6&񩪸!{\bFeQp!ZwLfT#P@<Arh4*T<È!3Zh2 k)i9Ɩ(xb1Bf[-ニ䏥wVEeAb3Ij~m//uU`55u%$;s,5͗-h] X OU1ǹm>xbkUYPNdN:(+i phy&U;"ZTLO1 - [f Km?i*Bcez|u7hРw4\N}>lxo|<,_9՜sb; "DU3VKP,pnj7y1gaGYRuJae` GPds\(dPJ}2OT~xb 4hp젵 2T HT:|A#chwn8N^L`Z/ /%9 M>{I=pKf>p 4#;hEP%Iioվ%L".!ˣjv k-pi3fz|!bHv=!*[Qw.Q]\w5KS.hMUkmV^)UO[v8phգ*W!fƔ.eg^,b5D jbE[NFGOmf>D )I[X B.d.< sz <[r!*USB)@)E "PBptJd~amѫ~8V.1 -wRD'V-=_Х(s:?? P VTOeB;7[fd%X+dFmRm#φA"11q33Fʠٓ>}bb1tĻbbF*hP4F[k|p*dc3CP$!5eYBњm3^ dBwQūAB_jee?տɔ`Rے͢G&63ɖ,zDVE~ ՟0s2@̅d g. 8vU6Mm B& *PՎB5QIhS+B,J$]>,Okjԣb~39v3 9J-]$F{?ۡzj 4hԡ@4ξ OY|!5.*=W&:޿Û_1ޱK=,ApU $Eg%3S7ɣIf4 E‚ ku^fe k{yߵ 6xEJsif7?ΩNhd$FgĽy ||ۋh 0Z*`L #d28nU/z N52 *|P11Xk)hi5!y D'ұ{tDDMg)|GlQ"4 WyEi .İ^ȟ"22ۏ(-3y%Jj!,)33ʤ^(LjhŹ8#Ν91Jp!&P7,!]KtZ:d:*GR Nt*e$auSP> !RcV~2s8'fd8c`jp]HD[V 40tT̈́8ƘcP%Đkz>'L:nOLo/U%Q!J[]e}1pj$\B:t4TPa "C_y'Pć Ƣi^y2>\-xӷ:7, L͍q֮9lH ?u8" emN&Zjڴ/:"X,rWf<6%dUۆQeMDFQXscZH E!>_E:;w }WKz\wPL u H !hxТȌȕi)5Z%S٘DoH=H1ɐT#CFDLњ0:cCuF[I>xT=z 9 +37LAEӝH\Vd@M2j{ugKSXVەD[JhA_HL]A&|Pѡyׇ^fS2s+R ֭4VsgBO "1CkR /:B0|"jJ[J"I`y$+DTdR"P" |K:1,9h8%+"wj"|ܩGux5dq]тp gާ,A@Q46 @A{ I[D|}3izzYSZ:ѩ魳]H$M$TK?=)Bj ON[Y=nMJ(|ۂ`{D^iL jpA| [q羴%@+c"$'*o8~,̒mJO#(gyF2"WhG"o!ٳg{>޽{ٷL̐y Ύ ¬rtUVv?~[6͛X16-_} e4A[ 7IU1%M/m@:Wpob~ 8儌p+TpDTDLcI5VWGϱұY`ږg=ރ@,(z""p!KJǼe Bxq֥4@a%;yf@Z`{@`I'U ׍9y1&,F b0^gQu|Ixaֆhitg`n/J"HY1j>.^kIêT;IXݵN_7߽9m[ Ìdݝ x!tTR{,4PFAkZJH2/a#WĦg֣*lci̓IfRĴ#UVB$xGٽ)ffYl4زV񎟕R 2:2ʖ9s88mk*QBBauGz4Lydw lQC9sǧ2J;WF*T8V ݹ$\ReV"Y.!V:ZrhueYZQ2ڲV2)΢m!5,"BհƉFEL ueBI{тRBZkf0*N2QQ R@ʹ >)˪gs.UK>\;Lt=?ǯ̈)I^kKgL/Sw)Je=1jEV)N?wnGvWqI7V9!S*FLN67BY[#j !ZCH;VjLN7e$>!Z<}-J)+OF&}ޅz *C@sˣ⦛o[޽{YX\Xz37?HT 8p1GIײ'16x21ZS8:\JR!cPVdi,K>)2H r_OISi˜ڪP8Bc^3 qt $8v5 !4qY)A&v&W|pDDk~{jڔX4|<s䯙NoIEP濶(LL^A9Mܶ5CەY geuݸpǐ IDATiB4!#=mv]V.]%Ỗ`)RCtEI]˥O@1!~dL$@%Y?LaeZھo>~ \w <翫p##~Φff^>3N5:H\(o ."&^:/f,B§}'8LYfC~?Ww"phr`G/=g\61j /!~l:n)NXL#Psp༧p%m4ʽظТJP^ED v!m4F⽧i 0`YRP3 vƛЉh!+IzF%aI.&Hj+qVcZ0AZQRLOќ9L>Ľጓ6s'sjF'N0@vOsͩ} 54AZJ}V(M*o%9Eë#aZIL9jbG{&+Q`٤m*CúTfh hZ֤+Uv[Zt[UE}^M哟 \u RR|BY͛;yoh#Mw:mP@ر9cJL n'; Lj±pu{ "k].Oλ'ՑZ<-:F1}#9VNdZ#\S!pAPZhD¢Dp Y% "SRT@M]XgB0.Lk~G%wc->DuKhDB1@HpS6Cfmzvx,S)7&QYh T{Т)G+갅FI9; B(e%B(ɓo`%=V6O͑6Ddj)8Mk a9ρ)<gsͬG5Q ΐ? |rjI>ɵ$O^ON|b9+E QBdX%吣mSR+%TDݥbs=J*A{r<949ʕ]-[7sybcrUsкc<0׮wsoxky͏hw:t37;K$xʕlذkTضڕ]0Fzgnnd t$g}y<?=>/ wO}シmʵ_|sC}rӿi`U]f]?|o<(xj>z?l7pSWV !d:lC&{Y!ycPm LDO0FZ*9BhZ(oXgq.^茎( S0pjb kY€6Q> PJ)Jڲ4G|sBpiEFk<#\VfiNHdEQ1(_JҩwB׏T(bp6  m@*l.9 y9 w-ߺ;LnU`& fxV\8z@N="hL`ـ`GS*!=T&Hr%JZJ~>Q?R3|+sV хwwxi+0_5$ !^+ɲL-ZwĩN?~+!xn駝lj|U$R :ͼ aZ o{+o~Yf5?װe˦%nQ/'˖)Dw-߾0|:4ox+|{җ9{B'g}^p<{j-G-W,hד@g?|4בּaر *,p`;#&Q7U%"GĿ[,9!x/xro@DŽV% @+Mf45^BX둤{td> %f*娸!xQZ:}&Q1y K|HT1EkltRI$Dti_mZ"C3VD%PC~./iJt"dT|kQ*sa5.LLjE&>+, @4llh,r|{Ƀ{ȏbԨ5u*vu;.y _zV|!kɆN’HHЭY ~=HTHݹ=B"gTiZecґ VZDh4s>0337rK38'nذ~-۶m.~g$ssl\m4N048 M1<8DE2a%edxO{ {l޴˿3NN?z+Wh}`p`+_vEGe7|(w '?){ w8G5qGVd͊z&-޼*B`!ǁEKgșO0*`LwP};7g|skc.*TЉj4\A%kR-4;wq޲5~A )W[+iٌ!#Hw7}2IPJGR(iQ]ƥ}%9sEJ 3SjJ҂ QRMI(jPZdpC%SX x'4QB 42&^K@7p`!H MA(:y;z{U2(Pt0-R"cJM)EjɪuyR=}Dk0fxeEl83{灇G3W`L [V Oaέ8l3<%%:.mG;炒dQN2F$*,ZwBBQ}PLYVŰl`2SIHO)ədx ˠ %9OhpA)J |++7#'8x oۆR|1VgjCLOO3b arrzNc kpaVZ'`vv{W] ~©gI;v *K/fݺ㻞i--o)"PX˟C*Ran8yYlwף9M40NXg硅5_w<\rUTl]1߹ B#eDdt"RR>TLαX8"(On"2$ZB9QZDJ]TNXhXTQJ jV/RZtʗ-%)өHz 1HXG@+Ef ie'0(/|j5j!t zJ +]@GŌR |,UtAdJ_.` &clᘜfg昛efvqyO wUN|ѕ _i2:SEAxq|)<`L3t7 3>PxadHT9OӜ!,)`eYe mP>tKKD%Z~ǹeJJǤ6 tm@+<)`|>o0=3~wsoGF3_JR%/a˦Mg~:6n_ի9p mV^la'v30?4h9333115́C9ԝ<,/r;ٽw/<??[ ʗ_,kEi7=lF#s|#㦛o]±쳳wxʛ sכ>U(Nܘ1>>KYpyϵSX7qqYJn fB9 &T_C*STa!JV!ɶE ^\Rs} 5DiE1i$(HsN,IJŢcֈ2soJDۉy%5c>9G^wP%ɒQ؜Q1TCQE *JJAQ#!x(T|Pʷ9ꐴVn.UekTv1kqUR!mߢeLq$!\9;պ\!em]gX1ypO*Ns~?˻ԫ"5+hBwgw=-Éjs+Bcp\L%GdChْ:-Iˢў1З|T3:j1N$>hҎLKUڒ OM4Z- r$t78mm=$CRB^h6D14)K(mXwaQ0R3B`~Ø ‡Vq\w ֵHfb4I߾0I-_^pVLJ"Nն-:mKK QTf&3d. Q gcWm64'q _q[F5?]mVq՟"Ԙ`cq]clQxA,@jJ` ]_8cp,4=3h6M(ϰgck MJ$W޼z1YLZ",#V0AE2FvSv1±$Rovvrz'[7 ۟{˿ܞa©'?*^p9\se_2U?]r ;K_>Y}LLLwعs?zՏaZV\hк]Xdvf<>FFxG['(Cr˭ʗ155u7ȯ/DBi)vLN+^Lυزi3keч{ &'' /s=3=܃*<'j*z{ ^~aG0\ˀv>ǡy1U,xC$ÆQS :[K^kbF*P±CET8f%yƿ/]u]:s-IQX|z;q]9׾;y JEp>\EMi4yDֻ  1&} u!xhQ'FAyG д(PJ0t'Fؘ">h1*ClEʆ&z$B-C "o)KY/ @!dl!t_;WJ2{1\?ioQVoSM:!axdW0XkN.\p*V54x2.Y4~unU_03ըe #,(G-j`cdl裿%):~0BN #YSE؏:'ؑϑ!xzQD˽)% -.sά!h4g/_{cjzᅬR~;vPV m%OD"AuGwgyᇹ_n&88OnZ.HA׮NguE ˻>8@pa{-?cbtt$d;i߽/G}O޹QPỄ+uu֏Ot޴F#05sLY&nuiρ+2|Y_P}=19tTTu{y{,+Ga͊Qfnb `ḵKk+TGET8f9"`D*tQq`-1g(TDZ~BFElR@*G)32RX#BpdY=g@9u f_V2>|uCh4^cR SNLE{qOuOM0^:C+TCR*Hp(U'uZH,U˴'rhO1AZw;I JE(9HvےȉAJRdglڴSO`m@c(Su-O IDAT}o|f'g.d5we|ZR gNME>{!xWn5GG( Xo/.Dxr"<ks slL*J@i@^|"JJHxl V^4&ߺTbSjꞬF˛$A%~n05=$C)erfixb/=q 2Eٖ(~g>ϳj|Z*(2}%8gfg?˯/,{r 099^xA3*/-:_k+s\xy\xy019ͷ5^C࿽Zv:-~onjhk"a|n jMrq5YS";4^<*3Ť qXK"gڤGIx8SV5ҝṽjNHĉ X C~ŵ&ϡ'ODFk Z/k51Xg[X>qn?1F>lXV#ϗfńʫm컽gŽ'r۸PrI6^WJݕWQ۸ dôo;ǭ׮:9j##lݲ {6m껾۶|?fϓO.]\,^W^S³ރR%j-taA6/Z*ʪ<\G>7G>Hn":ו+cn[5R±GET8fPJcZ .xl@,>F%QR4yA PNStX^Pq/Դ\̱p2ZX-#jآHv&G 8<2(ό!hA uX_AA@=98r:61@!SQ8<.C&#-2" ~t$:j#QIѢMy.(X:s]HT(-CU)M*R>6.7u{ڦʚRzEQDMgSɕW#{lYm'ɮۅ:cfr| Šexplijt ķ s&sa1 ֥>-`_C8eA8i5 ׎sӎaN9~Tҷێb9ɨ/_J94LHNhDLiiiTDž5; :w7oK_xqc\˜"r'>Ž?e0'>IӯuWJ>WQ6wEjkіmԥ,A'D:/8\WswL/m1yyEm벲B S!xV]\bnI-> BlDrD _hlhcb됦x*2AAQku%`qv-q&pTU8$#]6D ǠYetlIO*OlnRJ1XXl4k]9ڵ>IV ҾIu BvV/E<.1tT/"!*4dH5>7RTYj8/'>00?LZY>Z<]kݠ [hV֤  ާxĐ[|KQV! 9,E͝k]D1 fQ^Ogf.ĥ  8<℡ԳaIEX伭5Ǟ-LTmYȤ򢧚l<.}:i}ֳMTޔDtTx \O}_^[6cz |+ʟ_033 tqSJuTER(Ȕ\: Qe@rJ'Q]Uۭ_ $گkYm:_[(wQ CCsy^æUGuYAUkXy;FƱ҅@(,3LO@g,50 r)) 'q!%`s(Mj|%'FҧԁQ2`K!ϣeo곮(uPG sUxvp'}vne`z +Ɩ-4;g$}k\ț5RNyi\uՕy[nv:;3??O>1(.^r<)ffg1n7֭ ($uwl޼/yɥ/{+_wy}s1N9$*T#-}\^bРbt@0 (ZT֌Q棟a:9aV:3&};g~U x˱)3a68̢C$ް,W㣚a_J.>oa!0ׄ|f<_eK'睤 1*"1g}SΥH (EJXsUR'#Er'56V$PdQ(*W ( @}C&3Hph4F+!J7(mR@j$YD2H>yp^A"##6N#ӚL" X]OD "veTҩvEt*lzѹ|g%u粝@\Wނe\N~Pu ."~-o 9nVlOF|r9rr0&dL.Z E87Y~ԍJ\& `}t;{}gΗezߍ2ƧlhY s %«~{}4,x֭Y*9SٿrQ/2T"n<, J +E)6ku!h)l3RpZ-$Au&HU#q:C~{bzgߢZ޻TKR->f dӸ@PKx^I'nKC,:u`:F%R{nY>*ORQ *R": fqηL0SױaaT-U<3d ~魳dɓVxooH$y$wHdR<; H$bg%aqEyytwב2;;-[\9d_\!UWrӷnaaqqZƍغe ۶m-[Xbbcs*{ʩFlْpŶ܍16ƦI zs\Hr $^ @pqoɽYuzZ{Μ#Y%t}ٳgϞk-Ʒ;=-]WJ/]zHi}L*XA-o~֮_G… [qB!ce֬̚5ŋ׷?+Vףm: NŖ333r}X'?Ͼއ_G٫̘)"$ z5Sm :FDžtuHyͰ6O09'ibnL6\N9sWo;T1~kwuK1Evc%SNHHB;ր^Sy5=mƫFiAIޚSȊ@VOBQA9 iJ!u%Ie(σbljo^yXErp!!d&DCDMcF2:6*b9!\.=jK. m~A-94:H5d!x8H=3>8PĄg2@&gXL4gBLĊ䶞B 0aOxf03zj$f%FYvNHiHAF%ռNiٓE>W/:U?\֮n!wy ,Xplyj zanV,YKM)'ZN9Dzp1Gss'1w,b|›'W{{nB_&afDЮ9+[ˮAx!>ϥZ416nēO=O<+W~_6lğ?%\6yk{8xzK/bQG F4x1lSJfT,LIƔjY2:l)"}?a WD)Q9y;.Ȝ}Ě7Ulw R_ڗg Yj咦r<Wy,:ĬiGjW~4oщxc3x=Ut8`s&.\Uc1MĴFid6 ws(O#k-DJ{2g(MN$qhE%%ͶI3T6b-=HE!P62DU$1<7XѱG2oB[!hZcK Y6]MQQ,_BV-$Z$td S,̖0{g2e yqVkf> -m8~LVl2up>f[L8`$c>ZKk#Φ$d@@@}ŗ5*ZC2 wv17ºMtTPjB~cr [&'Ѕ"nyR D]aOS^xs=kI ?1{ wwNV]C:x_t,zJs[{>`,O_'3XI˿زe+?:Ƶw7xz]& )?o)[|aN8* y)xŷ,4ӆ*<Lft$8iV{jIu0{\m>Bi〡hP0A !i9&."7!!B1Jc ys:ABMy֚J!.Fct 'B5"ZZ$5$hI buN9c H@x-UEb]mZBUi*Z6Q ݩnZ11i~Iۧ$J9p?%w͜y\ΩMgWn!ls08*.^X~ pYF68CXWg6Φmüc斛DF >' vlXIj'c#\7BD D-SQo QNPh 2ok큊zy@!7cҰx'0 妢fPCOЋ;5$I#?O|-?IO_[nemQ^|/1z#߾=.~K/'Ʉ\Ux IDAT sh }U,hr5/R -n<;8{R:?sEWGgq:'.^M,;_ wPȤƛv͋\k`$<{"~JISN>i{~D_=tJhoӷ* † /qݲM< >sFB5g䀱 o̟7 O>(w^gD~_Xs=o~Ñ|fg'vNz[9#K4ok}ijϿ_hB;,N:D: ʥؒ潧V144ĝw9MwOgl熧׺NٽC$QḧЖY]Og>|Gݤ;owW306߼Q*Tb,x|U04h]9s~a)F''y'!4u3):s-CpoHlH6ذճ?vSʪMZ{b{5^>z2avw' tUCcv.ӎI^Fmk6\@-A$Fܪ$cJ#dq QB D$Uh-ʥ}(EPH'Hi[$!)&Jhȕ4NE"OYp" 1,l!ucn4mJ9qۄ4JhR-WBl)VXZC{UI 0%ٔvG޺ͩ[L. JR|m.ФAg {IQnoF-zgFE8<ǚR!dn//i 9:RZkbK+ķ©XDocɱ'-R*E%o"x"LJEթ>Tet|qFPPj$2{O21ͻϚijɤٵ'$Zp:!8%v^c|?;T.g#/:~ϥ9kRΝox(Uk8xٲu;|ԆWi?uS8{"VN9O'O?39)xGY~Voj>3_[}ue96Wy#D%~?IJkfax #F`JḯfK{P5))iplKoahlPژA-k6"#ڠ1}$I}ɫRcb1 $PC UJGK&䤉V|>8Q,Q"IW>滘 Y1F NY5cQ:\YW8 BeyJ<6Ѹ&'%W4B[x|ݴ WdYlVj%ZT,Iː1L!pm '1؇UX:&mK4x[NשtPRc8cGn[P2 h&(c()p[9КuDbWrmm_eȸgÃw7%T )m[)F+ ttzz^jC)#R:Tl_jջP Rr|["J'4O3MQȔidGP*( DE[ؕ}}x|;:sΙY*:Gyȇ;{Vսx$fj]x,V~]R.w\]y?omؼe+w?_䓞8O~5fmwh3A9g.eчO{[m8붸BgT̞e{%cbK?e69o|a;fq[>orד5R&1bxcFW,K% '_ cY_ٖrEcu>7OfLʚ 8"BϬm;wLO]=/͛D_]e3{-{5뷶<3⮍6DL N ',ڨʉՆ4ς>*b`zZh' !F@ BBۘiMDm&H?^ 8T y%L$7b~ɟ: h! ZQ6qDL]$e ZDĐB5fHp3iNZШi !҉/R ihk[&G5xj^Lq[Z=zIυ`m[Մ#;.dqHC wz~F *hdp8# "1 W* }tw&ֽǣ߽(Գjgd!oRrX+C4Y<k MI7mVBPI#HaRډB"YD`drȵ'-DLn(5jK3thЦa~n=lnXv3O'à0ys%o\N-(Aiشi <0?ܸL`-%lܸ=o}[H iTv7h48v۪+9sf8[ "+ q=Ijc# =CcZ<[SMQ%LF__6H#IE< 1N2qy1CלּE#z;Qvqeg)Ւ333^|JU .:_X䪔o-'kA)dʂ+i,^Cow5:ǿz$}z jmEi!xCrs|E+/:*U(s(Ok4RBQҹ&>$7(kޣu$*I&ā%*` Qq{ b> "dYL>PFk낭684$4͢EkamĒ,Z,a!ÌVk>娕T)rJQY09jwdϕD@I[$5cB-y=&xe 唕?Wt3^S֝t*l 4E2Nkf o\Zc0XS>cۘ#5MnS3>(ELODPm}U?ZZ(#9=clAh[ \ysSZ}.HApE^Du+(R+46ߓ%Ә>5sNkŢ#._ZcWrPĀ7_'_kKJВUxqya'/lL:dM |o(8ma~W7QeftZfwf8xVr>y6hc/&b80 odӐeB 1ZBBWBT>Yt Ks)_abM$BfmhmGFǕ;BCܖVx Ecz*&V@Eu=ecqkw ĨlU3haJ^Ka_*~oVu"fWV5ˮHRG)EQ8[ުeX5 ki+q̤˔֩HX 8*WN|~;9!q!bDQw >"&j)BkMpl|HH H;'WsrUD5̚57"̞5# nVl'`,^tܤ sϽS;gNNLOdbM8vVy[޼WQ~zx8xǹs8;"]t|o>?ohRL*3jWbIeh;s{ &fKKyWcoˆg3>oC ?-~,>LҮ**\_ CJ:CX9y RLJbKVivH#:&hW6C!׈DV夋64J !x:dYۙ\PVp} P>`%sF+C&D]|)٨^NEŴ^ E :<2 VGkM4F),,kD%$4gk4 H<&ڔDbN=53w&:rIJ=!WD[Q;ruCh6#`JдN͘)PGPK^3N|M h%4RMY93)"Q#P)yk>7V&q()پџ;7F)N(XeTI8!Kf:ybfVdOhL4cu&WZ)DG"xZ nȳO?!.hE3Vs_`E}l A{ *0O>5Ã|i}_x8t9T:8$8|Hp%ڂI@i!8WУEiGJJkJ^^=&/>D_=(1xxqXeH2ut:Ҕ'RLiD_`"؊Y8DHNdx3^@DcEZ * ^HRKu>mk8h$Q;xEJw\;zۤw+7(Eϳz {ﻏ̚1i;8{_}yeoZ)JX-709K~*2:2ƬYMeevn.\KhBϱnJϼjN\d0W%v*KO;9T1lH 1'wC$=Qy&Ud**L6hm9 a ?FCI[ -:0Ƃ&h\PqU%䊈B"xw7<T*\xWl-My ,iFg9Y89BV$ԪXkm6bߪ^imFUQfjctNuD[Jv:P10oYΘ'\p :J'6R4 cYy|9XT|9XjN Jez=oPJh4AhNaB\k2U@y(sY~< ZJgRgӣҺ[pFig~} Ñ梓TDp Hxdp۞y셤3>EHHayuhRyWc* $uHllɛTn{me A+ W(%:g>^ >BJh$lף~Y][*w[xK6c4xX .|{pмͭ>#`N8F+Wt&@A[_{?gygn3^ǡJP q’H2Y9hƘrYQJw쯩B,h%vh>su!L3KW/b/2vl`Ɩi=FT.84X}PtDY({@_mm~1\jW.W&W a*h`1BF"8\06E=5Ar HTx 񚨢1EQM|ARcsغEa*VG+M64s(BIhYqb}zT(qeU&/ž4S_;՞Jd z1X\R̨Uf(i {X',džx>{IJ j!xR:+yShL"4t V:0)GDzj|x֙DՕj!FqR(*C ń$V85ňH NѤ@7fv>_:]n=o?+K##~|ª5w/sk%ttrj[n_:>oM+,gu:G~ؤ}a{NCv.A66hc&b8`m=咍A$;H(7YRtwuqhhD5D.xWx!;hW8B %VDEDcV&fI䤊Ib.Z42cqDvh<G`TM!Q1FƠTHCe"iqMCGEeB)/ |ZB63MZ%Vx!?I‚pk?}Kw8%|#d|c ̸C6 !xa]B2#j@Txb+֔%1kM$5Dla!fgliiU(B =|Í7Qw;9Yr%>p[4Yj5O=JYKOskk4z褭 !gѢxɧ >QGA?SN<G/h▟Jgw'gyƤLPAsy'kORŅD^(YĦWW|} ['T%XR. :s\&^WOxX/{ š%GtZ| ڶ6$ڊ6{(=yut-uFjdU!*t3y[xm7J-CBlHq2휏-HdG2D'HɰȫXI"H&Z Nh.!#I 1)WvCn(Y1dY$Qs*kaZ,EtP5")8pAU%QWUGhJpO%H¹G} :,:6TL,!u¤ j8[!)|8*V5\8e4&`2-[D.AHp҉̘B0yd^ۛnjmf>ScJH +%bVT$*.")}7ӷiV9Di^ؔT4mńܞFtT)CPd^qyQ(QdM6` hccšb(w=mG@__?˖2w89$1-:E^k,Bi!oxutuuN|ǀPTb=/_5V5&7M7/]*jfuZoe>+"l>Gm-瀑QŦ򪪘0 OU`x|"`R_-Fm"BZB@rV$y=XDPXkq>*ER/oϋEc oQV$F  lbDDFT Mxb>h'! e1$՞eT"@EݹH4IV)[3PQY& Zɀڎ)LkԐ^ SwhyeIP 2ʫT<`-e$Jz y01`&lNPӨI^q<;U_NmX96O$Գ8^JB#f C!*rW= bqN@ BrVrWm"C6!Y=:.i!/>V=ŎehxHJ'ΒNg׫ <8vRhV =c&M^u]q %@6QXk26?1xwBmyC/]vz~rRoªwtT+,^t.+Jsh4\Ms,8IdwC /8orS.v^KW}\hU %\qU#Ϧyn]oNVXcn}q4eÌ7\*7G?Khv&+>.b 8 ?+2$D3:6R*ګ;;+eh6B2ssFwQBr a!PT6 bbq! *6*s.CcDF"yH]:z#J؀cHI,&W4bLw9ɡ .c4M |R1ר@'ctp>ZF`8dY#I$Ih)E@j$I ) sʚΒfjwMcMԃ -zY]p2[CX==>cѠTI=VQ3TDM5]TPQ$dR!xAI*Q^i@o9FꞺ 4, '$!I2&|B%P1x/Zmr Palr)T);iʆOWrF6QJ]Tn*RS.$U`&J. TJRR$")ua+]$lڋv=j/܍*w.Tҁ g&[EA p[ /vw02::'뮿)_#e6.,##v\tEpE+Kl@l EgT:~h/5_}g-}q<Ҵ=k''{(kaCWu8ۇ<VvPSp~\BlTl^jxrUƗk8~1s[QM ]팘68h1m0Ƀ1D<E Y4 립ljˍ͂ulqy(EƠ׀DG >6 Қ*BTve1U$NRXI ,j9X )AJBl jDI'i&PT7Z3aRa=Q=ژ* Z4!t)J&༧:jP˄#F gpd9琄_1, bnQ2 =u'T҆Ll8y|JQQС5!sHQ$ZQϨg' /8= cDͯqс, Ƙx| 8UL,6Ă [Y*qɿF\ƪgp u.O-6={/AgZʼn:] QG1\"\AQ*'BȅS U4yDc>_<=s_tᤐr%Ͽ/JUozgy:(я93XpQE!?(v.@w+ b}$=]iJ_X^T}Cc)֤9NRJ\@DhYH K鼣 EJd߫H] fRl 593ĒRxTH2LUr[*X҄d]m$U~3 +!pRH'}Vsu:#*C_q$9(;HIP'T~@.E2W)q[fazU1֕Y*Y"4 hlDvwVטںT`QhJӅ\j֚@UC(ArP M%]GA!Ip’ʩ>3j.d˔w=EΑQP`0ʉduW)j-aW9ۦ'Drݷƒwˋ[kYY_PRUN^Wp9g(/n~GXZZOXxb6زG`%}x-G׌yLdg~`;I- -n_a}6msyp_ ̓#e?Жp {voG9k *|{?1"ƍE?c#q صSY̧sq*1q DD,o 1Y*d$1W%"2!Y5J18U|i;jBHnIUE(1SBku]MF%*L̸h{BWɨr;R-&hc]'1@Єuz.M-`"y`eA$OXu!dLY"ړ4L6فC,{U,MLM<2Q+y E ̨׎6JN82a7viCq\s*UHNaCL7rp:X2|MDEXQ|<)_W<LD/0eouEWBgѯb0ԵZ!.V'0"8aiXRP/c%a cD˰T=)YJ '&ðI^'EŚ6OkP>M/ Ǯ8qdž򾯳{Lt|k_}NPUs?C3i{IK.᭿޽{ !M7}?›/ s,^㒋.<}crJdn,$zoG?wlڳv8~u[ܱwv9oaֻ YdJ޾έ5?v 9&pً}E cN<Eg=s6i;[ZugVqRCix%gT92jY=&ئ8I3sn67P'oc? ӷsm cS;pSrJUXGEsfܕ>AƉcLx DsnBVR8C4M  .!f \ .= 7+N9"2c" teBOԝ $%w(zvr/dzVZPcpIL\!ZZ2a Vk_C)S'XRT^YXXDpu _Xi !(]83F# EsZ36) 9s>TIHt!цz[T':%ؽs GK5iu&qO\\1. c4ֱ`V0B'ux"^g9 )g >py@XYYPyOlp14ͅ!IIX9rz绸/ .8fkx٧+yо''s90>>{xǐr$>Uxds;wk_eo"ˋ;xӏma0~`˯[g/+x/AMp78 j~TeKxv_Xϣ(p= v^pc9N&D N=tFDh8m9J}Z MT:Dr]Zj!`gJLKxQX*%[ZI E*rHʶ^]N1II)gy[[V¤鋎[g-՜%)["Z+bhL1m2Ȅٻ+܆WDKNG):4jbfy36{{l>L?XX]d G2,+Ja}Ech-s6 Cb݌%/l[<,xa€Z Qp&,:A-_11 B"U]Ι!_jYUxZ\3;_뺞6+ti:;I("MpR]xxW^SVaX)Öa% ţx\)Fn|j-a4Qh-7|9:*IV4FqLp*.*Vq>Q F2F[nHaRYD5eHr86PZVzuzmymJسgo7p9g?{ 7}G>?KnvL_tl߾Ox󜈙 lqx:{Sl][7>q7}, ?r){3v, ;.0pGƃ:?}w^JGQ!^$Mo_dPs2J\.pH_?kv,|8 ] }b&`SԴj ,R;ALZ*xWКgjۇag9D*R y0MmT+rdjJ1b1QjUGU7l>1qٮ%m`yXLTI  qB,j!I:=\~6\!mwϻI98+/pƛ~Z$.'T{w=mWDV#e zu+zY?ٹK@7b؊]e޷)_ *.7}:yGy.c[bzw`mE"‹ .#_X?`^^t\z~EtcS93)`Eb&YGaf36֝THBi]R-+Ŏd)=1R[)N=N欐b^'@bFE/9Eo\<1$ šI*q0e)g8=ӆ 0 =Z=t,paAM:1&Tµ s|(Dpm&*cۙ6\n| !&pu1}0QktIsI-.`3UbB<%F|nEUtJ JU)ᐮ59>+̌$E+V")&ցK°%9΁ O%0!ƣH}KTo\ +^.)>:c?9p]qEs.C*M Nu6QHE)4is$ !%* jg ?IW?wko9M@xygmwܷ?g;|֖ȧN{S<_981s2bt!/Xn,-JLHV^%$nD D@nQD!Fr( &HTjHJy"uMl,g-P59X.k )OS&([*օI10i`ǂpd-f۝XrT麎#] lC\uYy8hWSDudov=Gn&93W^}>ߕkiOx)q95{p!Jknp#-6x77Q}1/޳;'ErVD޿h=[73i|l޴W. k3=Wn1`6n'p=qLU䬚sNsl98Y1s2! gUIY#|fE#iM+6t͕D`@B&]kIDCM9RCT5]2zqdĐ'UUR&#+%9 wXYUP$c{3e"i1\s{FӚlJ2xeq XQ44Xlo??b«ҶUTy%$+JZ` bh@%':TݫC,LC Xo %H]b _eh2nHR2 hң1+W/gUL&ٺ"4 u]dRUs_:a8WaE]+ccPJCR& 6ShYq$,eM [댶 vm]fummQ1=#D+J&+٭d4C<|pt6B͸SǑzMp1aⳝ\on}}G=-/|_>T`۶meg[neŅY5%/~1Ӽw7m^zslݽ{'/~|W0ܔcg qs\1y {_3_m9 W:92J³ϨXZ8I1h֊NT࿼6ߛ7ȘodVZ0U.>t;{%e鎏i1:ݱp 7V&>}˄i!%Cl98y1s24!phj J+ TPmY5#eGmUؖSJTNX^-^$Y3I%\.z`!BTSUfBk)bJ0U)ۇ5$*NքgmCwy+M"Y_Q:[g2d6h"9{.fg;o/54kmXkK/RMC婒G˪-dg ޚ4=!S%JH4eQ0HPg./-U.xTHLj94]G\,L*[dcSZC=2 K I}ȯ0g(ǂIh-۷:\)3bʁŦ%k'纔UQbcٰ}$CH|eu-)5lt㆕.w&sYIg*U!D8V@c8LXNI/BLrv8$mrv RD-?|‹)%~|g&sG_Y_Xe^ȿ_yo?+/~&RN2Jy/̌I߾K.Ԏ3O݈nmÇZv, k{I`46/81b{988ȩ8$%E0ژ޺dٯLz`ۓ!Fؕ(m21BD% L*UOk{Fq/T5:RX&M}-Cٻ})j!N `9$uFu!#& J*=\mN':W#U97ͱ ƖRyv SF]"uX P8ʤgh$րX QXpgd:!jr"K-JUNQ DFbBшH*hI$ٰ?ƄwLJ*rRLF2p$zaXWGhCˈ:GR:DxOPGPuSo4J'һ1ǩE}=0U<aEgnz Nrֶm;x<җdA?s%ڱOp\ 33et.:KQ7wܛ/p=<{?|AnIB̌xGO&gRۼK;ppǾǾ09F=_c D ʓ,RO/P'MGЅV8J *R*3b&۩ljKSH҄SkdB$]CH>Bo)DMӶ CyLШe2\HebjG7UU(711jCF޺=[:kMꡪNDPۜ(]0Oh,gĬ[]*pr($^Wsyrmq~E*[*΁"\y]y7!&$2j3)atF4b6޴1HUդ$Ұ T41tdS~g3~=Sҫm[.ѵJ%(mm;&?d"@$}Ɋ!% 6: 3RlzJg6BRiI0KT"(I-@(9FVb͖MvvRk= D vON3~אs`.$973-{ƼͶoF=הֻp8g|^>!sa0җnhi9Xk^ yuSӄĸTٵI b7>f0@0>rӈ6!bbQ8{m59r~xLs1Ǔ93)U3PҰ fOuXBI r67N;p\K9Qm17`K1go!∖ hJ6- ԾBBKUufs$7Ke6@c"VF\~Zyk| ;6ȒiPHꇔqftd̓%fIiwv*"61Ƒ&qGfTFeC5D$&Rd)ĨpAc`DN[P$ɄqQیC+{I2YQ2m[!Ixq*Xk& 5XXٲuȖ-Q,Rr_6Hc8::T$wQk]LLZwN1H] IDAThSp%nMR՞CmKdҟüoFP,?ČR=^ϪMs@oyrz'q {`}ǔУhc1UUI*y[Ȋ49w-0Oi>V2yϿƿ_T~}+WۿO` S^6/pd(7۞ڨOUf}?opoڗxp8*l_HxM =y_|r_?n{FWԈ;*㩂93)CHLsKnM2D'_&y_iv elI"b\,vx#*OX>O(W_j_#H۶DK2S*1BЫb%8NsKk# %;C%2Ј0&aN%2Xyؽʻ VFX/<4:1LUC'x%Rm@$KPcX "Bě`I-Eh0N0}oo^|.% R찔)}YWPU51K̶VBOTIÆ#tLBG =ݦ'G% t!vy5$RۑjX8JQ26yY]otIk.ֶ}tmﯟErsiw^(Lmns4fee*M7Jqg+Ids&ӓ%1Pqh5d1]߷Ġ{#cD ]H$bEJMB2c#n1%1IVn]!ŬRD 1 tLy,guU,ۏDU!tt1RyBmMEY5;E9P9L.ARO0%Tf 3K:)*BN1ʪ #40g0sNXk ]R>o=Bi4)e`aWk%`(1f up#x iGڎ&h"w4HfBp4)qxJ+_2O]|!:^<L&d4D ֤b;hC 5kΫf8;zȾG}/;wdqqfyqÅ;i95oN>NcSS^Ȼ>Ηc-\ /X"E`y)61&'EUvϿ➇5UW x2I#Z|"ƱmYQ[YaOkڻ^<䕗W /1S s"fSD9+_bﲝHra.j, t]|/ѕl[QDI̐h_$W`J)$ydU}sgh l"ƨĸDd%4d&Ld1qM\2*DdGDEdhhg{9z6tyW<ܺVս|gIH ks>R䛙$hӬ L0׏^K^Fao/Q9r }{Gymv,.WE|L]e52kG=K,p03mJׁ:R Q"R_NY) :%jF_VVBMvHȤ:|OˤJJ&XH+P!#Ye\R4XKY#J6bc( 5[=BRhĹ"M5s,vEIF:lk"b!,=0 So xOrD.bҜlE >d\gcDE57jȜ6ߩ&X"Bʬ+NӪ (E(E_K9ܬDJň磻MV;7ycIQnvnGy 2P$_T9.BiM `4Fqfc Kik!5+DlbxB.R5!RG{^- =HXbH\l\S~SS jr?RGbU Lq`c穣PE*,֩ڔ/+s XFC % Xc V6bD%*T^nBm5W+?xX*EP#8#Rgvdvn ږ¬WU`#!,KHj(u bĬi{+}:8jFCƘ2s[I9 MeUC׳tt6{cq5jvyy=2݋ĀYLVJ D9pc<{&n{7Fe6>HqW\}5y+t&0L&8gy/[o-[;x*:)ZEo5jn/qKh 0tgXAش-|>+1l "3HbHbM -K!1`vʪC"?MBP6W.8pdži5+1F;w-DWRSW[ָ@e]&KELyqVjujQٳ".9O0!)'L:!ثj(Kb/b㣡[+,>uL}r1G+nV,I 1.sUM {ʠDDp ֥cs uٵT>!Y3Y uUCYa&KSH)DJcb(Dh@ F'~^WӐ#+U T$0ڗTC}Y?͹l|ɡ*(QD 5b2"˩JRt}838ss"|񦯲}箼/p2=5߀<18$ W}ƈc{_L͓hҿvrQR@5pϖs >ZN9Xe83#iSϮaú>qv/6o}6 )Fغ̳}=c'y<;4c1(ƣ1P`łX%&FbXi":l6^c#1+dj"$2_sCF `rP`"6DbD,}D]nH{gtZeDR)e ,M=7(*3IuMp50I5UZf& VͶq"&[kiZchE:6xBH…Xgy5VJ7'67OX__ïsn곇U42F KQlۢV TQֵ"tYl7 mg;:{i̬E͘y2@ ĿvA| U>//p>PU.>c޽^?|泗swq3N~\a1D/pK'$ 5]<3/VH}3-D7ض{sRs `LyX>Gkص}t46*9{fW}$/>Mije7vV׮UN|\UM]=,JgWrީͦ9c1it%6D"uTCHM꘼Rr`XsӤ? \2ŘR5@*̨|n9&G+B'1FJS✣[5'0sĄcS[@1\{zp)K7nΠ)k v+<ѧ!ŽIt?2ٲd ,W5::rdF D`(ж&m_ u4++1 qL!+jV36IdeZbUԦI ,l IDAT<Ƥ n\|)yla`_1g yQgb8l [4B )UT3hvଡNTڤ ^\p2 c4=ު3&eXE C &YKTJ-Kö=^>kUcg :65䄮n)vy;Վ{8ҧQ ]qhMwsW?:'[v.W6y_Oﺛ= T8n>j=W_s|J>g@1zp_W|b}$7 –?~5m~Һ'}G [w{p K`xk/%ϙbӃ5;敽srwdLǤe{?qe]V'44p3;%il{cC 1$"'h FI'dSqz%=V5 Ԑ&ŋ56U8EĠ$EIƁHQC4Ʀ&WlzǥIa% uo{: Ä[&4X19{f1D[P{*vk<αmȮj#wr2% (3D,"GL341n`ձ96&%L]jP h(KUrUBD4үk+v.RmR~`j۹Av (#K 3c"NڕN!K+b4+$0a,-@ͩQͤ&uiMvZK.5/p-f:5_sk=ɧy)-_SiZ^3Լh9 7;_V[~g8܉x^c13Aj"\~Q1:bo*SW4hHS]uFNȿH9prF4B=A#J*LE11@4`JMq$|ST-qodbeiam ZEʾT0EPk`zQtmU* w/`bհmF<>bV*@UPEC5fbЬH*s"&IdHJR,:xXe Uh[S{AFKV-2``bC4vuf5"*HO™c@4o1%unSU&PJ3Sʵ1BaRdt #tf&3eʞA|5x(|3 kaR&=*p>UnW@b p ZV#ۖ!E&oMpa5LsѼe/~Bۊ1s~FMpsUnz5O1d^ŊLOG{OkS1Ԥp۔Y5+" ˁO} A6РbrJA~x_0G?6;,7e=y ˄5;g&Z_2Nm?;?c11IS-,A"F%{:F-BRM!C8%F"ED:ؓ}S |ԚD͠J!n7VD0JlljOjH:x"52\zWm10Q S)tlCc*KԔtRBU3jT0nm@֜2.>HgLwqtq4z) u>}o CFcH7&ˍT=>fFWWj% ~xM難B혙ni ¥ ek 'UQVn1WB+~-F-Ri lcш1Ja(,S':q[Z]09F(LnσS1! ʚ}1 i2\ukzݐaL_˵{rsMØbIKj3?} N 1,cPE`K^Ml=?,dfN?ά;p842Oi{oXw4ьm_|Q*)M~JR{W,p֚'ʹ*I(z_O})p.:o;*(BUyGѯFƅ.p]k)c1 D KEc*{|L9"i\%ƕ k&-!Ζ5y5j$#մjiV,ZV'bҤ4䶱|4Xaٶe&[%Kڎ0I k) qFp,,Vj;P&Dق*xJcl9I*xk4ȚP4筄 "RׄڧlqgVv C:P72WIZWEg*6(aHќ~99|.׼^YSV_mMIe6,DF S-GaL>y ɆI!!E!PbcDR{Rn%+BYڜKɤ$UY:-ƲԜ}IaeLu (Ux"Iic1RHDCĐlX] kO5*=FbWa _nk.Pl%ooQ _>^?Q6yFݑ*6|'pW]u]Q3<pmsG{Tc #zОy.nuC;˳Oڟ@eSg{OLD.չq"ZukJ&ǟK$:3F1c1ơnCo(!Zc zB8l:\1İ]jR%CI!ّJW,  cmg u&Ҥw`12+A#FS-46"6Yǜ+cLzNjL&w/x>PMU2r]" Qf adU%7<8)'{MHp΢} nIXjQw%)x*}M]Wxaڝh]3QXgnOjQ蠱+H!)3T<.6MUl̊!K+ۨ,3'syV#}8`ajé'?("x_G[oc]h?Ϡns _|c'Ën7ft3V>㛶UኯTd(/p42ߚYE=-S\/my׿cwՆyTVզk-?02~AR;~6U=5OcN͸Jx!|O_!)^n)Fl5*ҭ#=/H BB]G_Gzud Rd%Q 5>14Y a۰T|_&m'hRDRZбJUG)V>Z0I0_+rxg~Lpq<(#q|ls?M|xd qF~睃sQ1P0.:7\1:blp$UCክ_ݭLHͰ& Fpm*檂~d0F]# HDhiIMĝT!-UVR*?BW+UD,,W+B,T}uT* Ԥbj٧{87fh_0q3DcQJUhh -#L(7ơ}cP xjvS-19~/tţ[Ekgo>G#XI'=v|^}woB31Ȕ__i ZOnOc7ܼš_6Ĩ"3>MG hE|G Xc01 "[j7mO&j Ma{181&b8hHFū`" HkT!T)D8$ NRqIJb$s'*HZGUƦzdbʊ !PuR-H| &K8cq9FB6a(ऩOQU4\e=U8ą tkHV=g-M W{~kB&ՄŇ@W95exRǣʍPQK*$XH8P&Ic>#FPE*8-+VИrէW)V &T3{I6C)Ig1eD)Rpmaఉ2Y7y,ep"B V$>N#9՘hD+D|` t=lE2}渆K̾bn~$XPŤZzfnHkq-Ԥlc 'tG{7o榛r@)/ 90;3osֳx dL()tݹs׏AZy[oQ [ٲu[~;9|.k5ӆUntj _c\>&sۜ{J*?OYEٻ\-; 3 Ɛ|?;@8 M3?8MuOPccLČq`b vEr%ֿtEj.ʨs4nACa e&[F2 T._T"?6["Pj+p"WR3S&_ǎi0.#ʓJj,y᪭G8-iрJdCsQG1=9Iht G/(ER.*>|Y$RYEcƐ\!^` jyT%bU#Q& aHɪJ{{55Pkk 0y-OsSbxl!Lf`ʶ'@SG1RH!*b8|6܅u"X=ٳe!UUD{:6훳{(V>KNP֡&FXF^}hsbL_GUO̔9:ȑ'ә= WNbb;k̇SU|# Z-%?٬(So&c:?1\Ym۶,EYvy,/&pNկ~簾&xsʫNcmN['IU6>P ?^r_Y3cW0IkeKCU»?uP"pa~.E/Ͷh& B 4j12OB1$_$e @ACZc 13y/W)zZ֢jq%j ͙(YT_"eM^(TFE}:&)g( BH-L쌴Kց;զc( ee9,Э-mc!pyU7KZn*e@?xpLn@N1[CUDWpA&˰m O*r@l"|:EcPJI1:+f Srp^L 5W4Mр q% LH8ZFhB(J!BMUYU&= gQtրqD,3"7/*Hםn+;[6\rQF~Dͯ/<) ʅ/N ag"޷}?v]syx$֌lg?pcM_XK3Ei~d릭5o?3xLc 7b'1GZ]27(ga-y-7߳~NKxS7b*bR:OJ106bӀXHD s^O(7+0NRuH"JBTLΑ+PŔ"$ +Yb[c9wH0(*| X1ܨ`5["nu'X\cReo,͜pjzQ\ f y>D&ưRcb d„DFbPf%eTz +6mJpNhTMpARӖ1+Z0Qz`D%DrWN(3h+F),g, RYLn2LBSϔX)t dY0*FR,Buc$@hؤ3'++BĄF`5&C",k֑'"`0{N5/*[!GUvUzNtڼs#RC'y UvW+z O)p=ο~7UW֯Ϭϟt./5xw}xo(Pon6m>y}~;9Gb96pS{?5G޿,oi~p9c<1 q`ADDi*"bbSklJ "6zG%xǕGLjI6MK`+$JN+Ḧ́׉4X.Y!7U|_~Ӹ[#&-3*"0g-هN#H:ä́e, Ȣ*ۥ0Pi*u>dŌ͆F*mXc*k<}R8S~j3ldmiI/q"Pőc\inνIPcU9_.4c(aP&\ =OM8a IDAT5BDMJ]VXQVMLeRAARGD,u+cYY,j} [{\fR.{>+c|x֨u!r$I^F bbDٗ$潱&5΀*SOǘ!qk&ؚth@`ʫ670i/oHXH&_?J"&nkm[>_ȞE7@o'b1INZoy/凿gN |u{z(*_gb'_&j|ؓXU.?go[sq_>uCC|EzU^ⓕŲS?IMk9zӱf11c4fuFj(ÌXic 1 !v‘ b$U];c*%UU Hz|ͩ+R#8:kWb?Y@]2:|USEt92i?T`!(z?x<(ή=+gVT0Q#FGS*F N;(6*&Aa;і]E11P%U-WQbJT>dj)LIY8q0nsؙ/9gvYw S 1iÙ0Zf';,jS܋+N.r^M@BՀD0v$L,wdDhH8C+UX.itU_BS ^~T>`ŒYmEe@aÈJɬEbL"&ۓj3:e)DP!HԀ5J -Xp?EoZ/"=$xЈzhT>se,wtv^yq &;Vl\53ï[xw=1W{C O0;wz/GoSQ01?dmswۓaz43v"UW7uO MV_+cwo'MfLn\K훻E;={N.kdgqet~u8Iee18hkalp)3#+H1(0$>/6WXjX0|.{> @/ԞCj_R$o`ؔ :4Fb^o%>mn1I4Y64xRi1oLja`EpRY 9 L{SViPn>TgucN%ytS,o,,-]j@+fSЈJz]D5;Ha, dz]:9|'0BzXCa IIa@΃'뚺pF(Jk9+ ) ̴S”-atұٺfصX`c&@]JJN9Jg&@BI * 2]z1V˩hMjZmkY5A*dEõk\m#"3e7`q1xŊbwfs.ء r2gq ϛVo߾ߟLO֟{3/`rg3/}Ʌ~zWD"n1Olwl}!~@ s]?3|pvB\s͵؞4Ʒ!p䪂?5Op+90+ȃj9˵| K᏷s=5!1!7/|icOC].w~4) 8yo9Yf s1Jp1c"q'Iu6yI+Wo5mmr2Ĭm@`QMML6OĭxU|nْ"֚XS<InKjcb֞"'hZ8In ~GqJ"6UnRsG<6s0]b'g=|뷰~}Uub)'$m Ύ+ 8cYb]zkc-AS>QEJ rj&bYvhQȺ`l}9`vI)DJCW}﹝^::.Y&=uXX*(iJLDC B$ڑ6)eu'r̤2[x*e9S/w=Uncl*#SNM딷"myYc,(( Mr6w,Y JҶ"+{YmK:Bv"mr6 9(Ơ} 69J%7߀۶(-ZMa[tEDA\_e}ڽ.ʂ\rc9lڑ7<=5śF>]^lIztbc$ Tcv*ϻ}t"ܽio?-*W_s?Uc|{X. =|zGsشZ>cxƯռw?6Asu9d qP娇9w׃h!{r?5E4cb 1c+eLY#Y.'$ș:=8DjDRpiԤƉ >d>Ԟ`<'lmc>icfc@-HHH$վz̈{o7+IHB%Js|R:2Z|MUA1nDr%p*|gpc⣲kfG}F[c\IY<XQK8ve;PxE]ΪY1 CzSC N[T5b"f&8>MpSg7l];/_y{9:9^3N9_"۰YY<;r1դf') :3eTT`KsΜ0B,JFnFŋeanQ/ķJ4 %f>${1$LQGJQTROS"DLRtssZ9N?!sH4t&4&%e'8"ws5Msl5S4  GXI#L?~< 2~hNS-$qCTӉpϽsGݾz^dE;Y/ K~}9&onsPz#?l2FeDw]`&y׼`9紂 felcE_%bZ2Lb̤6!(uxrXWQC:q0Ħ"U0̰uJ$*AiSnL4L}@ WTUMU~iUB"YbJGrڊ\[-YcC$ϴT.MJΔ!m-pp8iI*ؽ z{0V:qXJ6a"",U5=Z## &WBcuA/ ְ4NE/+J1̟|N _8Xb;P5,#V;[x vanZaSkTUsr0:9}Nqv8fMo9vw]{|fe8!`e\{Bcz݂pMx5T>UY&)RgFD¦:CXk<48Dd5SHָ@D$QQ(n_4u ։Y㕁9fafOiBZ@g^>;pڎUdèϵѕﻬ+ uo`~~?!/џA;׾d_7}V>7 .ܿ!{c< 7FQO73E?TXl ~}K|UW /(1t|%/pwgvqr{ԼS+: S#0Je^W(~Rh񣀖iqJ1=(U֤Z󏱂4,&K 1˜Qq` i˵jB"T5U^9#)U&!DE`]db;:Wڬ6P֬dē!LNCG1V})bCpt;u7'8{!ĠȈVBKѣZ CdK009?W'L f;C+ԡ@)[\[\ \;4@=<KthQ*߻6mq.ՓMMQM`2'M^E"t;zR45/b;Y"Zg<kzAUqȌ3t ݷĶ#A|V1b'{ բ1XDg9^F0- A|F4~j~~" TLWD26*|"F$ׅL75)(E-U)$cxjQ羐0'>߈MW󨦳;Dc[.~إ}|a8.q?S:Ŀ{,,c|wWgϘ8g_3?bj8\F&`TT8,kx*!j{͉"]R?WtoѢ-┡x+5`CVr^b|X*:H l"fJ1^4ῐDF1EQd FF- y?FTj]IUYeb0V>~WnRUk̍=zp9;\~Z <\Y$&CO7LY+ ")E:F!1#8U|E캛0 >e)4G( \`l *lgbixJ=EW=3uL*]R8f~aFL:iSRU(,-88%}+Ɛƕau4bqR禎(D,36:A躒&%NlsDe!_1FSwnX'M>LR1u!ܚ!0l(ki.a򞥚| (u;Q]kɄgMķ7v _[d6oY൯}dPkW֩y_D26mɟ͛o7Eͷoɑ0S*O}'lw%/|Vth#L œηvyGVo5zد:r7|"?9x,goLkLy˳pd%@\̧r߾@*` kx~ y;ĤHi-ZȠ%bZ2Li\.)Ie$~'&)Z|8qIh$bBH$J(r6 #&M?\clPB&e=JQ3S UbT_'ɤF&'Xw\1W8oo9N#j Aє3A~(гб*0Jv-Qa9)q\qn`r|eX{^*fJ-g0yhR*`xr~VkJӱPZ+f2WD aubv`Qk֧X1nU6PUU"+#zfq 뤾2#Ҹ Q@ұ V >F\!TQlAY'b!?F~M괊C]T+j Q#愛wM\4 VĆp[XcJZE̩@T峟G9^s+yڎlCzօ\$ϑ#fgiJGuwwȟ%v$Uև>qՓ hIUodeeIܯ-٪df?޹ϟjO];brѿ/Y1z~"/KJ.:2-)d$ƶZ{}\Yv_M[+nވGHCn[ӷ:~Yɥ E5DLהkQ<-I,k+Z6U5!#+XZ&3BZfV *>5YIB!Nb=A#A3pkg(9fxn8"wF9g-5hJx$ubbj!cgP(CĘp̓ZdSx㞭Cdso*ru{9.kيgc8걪1g19NJБ#js!Dd{KՔ:0m0VaY;(:=|:JشqPnL{B4u5gJ^uq+)`"&YBi[ [kH;c BX+ٜ۬1m,^ .!NK&_>|WI5$Y$Gޏl'≡q:x] m}aa#W^P#vk~W}ۿݽ_evq"p-;^ѣa8>G=2VVVoW][-Z4 4.erkCT~ПtuFc)ߵ̮ʧ2?cض`y%=glf "5[*rt%ph1F@㞕HUCd}+8qEN)sz7^oq.rm>l+-ZQ@KĴxA![}rIl"zB X*_S&U p8bs`VLXsL#DGa ,BS 4Zɺb94'Y.L}c 5FLd{2HIꯉjQNMK)S4\8[X4FjM?K$oD'_{iZSKw鈳XRcس6?t#/gdqF/25PX98T<ƞHSUZOWM-M`ֽ5`s}ٔ2H2t4XaWpGs= IDAT@a J1xRXsڞaY;]Eo3i72&>Z4 U^VRSn^(9î{m 3*cLCzhj1y)_Iש HڞϜH4d[xfGDka}'W9dgj ;:pu{\Gǽ!x>1N-Au__ړZxScgwxׯ@|yݰʝTԾ)~\Sl]<G#o;rn)JCHUT^X+*-ވ4W< 3:[t ?Y.ib&EmDL/7F,fmWS0d c"WFqXcQM?#*BEFĘdA&ꑤhq b2%& uM-O5CIՒAM6ȉ9Ũlhebz&EzTjw-,2&V0c *CdB L{|v |P:>IV PS7)kTcRXHC݅`"K?z{|6#}j8#Dfdqw%vWcP0W0f@3 k%1gaA]idf~;evQt{b&*c,nj-" Ɋ) ]vCFtm"J&DH$JTʸT#EhlFnb-燩Y^R&N .k015@]?OHF0VIa>΍ͨ( TQbv謡0PF%⇉GNSFDZɃ埙qWLvw'ե}oswtUxM ٞ-ZXtti /8%gpwjr*x'Wc4#`0TSM6(ަR}G+l[(.?>]aӼ9s[hFKĴ8hl m>ƤV&l48$wD"q} "[FrQHY5L |ۚr)WQAsɘUuQsÒlJJ$Tf.odﮛDU6°w`1 TiK;98C j^7 'I]@r|j/ 1 {T"VaXTԾeP"AUՔbe*7%ej WW:D^3K<[x׍y=0ybC9;J9^سv kq@ɱ@5%Mzv5#b ԵKMoqdhbqtΞn:4*R;1 g/d{{raO)5M>#N&AMmZx+k8Yt;Q]p!*H:fwle1ΘU1ՙG[wnú#е&SѺjec5B:Aj(LtJq1Yb\#HR47b"58^HH̭_I֔@ "D*cc;%[TN`SG=ඟ2?BZ<,lf|C߻~o6k]xs7q}+_Q:Co~;wuIS뮽+^*moZxhKpY%+WE ܽgnp]5WȠ I?D~)t:p^rq=ù;K-Xfzi1/[hN)ɶiuQX|  #i8 vSR]`QM5/D8rN*HuGDۨ)XIӴvj Ql%Ƙs62'0 -o/9>Z1WY"ZjZ I#@VaUkRIHu&T[HºInbT&)\  QkGP`(k"hB~ӧp[X:C1=&){BR6tK$eC(J-@>CEmjFʶ#9.y+گ0rMŚ*h*蝻 Cu5&VZK,*keBYXJ+`\NkmEQ9gR$7髌UBTcA&gX\:9Ա5I6H(BG I=E4a{W8^†)NI$¶myޥfBE#јK+,Kj_W^p3sQ|x|RT[ocqy yS׉P1³Κ_6G0[l?l+_wuFǏP?m+2(ʆ-[ &{[]@p u]SU5#-$DadNeW29&i9MV<Ic kOcRUB #/*YֱЍ@%PiC:׮L-{J ~ζM!#ٳg/s윲&囷r7oi3`qWMzWZxAOHR3Irb8oGy;JzY `$=9> VJ}% |0;k2uu-ag躘ײӢE)Z")JcONW;7MnέDAC^mY+2ڧ,V1:9֫NZjM1 CU{`@l>[ ȧsp_G5ÖpQWgJ0ZHFCXkeE_ּD޼LJ>e4$ϔf'5Qw"5J- m]KgaAxV? QlY0?R6pcXE8eCWX&T*s<.#UeLm( G)crI]1"Y㼳9{7SYru;$ϑг¥fZj1V0N8gDFuML6ۭVVn`{$C:FbNlM&U6 %}v]A1C kȊFkD(*Tt.y0ٲRqM#~އDAD8mD *H /('X &^9XՀŇ(b'#ӥ߬5 ۡq-Mt&ú1I:Hץ\+#e?أ4Rt M`p^^".aRNʌ5l|h{%>QcE(ISX\]p%A#5Oљ:K(覠bbRZEr ~r}Q`e8 Q 41Die fN"t]t$"D,u)&sk=گTBj* 6{C 6+YHi  FɥH cm:o09FJgqbX !o{HȱE>/p^ǖ-' &?:7OB}r'?^p/|I&nvLȢ0::c*UNj}LHZӐG)tǂPa+PH  JRaPV+l\&i7%)jMD+t~s@YX:RÆ+˜[o~Uvn5l_(([^¨8bj LoeGwa i_ƀ5T9*p]1 J+TJ ! vft YkI7dM3]h l1XYyJ' sފI;^)p:%%1{UY2 YZ ͧ@67GmR' (lӨa8ԡNY#र 1׼I0tCWa݄+ Km fp.5陇t>}7L!|?A';?~@mT>~׿l۾ޣ/W)HUE~z[ZxFD+#N-Z01-NDMuM f%~M)fR }(ـt(Rqh5YD|dc-h(> T)Ե"`!e֨t0d9I1: X R]՘:3;sRc4* E V+kI[2og:BU&U'ֈX dBJ re=`ObPǜ8+cfq` 6v"w^2hs }ev&aћro{zGWV(c403:g UL( hݎLgx?NU ,ټs'bx_+8b*FM] YBg>yB\SCME*7OפAN fJ#jMK!hcg)5Yd4B:K:*Su"-`|5)Lj5DM ' u 6m[`lzX\\o?Y~7m˖Xv{w3ؙ35|cǎo|Px>ȡψlq7-zS+-ZhѢESn)C :5VOȊkn> hH\ka#t]8xhM5moXybL6i5ΔXdY14ILI{UdSFI6([$p/Zr\ɞaMmrUtӁsphXMBi-gXf;Ta*heY"Lड:Nj:Ki.;)HQ t!# ?00Sgo! Z*| ۊ1Ҧm4!AS5J- !$1Pj"QyImES Hɺ~6R徥gN~F} [lǑr@œJQfF8 ml3DԚޫ,11)T(o3l79dFC>2HCxugSΊKa ,e~;o <:-TfV{<8{܋_>Hc"\:"Q P+rDXtP^+"z3[WTՊ{X503ә0*F*%sxex.% Jik;o҈݋jq'1PcE!2h 52[RՄ4)Ƥn+fr&D1X =W+JdTCa"D#xh[4JDШ>gE,t-VRR51U*eq}h'KW?Uoz=6mzLe߁|λy?IFt5*??+ПTW}BF3{g侷hѢE-$ Ԍ\_+֬~JVdq [Lba j5" CJӷBi Ô" Ecbm5ܳX#:Zkm5sv((sųSLd5K Jx{/<_tѩޕ-ZhѢ-"bQ#&xtmzS~&oF8x0טV*ňlDڤa3<]E1::PEj</YЀ$UuŤ]'hbj 89C9W5A)غ̹Y-wy/gs㜶aw|ȳ╗m/9jf Bf=)kZ%SZ@O`,Pǚ-O>ƕ͚J$u`mLGo/}c X\ʃH"dVU(1A7î(Չ*#!**q5DK-(l| H5e̬Uf1y"]4& R'%!4%H 2ֲ[c_T39zJe̴x@կ<7 i>au0/M ?P6 7ͷ~ =.HӖlѢE-~dfĴ8eh#38'i|4b DJWMjK5& 1& o8ȺN!&IY,踔-ScM44Rp05Hz`LjGn?8fgGҵ)5l3);R^:&fps8sl*3.o:(LhT;Ӄ~mc,c|HɅt\D1'8DN(A "*( P%)wPU{-(\R+k)tEQ#\kobLd*<9^)Y{RزRUq|X%T1$%LC. uVlYjW޳4PW֦Ebjr.ZVEuL4ic͂%;wZ Aq'E\$qٚш#x ?1!p#&;;o~xh8b ϐJHQ\ }Uy>?$6޾y*_BiczR9r{wO}/]*MZo~iFuB-BZ}݃0`| 9+M(bJX( KYXtjŎf}3ͲH{rEUr9GBQ #LB;R1T24j| Q뫹itl Lx s<6 I"Zc rvR&K[]SuAiեj99Ɨy;H,1-P\k/~|w?zk~*,LTh3}vNŠֲ"\w711ZT`,FL=ٔ.ἂeeBSĕ/X\^%6" Is&ps&8x4\ 7L:d}C~bzc1ɿOa|+_[|zW]o?R7` x[`& gHܗ~Y$cZyl~> VUXt-ј:TCQSxr+cVfR,Wv4ӄ4+{ }#tBj<9V&UYŋ YMaeq5t/Ěc1%ȹc=y&Oz |-*c șGZ]qh@r9nx>xΩQ W2W(b?919ŋC5s-RUh B mMB+ TV@rpviciߙLƨr4C0RV*+5p3Zhi´(C:4w%EqhFtcu&_fms 7fūokк1$">ԬgἣU]9>}>` xb bSTH{EJp4Kqe%}lJr'S f9 ~ p"S#T_ G^M A+dZ7(UO^՜,KO_1Ħ/MQɸOpVxh"TN82 mV- Մ`hfC&xǸ)]Θ5FE9ɋ0ړ?&FDA\c7gRDD\l17pDiN&6hMˤl?KMkr<<{'] 0`3a8"Y k3΃Y9hT1kEbT{\YSyLh&jf}pQ bN\ ޗPѬ BҌZ6-DL!S Cs@| He.BoI9Ҽ,q|Yt=-_OL9;D̕6S'}~l=[`>w=3Qq+"i9tNKzrP I!n'R.V&Bބ-Ǔყ=di򫦚IKe ]IJr}zUˍ8嶝$p.>Pظ*oƌ{oY(ۿY& _ʟ~ Lu-GGf9'>Fkk꺦kB#*PU51>רXbhۮ!U>l6K_J!ݟ~G{Me 0େpЅ ^ޣmO&ue2< Iu6)(fbpw3c,C#&d N'#67<*9@5Ʉu.?MPU1Sb6JXu1%ڮ#Ƅ|ɗ򞺪hf"i4sΞb43p"4bl^*2)&BhhDԫ<t9Um9i"ғOsmbdXe.|QyWзU*F}d멁0`& g6pk,rc$>@e83j=_Q{ݡ(8W3Ӡ2j-QqjSɴHZ27*K![.TY߬]sC ڬb f'voSb^SG"sL@'x_~fp駹;6o"vЈC]QpB1GZʹ3xB6Uic;")OT;gyDg{tn#nF RZg\}i^z- T 8x9Pc8 ". K826'=/X/ÛA$NԖ.2*UMr_y]n "1KJÒ- !R -(S!jL;z91KP-QK "d9dli!"?G0{^~Nʝ*Wd\[8E/IJs둝ޝqkIǩc rwtb^XU;lj{ 0`9"f=䬼|.-E%vTy1veu$c9`9$ R24xq\12~i%f) %ʄ7cT>*<if+se;Xz)2i{Q+pXYqж <~ߺv.| /::.mwV‡}c~[5Ls,&rv3rZRUeus.m ?pn:ˑV7D<qA4&*$ԌEd)kEJ6J137w3|9Gx'acqrek7bؙ'n-`2'G2 ydT}Ȭ)eS׬OfY-UU+T!JHv)Z_T$(\c\v6FPc%FUQ@lcWrNM ۨ2eRk* 9Q*!q4W^l!|V/_ݜ%`'?o@?uw<=N(Sİ?ؓ?kr玗?{;9zs۝;i:\˚ 0`3a)ݫ{lwNeյQMLI]lJAn ܘa>拓0m$jZjĹU [{G 1G\xsb\236X{K?ּʿY6KbڔdR]1h&S3kAZpsw'.Wv0m*|ﴜ t[ąui㜔ddԡEb{6׮`"1&MY_?G/F^U<2IJ3,>fU"(Bpt<5ϙy`|[ןk b[*׎(qIJIbF2QCWM*}otB2=md qgYB0g3][B))^uQM%9&}s* S%'dL/ǡ AH4rFL8T3;A09[I% $рQyqWya']y=`MzsDuioPnQ_~s!xT!{C{^F(e(њG*,&*bK9*z0[G֤eLI gVjOl/tVG rbŵ)kr_l|s>qpgraս'c<1:vY#Д)ʜżc _N;rJuv߹iҜې9^lqi pby5PVmBSVaWg\ 땣֖-mvkO8:3bQ5EAٴ#[p38l;s.} 0` f@ gXnd\Pֹ/pݣ9WsQ na)19: 88?U2҈`cٱr홱 G !8%Dϱ(2D JMol0 QT<+Q+˪^53LdEzTW39)@2#3RxNJP2ԈRZ,\;g 8RYxYLv9ѷo㩜<"%&Ho5rs#c.jkK Mp:;HlL*y;8:zh MU{Rg 0`+"f=C =F ]:WN9>ެoYqz;Cרb x0-MK'+@%ѱlI:bL+e=؈/_#xO\๛E&gxz ػ=O<#N8KiGsژȩYRT.sL͚coaʭ=sڃ=k٬ `ȥ3ww{WC5VfjΕO2o9fs͞E㘉YEFiRŻR؉AM7@JJtɉ^+%@9 N^DBX_]n\J֎7DKWsmIzŊ$LOYsoqQJP^o᝱dazwo3ڨػ*؊@<\OU󞧄s[sOu\6nl+>3g(1DF:8؛+^Y]6LRPiڗ^ DO50{kYkhCbeZ s Nq1ׇ a5N@ٿ9OW!`-]S$ٱ/5)*AH͑0 c%^8JN0b2r-2Sϴ l|w/;*ŵ@aSCƀ 0`1"f=_(THO׷,3&>|504Ũv.#cj<f?K՛%kF`mO}Ʉ[/fo|lJتR%`Ǭvg꫷uׯ͎hH=fc逸MHS '77e##"\9<6nVv"'p\MX=bwgWav2',n淩!!J^IΑ.y:œU)cC1*%ẒABPj=Sy,b&%@2R9wx*g srj\DQUjGΘVxi|QȘ!T,*q vX2rRDE4\2FUK9c4!=;N B2߸yi?ko\ e=cɕ 0- +Ym=l^:v`)Yn sjT#;W6 SA/aUJUr#/F01)8b\gpڄ(P4̈́ g<{{{,r,Xo\UFeup8g,9ehlVTut2B#D̉oZ X?6uk&>Q}J/ĄEJFԌQr>5Dq/|\Cf$3[<>1g|W>~3CD]95BG egov}+W41eTyT3{1&E45{Cq+ \%8!1 q]a"Xyh7ze8D4YoMY/&|W;S'[w6+/guQK񆭩[0Zw"/ uJS6<&]{[J11)!Zf;ڽZS34U=C !})jɂ8]*G2.Pk_) DK`Rmےbć5 )9&TXq}m48uJs:N+x栮**0+v5hSF(GPVuwb#rj97vl/4qΣ&(?>"G»?^c㽗H|:<`Pmhk7ڙ}&;\l9CQ%ͻ. ]JLjᝏ+\u֨<1BT3Ż3QJ1R[d rphrюќ*Aw::32dD#HKsqg\?<2Ϡ}M3hS<kMZ^17m ΟEBSC`mku20͸k4ۇkoeQo n%Ȫ ;O4% )if\>3h ,6ZK 4>?lLH䤴貣JC0`{o)TpG KpP,R ύVU;y'_n׾9<\ b¯~zPW"+,*4 --inqCm?:KbJdߡ W9NRU#b{[>f=юqFB˄[ĸUGF|x3 ΗZ讋,9p֨EJR#a`EUFF%|7KnUDVi-}-Rdf# )95R5ݥ^[LusNGt+64zU1RCT՞z4+FM 9wnڧΐnA) IiqGۼp.)yDvGS;Yi2ب1cTd|Jǟ\l05%xέ5 peqqf98yR\sg8cflJky6 LgfnNܼ`g{vG פFGM4kxcw<gE& O] D \߬fّѵ*0阦aTB28?ږĖA 9Cn_A5.]Wnvc$ 'L% 0+7.b.v-;EGSˈQѬo _p"9u8u=Waơop6Lx!Ɍ`"ᮌ}P |􁆇ELd kĄ?~%[YǸHʎ[D& CZV{1u|XR_}.vys{r+EwQNdwϘ+9Ix4F5ɭ&RU]VrKu?u]DJhP 9'+]Xבsfe\1bZr}P3KS+r +vwuR^V G>-8U(^R|}ns`4>F쫇Tx((DxdS:lՎ :_|s#1riQ:w,X䖫 a+a2`/y_e 3￘>[I< # "x/_z7Wr:vl_~uۯb| 3{ȓsVTMt2 xa b3|/ <9qdu枵vm]]]/ȎȦ"*n}gAg|uPAqdFnz+rϼk,g?Nܛ7kņӕ7"nDܸ=)#6t96Dc3V .ڤDA(?s*G$ E-nF Q$$ $ge 5CI D@JHyd @߮XohRHP" Po C~BX%h[H)>BԽh8I"F}:bݴYghi-YRPo 2329r8PF!j!Ja&m)/mk߃׮8c01V mІ5+pΓd#%,u5ֶ;zтbDZ:!G!C 1C|Hr܁ԇ7)e.٦xW( #eɷ=3C_j֞[Ϻ2 e|N;iV6Nm>IKIډh=lQS;Cź3f&z4W\6N '.jgXXɘMrL6$&i^[<.ịŚa ͵i۰QRʔId]H XcjoRMޒ%A-ܒ䂕fR#`cIA=V rT4S6V԰iҦ"AH*%*B8T@[Q!G"B!9II%!8@"c8FӒpb?c i17M1:GkX4DHkF E2s[+8D[TP&YBaCH0VR~A=0BP/)ΦtGs,+e\} k!Wl15qnS IDATN]K)=,Hb!K@Ѯ3͊z8{fBN Bos.R5E|!69G,s 0f!1CN Eblъ!hȊzt$d"xlo1$ds8.n}$d_?tцSLB]*{K Jhh"o [u &vZt۫6eIv:1:ȌdYΚbIA![fbO &F D3TJ^d}IP.XGX6*#{bFdpA %K!v9zhv:O`)V8ƦPbO>80f;Nu,&wTJeRpɶg4. bA?aE𩄵)޻a.`~r!ɤǀPivoTd_0̯b=cvE ͮ9OqLU{#3Xpq)K G J%ǎ){~yD%98[I;sov,wɈ#S? ] 3̷, .fa!]KGT#I#qs AImXҴ|)ș[6)4zO*(X}G2>wIK;Dczqϸ,df`!<1C6ѻY#;zM[ٲzK\Te령¸UY!`IW9BJDbC5k Z1 Bhmnama*&&I5N` uCm3TbIIUPHRg#)ᮃ-:k].āj1UgOiN6|s6 }{b1Hh^עջtDH"#X1C<3o,l{?Vב実# c{Y^P*`$S3t5qx7,k$)r}*A)V?UGUhW2'JwpNf8a.?cb8xyg5qس =O<j}(y[3~-/5ƂNίI}'2]NPo !>̭h]Ʉ5G'uEvqA"y%!en<"kniι*~m?:.L) 6{S:7އ5? Cިj׼s$[Wq AJ-:h覶J:儧xÏԸ?faIs?^-4RNd{ȯHg]ȧ{O5 !0ȳja[ȩF=3e;' E7߭_9}f^~n$g8U0 jV2`N"AmXGb,rٗbzjB #@U B La#H*Pb'Zʛ/VǙ_[!=5K7I:+yd F` j\$0͎/L~ f;6TqQJF3r@ q;VG1Q uX5s&:Sc5B Dצh9C2ErzfiLMOe6D̈́SPHD\"t\t&7mAwtmA,+watvfIY=ɇ@cuRIn̲cLi.J`Va_¾ZD3 YVXtm7mZ V k,h͑!QxX R˜RE)5)9AZMI5dֿDzPB8AfTj$ JdZjC /.9ھe*<˜g q&@2Qje?b!lpqT;}Ɍs+s$#Y|fpC =UM) D8Mwe_uHuvf`۹qǽ-aï@k}oZ&4}]ߦ6Fqϡ;/?Jۗ"a+[Nk#ɴr]%C>PVbq(+NR'n_&7ݝc9y֎cw},Zx !I;3+YpvG;VjeA7-> uꞵc~խy{fy띇L;ά8tᛲc w9>o70m'>n7󏦼IS5}m1`~wWFo(I!361sD`|DR*1O֟%tγr˟{xw\?s2Sc-ΠuoɧWTaźxDBMEo(=!2Sc4Du01.E >BI#ʫrF{`g dllvc1u/TcjO4~&|qHQ? kSDil ٴs'#}@tsP +QRFƧى;2.h΍@itY ekEZaYFXQ^b%FHlQt69%< SJ:(9L:ˍ; N#C R3Xc:CuciPpC9I4 8s4t3A؊EBbpB!w=o"xQv#l;d9T” ﬥ**Q ,mɭ';OfE`SY=ԿÆA/}!عA7Lwr#y\SR@7w m7lXni6['F2 ╷w=9[Ҟ@e_Y\m~e3AZ~}-ހڿ߆ku!Hs_|,etW}K@=&L~%i\^$}nSb!.>p]rS^>'X5Y$@m6VB(fzr/ o\}w΁,Z銠HQPx5V W"J kZrќ#NpqqT1ia(^tᛣCIoa0ERャ.JŹ*\ɀQYco_鵽8hNYǹe=[ <{͜:zh-P 4WAFGQҰxdQmq&ZtSCf8&t6QwJ]k0DGeNxY$&N!$hZ ī v,WЊ婍0 i,RIp~fXҴK#""%bt֑%&3D D(¡ An v(e bL )b X[i2F .0*(7$23Nf f%kCf奀r$5-HYPofg}gWEKsH% ;@Ka,U?+z>$8\!?}3bR}ؒ}DKۥC&k;Z.73wǶ.oBտ9y뻛_HvwgAxڼu*a9b&,5<xL(rk9`̬?m4y {6yݯGT=pG6Ύ>Hcřb/wyז4=Vz3Ϧ*¾.X-օMuma޲h,g 7ߓsC)/~Z'ɂaU8 a Gs^?*^5"$B1^x1nkB!QRIvo3r2wZcvK2B:cηZ$]wlXPB`?Yx*!LB.1ZiL\i6,ټH)/dq;FS8!Xi$8 $ }m٢缷~o+~' OU(Q:0E0>J[C 8Z  2~N=W\5W3нwr7^hFof, G-&k`N*N;%]+㴳2"GL#I$BZMR Hq^B>^q1UX/aYƻ =N ._4ձS\k2AzVQޘϯsH)JaH_:O1/حҧJP4ϐࢋ+ī`<_HջQu"̚+JG,Img`N ēKp@}s3;;Kul/zOQ: `q)\311 ^sl_##@CkE]s'Ow$ǰ/ VCٻș3gM7&Tx˨zO|7q/v#|3pLNLoy n‡>1.aMSaC|[@GoK"7@" _檋pi1O|"$ >yWow! 3Pezrz3W |ꋚ'rC, B^~yD"1αbl'pC9{6?}*428߽YkkP36*?9yR"Y890cQK;HsR,yÏxueT8ru+e9ܬxF0[$ܮ2Y 8lPJ09'wQ7(餆H-^^һO:zQ>#ሤ1O߯HSɿ|1JZS. I3Κ~ګ qz%7>f!䫆EX'0XgOD'RV!E_ًWtud 1}B15QcbsI[59t 50XEE\7E5Ƣ@[* BxRPA%TCA%˒j!,6[9]Zz K=ogdq8T8uz@ QDf,Q H+1IxxppϦX^Z( Fz%3$KBRHSJB)b2nx .4¥;ٵZj[W^˥|/yLrNfݦ|0wf)al| Q*PM0}1%G$-h4װNR)W_a*3HUDƖ}uv_&ӠjhnwȲS1&!MR@SnDBJ$,J(%OZSsS\N7IQ:a`A)K8*CEm9n+{.߄sՔzf/q kׅ@IJKެy-ќl3,v%+k+|b9ǧ?s3xpgɓV;~]|o}Y>?c3333_++ ^7ʯ_Blټy[ؼyWzhڼoOgJ1~3C8x}$I(1/SO~;|+?ٚG>»odżE/ ˫J\s合ZRpþϻ:GUK ^kw<ې29'}gL)nTB/)k?\cFe'S-nÄWϊ(< IDATZȹ~U"w\#0DP+]h%Psϑ_$9|xWAX|X_n|#~{GTAd ty:~GAJU{Br͞A 8A朗c0- ]pծ!!Ym5 +|w{kJr#!_m%^#g/ef6Y8+(|v0, ~GyKCjQ8U?lowv7* |MxT()I]x;V 9Y.x bH `:`PV:_&=CVI0GwnWNp]cqysgX[ZC,͢A)ʥPI)$Rh'(nJ-ݬCkȌ%m|BhV>l}%V*T
rt I+b[YE`SJ,6hkNdIPۧ3PHcYHXcauA֢dƢ)&)s+0=YgkRBPv-*@VK0@x7 SKRJVVVrx1o|c;~EEipϱ<꿧g1 UÅ>ʯ[_.=G9'*^CUoVMϺW^H['cqհm&Sf`c|Gdu}-c27 xNf\# _[\ ډcnQ̟xWXqr a q])Wbژ::^,; duS˛zyWL䷖qV}?'^"S;gN @ c8L=rù`J摽YK*T"y'ع KVo!?ؓ0HZc\sڝ>6Yb97_1C<ƾuhPi7C5PRѢ@+BL^¢'Q*R1 jvFR,-0HGw*Y[u$ik ȿ{A"d1@HR$ #dpxA{$م@IZ)9Xmull\gi5bR9`ତ崺( і$(U;ؾZzz,REW ;Cw1% Rw+^*Ɉ4];R bjV%QN:{ݗ_K!n={ghvKiˮ"i.R*W%pA + f#VbVk|),UR+ȝdrfN< 060E,&fHG$9m`;ktw &2_CdXDKQOW Uy;s *G$5jl81[v>#dTa?\Q[1w T6YotpQǝRWv,,:A.& !%=מ0(3/(PNr.'XrqŊǖDk:dp? EQ!1C<1tºK/{RE$` CbeD[n.XJKXsb!ʘFB3]8RKM Yb66Ț :<#:av(QsMm?|cE/VB`EGS'O9rG8:nRVZsA\s(cNjJTw$IBEJI9iJcv&''}7M/xdB@\ǵGrqJ;vPU"NGvcE Iy`evJ"yA`ٷ]u$H@9Ne@q>}E+ߵ&G;_3eh1@yog c}!M" ^]ܫb3.TԼBo25ng_Q9WŏI—F9jWYTгjB'Ă0X?ٽA/bH dĐ !]y%)Z֋G|ܴ)*e:E.)[,J9`IfW<7VV9y`uFidYۗya@k \OwEG;gHaB 8e7^+^B) bBE%cRRdZ]0;W(JRHcWڴ;MUN/ZB*iS#FQ8vC$4kfKm6&k! pz/?" My{9zm4=w0znT@E%D!jnsm!R.4*NJ}z+hu$F(#cV&R Ӵ6d nr8cYNlpAw;R:mÀ$wc1^Iڄ!8Y&(g<8Hma8BMBQ0v+O- .wRGq0Z ()YĜcQJ!(g?ݻ|Bd߬AÇ?1ҲMz/6TC?S,#/Nin݆;vpe޽fJ133]`u89^`8:q?ر %}gfx'˿òZ.>|duFI!{l֜Hߤh7}ⓟZpǎ/~!b֙Qlmm;Zò,֯_OTbzz}/zR чpl;6&.ӅVcO=PJ1qq7E1sy-[jep`׾3wq4=Gٺm;k6a066?TUַ0'?! R!Z4q=<YNngmhgGB]D4(I mEOrS>w>F#M0+X't%{{Lt2k'1@y궋#?՗ 'j̯1b0>sW+/q*`gg6z[~:o)^̱,[;w<-IwLV7Rez9^=e3NC bH`ѨSƋ ~WƫLWbBF)$&;mXfk ŁI("5 -B])$6Hi$)"d.N=^ s. (06Tx5efيwt$( e6(#mjFB:GEE49#S F&JR+z3Xvi"mQ,h]k2^ xkzR u i.fc!:(IvrXA+k@D:*(yY')uuLN eN۶r Za9.yIojNP~쟘i4V>,_l;' IW^r O+Q$: m Fy'D%Le *<_ \Gnp7葸v54iȸfSh-0j[QG.kq˕/ġ vxlfHΣ!z,K+;G|d*j'gnNAd"A5q9;1Znb)Z,1R$ įr3Oim;^ ?LUSLLplt\4Zfy\! *455ʄҠCN5RI/q:V/u[4W! 9NMpXv 8,cn CڕaXdJ"*AӢD% "ho$1Yjf aCu+EQˢ,2Arr=$O3.i[E{Ft_KoN2YHoX?c`c3Z3y>;03[6&#tu-R4-e;+jR9v|19Ρ(vu Wncv,\W)M(q-? p GRiTt0Vo/g|. ƃ`eIXDIxbHdR`4BiR:Zs U#0 QJ8)z\w޿'V448׶CҊt*rtd1HaD`Y*sl@&%)%RJ"OŊyp !Ud!ocfffl6ٽ{7?o#?Zqz{{i6=ѣrmo.ܲ͛6白!p] 6l2,cccX1̎;^s5ih4Zz;}=o rlYcY-BQU/p߃]0?[v 'Y Wp0WͻFJ;$8z( Nslza=66۷oyo~cH"gV`h_}ϻfbCtrElϱnj]t'}91~s}jo~x 󿼝|r4_{,6xpn06cOf鴨 IDATkxI/_?]Ud[ !x{l2H1ԔQ a7m rnǂzpt"Z! k 0WlY-xӵY-u:^X` Y3HaysgN16cbHp:T$ICج*:FX&făvR,[RI@Ȅ]bFڱu4XVSZ26PQn*IB 5+h4 [ab05~`{=tiFvhOU4F!B;^F{ǫx~cIf+2ˊiu_#mCO΢ԈPO.blI00'%2ᰱ<[VӌL͑vZ\zX c貶?ǁ:SU?NZHmpXS)1V{lK6R1 "!I ccV ex5_H#,3o ^Α䑯=K(}dzv"S"/f2X,*"ajB#, 3˜8U+Q("M5Q(f+Xf%e.Vw5(8d3Z lomQ:ƶd Z kHg,juC5Va΋Gɽ@̈Di·1qzE\M-9|{r+='107W;֧7=[0V$R3Iy+[Fn&4=]؎W]ƾ}0ưmn6_U\7O6y>SS3|o>M.^Roa𲶬D+2BH Rx&Jh,f-̥d2yl`8tGD}jz{aT*c.q(efŦM%Qn6?%O>Rwptt%K?Oc|G96>cTNp<}@q'?_ M=MR{(J^R iE5[w6úe(TGz)2b.~K7^+-H{G㚛/KNLU;|; dĒ$:t04 r)S>=k4PiDW8VaxÕ>"I&%ذŶ;Dq4@op>lHٚmfKb% f)6d֒z83(`h RD0X0XHs^ȫ3ܟ)wl1si _Ij^{YٟfiLX.l Vd)+ lnhRi'bYzJ0Oq*D Ad|ڎR1EW Pz%cY$M m l=A;F:Uq9/ d*83j8r< 5 JKqBbD*ds `lBt%t13zη"B{+ lٸa?>mw_uB|neYrͬ\18E<" &mw3L7!qj)gق8%RU,JY\n^$X2|Y}c|m!l a$Nb[O`~4# GCC>׆TZ aBΏVƌuɈ?nНj[~ "}GGp@*U l2ij5ǚsFU?Zef(xzS_weo)KwR.9Gy37y˒h8A^C:PotcY(+>F30`s͕p^c!jRH+@C;+sz:ty-7rMZ? S )-)\%,Cjz@_ >1}69h%')~˸S+kl~ǻ(fޘYqf%_|JbY)[ G-P#NAٴ*)f]xҞ˻*֬(h4k2>Q)BJRP @$XAK-$Jka̤1` A>Sض@)dDxe0ũ#-R82r%t Thc(t,teRM"@IY4/M#U0Bl_n{M092Jsx¬4oetT Ҥ-4/YŞc5r% f@&eZL!䲒eaْrN&jec+JRSH'FaS6)WAݳa'НIi1u>B L(О^>&vRXE)l'"N'gzS.VNt.[9I.C"-l n\' &Kc.J$)% 5Y +R̡hc ^կ~>X@%8oyi&ρҗb+߹)k_GGXf5o~כ1[,>WuV6ucjjɩ$Vkx\eƍ|:D-K3?3u.nIhyȦ%S08x\K7Ȏg7fgv')Ն0QPpdNy/qreہ4VZQ*dK-<>FS; }"q)b)Xby !$RDƴW%+IOZ0H ݘǫ@!M"N0"Zp cƄm(l gYE:&JM>s23JZ2J@,Ŷ3HHi$jAu!? Ah43Z=`d3[ &4~i"%ָȓ["l8'oYJI&/G6PY2S󱣸5)} &bpR@cI91Gg X9Fġ &UPi2EpJ+ƧjTjF m^م12s)=Kg1U @88]Dz-h4|-#L,kE]`eb!J,!ap-XޝtC ɺ]4zH3ϮKB|IZRƺhD"jl\NI!-MrR\CoܔöfI;\iID =vGα"n6]bVTJ80MQ,:؎Ķr6Ԉ{$*- ͋v k_&!Қv߹^\7z֛^@p5pw0>9|cÆ :x;w%^DPS~3uF#Ew}KS 29pDͧ>8|AgI!,t~|&u$cD|:.I7OY)<* P0JRikH[?cE\$m W;|~$Ȼ䒶68':NӱbDȏ\ltY܏bTH,*߮^R,s,1KN(:dF`a i(H_iPʘXPZ$EeuŌ.r NpR)LI)QL[iT6m+^b@IOLD|c]K=Y:*9Il5R"`YRh{!iYqTwqx[tZk H!riɲG)G? L5$44%$qp-ԨWg=V C,JIf /Ed]c` -75hHHzLVgP0^ӓq\A0Н}ԃ;v#mM*G+)FEÇcXKJ^z {}N5?I}Q47|ǨT-S)w03;˶8>9f٫ȯN 5**$EQr)U>Zx4kyCrګϺfk~WFq]8^y???3^wJߜGv Z'4 ,dg>Xd硐?r`4`|FH.LZ0+pkLqZ G$8=I_~ <[stJSR)ɦ-69DXam.9c>ȋ 9%ox Y6./!Kul`-.Zx ]A℞+J~4s l4JߧXSzYlg3[[.AyK=]ކغHK-U,uxi٬j^ví ,_I^i{)Aֶ&vr,)!PN/͆9z]{3rt ǶXb5yTN0]i)L 5õ.g~i C۶,eHB^$RARRZ8ұ iQ*^QC qpr^C$DұN܁։M≲Dcӗs)7CjWJ i{\+!-iH],BT}ͱ& w,H)fI*J@4ۢ'RDFXXDap-f>9s)͐k,v8\e"ejASL}0MlYS`dc JW/Pn*iPrrӌmW]CËwܧTWr1pdjpmAwaTBEl:C.FDz*M)E ""JӤi6n+ox9ٻw?)9ܽB!Ͽ}v޶x_Kwwע&J&oKțHOf=Ϸe||0lٲ4W;P.ܲ׾R㛌l0x1>1>zzxk^yq<۶喛fێ<#9zaٲe\~˸ed;}p7 X]䑰"AXFdql3bKPCaT5^SzŬ1st/$UT34|R`ۂEOAFSU)M)LUS|:[bᴀfW!дQna^ˠ~3,B e}sQ#^~r_Gw.<"uJ$qI$)(DO1GX ֶOMbcqX4~d߾t,v;hQڰ+tNjCObbG,`l҂ziba]l&Şu?X$+7I:4Ѥ8e=-ᇱ%kQ-)Mq-8T@a"N)2\\ZP"jؼ@L792㱦/p$ `|m#Jز*CWGi eixGvgdٴ ˣ(&֬рu*~iŲD$zX};uG;vXFwM{_Fʭhi@(bƠw91JX[ϫ9 \#]0n{\p>D Lv}D(:Y&bv>I!-}mm:Emukǹj'q-~ (-;m6>R)k%BbYXBSr kAَ;gSo\gsmS1 >YtvpPt}s~k:o}\1 }Okqb4>yӈXH9]8~Tm<ˆ?1KIK񼅊ɉQm{e&hu\ZR4|P~ dRfC9.XapٌډpĂ;RZ'FT,%%OZ1mnMA]c] 6Ze2`8U*05N1V;I)3Qȹ {jhزWV94ӤE\>Fg"Y[$0gF|d alc'\)bc[kqYiIW r:^e'L4y͸y\*Q(*ڒDDFqldgj clγnY'Ty>%kz͆l;4dY!C&klZQd;d% 4 1`dڣi U 2+^HWsDO{D:`@7^]9=&DZjY71 ]ҌLTp#m *MU,j^90`p>vh! kK`!X72&RJ 놳ܳmZоQIь"! X|9f2M#f 8z?q,'M8.=ri˲Ȥ3t9Z I.EQkFLȅZ35O 2FݣHe2WT0IŬMvuDؙ&QJaYA&=dǡ^=e~ۘΦ˟l9w'nO\|ܧrV]u;SN??]ܟy]XXX% f)t Xe1D&iCl~+֞1j{k7c`&ADAɢ&1Jh&a,wO ckѸEڲy{xb 0Thw1&\"#P' YBD@J)Lʕxf0PLQ53FSSL; 8>qxpG_E4ţ{KQk*W|ADwa@u|ɻlY;J#x)"aH53uDZ*8V(r3}4ڥڈ-C]Uǫe͚#,߽׷2*U\į$}=E~55IL3#vbm#wӃtK4+r4X{87+?G;L{ߜfd#ݘYQVc-E񞧧).>sCmj,UO4' 1cSđdUEŏ9cwp.(*yA=Ei5W/1Bk‘@"9_(X]׏ٝv_s5Yzk9䳔~у{Ӌ32!ʒkβJkT3$ֆaPhL{seV1i8V==/JQ} ":4< pbCkcZ7rc{^g|:7=vF4?ѤbZ7"7~#4:ͻll߼o:.³;& v7桫Ơm 9sd7_: *cz% ="5M#snꪭjm]Y ΩQ/KJ!򠩺 ]dX2|q2Kk~Ƀ9YTqЍɛ7n(+*76(pHV88!yzf:g]Z~d͝QIJ,sͳJ ӈ {G/)ctc&dEؘN,#+ l%vQʋ{/\γ(P֤tbYE-c#,Kjg9O+ٚ44cҋ@"3;?$3֥t2[4]""ŌtH.{;3:IB==.u]1&<+yGwdM$%G~EYicx"f^gV%;A i^sԆ18fk9kX"{Cm=Gg%IP5A|xu^PްOwY-ˬd+ vb⨩ᮭp#Ӗ׎:邭;S* Fl]Q.*ܱkMǻT /YfB(j~ߘZZZZZZZZZZ~oL|ky_u5(5_9d:͈5\մzR@HB) F#)QJi~JJ;#No4 dQ#s:HP . C^ڷf6"i $ @yT00*B%)"՜-KXW,0/4JHvF fi.Hdz'ܟ$Xˬ'uBwTF=nA%<%q&kyMo%|z0 Ӟu; d[aDH2[|gX*0aҏ.S2˴`YkWí.ia9^d|eo1Hy`gHΓq'd2%,yeΐI'{<׬a7 T#e'K ct˺|fIU >ޘ47<9I3DRQJZh*-kKUtҍ<[}ɤ*sL[,-˚N7|˔Uj웚_??_b;W(!QJ50/Qev刹juRKKKKKKKKGUny%PyFt]}nhax)yj,'WY3 ݌mrh&3x/P-͵^/+kjS9G FT jRq̲Ќ$yʲ˂:>7c?fY8fdY2섌d`+K*. cN%G󊢺d*5AUfY'<ѮIJm=UI/R(*VLc<%<9O1s;Jjê4Za((y}Y0섬5BJNa̳79>itC:eU9B#e{.&{Wo PՖA7`ad3ڱ ~p\R(p+ =˒A7 . m ye8]TQЌ;{lrg.rkWD2xƳUEY7q"X+035‹7"UaZZZZZZZZZ~شwJGh*W/#vň[l&aE4Y "A/Ŵ2*0D>GP8oۡR&wmDӘ|/SYO^<뢤eyδ*6lrj 9*ka%rkKqZN;]ajƽq'nDgKŝaB$j2$0Ex +U8JPBp8[{R8>{וmIwƵR[|#[ @e,N{ʒmt_sڂv︻޷ 04}w\D;Gmw̷kof<4RGFn 1?oFE@(""Ƃ~wA+ CAms8qIn@ F$BE]5Q;^;yWFMuu#kj)+-@*<@G$T̳m *T1**gX5{Bހ0|bi30γ=I/lU NgJpo7'uG, \UmRP[YnN |IYj3y~7Ow_/Lz竊5' /. *Xg5̲B[ެ`^YTLb~͂@8'XӢ0o*bRՖy0ͲbQ8o'||/W,(fyHBUmxz?85;^y|0Yŋ ,bUXoyqrw:Dbyq`c yq0<RK@a>u]#D= :||A79H^6^WL=‘5=+!EJbp}y5g^4ζ*[(={Gw6B @WB7-fIHAI=t)~"&~,{!~DG!zj|5`7pW f C)7,$7 ^L56f{5yDE)!w:^ f)sAUy}4G[ JLJ}*8^Vboa yu`X_ԵcgL7%C+ɺ4-Jv nsY1$PT"||( *e߽Lkw2fI<לI_G +,-wBR*c%WW@D"89W|2DވIaq(܈FJa̍HHFzӀt ~omMjiiiiiiiiA 1-̽XՂapR";v1ދ'om 8ne!!q8H" :QH(%ߪvn%|lr3XokQx)ߊi y[zw(A]kx+X5Bs=D_~YZ8'|xw,DU֖qJ8k&(T֐{ӈ^}|ųBXG q<ý.;_ 9jQz>{d؉E Y€Q/Fq\ G3M^4wFa-r6yAVhᔻ[=^] =(eʪ!()/NsˣUG+WH$1E^-pS!I"v5;{ثV[BzVJ!=DB!]nݷZZZZZZZZZZ~XBLʻY1l琈 G̻;qV`ZZ*㨌6g clgq^縮؈%ۄ6HP2@z{ yEߓM@$lruT /,˚Ӆx?~;gڏPRɢ}zxG>#78oeY!eN/VFVUɴhvEɸЏLksLz!˚R c g3,eΠ+ 9_8Oƍ8q$$B"-!7MЮggk2*:|r8`"%3q*)kn?DѸӂi/$ Ъ0 z 3,4̰HOOqhy'X뙧I !qg2FhyqMVA2 ~ĺ48O /.r.iN ޘ_<p;nLv Ѻq) Tz5'0s]&6irشi b&&n6-޼mjGx0Rkf\96nkqH] 6΁w7y 7# :Wړiݼ~[IBu /,nG);9w0ޡp*62b]sg LVQ'F1̳n 0G`eayu; xcҏe2T3v$gg9PmvT1MwT9$AX5UUXǺlڠ>7&%Ee+M.8$-Nt$"--RpՔ#FpG.Om{1^S^VKq<3tuS³[ZZZZZZZZZsbZ7°iy]Ky+ZW@ȷ_*{?UelA)Ċ$$a@I:qDA@(@!B78^;y~7BFq͸u+!qXdYZN(`+ Bqlq";dS "1Xa'ՖQk.ӚF8닜UiH nȣȉ XWQDIN ަ!LTr,5C-rJŪe|VK&盦YZqgs8ғՖEgoua\ÄqyT8T()X0a{է~pQWy84p]ZK{-N6qc6kWk;|]_PGTK?Opjiiӡ]-#o{wogI178Q ;GnsgE7WB3JFA I( AGI)suZ=8goq7G +|S{''7 KW׃|ol7FyGHtm!a #2˳4<w:hhen9_H(E XdEɽIaºv/2Gnˣ5& AIy4MD&ewd?s

n*ߙ1s-sԻ9C\ *;1~t {nLEJ)R 7ku8Z9rpיfiwnbwM1ϖ#7OJHP% ~' )P!˗+B)Clmyv@ louHieɫa7Ǘ ;!˂^ 0~IDATpDJ0Gh(%;[b׌Ki(jͣݘA`,-XW||Ū4KY0{ k9]HNlJ[1Jy9 c-݈q//J"DQ[:^3 ͲG5?^i1s(XjoAi Aހqܸxp]h \jܰ3JXy9h " 2)I1A^k0`]VF%,س $@[u޽8v~ounu%'o1"I] l!;{a`  Yf3x\ 'rlɖn.$9YJ8$bUH!8o}=UeR$͢47TRU5~/yY܎,;󫆫/K?˯~SoEW /Wpd7q [EZEmG&gSxZOj6tZdgs#;khgsf7:\_KIJ9Qu*ѐASLc0kѹu=4$5U/U]~T%PCD2- VUuH~ ;ܽv+ͣ:v3e~9h[üOlr}~_{W^ȍNU?UQ糷n_yOKQfzp.Ӄ~t;ܾ^vndknRu^TtɃܺN1u]緳>Js̜wA?2V3[[znm^7wl~7﾿l7SfPtckEn{;_zx?/^NU<-sfv6:yqN#F;Zi6yγl6n_Un /^a?<LM+<9]$9,7F^z`F֍l4rZ;7sgw=;߾w^KѨG!hqV*u]A:0!IUUP 3u]k_Z|bY0`yu7s܏[};o|eO}+>> 8A G cU24I1f/f8iH*j>>NU'N3׶sm{+7vwrFvofZ;ۛt)үR,v?1,itԩA?ՠL]WG4M]S$eA1x*{5mi4nJL_u޹Oyv)*vz~OlyWH^V~u/uj$=:[o?̧oYej4rPVyvXn+VGeϽì7އ<|z;wo󹻛I/;덼N7sNI_>ͭV:4e/lׇeGy^{9oK3n7O^|rs-_v#?~(7sk^5z;w;iH[I7eʃn/F+Nu~q n4u*"v#ί?|"un_kyZ'/nnV>xAV3fQn<>Lhl<|Myx{*u+O{y v7{G͝Ciu~?uMKoұ):Eha^?>Sܸ6vrv ?T\/4 `)f?76vR435FU3EFJRQ׃J#A[ܢ\3RgY4HRWX G*RKMU˽{yK}4df#~'LhHqlOq]L#AU)R׍JQ2U]%ucpE1*U9+?VUI9/[Ez_[_W^H#/$Wo(zv޾a:kE=+ޓ^^LsAZl_{{_lޓ*w=LWI{U~f~u^{O]ong>KEvçjz޹~U,[lrFhү[p0;|z>xZpN>|vI[[yҭrدzG)Sśp?ýtfW<|M_4ek?,g{`nϝuzghbD , `A1 "XA bD , `A1 "XA bD , `A1 "XA bD , `A1 "XA bD , `A1 "XA bD , `A1 "XA bD , `A1 "XA bD 1/t1XJ\d jUbV `A1 JAiƀx1rĬ̇\j (V"` bdYʁ kTLAyGreW5\/ KwA~+e16pj^21Wf\Q21\/ .+qA̸/d=(95`C!i4 fi1˪ls0뫑mZ"r."f֋gx>lq^8GFL?MF0梂I=SUK<.0G={+flB,I׸_b5/\ݛ^߳WSzƜR*.bc5F4~_{?8?IUWC?Å3՘`fR̅ eZq5/f7c'bEQp}qG1ަ9}sJUO~/|gVz~_OITÌ)G˜qHV"f៭~޽Op }7_}?Il(|)8,)ĜФ^ȼ.hRU|вW_}Oz(G0ՔIp!LB"'' Y]s$?yϯzS\'0ųgo~/ .H STUst6}>nx~1ïI־oo}+wR|[u]W`hzO|1oo$9~Of I1}c?L8ɔ0fR1okhY>OH'+[#x10a*vRPQ0M:I}h21L \5akҹUqI3!B&BYoVW2ɔcںYØY֟tp=4ҳ[~fnA¼br0&#A,N;YC@e0K01i')z̲]j,q3j1'm7ia̴`,!̴M;I.]<70fܶ=N6iѸm2CPsB$i.ꍎ3>!`9ivؤF6-d9iޣ_DǢ˜Lf4ԧb4Mp'msR3ii]j\`0&–Y>iYjXJ,SL iF[&+ČsR3MxOz/`Y.?iy0ϻϙ,G̰c z^8,YY×L`f=ʅ0Y,C?vJ>yi}fNSA3v']t0~+\v?SnDzӞ,Ch1vrcVL[~^U0go.%`Wse'[ViCIUsf†cf}E5ʬ\& yY*9Ϡd%Bhs^ޑ aaE*f0oTs2 ~ ~~0le?.KHpYlC\]p c5Wɥ `]*\#\.|vBtj.us쪆W`\e@b/l]eap Zcp7IENDB`podcasts-25.2/scripts/000077500000000000000000000000001500126606300147505ustar00rootroot00000000000000podcasts-25.2/scripts/dist-vendor.sh000077500000000000000000000020751500126606300175510ustar00rootroot00000000000000#!/bin/sh # Since Meson invokes this script as # "/bin/sh .../dist-vendor.sh DIST SOURCE_ROOT" we can't rely on bash features set -eu export DIST="$1" export SOURCE_ROOT="$2" cd "$SOURCE_ROOT" mkdir "$DIST"/.cargo # cargo-vendor-filterer can be found at https://github.com/coreos/cargo-vendor-filterer # It is also part of the Rust SDK extension. cargo vendor-filterer --platform=x86_64-unknown-linux-gnu --platform=aarch64-unknown-linux-gnu > "$DIST"/.cargo/config.toml set -- vendor/gettext-sys/gettext-*.tar.* TARBALL_PATH=$1 TARBALL_NAME=$(basename "$TARBALL_PATH") rm -f "$TARBALL_PATH" # remove the tarball from checksums cargo_checksum='vendor/gettext-sys/.cargo-checksum.json' tmp_f=$(mktemp --tmpdir='vendor/gettext-sys' -t) jq -c "del(.files[\"$TARBALL_NAME\"])" "$cargo_checksum" > "$tmp_f" mv -f "$tmp_f" "$cargo_checksum" # Don't combine the previous and this line with a pipe because we can't catch # errors with "set -o pipefail" sed -i 's/^directory = ".*"/directory = "vendor"/g' "$DIST/.cargo/config.toml" # Move vendor into dist tarball directory mv vendor "$DIST" podcasts-25.2/scripts/test.sh000077500000000000000000000007371500126606300162750ustar00rootroot00000000000000#! /bin/bash set -o errexit set -o pipefail set -x # $1 Passed by meson and should be the builddir export CARGO_TARGET_DIR="$1/target/" export CARGO_HOME="$CARGO_TARGET_DIR/cargo-home" # If this is run inside a flatpak envrironment, append the export the rustc # sdk-extension binaries to the path if [ -f "/.flatpak-info" ] then export PATH="$PATH:/usr/lib/sdk/rust-stable/bin" fi cargo fetch --locked cargo test --all-features --offline -- --test-threads=1 --nocapture