From 3795d9e4ff7aad090d0968ecd6a6132d922a68dc Mon Sep 17 00:00:00 2001 From: Astounds Date: Sun, 5 Apr 2026 18:47:21 -0500 Subject: fix(playlists): make playlist parsing robust against filename and formatting issues - Use glob lookup to find playlist files even with trailing spaces in filenames - Sanitize lines (strip whitespace) before JSON parsing to ignore trailing spaces/empty lines - Handle JSONDecodeError gracefully to prevent 500 errors from corrupt entries - Return empty list on FileNotFoundError in read_playlist instead of crashing - Extract _find_playlist_path and _parse_playlist_lines helpers for reuse --- youtube/local_playlist.py | 66 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 20 deletions(-) (limited to 'youtube') diff --git a/youtube/local_playlist.py b/youtube/local_playlist.py index 968f1a6..4620129 100644 --- a/youtube/local_playlist.py +++ b/youtube/local_playlist.py @@ -8,6 +8,7 @@ import html import gevent import urllib import math +import glob import flask from flask import request @@ -16,11 +17,34 @@ playlists_directory = os.path.join(settings.data_dir, "playlists") thumbnails_directory = os.path.join(settings.data_dir, "playlist_thumbnails") +def _find_playlist_path(name): + """Find playlist file robustly, handling trailing spaces in filenames""" + name = name.strip() + pattern = os.path.join(playlists_directory, name + "*.txt") + files = glob.glob(pattern) + return files[0] if files else os.path.join(playlists_directory, name + ".txt") + + +def _parse_playlist_lines(data): + """Parse playlist data lines robustly, skipping empty/malformed entries""" + videos = [] + for line in data.splitlines(): + clean_line = line.strip() + if not clean_line: + continue + try: + videos.append(json.loads(clean_line)) + except json.decoder.JSONDecodeError: + print('Corrupt playlist entry: ' + clean_line) + return videos + + def video_ids_in_playlist(name): try: - with open(os.path.join(playlists_directory, name + ".txt"), 'r', encoding='utf-8') as file: + playlist_path = _find_playlist_path(name) + with open(playlist_path, 'r', encoding='utf-8') as file: videos = file.read() - return set(json.loads(video)['id'] for video in videos.splitlines()) + return set(json.loads(line.strip())['id'] for line in videos.splitlines() if line.strip()) except FileNotFoundError: return set() @@ -29,7 +53,8 @@ def add_to_playlist(name, video_info_list): os.makedirs(playlists_directory, exist_ok=True) ids = video_ids_in_playlist(name) missing_thumbnails = [] - with open(os.path.join(playlists_directory, name + ".txt"), "a", encoding='utf-8') as file: + playlist_path = _find_playlist_path(name) + with open(playlist_path, "a", encoding='utf-8') as file: for info in video_info_list: id = json.loads(info)['id'] if id not in ids: @@ -67,20 +92,14 @@ def add_extra_info_to_videos(videos, playlist_name): def read_playlist(name): '''Returns a list of videos for the given playlist name''' - playlist_path = os.path.join(playlists_directory, name + '.txt') - with open(playlist_path, 'r', encoding='utf-8') as f: - data = f.read() + playlist_path = _find_playlist_path(name) + try: + with open(playlist_path, 'r', encoding='utf-8') as f: + data = f.read() + except FileNotFoundError: + return [] - videos = [] - videos_json = data.splitlines() - for video_json in videos_json: - try: - info = json.loads(video_json) - videos.append(info) - except json.decoder.JSONDecodeError: - if not video_json.strip() == '': - print('Corrupt playlist video entry: ' + video_json) - return videos + return _parse_playlist_lines(data) def get_local_playlist_videos(name, offset=0, amount=50): @@ -102,14 +121,21 @@ def get_playlist_names(): def remove_from_playlist(name, video_info_list): ids = [json.loads(video)['id'] for video in video_info_list] - with open(os.path.join(playlists_directory, name + ".txt"), 'r', encoding='utf-8') as file: + playlist_path = _find_playlist_path(name) + with open(playlist_path, 'r', encoding='utf-8') as file: videos = file.read() videos_in = videos.splitlines() videos_out = [] for video in videos_in: - if json.loads(video)['id'] not in ids: - videos_out.append(video) - with open(os.path.join(playlists_directory, name + ".txt"), 'w', encoding='utf-8') as file: + clean = video.strip() + if not clean: + continue + try: + if json.loads(clean)['id'] not in ids: + videos_out.append(clean) + except json.decoder.JSONDecodeError: + pass + with open(playlist_path, 'w', encoding='utf-8') as file: file.write("\n".join(videos_out) + "\n") try: -- cgit v1.2.3