def _setupLyrics(self): self._logger.info(">> Setting up lyrics...") if self._player.getCurrentMusic().isAbsent(): self._logger.info("No music, return") return currentMusic = self._player.getCurrentMusic().orElseThrow(AssertionError) lyricsPath = Path(currentMusic.filename).with_suffix(".lrc") lyricsText = lyricsPath.read_text(encoding="gbk") self._logger.info("Parsing lyrics") lyrics = LyricUtils.parseLyrics(lyricsText) self._logger.info("Lyrics count: %d", len(lyrics)) self._lyrics = lyrics self._prevLyricIndex = -1 LayoutUtils.clearLayout(self._layout) self._layout.addSpacing(self.height() // 2) for position, lyric in list(lyrics.items())[:]: lyricLabel = ClickableLabel(lyric, self) lyricLabel.setAlignment( gg(QtCore.Qt.AlignmentFlag.AlignCenter, Any) | QtCore.Qt.AlignmentFlag.AlignVCenter) lyricLabel.clicked.connect( lambda _, position=position: self._player.setPosition(position)) lyricLabel.setMargin(int(2 * self._app.getZoom())) self._layout.addWidget(lyricLabel) self._layout.addSpacing(self.height() // 2) self._logger.info("Lyrics layout has children: %d", self._layout.count()) self.verticalScrollBar().setValue(0) self._logger.info("<< Lyrics set up")
def findPluginClassesInFolder(self, folder): self._logger.info("Find plugin classes in folder: %s", folder) from IceSpringMusicPlayer.common.pluginMixin import PluginMixin classes = set() pluginRoot = Path("IceSpringMusicPlayer/plugins") for path in Path(folder).glob("**/*.py"): package = ".".join([ x for x in path.relative_to(pluginRoot).parts if x != "__init__.py" ]).rstrip(".py") for x in importlib.import_module(package).__dict__.values(): if isinstance(x, type) and issubclass( x, PluginMixin) and x != PluginMixin: classes.add(x) return sorted(classes, key=lambda x: x.__module__ + "." + x.__name__)
def run() -> None: from IceSpringMusicPlayer.utils.logUtils import LogUtils from IceSpringMusicPlayer.utils.pydubUtils import PydubUtils LogUtils.initLogging() PydubUtils.patchPydub() sys.path.append(str(Path(__file__).parent / "plugins")) from IceSpringMusicPlayer.app import App App().exec_()
def addMusicsFromFileDialog(self): self._logger.info("Add musics from file dialog") musicRoot = str(Path("~/Music").expanduser().absolute()) filenames = QtWidgets.QFileDialog.getOpenFileNames( None, "Open music files", musicRoot, "Audio files (*.mp3 *.wma) ;; All files (*)")[0] self._logger.info("There are %d files to open", len(filenames)) self.addMusicsFromFilenames(filenames)
def _loadConfig(self) -> Config: self._logger.info("Load config") path = Path("config.json") if not path.exists(): self._logger.info("No config.json file, return default config") return self.getDefaultConfig() jd = json.loads(path.read_text(), object_pairs_hook=Config.fromJson) config = Config( **{ **self.getDefaultConfig().__dict__, **{k: v for k, v in jd.items() if k in Config.__annotations__} }) self._logger.info("Process plugin configs (%d plugins)", len(self._pluginService.getPluginClasses())) for plugin in config.plugins: self._logger.info("Plugin in config: %s", plugin) jd = json.loads(json.dumps( plugin.config, default=plugin.clazz.getPluginConfigClass().pythonToJson), object_pairs_hook=plugin.clazz. getPluginConfigClass().jsonToPython) plugin.config = gg(plugin.clazz.getPluginConfigClass())(**jd) self._logger.info("Process plugins not in config") loadedClasses = [x.clazz for x in config.plugins] for clazz in self._pluginService.getPluginClasses(): if clazz not in loadedClasses: self._logger.info("Plugin not in config: %s", clazz) config.plugins.append( Plugin(clazz=clazz, disabled=False, config=clazz.getPluginConfigClass(). getDefaultObject())) self._logger.info("Sort plugins") config.plugins.sort( key=lambda x: x.clazz.__module__ + "." + x.clazz.__name__) self._logger.info("Load layout config") self._loadElementConfig(config.layout) self._logger.info("Loaded config: %s", config) return config
def persistConfig(self): self._logger.info("Persist, refresh current config") self._config.layout = self._app.getMainWindow().calcLayout() self._config.volume = self._app.getPlayer().getVolume() self._config.playbackMode = self._app.getPlayer().getPlaybackMode() self._config.frontPlaylistIndex = self._app.getPlayer( ).getFrontPlaylistIndex() self._logger.info("Save to config.json") Path("config.json").write_text( json.dumps(self._config, indent=4, ensure_ascii=False, default=Config.toJson))
def loadTestData(self): self._logger.info("Load test data") paths = Path("~/Music").expanduser().glob("**/*.mp3") musics = [MusicUtils.parseMusic(str(x)) for x in paths] self._player.setFrontPlaylistIndex(self._player.insertPlaylist()) self._player.insertMusics( [x for i, x in enumerate(musics) if i % 6 in (0, 1, 2)]) self._player.setFrontPlaylistIndex(self._player.insertPlaylist()) self._player.insertMusics( [x for i, x in enumerate(musics) if i % 6 in (3, 4)]) self._player.setFrontPlaylistIndex(self._player.insertPlaylist()) self._player.insertMusics( [x for i, x in enumerate(musics) if i % 6 in (5, )])
def setMedia(self, media: QtMultimedia.QMediaContent, stream: QtCore.QIODevice = None, realDuration=None) -> None: self.blockSignals(True) super().setMedia(media, stream) self.blockSignals(False) self._realDuration = realDuration self._lastFakePosition = 0 self._bugRate = 0.0 if realDuration is None: filename = media.canonicalUrl().toLocalFile() file = taglib.File(filename) bitrate = file.bitrate file.close() self._realDuration = Path(filename).stat().st_size * 8 // bitrate
def removePlugin(self, plugin: Plugin) -> None: self._logger.info("Remove plugin: %s", plugin.clazz) assert not plugin.clazz.isSystemPlugin() assert not self.isPluginUsedInMainWindow( plugin), "Plugin used in main window" stem = plugin.clazz.__module__.split(".")[0] path = Path(f"IceSpringMusicPlayer/plugins/{stem}") self._logger.info("Remove plugin folder: %s", path) now = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") targetPath = Path(f"IceSpringMusicPlayer/recycles/{now}/{stem}") self._logger.info("Plugin is backup to %s", targetPath) targetPath.parent.mkdir(parents=True, exist_ok=True) path.copytree(targetPath) path.rmtree() self._logger.info("Remove plugin from registry") self._app.getConfig().plugins.remove(plugin) self._logger.info("> Signal pluginRemoved emitting...") self.pluginsRemoved.emit([plugin]) self._logger.info("< Signal pluginRemoved emitted.")
def addMusicsFromFolder(self, folder: str) -> None: self._logger.info("Add musics from folder: %s", folder) paths = Path(folder).expanduser().glob("**/*.mp3") filenames = [str(x) for x in paths] self.addMusicsFromFilenames(filenames)
import importlib.util import logging import os import re import shutil import sys from subprocess import Popen, PIPE import PyInstaller.__main__ from IceSpringPathLib import Path from PyInstaller.utils.hooks import collect_submodules from IceSpringMusicPlayer.utils.logUtils import LogUtils name = "IceSpringMusicPlayer" vsHome = Path(r"C:\Program Files (x86)\Microsoft Visual Studio\2019").absolute() vcvarsall = vsHome / r"BuildTools\VC\Auxiliary\Build\vcvarsall.bat" ffmpegHome = Path("./ffmpeg").absolute() fftwDll = Path("libfftw3f-3.dll").absolute() assert vsHome.exists() assert vcvarsall.exists() assert ffmpegHome.exists() assert ffmpegHome.__truediv__("ffmpeg.exe").exists() assert ffmpegHome.__truediv__("ffprobe.exe").exists() assert fftwDll.exists() LogUtils.initLogging() logging.getLogger().setLevel(logging.INFO) logging.info("Removing application directory if exists...") Path(f"dist/{name}").exists() and Path(f"dist/{name}").rmtree()
def _verifyPluginFolder( self, folder: str) -> typing.List[typing.Type[PluginMixin]]: self._logger.info("Verify plugin in folder: %s", folder) oldClasses = [x.clazz for x in self._app.getConfig().plugins] root = Path(folder) if not (root / "__init__.py").exists(): raise RuntimeError("Plugin folder must contains __init__.py") targetPath = Path(f"IceSpringMusicPlayer/plugins/{root.name}") if targetPath.exists(): self._logger.info("Plugin already exists.") raise RuntimeError("Plugin Already Exists") self._logger.info("Copy plugin folder to plugins dir %s", targetPath) root.copytree(targetPath) try: classes = self.findPluginClassesInFolder(str(targetPath)) classes = [x for x in classes if x not in oldClasses] except Exception as e: self._logger.info("Exception occurred: %s", e, e) self._logger.info("Remove folder: %s", targetPath) targetPath.rmtree() raise e if len(classes) == 0: self._logger.info("No plugin found in folder, remove folder") targetPath.rmtree() raise RuntimeError("No plugin found") return classes