diff --git a/app/main.py b/app/main.py index 2d71eac..73132a7 100644 --- a/app/main.py +++ b/app/main.py @@ -123,14 +123,10 @@ class ObjectSerializer(json.JSONEncoder): elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes)): try: return list(obj) - except (TypeError, ValueError, RuntimeError): - # If conversion to list fails, log a warning and return a placeholder - log.warning(f"Failed to convert {type(obj).__name__} to list, using string representation") - return str(obj) - # For objects that can't be serialized, return their string representation - # This prevents HTTP 500 errors while still providing useful information - log.warning(f"Object of type {type(obj).__name__} is not directly JSON serializable, using string representation") - return str(obj) + except: + pass + # Fall back to default behavior + return json.JSONEncoder.default(self, obj) serializer = ObjectSerializer() app = web.Application() diff --git a/app/ytdl.py b/app/ytdl.py index 2c241cb..84151a2 100644 --- a/app/ytdl.py +++ b/app/ytdl.py @@ -44,9 +44,41 @@ class DownloadInfo: self.size = None self.timestamp = time.time_ns() self.error = error - self.entry = entry + # Extract only picklable playlist metadata from entry + # This prevents issues when shelve tries to pickle DownloadInfo objects + # that contain non-picklable objects like generators + self.entry = self._extract_picklable_entry(entry) if entry else None self.playlist_item_limit = playlist_item_limit + @staticmethod + def _extract_picklable_entry(entry): + """Extract only picklable data from entry dict. + + This is needed because yt-dlp may return entry dicts containing + non-picklable objects like generators, which cause errors when + shelve tries to persist DownloadInfo objects. + """ + if not isinstance(entry, dict): + return None + + # Extract only basic types that are picklable + picklable_entry = {} + for key, value in entry.items(): + # Only include playlist-related properties (used for output template) + # and other simple string/int/bool values + if key.startswith('playlist') or key in ('id', 'title', 'url', 'webpage_url', '_type'): + if isinstance(value, (str, int, float, bool, type(None))): + picklable_entry[key] = value + elif isinstance(value, (list, tuple)): + # For lists/tuples, only include if all items are basic types + try: + if all(isinstance(item, (str, int, float, bool, type(None))) for item in value): + picklable_entry[key] = value + except (TypeError, AttributeError): + pass + + return picklable_entry if picklable_entry else None + class Download: manager = None