From 216dad6db84474a42e329014376915a680f09a39 Mon Sep 17 00:00:00 2001 From: WilliamTahar Date: Thu, 5 Feb 2026 19:55:25 +0100 Subject: [PATCH 1/2] :bug: Fix blackscreen with youtube link --- src/player/video_player.py | 67 +++++++++++++++++++++++++------------- src/yt_utils.py | 35 ++++++++++++++++++-- 2 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/player/video_player.py b/src/player/video_player.py index e15406b..ee291a3 100644 --- a/src/player/video_player.py +++ b/src/player/video_player.py @@ -178,8 +178,14 @@ def volume_fade(self, target, fade_duration_sec, fade_interval): self.fade.cancel() self.fade.start(cur=cur, target=target, step=step, fade_interval=fade_interval, update_callback=self.set_volume) - def media_new(self, *args): - return self.__vlc_widget.instance.media_new(*args) + def media_new(self, url, http_headers=None): + media = self.__vlc_widget.instance.media_new(url) + # Add HTTP headers if provided (needed for YouTube streams) + if http_headers: + for header_name, header_value in http_headers.items(): + # VLC uses :http-header=Header-Name: Header-Value format + media.add_option(f":http-header={header_name}: {header_value}") + return media def set_media(self, *args): self.__vlc_widget.player.set_media(*args) @@ -231,8 +237,14 @@ def centercrop(self, video_width=None, video_height=None): logger.debug(f"[CenterCrop] Crop geometry: {crop_geometry}") self.__vlc_widget.player.video_set_crop_geometry(crop_geometry) - def add_audio_track(self, audio): - self.__vlc_widget.player.add_slave(vlc.MediaSlaveType(1), audio, True) + def add_audio_track(self, audio_url, http_headers=None): + # Create a media object for the audio track with headers + audio_media = self.__vlc_widget.instance.media_new(audio_url) + if http_headers: + for header_name, header_value in http_headers.items(): + # VLC uses :http-header=Header-Name: Header-Value format + audio_media.add_option(f":http-header={header_name}: {header_value}") + self.__vlc_widget.player.add_slave(vlc.MediaSlaveType(1), audio_media, True) def _on_button_press_event(self, widget, event): if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3: @@ -418,24 +430,35 @@ def data_source(self, data_source): elif self.mode == MODE_STREAM: source = data_source['Default'] - formats = get_formats(source) - max_height = max( - self.windows, key=lambda m: m.get_geometry().height).get_geometry().height - video_url, video_width, video_height = get_optimal_video( - formats, max_height) - audio_url = get_best_audio(formats) - - for monitor, window in self.windows.items(): - media = window.media_new(video_url) - media.add_option("input-repeat=65535") - window.set_media(media) - if monitor.is_primary(): - window.add_audio_track(audio_url) - else: - # `get_optimal_video` now might return video with audio. - media.add_option("no-audio") - window.set_position(0.0) - window.centercrop(video_width, video_height) + try: + formats = get_formats(source) + max_height = max( + self.windows, key=lambda m: m.get_geometry().height).get_geometry().height + video_url, video_width, video_height, video_headers = get_optimal_video( + formats, max_height) + audio_url, audio_headers = get_best_audio(formats) + + logger.info(f"[Stream] Video URL: {video_url[:100]}...") + logger.info(f"[Stream] Audio URL: {audio_url[:100]}...") + logger.debug(f"[Stream] Video headers: {video_headers}") + logger.debug(f"[Stream] Audio headers: {audio_headers}") + + for monitor, window in self.windows.items(): + media = window.media_new(video_url, http_headers=video_headers) + media.add_option("input-repeat=65535") + # Add network caching for better streaming performance + media.add_option(":network-caching=3000") + window.set_media(media) + if monitor.is_primary(): + window.add_audio_track(audio_url, http_headers=audio_headers) + else: + # `get_optimal_video` now might return video with audio. + media.add_option("no-audio") + window.set_position(0.0) + window.centercrop(video_width, video_height) + except Exception as e: + logger.error(f"[Stream] Failed to setup stream: {e}", exc_info=True) + raise else: raise ValueError("Invalid mode") diff --git a/src/yt_utils.py b/src/yt_utils.py index cfb704f..ae3cd74 100644 --- a/src/yt_utils.py +++ b/src/yt_utils.py @@ -1,9 +1,34 @@ import yt_dlp as youtube_dl +import logging + +logger = logging.getLogger("hidamari") def get_formats(raw_url): with youtube_dl.YoutubeDL({"noplaylist": True}) as ydl: - formats = ydl.extract_info(raw_url, download=False)["formats"] + info = ydl.extract_info(raw_url, download=False) + formats = info["formats"] + # Extract HTTP headers from info or formats + # Headers might be in info or in individual formats + http_headers = info.get("http_headers", {}) + + # If no headers in info, try to get them from the first format that has them + if not http_headers: + for fmt in formats: + fmt_headers = fmt.get("http_headers", {}) + if fmt_headers: + http_headers = fmt_headers + break + + # Ensure User-Agent is set (important for YouTube streams) + if "User-Agent" not in http_headers: + # Use a standard browser User-Agent if not provided + http_headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + + # Ensure each format has http_headers (use format-specific or fallback to common) + for fmt in formats: + if "http_headers" not in fmt or not fmt.get("http_headers"): + fmt["http_headers"] = http_headers return formats @@ -29,8 +54,10 @@ def get_best_audio(formats): filtered = list(filter_audio(formats)) if not filtered: filtered = list(filter_audio_video(formats)) + if not filtered: + raise ValueError("No audio format found") best = max(filtered, key=lambda x: x.get("quality", -1)) - return best["url"] + return best["url"], best.get("http_headers", {}) def get_best_video(formats): @@ -45,8 +72,10 @@ def get_optimal_video(formats, height): filtered = list(filter_video(formats)) if not filtered: filtered = list(filter_audio_video(formats)) + if not filtered: + raise ValueError("No video format found") best = min(filtered, key=lambda x: abs(x.get("height", 0) - height)) - return best["url"], best["width"], best["height"] + return best["url"], best["width"], best["height"], best.get("http_headers", {}) if __name__ == "__main__": From d73aaff61de4130ddc950e44cdd241a8927425ba Mon Sep 17 00:00:00 2001 From: WilliamTahar Date: Fri, 6 Feb 2026 09:32:12 +0100 Subject: [PATCH 2/2] :bug: pass HTTP headers to VLC for YouTube stream playback --- src/player/video_player.py | 27 +++++++++++++++++---------- src/yt_utils.py | 5 ++++- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/player/video_player.py b/src/player/video_player.py index ee291a3..1643442 100644 --- a/src/player/video_player.py +++ b/src/player/video_player.py @@ -182,9 +182,12 @@ def media_new(self, url, http_headers=None): media = self.__vlc_widget.instance.media_new(url) # Add HTTP headers if provided (needed for YouTube streams) if http_headers: - for header_name, header_value in http_headers.items(): - # VLC uses :http-header=Header-Name: Header-Value format - media.add_option(f":http-header={header_name}: {header_value}") + user_agent = http_headers.get("User-Agent") + if user_agent: + media.add_option(f":http-user-agent={user_agent}") + referrer = http_headers.get("Referer") or http_headers.get("Referrer") + if referrer: + media.add_option(f":http-referrer={referrer}") return media def set_media(self, *args): @@ -238,13 +241,17 @@ def centercrop(self, video_width=None, video_height=None): self.__vlc_widget.player.video_set_crop_geometry(crop_geometry) def add_audio_track(self, audio_url, http_headers=None): - # Create a media object for the audio track with headers - audio_media = self.__vlc_widget.instance.media_new(audio_url) - if http_headers: - for header_name, header_value in http_headers.items(): - # VLC uses :http-header=Header-Name: Header-Value format - audio_media.add_option(f":http-header={header_name}: {header_value}") - self.__vlc_widget.player.add_slave(vlc.MediaSlaveType(1), audio_media, True) + # add_slave expects a URI string, not a Media object + # Set headers on the player's media so VLC uses them for audio too + media = self.__vlc_widget.player.get_media() + if media and http_headers: + user_agent = http_headers.get("User-Agent") + if user_agent: + media.add_option(f":http-user-agent={user_agent}") + referrer = http_headers.get("Referer") or http_headers.get("Referrer") + if referrer: + media.add_option(f":http-referrer={referrer}") + self.__vlc_widget.player.add_slave(vlc.MediaSlaveType(1), audio_url, True) def _on_button_press_event(self, widget, event): if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3: diff --git a/src/yt_utils.py b/src/yt_utils.py index ae3cd74..9ec7316 100644 --- a/src/yt_utils.py +++ b/src/yt_utils.py @@ -22,9 +22,12 @@ def get_formats(raw_url): # Ensure User-Agent is set (important for YouTube streams) if "User-Agent" not in http_headers: - # Use a standard browser User-Agent if not provided http_headers["User-Agent"] = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + # Ensure Referer is set (YouTube checks this) + if "Referer" not in http_headers: + http_headers["Referer"] = raw_url + # Ensure each format has http_headers (use format-specific or fallback to common) for fmt in formats: if "http_headers" not in fmt or not fmt.get("http_headers"):