### Build Cine from Source with Meson Source: https://context7.com/diegopvlk/cine/llms.txt Instructions for building the Cine application from source using the Meson build system. This includes setup, compilation, and installation. ```bash # ── Prerequisites ── # GNOME Runtime 50, libadwaita >= 1, python3-mpv, blueprint-compiler, meson >= 1.0.0 # ── Build with Meson ── meson setup builddir --prefix=/usr meson compile -C builddir sudo meson install -C builddir ``` -------------------------------- ### Build and Run Cine as a Flatpak Source: https://context7.com/diegopvlk/cine/llms.txt Steps to build and install Cine as a Flatpak package for production use. Also includes commands to run the installed Flatpak application. ```bash # ── Build as Flatpak (production) ── flatpak-builder --user --install --force-clean build-dir io.github.diegopvlk.Cine.json # ── Run installed Flatpak ── flatpak run io.github.diegopvlk.Cine flatpak run io.github.diegopvlk.Cine /path/to/video.mkv # open a file flatpak run io.github.diegopvlk.Cine --new-window # force new window ``` -------------------------------- ### GSettings Schema Key Defaults Source: https://context7.com/diegopvlk/cine/llms.txt Example of a GSettings schema key and its default value. `open-new-windows` is a boolean that defaults to `true`. ```bash # ── Schema keys and their defaults ── # open-new-windows boolean true — open files in new windows ``` -------------------------------- ### Restore Playlist at Startup Source: https://context7.com/diegopvlk/cine/llms.txt Restores the playlist from `~/.config/cine/last-playlist.m3u8` when the very first application window is opened. It disables the start page and uses MPV's `loadfile` to load the playlist. ```python def restore_last_playlist(window, app, win_mpv): if len(app.get_windows()) > 1: return # only restore for the very first window if os.path.exists(LAST_PLAYLIST_FILE): window.start_page.set_sensitive(False) GLib.idle_add(win_mpv.loadfile, LAST_PLAYLIST_FILE, "replace") ``` -------------------------------- ### Handle External File Opening in Cine Source: https://context7.com/diegopvlk/cine/llms.txt This is an example of the `do_open` method within `CineApplication`, which is triggered by the `Gio.Application` framework when files or URIs are opened externally. It handles scenarios like drag-and-drop or 'Open With' actions. ```python # src/main.py — do_open called by GIO when files are opened externally # Example: user drops two files onto the Cine window icon ``` -------------------------------- ### Get Active Mouse Button Bindings from MPV Source: https://context7.com/diegopvlk/cine/llms.txt Retrieves the current mouse button bindings configured in MPV. This is useful for understanding user input configurations. ```python get_mouse_bindings(player) # Returns: {"MBTN_RIGHT": "cycle pause", "MBTN_LEFT_DBL": "cycle fullscreen", ...} ``` -------------------------------- ### List All GSettings Recursively Source: https://context7.com/diegopvlk/cine/llms.txt Command to list all current configuration values for the `io.github.diegopvlk.Cine` GSettings schema. ```bash # Read all current values gsettings list-recursively io.github.diegopvlk.Cine ``` -------------------------------- ### Register Custom App-Level Action with Accelerator Source: https://context7.com/diegopvlk/cine/llms.txt This Python code demonstrates how to register a custom application-level action with a keyboard accelerator within the `CineApplication` class. It defines an action, connects a callback, and sets the shortcut. ```python # Inside CineApplication._create_action (used in do_startup): def _create_action(self, name: str, callback, shortcuts: list[str] | None = None): action = Gio.SimpleAction.new(name, None) action.connect("activate", callback) self.add_action(action) if shortcuts: self.set_accels_for_action(f"app.{name}", shortcuts) # Registered actions: # app.new-window → n # app.quit → q # app.preferences → comma # app.about → (no shortcut) ``` -------------------------------- ### Sync GSettings with MPV Player Instance Source: https://context7.com/diegopvlk/cine/llms.txt Applies GSettings values to the MPV player instance. Handles subtitle appearance, language preferences, volume, hardware decoding, and subtitle background styling. ```python settings = Gio.Settings.new("io.github.diegopvlk.Cine") def sync_mpv_with_settings(window): """Apply all GSettings values to the MPV player instance.""" player = window.mpv # Subtitle appearance player["sub-color"] = settings.get_string("subtitle-color") # e.g. "#ebebeb" player["sub-scale"] = settings.get_double("subtitle-scale") # e.g. 1.0 player["sub-font"] = settings.get_string("subtitle-font") # e.g. "Adwaita Sans SemiBold" # Language preferences (comma-separated ISO codes) player["slang"] = settings.get_string("subtitle-languages") # e.g. "en,pt" player["alang"] = settings.get_string("audio-languages") # e.g. "en" player["volume"] = settings.get_int("volume") # 0–200 # Hardware decoding hwdec_enabled = settings.get_boolean("hwdec") if hwdec_enabled: player.command_async("vf", "remove", "@hflip") player.command_async("vf", "remove", "@vflip") player["hwdec"] = window.conf_hwdec + ["auto"] # window.conf_hwdec may be e.g. ["vaapi"] depending on GPU vendor else: player["hwdec"] = "no" # Subtitle background styling sub_bg = settings.get_boolean("subtitle-bg") if sub_bg: player["sub-border-style"] = "background-box" player["sub-shadow-offset"] = 8 player["sub-back-color"] = settings.get_string("subtitle-bg-color") # "#97000000" = ARGB else: player["sub-border-style"] = "outline-and-shadow" player["sub-shadow-offset"] = 0.6 # Volume normalization via FFmpeg loudnorm filter if settings.get_boolean("normalize-volume"): player.command("af", "add", "@cine_loudnorm:lavfi=[loudnorm=I=-20]") # Normalizes integrated loudness to -20 LUFS ``` -------------------------------- ### Launch Cine Application Programmatically Source: https://context7.com/diegopvlk/cine/llms.txt This snippet shows how to launch the Cine application programmatically, equivalent to running it via Flatpak or `python -m cine`. It includes the application ID and version. ```python # src/main.py # Launching the app programmatically (normal entry point is src/cine.in) import sys from cine.main import main # Equivalent to running the Flatpak or `python -m cine` # The application ID is "io.github.diegopvlk.Cine" exit_code = main(version="1.2.6") sys.exit(exit_code) ``` -------------------------------- ### Open Files and Configure Window Size in Cine Source: https://context7.com/diegopvlk/cine/llms.txt Opens multiple video files, probes the first video for dimensions to set window size, and loads files into the MPV playlist. Pauses other windows. ```python def do_open(self, files, n_files, hint): win = cast(CineWindow, self.props.active_window) open_new = settings.get_boolean("open-new-windows") or not win if open_new: win = CineWindow(application=self) win.start_page.set_visible(False) # Probe first video to set window size first_video_path = None for gfile in files: first_video_path = self.find_first_file(gfile) if first_video_path: break if first_video_path: try: cmd = [ "ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries", "stream=width,height:stream_side_data=rotation", "-of", "csv=s=x:p=0", first_video_path, ] output = subprocess.check_output(cmd, text=True, timeout=2, stderr=subprocess.DEVNULL).strip() # output example: "1920x1080" or "1920x1080x-90" (rotated) parts = output.splitlines()[0].split("x") width, height = int(parts[0]), int(parts[1]) rotation = int(parts[2]) if len(parts) > 2 else 0 w, h = (height, width) if abs(rotation) in (90, 270) else (width, height) win._set_window_size(w, h) except Exception as e: print(f"Metadata probe failed: {e}") win.present() # Append every file/URI to the MPV playlist for gfile in files: path = gfile.get_path() or gfile.get_uri() if path: win.mpv.loadfile(path, "append-play") # mpv command: loadfile append-play # Pause all other windows for window in self.get_windows(): w = cast(CineWindow, window) w.mpv.pause = (w != win) ``` -------------------------------- ### Right-Click Context Menu for Playlist Rows Source: https://context7.com/diegopvlk/cine/llms.txt Creates a context menu for playlist rows when right-clicked. Provides options to open the item's location or remove it from the playlist. ```python def _on_row_right_click(self, gesture, _n_press, x, y, path): menu = Gio.Menu.new() menu.append(_("Open Item Location"), "row.open_location") menu.append(_("Remove from Playlist"), "row.remove_item") # ...creates a Gtk.PopoverMenu with Gio.SimpleActions bound to the row ``` -------------------------------- ### Control MPV Video Properties with OptionsMenuButton Source: https://context7.com/diegopvlk/cine/llms.txt This Python code maps UI elements to MPV video properties for controls like aspect ratio, crop, rotation, flip, zoom, color adjustments, delay, and playback speed. It synchronizes with MPV state and writes changes directly. ```python # src/options.py — video option controls mapped to MPV properties # Supported aspect ratios (index → ratio value passed to video-aspect-override) RATIOS = [None, 16/9, 4/3, 1/1, 16/10, 2.00, 2.21, 2.35, 2.39, 5/4] # ── Aspect Ratio ── def _on_aspect_changed(self, dropdown, *arg): idx = dropdown.get_selected() item_str = dropdown.get_model().get_string(idx) val = "-1" if item_str == _("Original") else item_str self.win.mpv.command_async("set", "video-aspect-override", val) # Example: select "16:9" → mpv set video-aspect-override 16:9 # ── Crop ── def _on_crop_changed(self, dropdown, *args): selected_idx = dropdown.get_selected() if selected_idx == 0: self.win.mpv.command_async("set", "video-crop", "") # reset crop return w = self.win.mpv._get_property("video-params/w") # native width h = self.win.mpv._get_property("video-params/h") # native height target_ratio = RATIOS[selected_idx] current_ratio = w / h if current_ratio > target_ratio: new_w, new_h = int(h * target_ratio), h # crop sides else: new_w, new_h = w, int(w / target_ratio) # crop top/bottom self.win.mpv.command_async("set", "video-crop", f"{new_w}x{new_h}") # Example: 1920x1080 native, crop to 16:9 → "1920x1080" (no-op); crop to 2.35 → "2538x1080" clamped # ── Rotation ── def _on_rotate_right(self, _btn): curr = self.win.mpv["video-rotate"] or 0 self.win.mpv.command_async("set", "video-rotate", (curr + 90) % 360) # ── Flip ── def _on_flip_horiz(self, _btn): self.win.mpv.command_async("vf", "toggle", "@hflip:hflip") # toggle horizontal flip filter # ── Spin controls (zoom, contrast, brightness, gamma, saturation, hue) ── def _on_zoom_changed(self, spin): self.win.mpv["video-zoom"] = spin.get_value() # float, 0=100% def _on_contrast_changed(self, spin): self.win.mpv["contrast"] = int(spin.get_value()) # -100..100 def _on_brightness_changed(self, spin):self.win.mpv["brightness"] = int(spin.get_value()) def _on_gamma_changed(self, spin): self.win.mpv["gamma"] = int(spin.get_value()) def _on_saturation_changed(self, spin):self.win.mpv["saturation"] = int(spin.get_value()) def _on_hue_changed(self, spin): self.win.mpv["hue"] = int(spin.get_value()) # ── Delay & Speed ── def _on_sub_delay_changed(self, spin): self.win.mpv["sub-delay"] = spin.get_value() # seconds def _on_audio_delay_changed(self, spin):self.win.mpv["audio-delay"] = spin.get_value() # seconds def _on_speed_changed(self, spin): self.win.mpv["speed"] = spin.get_value() # 0.25–4.0 # ── Reset all options at once ── def _on_reset_all_options(self, _btn): self.aspect_dropdown.set_selected(0) # → video-aspect-override = -1 (original) self.crop_dropdown.set_selected(0) # → video-crop = "" self._on_rotate_reset(None) # → video-rotate = 0 self._on_flip_reset(None) # removes @hflip and @vflip vf filters self.zoom_spin.set_value(0) self.contrast_spin.set_value(0) self.brightness_spin.set_value(0) self.gamma_spin.set_value(0) self.saturation_spin.set_value(0) self.hue_spin.set_value(0) self.sub_delay_spin.set_value(0) self.audio_delay_spin.set_value(0) self.speed_spin.set_value(1.0) ``` -------------------------------- ### Configure Cine Settings with gsettings Source: https://context7.com/diegopvlk/cine/llms.txt Use gsettings to modify various Cine player settings. Some settings can be reset to their default values. ```bash gsettings set io.github.diegopvlk.Cine subtitle-languages "en,pt" gsettings set io.github.diegopvlk.Cine hwdec true gsettings set io.github.diegopvlk.Cine normalize-volume true gsettings set io.github.diegopvlk.Cine subtitle-color "#ffffff" gsettings set io.github.diegopvlk.Cine save-session true gsettings reset io.github.diegopvlk.Cine subtitle-font # reset to default ``` -------------------------------- ### Mouse Button Number to MPV Key Name Mapping Source: https://context7.com/diegopvlk/cine/llms.txt A dictionary mapping mouse button numbers to their corresponding MPV key names. ```python MBTN_MAP # {1: "MBTN_LEFT", 2: "MBTN_MID", 3: "MBTN_RIGHT", 8: "MBTN_BACK", 9: "MBTN_FORWARD"} ``` -------------------------------- ### GTK Key Name to MPV Key Name Remapping Source: https://context7.com/diegopvlk/cine/llms.txt A dictionary for remapping GTK key names to MPV key names, used when writing to `input.conf`. ```python KEY_REMAP # {"plus": "+", "minus": "-", "Escape": "ESC", "BackSpace": "BS", ...} ``` -------------------------------- ### Recursively Find First Regular File Source: https://context7.com/diegopvlk/cine/llms.txt Recursively searches a Gio.File directory for the first regular file, skipping hidden entries and symlinks. Used for probing video dimensions. ```python def find_first_file(self, gfile: Gio.File, visited: set | None = None) -> str | None: """Returns the path of the first regular file found under gfile (local only).""" if gfile.get_uri_scheme() != "file": return None if visited is None: visited = set() path = gfile.get_path() if not path or path in visited: return None visited.add(path) try: info = gfile.query_info("standard::type", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, None) f_type = info.get_file_type() if f_type == Gio.FileType.REGULAR: return path # ← base case: return the file path directly if f_type == Gio.FileType.DIRECTORY: enumerator = gfile.enumerate_children( "standard::name,standard::type", Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, None ) subdirectories = [] for child in enumerator: name = child.get_name() if name.startswith("."): continue # skip hidden files/dirs child_type = child.get_file_type() if child_type == Gio.FileType.REGULAR: return gfile.get_child(name).get_path() # first file wins elif child_type == Gio.FileType.DIRECTORY: subdirectories.append(gfile.get_child(name)) for folder in subdirectories: found = self.find_first_file(folder, visited) if found: return found except Exception: pass return None ``` -------------------------------- ### MPV Input Binding Table Source: https://context7.com/diegopvlk/cine/llms.txt A subset of internal MPV key bindings formatted for input.conf. This table maps keyboard shortcuts to specific MPV commands. ```python # src/shortcuts.py # ── Internal MPV binding table (subset) ── INTERNAL_BINDINGS = """ SPACE cycle pause; # Play/Pause LEFT seek -5 exact; # Seek 5s Backward RIGHT seek 5 exact; # Seek 5s Forward f cycle fullscreen; # Fullscreen m no-osd cycle mute; # Mute/Unmute s screenshot; # Take Screenshot With Subtitles S screenshot video; # Take Screenshot Without Subtitles z cycle sub; # Switch to Next Subtitle Track a cycle audio; # Switch to Next Audio Track [ multiply speed 1/1.1; # Decrease Playback Speed ] multiply speed 1.1; # Increase Playback Speed BS set speed 1.0; # Reset Playback Speed ctrl+l ab-loop; # Set/Clear A-B Loop Points L cycle-values loop-file "inf" "no"; # Loop File 1 add contrast -1; # Decrease Contrast 2 add contrast 1; # Increase Contrast ctrl+[ frame-step -1 seek; # Go Back One Frame Ctrl+LEFT add chapter -1; # Seek to the Previous Chapter """ ``` -------------------------------- ### Flatpak Host Permission Check Source: https://context7.com/diegopvlk/cine/llms.txt Checks if the application has host filesystem permissions, which is true when running outside of Flatpak or when the Flatpak has `filesystem=host` granted. Used to conditionally display warnings. ```python # Returns True if running outside Flatpak, or if filesystems=host is granted has_host_permission # used to conditionally show save-playlist warnings ``` -------------------------------- ### Supported Subtitle File Extensions Source: https://context7.com/diegopvlk/cine/llms.txt A tuple containing the file extensions for supported subtitle formats. ```python SUB_EXTS # (".srt", ".ass", ".vtt", ".sup", ".idx", ".sub", ...) — 22 formats ``` -------------------------------- ### Add Files/URLs via Drag-and-Drop Source: https://context7.com/diegopvlk/cine/llms.txt Handles dropping files or URLs onto the application. It differentiates between local files and URLs, loading them into the MPV player. Supports directories and various media MIME types. ```python def _on_drop(self, _target, value, _x, _y): items = value.get_files() if isinstance(value, Gdk.FileList) else [value] for item in items: if isinstance(item, Gio.File): path = item.get_path() or item.get_uri() is_url = not is_local_path(path) if is_url: self.mpv.loadfile(path, "append-play") # stream URL continue info = item.query_info("standard::content-type,standard::type", Gio.FileQueryInfoFlags.NONE, None) mime = info.get_content_type() or "" if info.get_file_type() == Gio.FileType.DIRECTORY: self.mpv.loadfile(path, "append-play") # directory of videos elif mime.startswith(("video/", "audio/", "image/")): self.mpv.loadfile(path, "append-play") elif isinstance(item, str): self.mpv.loadfile(item, "append-play") # URL string ``` -------------------------------- ### MPV to GTK Accelerator Translation Source: https://context7.com/diegopvlk/cine/llms.txt Translates MPV key names into GTK accelerator format. This is useful for mapping keyboard shortcuts to GTK widgets. ```python # ── Key name translation: MPV → GTK accelerator format ── translate_mpv_to_gtk("ctrl+z") # → "z" translate_mpv_to_gtk("Ctrl+LEFT") # → "Left" translate_mpv_to_gtk("SPACE") # → "space" translate_mpv_to_gtk("PGUP") # → "Page_Up" translate_mpv_to_gtk("S") # → "s" (uppercase → shift modifier) ``` -------------------------------- ### Check if Current Playlist Matches Saved Session Source: https://context7.com/diegopvlk/cine/llms.txt Compares the current MPV playlist with the one saved in `~/.config/cine/last-playlist.m3u8`. Returns `True` if they match exactly in order, `False` otherwise or if an error occurs. ```python def is_same_playlist(mpv_playlist) -> bool: """Used before replacing playlist to decide whether to save position first.""" try: with open(LAST_PLAYLIST_FILE, "r", encoding="utf-8") as f: saved = [l.strip() for l in f if l.strip() and not l.startswith("#EXTM3U")] current = [item["filename"] for item in mpv_playlist] return saved == current # exact order match required except Exception: return False ``` -------------------------------- ### Manage MPV Playlists with Playlist Dialog Source: https://context7.com/diegopvlk/cine/llms.txt This Python code defines the Playlist dialog, which displays the MPV playlist in a GTK ListBox. It handles drag-and-drop reordering, external file drops, context menus, playback highlighting, and saving playlists to M3U8 format. ```python # src/playlist.py ``` -------------------------------- ### Bind GSettings to GTK Widgets Source: https://context7.com/diegopvlk/cine/llms.txt Establishes two-way bindings between GSettings keys and GTK widget properties. Changes in the UI are automatically reflected in GSettings and vice-versa. ```python # src/preferences.py — key bindings and live update handlers class Preferences(Adw.Dialog): def _bind_ui(self): # Two-way GSettings ↔ GTK widget bindings (no manual sync needed) settings.bind("open-new-windows", self.open_new_row, "active", Gio.SettingsBindFlags.DEFAULT) settings.bind("subtitle-scale", self.subtitle_scale_row, "value", Gio.SettingsBindFlags.DEFAULT) settings.bind("subtitle-bg", self.subtitle_bg_switch, "active", Gio.SettingsBindFlags.DEFAULT) settings.bind("subtitle-languages", self.subtitle_lang_row, "text", Gio.SettingsBindFlags.DEFAULT) settings.bind("audio-languages", self.audio_lang_row, "text", Gio.SettingsBindFlags.DEFAULT) settings.bind("hwdec", self.hwdec_row, "active", Gio.SettingsBindFlags.DEFAULT) settings.bind("thumbnail-preview", self.thumb_preview_row, "active", Gio.SettingsBindFlags.DEFAULT) settings.bind("normalize-volume", self.normalize_volume_row, "active", Gio.SettingsBindFlags.DEFAULT) settings.bind("save-session", self.save_session_switch, "active", Gio.SettingsBindFlags.DEFAULT) settings.bind("save-video-position", self.save_position_switch, "active", Gio.SettingsBindFlags.DEFAULT) def _on_norm_volume_changed(self, settings, key): # Called immediately when the toggle is flipped in the UI norm_enabled = settings.get_boolean(key) if norm_enabled: self.player.command("af", "add", "@cine_loudnorm:lavfi=[loudnorm=I=-20]") else: self.player.command("af", "remove", "@cine_loudnorm") def _on_sub_color_selected(self, color_btn, *arg): # Convert GDK RGBA (0.0–1.0 floats) to hex string for GSettings + MPV rgba = color_btn.get_rgba() hex_color = "#{:02x}{:02x}{:02x}".format( int(rgba.red * 255), int(rgba.green * 255), int(rgba.blue * 255) ) settings.set_string("subtitle-color", hex_color) # GSettings "changed::subtitle-color" signal fires → _on_sub_color_changed → player["sub-color"] = hex_color def _on_sub_bg_color_selected(self, color_btn, *arg): rgba = color_btn.get_rgba() # MPV sub-back-color format is #AARRGGBB hex_color = "#{:02x}{:02x}{:02x}{:02x}".format( int(rgba.alpha * 255), int(rgba.red * 255), int(rgba.green * 255), int(rgba.blue * 255) ) settings.set_string("subtitle-bg-color", hex_color) ``` -------------------------------- ### Save Playlist as M3U8 Source: https://context7.com/diegopvlk/cine/llms.txt Writes the current playlist to a file in M3U8 format. Includes extended information for each track, such as title and filename. ```python def _write_m3u_file(self, mpv, path): with open(path, "w", encoding="utf-8") as f: f.write("#EXTM3U\n") for item in mpv.playlist: filename = item["filename"] name = os.path.splitext(os.path.basename(filename))[0] title = item.get("title") or name # prefer stream title for URLs f.write(f"#EXTINF:-1,{title}\n") f.write(f"{filename}\n") ``` ```text #EXTM3U #EXTINF:-1,Movie Title /home/user/Videos/movie.mkv #EXTINF:-1,Live Stream https://example.com/stream.m3u8 ``` -------------------------------- ### MPRIS D-Bus Interface Implementation Source: https://context7.com/diegopvlk/cine/llms.txt Handles MPRIS D-Bus communication, including emitting properties changed signals and processing incoming method calls like PlayPause, Seek, and SetPosition. It also maps MPV loop states to MPRIS loop status and prepares metadata for D-Bus consumers. ```python class MPRIS: def __init__(self, app): self._bus_name = "org.mpris.MediaPlayer2.io.github.diegopvlk.Cine" self._path = "/org/mpris/MediaPlayer2" Gio.bus_get(Gio.BusType.SESSION, None, self._on_bus_acquired) GLib.timeout_add(500, self._sync_player_state) # poll MPV state every 500ms # ── Emitting PropertiesChanged signal ── def emit_properties_changed(self, interface, changed_properties): self._con.emit_signal( None, self._path, "org.freedesktop.DBus.Properties", "PropertiesChanged", GLib.Variant("(sa{sv}as)", (interface, changed_properties, [])) ) # ── Handling incoming D-Bus method calls ── def _handle_method(self, method, params): p = self.player if method == "PlayPause": p.pause = not p.pause elif method == "Pause": p.pause = True elif method == "Play": p.pause = False elif method == "Stop": p.stop() elif method == "Next": win._on_next_clicked(win) elif method == "Previous": win._on_previous_clicked(win) elif method == "Seek": offset_usec = params.get_child_value(0).get_int64() p.time_pos += offset_usec / 1_000_000.0 self._emit_seeked() elif method == "SetPosition": pos_usec = params.get_child_value(1).get_int64() p.time_pos = pos_usec / 1_000_000.0 # ── LoopStatus mapping: MPRIS ↔ MPV ── def _get_loop_status(self) -> str: # MPV loop values: "inf" = loop forever, "no" = no loop if self.player.loop_file == "inf": return "Track" if self.player.loop_playlist == "inf": return "Playlist" return "None" # Set from D-Bus (e.g. GNOME Shell media controls setting loop) # "None" → loop_playlist="no", loop_file="no" # "Track" → loop_file="inf", loop_playlist="no" # "Playlist" → loop_file="no", loop_playlist="inf" # ── Metadata dictionary sent to D-Bus consumers ── def _get_metadata_variant(self, title): duration = int((self.player.duration or 0) * 1_000_000) # microseconds return GLib.Variant("a{sv}", { "mpris:trackid": GLib.Variant("o", "/org/mpris/MediaPlayer2/Track/0"), "xesam:title": GLib.Variant("s", str(title)), "mpris:length": GLib.Variant("x", duration), }) ``` ```shell # Interacting via CLI (playerctl): # playerctl --player=cine play-pause # playerctl --player=cine position 120 # seek to 2:00 # playerctl --player=cine volume 0.8 # set volume to 80% # playerctl --player=cine loop Track # loop current file # playerctl --player=cine metadata # shows xesam:title, mpris:length ``` -------------------------------- ### Save Current Playlist on Window Close Source: https://context7.com/diegopvlk/cine/llms.txt Saves the current playlist to a file and enables MPV's native save-position-on-quit feature. This function writes the playlist to `~/.config/cine/last-playlist.m3u8`. ```python def save_last_playlist_file(win_mpv): win_mpv["save-position-on-quit"] = True # MPV writes per-file watch-later configs with open(LAST_PLAYLIST_FILE, "w", encoding="utf-8") as f: f.write("#EXTM3U\n") for item in win_mpv.playlist: f.write(f"{item['filename']}\n") ``` -------------------------------- ### Detect Local Path vs. Network URL Source: https://context7.com/diegopvlk/cine/llms.txt Determines if a given path string represents a local file or a network URL. Used for handling file access and stream sources. ```python is_local_path("/home/user/video.mkv") # → True is_local_path("file:///home/user/video.mkv")# → True is_local_path("https://example.com/v.mp4") # → False is_local_path("rtmp://live.example.com/") # → False ``` -------------------------------- ### Drag-and-Drop Row Reordering Source: https://context7.com/diegopvlk/cine/llms.txt Enables reordering of playlist items using drag-and-drop. Moves items within the playlist based on their source and destination indices. ```python def _on_row_drop(self, _target, source_index, _x, _y, dest_index): if source_index != dest_index: if source_index < dest_index: self.mpv.command("playlist-move", source_index, dest_index + 1) else: self.mpv.command("playlist-move", source_index, dest_index) self._populate_list(dest_index) ``` -------------------------------- ### MPRIS D-Bus Interface Registration Source: https://context7.com/diegopvlk/cine/llms.txt Registers MPRIS D-Bus interfaces to enable system media controls. It polls state periodically and emits PropertiesChanged signals for state updates. ```python # MPRIS registers the org.mpris.MediaPlayer2 and org.mpris.MediaPlayer2.Player D-Bus interfaces on the session bus, enabling system media controls (GNOME Shell, KDE, headphones buttons, etc.). State is polled every 500 ms via GLib.timeout_add and changes are pushed as PropertiesChanged signals. ``` -------------------------------- ### Format Seconds into Human-Readable Time Source: https://context7.com/diegopvlk/cine/llms.txt Converts a duration in seconds into a human-readable string format (H:MM:SS or HH:MM:SS). Handles durations up to days. ```python format_time(0) # → "0:00" format_time(75) # → "1:15" format_time(3661) # → "1:01:01" format_time(90061) # → "1:01:01:01" (days) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.