mirror of
https://github.com/hanxi/xiaomusic.git
synced 2025-12-06 14:52:50 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23d321a722 | ||
|
|
20945954b1 | ||
|
|
d6c2078917 | ||
|
|
a5b8dc639c | ||
|
|
84751e0d68 | ||
|
|
e759658481 | ||
|
|
d83100588f | ||
|
|
83d0e02eb4 | ||
|
|
20f1f33b6c | ||
|
|
fbb5d26c28 | ||
|
|
2c21778675 | ||
|
|
959acd8fb7 | ||
|
|
148c5b7621 | ||
|
|
d7a2afba48 | ||
|
|
559ed23214 | ||
|
|
1d1e63df8a | ||
|
|
27e9d92a0a | ||
|
|
69573f3fa4 | ||
|
|
edafd79140 | ||
|
|
7c45d93fea | ||
|
|
f19a7e1080 | ||
|
|
5e0ae07978 | ||
|
|
116a05ce4b | ||
|
|
d6fdee5905 |
38
README.md
38
README.md
@@ -48,7 +48,10 @@ docker run -e MI_USER='小米账号' \
|
||||
|
||||
启动成功后,在 web 页面可以配置 MI_DID, MI_HARDWARE, XIAOMUSIC_SEARCH, XIAOMUSIC_PROXY 参数。
|
||||
|
||||
如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678
|
||||
### ✨ 修改8090端口
|
||||
|
||||
如果需要修改 8090 端口为其他端口,比如 5678,需要这样配,3个数字都需要是 5678 。见 <https://github.com/hanxi/xiaomusic/issues/19>
|
||||
|
||||
```yaml
|
||||
services:
|
||||
xiaomusic:
|
||||
@@ -69,6 +72,37 @@ services:
|
||||
|
||||
其中 XIAOMUSIC_VERBOSE 设置为 'true' 时表示开启 debug 日志,遇到问题可以去 web 设置页面底部【下载日志文件】按钮,然后搜索一下日志文件内容确保里面没有账号密码信息后(有就删除这些敏感信息),然后在提 issues 反馈问题时把下载的日志文件带上。
|
||||
|
||||
## pip 方式安装运行
|
||||
|
||||
```shell
|
||||
> pip install xiaomusic
|
||||
> xiaomusic --help
|
||||
__ __ _ __ __ _
|
||||
\ \/ / (_) __ _ ___ | \/ | _ _ ___ (_) ___
|
||||
\ / | | / _` | / _ \ | |\/| | | | | | / __| | | / __|
|
||||
/ \ | | | (_| | | (_) | | | | | | |_| | \__ \ | | | (__
|
||||
/_/\_\ |_| \__,_| \___/ |_| |_| \__,_| |___/ |_| \___|
|
||||
XiaoMusic v0.1.81 by: github.com/hanxi
|
||||
|
||||
usage: xiaomusic.py [-h] [--hardware HARDWARE] [--account ACCOUNT]
|
||||
[--password PASSWORD] [--cookie COOKIE] [--verbose]
|
||||
[--config CONFIG] [--ffmpeg_location FFMPEG_LOCATION]
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--hardware HARDWARE 小爱 hardware
|
||||
--account ACCOUNT xiaomi account
|
||||
--password PASSWORD xiaomi password
|
||||
--cookie COOKIE xiaomi cookie
|
||||
--verbose show info
|
||||
--config CONFIG config file path
|
||||
--ffmpeg_location FFMPEG_LOCATION
|
||||
ffmpeg bin path
|
||||
> xiaomusic --config config.json
|
||||
```
|
||||
|
||||
其中 `config.json` 文件可以参考 `config-example.json` 文件配置。见 <https://github.com/hanxi/xiaomusic/issues/94>
|
||||
|
||||
## 开发环境运行
|
||||
|
||||
- 使用 install_dependencies.sh 下载依赖
|
||||
@@ -291,6 +325,8 @@ services:
|
||||
- XIAOMUSIC_KEYWORDS_PLAY 用来播放歌曲的口令前缀,默认是 "播放歌曲,放歌曲" ,可以用英文逗号分割配置多个
|
||||
- XIAOMUSIC_KEYWORDS_STOP 用来关机的口令,默认是 "关机,暂停,停止" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_KEYWORDS_PLAYLOCAL 用来播放本地歌曲的口令前缀,本地找不到时不会下载歌曲,默认是 "播放本地歌曲,本地播放歌曲" ,可以用英文逗号分割配置多个。
|
||||
- XIAOMUSIC_ENABLE_FUZZY_MATCH 设为 true 时开启模糊匹配(默认),设为 false 时关闭模糊匹配,支持模糊匹配歌名和歌单名。 具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||
- XIAOMUSIC_FUZZY_MATCH_CUTOFF 设置模糊搜索匹配的最低相似度阈值(默认0.6,可以配0到1直接的小数),越小越模糊,越大越精准。具体见 <https://github.com/hanxi/xiaomusic/issues/52>
|
||||
|
||||
## 讨论区
|
||||
|
||||
|
||||
32
config-example.json
Normal file
32
config-example.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"hardware": "L07A",
|
||||
"account": "",
|
||||
"password": "",
|
||||
"mi_did": "",
|
||||
"cookie": "",
|
||||
"verbose": false,
|
||||
"music_path": "music",
|
||||
"conf_path": null,
|
||||
"hostname": "192.168.2.5",
|
||||
"port": 8090,
|
||||
"proxy": null,
|
||||
"search_prefix": "ytsearch:",
|
||||
"ffmpeg_location": "./ffmpeg/bin",
|
||||
"active_cmd": "play,random_play,playlocal,play_music_list,stop",
|
||||
"exclude_dirs": "@eaDir",
|
||||
"music_path_depth": 10,
|
||||
"disable_httpauth": true,
|
||||
"httpauth_username": "admin",
|
||||
"httpauth_password": "admin",
|
||||
"music_list_url": "",
|
||||
"music_list_json": "",
|
||||
"disable_download": false,
|
||||
"use_music_api": false,
|
||||
"log_file": "/tmp/xiaomusic.txt",
|
||||
"fuzzy_match_cutoff": 0.6,
|
||||
"enable_fuzzy_match": true,
|
||||
"stop_tts_msg": "收到,再见",
|
||||
"keywords_playlocal": "播放本地歌曲,本地播放歌曲",
|
||||
"keywords_play": "播放歌曲,放歌曲",
|
||||
"keywords_stop": "关机,暂停,停止,停止播放"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "xiaomusic"
|
||||
version = "0.1.77"
|
||||
version = "0.1.84"
|
||||
description = "Play Music with xiaomi AI speaker"
|
||||
authors = [
|
||||
{name = "涵曦", email = "im.hanxi@gmail.com"},
|
||||
@@ -19,6 +19,12 @@ requires-python = ">=3.10"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/hanxi/xiaomusic"
|
||||
|
||||
[project.scripts]
|
||||
xiaomusic = "xiaomusic.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["pdm-backend"]
|
||||
build-backend = "pdm.backend"
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.1.77"
|
||||
__version__ = "0.1.84"
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
|
||||
from xiaomusic import (
|
||||
__version__,
|
||||
)
|
||||
from xiaomusic.config import Config
|
||||
from xiaomusic.xiaomusic import XiaoMusic
|
||||
|
||||
LOGO = r"""
|
||||
__ __ _ __ __ _
|
||||
\ \/ / (_) __ _ ___ | \/ | _ _ ___ (_) ___
|
||||
\ / | | / _` | / _ \ | |\/| | | | | | / __| | | / __|
|
||||
/ \ | | | (_| | | (_) | | | | | | |_| | \__ \ | | | (__
|
||||
/_/\_\ |_| \__,_| \___/ |_| |_| \__,_| |___/ |_| \___|
|
||||
{}
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
@@ -45,6 +57,8 @@ def main():
|
||||
help="ffmpeg bin path",
|
||||
)
|
||||
|
||||
print(LOGO.format(f"XiaoMusic v{__version__} by: github.com/hanxi"))
|
||||
|
||||
options = parser.parse_args()
|
||||
config = Config.from_options(options)
|
||||
|
||||
|
||||
@@ -88,6 +88,13 @@ class Config:
|
||||
enable_fuzzy_match: bool = (
|
||||
os.getenv("XIAOMUSIC_ENABLE_FUZZY_MATCH", "true").lower() == "true"
|
||||
)
|
||||
stop_tts_msg: str = os.getenv("XIAOMUSIC_STOP_TTS_MSG", "收到,再见")
|
||||
|
||||
keywords_playlocal: str = os.getenv(
|
||||
"XIAOMUSIC_KEYWORDS_PLAYLOCAL", "播放本地歌曲,本地播放歌曲"
|
||||
)
|
||||
keywords_play: str = os.getenv("XIAOMUSIC_KEYWORDS_PLAY", "播放歌曲,放歌曲")
|
||||
keywords_stop: str = os.getenv("XIAOMUSIC_KEYWORDS_STOP", "关机,暂停,停止,停止播放")
|
||||
|
||||
def append_keyword(self, keys, action):
|
||||
for key in keys.split(","):
|
||||
@@ -98,14 +105,14 @@ class Config:
|
||||
def __post_init__(self) -> None:
|
||||
if self.proxy:
|
||||
validate_proxy(self.proxy)
|
||||
keywords_playlocal = os.getenv(
|
||||
"XIAOMUSIC_KEYWORDS_PLAYLOCAL", "播放本地歌曲,本地播放歌曲"
|
||||
)
|
||||
self.append_keyword(keywords_playlocal, "playlocal")
|
||||
keywords_play = os.getenv("XIAOMUSIC_KEYWORDS_PLAY", "播放歌曲,放歌曲")
|
||||
self.append_keyword(keywords_play, "play")
|
||||
keywords_stop = os.getenv("XIAOMUSIC_KEYWORDS_STOP", "关机,暂停,停止")
|
||||
self.append_keyword(keywords_stop, "stop")
|
||||
self.append_keyword(self.keywords_playlocal, "playlocal")
|
||||
self.append_keyword(self.keywords_play, "play")
|
||||
self.append_keyword(self.keywords_stop, "stop")
|
||||
|
||||
# 保存配置到 config-example.json 文件
|
||||
# with open("config-example.json", "w") as f:
|
||||
# data = asdict(self)
|
||||
# json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
@classmethod
|
||||
def from_options(cls, options: argparse.Namespace) -> Config:
|
||||
|
||||
@@ -291,7 +291,7 @@ class XiaoMusic:
|
||||
except Exception as e:
|
||||
self.log.error(f"Execption {e}")
|
||||
# 最大等8秒
|
||||
sec = min(8, int(len(value) / 3.3))
|
||||
sec = min(8, int(len(value) / 3))
|
||||
await asyncio.sleep(sec)
|
||||
self.log.debug(f"do_tts. cur_music:{self.cur_music}")
|
||||
if self._playing and not self.is_downloading():
|
||||
@@ -311,8 +311,8 @@ class XiaoMusic:
|
||||
async def force_stop_xiaoai(self):
|
||||
ret = await self.mina_service.player_pause(self.device_id)
|
||||
self.log.debug(f"force_stop_xiaoai player_pause ret:{ret}")
|
||||
ret = await self.mina_service.player_stop(self.device_id)
|
||||
self.log.debug(f"force_stop_xiaoai player_stop ret:{ret}")
|
||||
# ret = await self.mina_service.player_stop(self.device_id)
|
||||
# self.log.debug(f"force_stop_xiaoai player_stop ret:{ret}")
|
||||
|
||||
# 是否在下载中
|
||||
def is_downloading(self):
|
||||
@@ -610,8 +610,26 @@ class XiaoMusic:
|
||||
except Exception as e:
|
||||
self.log.warning(f"执行出错 {str(e)}\n{traceback.format_exc()}")
|
||||
|
||||
# 检查是否匹配到完全一样的指令
|
||||
def check_full_match_cmd(self, query, ctrl_panel):
|
||||
if query in self.config.key_match_order:
|
||||
opkey = query
|
||||
opvalue = self.config.key_word_dict.get(opkey)
|
||||
if ctrl_panel or self._playing:
|
||||
return opvalue
|
||||
else:
|
||||
if not self.active_cmd or opvalue in self.active_cmd:
|
||||
return opvalue
|
||||
return None
|
||||
|
||||
# 匹配命令
|
||||
def match_cmd(self, query, ctrl_panel):
|
||||
# 优先处理完全匹配
|
||||
opvalue = self.check_full_match_cmd(query, ctrl_panel)
|
||||
if opvalue:
|
||||
self.log.info(f"完全匹配指令. query:{query} opvalue:{opvalue}")
|
||||
return (opvalue, None)
|
||||
|
||||
for opkey in self.config.key_match_order:
|
||||
patternarg = rf"(.*){opkey}(.*)"
|
||||
# 匹配参数
|
||||
@@ -629,16 +647,14 @@ class XiaoMusic:
|
||||
argafter,
|
||||
)
|
||||
oparg = argafter
|
||||
if opkey in KEY_WORD_ARG_BEFORE_DICT:
|
||||
oparg = argpre
|
||||
opvalue = self.config.key_word_dict.get(opkey)
|
||||
if not ctrl_panel and not self._playing:
|
||||
if self.active_cmd and opvalue not in self.active_cmd:
|
||||
self.log.debug(f"不在激活命令中 {opvalue}")
|
||||
self.log.ifno(f"不在激活命令中 {opvalue}")
|
||||
continue
|
||||
if opkey in KEY_WORD_ARG_BEFORE_DICT:
|
||||
oparg = argpre
|
||||
self.log.info(
|
||||
"匹配到指令. opkey:%s opvalue:%s oparg:%s", opkey, opvalue, oparg
|
||||
)
|
||||
self.log.info(f"匹配到指令. opkey:{opkey} opvalue:{opvalue} oparg:{oparg}")
|
||||
return (opvalue, oparg)
|
||||
if self._playing:
|
||||
self.log.info("未匹配到指令,自动停止")
|
||||
@@ -703,6 +719,7 @@ class XiaoMusic:
|
||||
self.log.info(f"根据【{name}】找到歌曲【{real_name}】")
|
||||
return real_name
|
||||
self.log.info(f"没找到歌曲【{name}】")
|
||||
return name
|
||||
|
||||
# 播放本地歌曲
|
||||
async def playlocal(self, **kwargs):
|
||||
@@ -759,7 +776,7 @@ class XiaoMusic:
|
||||
await self.do_tts(f"本地不存在歌曲{name}")
|
||||
return
|
||||
await self.download(search_key, name)
|
||||
self.log.info("正在下载中 %s", search_key + ":" + name)
|
||||
self.log.info(f"正在下载中 {search_key} {name}")
|
||||
await self.download_proc.wait()
|
||||
# 把文件插入到播放列表里
|
||||
self.add_download_music(name)
|
||||
@@ -828,6 +845,7 @@ class XiaoMusic:
|
||||
self.log.info(f"根据【{list_name}】找到播放列表【{real_name}】")
|
||||
list_name = real_name
|
||||
self.log.info(f"没找到播放列表【{list_name}】")
|
||||
return list_name
|
||||
|
||||
# 播放一个播放列表
|
||||
async def play_music_list(self, **kwargs):
|
||||
@@ -853,7 +871,8 @@ class XiaoMusic:
|
||||
async def stop(self, **kwargs):
|
||||
self._playing = False
|
||||
if kwargs.get("arg1", "") != "notts":
|
||||
await self.do_tts("收到关机口令,再见")
|
||||
if self.config.stop_tts_msg:
|
||||
await self.do_tts(self.config.stop_tts_msg)
|
||||
if self._next_timer:
|
||||
self._next_timer.cancel()
|
||||
self.log.info("定时器已取消")
|
||||
|
||||
Reference in New Issue
Block a user