Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bugfix](*) several bugfix #891

Merged
merged 5 commits into from
Dec 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions feeluown/app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,16 @@ def create_config() -> Config:
'ENABLE_YTDL_AS_MEDIA_PROVIDER', type_=bool, default=True, desc='YTDL 作为备用资源'
)
# For example::
# [{'name': 'match_by_source',
# 'source': 'xxx',
# 'http_proxy': 'http://127.0.0.1:7890'},]
# [
# {
# 'name': 'match_source',
# 'pattern': 'ytmusic',
# 'http_proxy': 'http://127.0.0.1:7890',
# 'ytdl_options': {
# 'socket_timeout': 2,
# },
# },
# ]
config.deffield('YTDL_RULES', type_=list, default=None, desc='')
# TODO(cosven): maybe
# 1. when it is set to 2, find standby from other providers first.
Expand Down
33 changes: 27 additions & 6 deletions feeluown/config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Optional
import logging
import warnings
from collections import namedtuple
Expand All @@ -15,19 +16,33 @@ class Config:
用户可以在 rc 文件中配置各个选项的值
"""

def __init__(self):
def __init__(self, name: str = 'config', parent: Optional['Config'] = None):
object.__setattr__(self, '_name', name)
object.__setattr__(self, '_parent', parent)
object.__setattr__(self, '_fields', {})
object.__setattr__(self, '_undeclared_fields', {})

def __getattr__(self, name):
# tips: 这里不能用 getattr 来获取值, 否则会死循环
if name == '_fields':
return object.__getattribute__(self, '_fields')
if name in ('_fields', '_parent', '_name', '_undeclared_fields'):
return object.__getattribute__(self, name)
if name in self._fields:
try:
object.__getattribute__(self, name)
return object.__getattribute__(self, name)
except AttributeError:
return self._fields[name].default
return object.__getattribute__(self, name)
elif name in self._undeclared_fields:
return self._undeclared_fields[name]

# Requirement:
# User may define config like
# app.plugin.X = Y
# When 'plugin' is not installed, such config should not raise an error.
# To achieve this, return a subconfig when accessing an undeclared key.
logger.warning(f'Undeclared subconfig: {self.fullname}.{name}')
tmpconfig = Config(name=name, parent=self)
self._undeclared_fields[name] = tmpconfig
return tmpconfig

def __setattr__(self, name, value):
if name in self._fields:
Expand All @@ -38,7 +53,13 @@ def __setattr__(self, name, value):
# TODO: 校验值类型
object.__setattr__(self, name, value)
else:
logger.warning('Assign to an undeclared config key.')
logger.warning(f'Assign to an undeclared config key: {self.fullname}.{name}')

@property
def fullname(self) -> str:
if self._parent is None:
return self._name
return f'{self._parent.fullname}.{self._name}'

def deffield(self, name, type_=None, default=None, desc='', warn=None):
"""Define a configuration field
Expand Down
12 changes: 1 addition & 11 deletions feeluown/gui/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
import logging
import json
import os
import sys
from collections import defaultdict
from typing import TYPE_CHECKING

from PyQt5.QtCore import pyqtSignal, QObject
from PyQt5.QtGui import QGuiApplication, QPalette, QColor
from feeluown.utils.utils import get_osx_theme
from feeluown.gui.helpers import get_qapp

if TYPE_CHECKING:
Expand Down Expand Up @@ -60,15 +58,7 @@ def initialize(self):

def autoload(self):
if self._app.config.THEME == 'auto':
if sys.platform == 'darwin':
if get_osx_theme() == 1:
theme = DARK
else:
theme = LIGHT
else:
theme = self.guess_system_theme()
if theme == DARK:
theme = Dark
theme = self.guess_system_theme()
else: # user settings have highest priority
theme = self._app.config.THEME
self.load_theme(theme)
Expand Down
6 changes: 5 additions & 1 deletion feeluown/library/uri.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ def parse_line(line):
>>> model, _ = parse_line(line)
>>> model.source, model.title_display
('xxx', '没有人知道')
>>> line = 'fuo://xxx/songs/1-1'
>>> model, _ = parse_line(line)
>>> model.identifier
'1-1'
"""
line = line.strip()
parts = line.split('#', maxsplit=1)
Expand All @@ -223,7 +227,7 @@ def parse_line(line):
else:
uri, model_str = parts[0], ''
ns_list = list(TYPE_NS_MAP.values())
p = re.compile(r'^fuo://(\w+)/({})/(\w+)'.format('|'.join(ns_list)))
p = re.compile(r'^fuo://(\w+)/({})/([\w-]+)'.format('|'.join(ns_list)))
uri = uri.strip()
m = p.match(uri)
if not m:
Expand Down
25 changes: 20 additions & 5 deletions feeluown/library/ytdl.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from typing import Optional, List, Any

from yt_dlp import YoutubeDL
from yt_dlp import YoutubeDL, DownloadError

from feeluown.media import Media, VideoAudioManifest

Expand All @@ -11,14 +11,19 @@
class Ytdl:

def __init__(self, rules: Optional[List[Any]] = None):
self._default_audio_ytdl_opts = {
self._default_ytdl_opts = {
'logger': logger,
'socket_timeout': 2,
'extractor_retries': 0, # reduce retry
}
self._default_audio_ytdl_opts = {
# The following two options may be only valid for select_audio API.
# Remove these two options if needed.
'format': 'm4a/bestaudio/best',
**self._default_ytdl_opts,
}
self._default_video_ytdl_opts = {
'logger': logger,
**self._default_ytdl_opts,
}
# For example::
# [
Expand Down Expand Up @@ -53,8 +58,13 @@ def select_audio(self, url, _: Optional[str] = None, source='') -> Optional[Medi
ytdl_opts = {}
ytdl_opts.update(self._default_audio_ytdl_opts)
ytdl_opts['proxy'] = http_proxy
ytdl_opts.update(matched_rule.get('ytdl_options', {}))
with YoutubeDL(ytdl_opts) as inner:
info = inner.extract_info(url, download=False)
try:
info = inner.extract_info(url, download=False)
except DownloadError: # noqa
logger.warning(f"extract_info failed for {url}")
info = None
if info:
media_url = info['url']
if media_url:
Expand All @@ -76,11 +86,16 @@ def select_video(self, url, _: Optional[str] = None, source='') -> Optional[Medi
ytdl_opts = {}
ytdl_opts.update(self._default_video_ytdl_opts)
ytdl_opts['proxy'] = http_proxy
ytdl_opts.update(matched_rule.get('ytdl_options', {}))

audio_candidates = [] # [(url, abr)] abr: average bitrate
video_candidates = [] # [(url, width)]
with YoutubeDL(ytdl_opts) as inner:
info = inner.extract_info(url, download=False)
try:
info = inner.extract_info(url, download=False)
except DownloadError as e: # noqa
logger.warning(f"extract_info failed for {url}")
info = None
if info:
for f in info['formats']:
if f.get('acodec', 'none') not in ('none', None):
Expand Down
11 changes: 8 additions & 3 deletions feeluown/player/metadata_assembler.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ async def fetch_from_song(self, song):
)
except ResourceNotFound:
return empty_result
except TimeoutError: # noqa
logger.warning(f"fetching song's meta timeout, song:'{song.title}'")
return empty_result
except: # noqa
logger.exception(f"fetching song's meta failed, song:'{song.title_display}'")
logger.exception(f"fetching song's meta failed, song:'{song.title}'")
return empty_result
return usong.pic_url, usong.date, usong.album

Expand All @@ -51,9 +54,11 @@ async def fetch_from_album(self, album):
)
except ResourceNotFound:
return empty_result
except TimeoutError: # noqa
logger.warning(f"fetching album's meta timeout, album:'{album.name}'")
return empty_result
except: # noqa
logger.warning(
f"fetching album meta failed, album:{album.name}")
logger.exception(f"fetching album meta failed, album:'{album.name}'")
return empty_result
return album.cover, album.released

Expand Down
2 changes: 1 addition & 1 deletion feeluown/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def init_config(self, config: Config):
.. versionadded: 3.7.15
"""
myconfig = Config()
myconfig = Config(name=self.name, parent=config)

names = [self.name]
# Currently, plugin name looks like fuo_xxx and xxx is the real name.
Expand Down
2 changes: 2 additions & 0 deletions feeluown/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,14 @@ def run_afn_preemptive(self, afn, *args, name=''):
if not name:
name = get_fn_name(afn)
task_spec = self.get_or_create(name)
task_spec.disable_default_cb()
return task_spec.bind_coro(afn(*args))

def run_fn_preemptive(self, fn, *args, name=''):
if not name:
name = get_fn_name(fn)
task_spec = self.get_or_create(name)
task_spec.disable_default_cb()
return task_spec.bind_blocking_io(fn, *args)


Expand Down
8 changes: 0 additions & 8 deletions feeluown/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-

import logging
import os
import sys
import socket
import time
Expand Down Expand Up @@ -80,13 +79,6 @@ def elfhash(s):
return (hash & 0x7FFFFFFF)


def get_osx_theme():
"""1 for dark, -1 for light"""
with os.popen('defaults read -g AppleInterfaceStyle') as pipe:
theme = pipe.read().strip()
return 1 if theme == 'Dark' else -1


class DedupList(list):
"""List that doesn't contain duplicate items

Expand Down
10 changes: 7 additions & 3 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ def test_config_set_known_field(config):


def test_config_set_unknown_field(config):
"""Set unknown field should cause no effects"""
"""
Set unknown field should cause no effects.
Access unknown field should return a Config object.
"""
config.hey = 0
with pytest.raises(AttributeError):
config.hey
assert isinstance(config.hey, Config)
config.plugin.X = 0 # should not raise error
assert isinstance(config.plugin.X, Config)


def test_config_set_subconfig(config):
Expand Down
Loading