1
0
mirror of https://github.com/hanxi/xiaomusic.git synced 2025-12-07 15:02:55 +08:00

Compare commits

...

18 Commits

Author SHA1 Message Date
涵曦
512efb595a new version v0.1.40 2024-06-12 17:21:13 +00:00
涵曦
e5dea8e693 新增刷新列表指令 2024-06-12 17:21:09 +00:00
涵曦
a704f8003c new version v0.1.39 2024-06-12 17:13:00 +00:00
涵曦
349a25ad58 新增播放列表功能 #51 2024-06-12 17:12:07 +00:00
涵曦
746f46edb3 new version v0.1.38 2024-06-12 15:39:23 +00:00
涵曦
4a29c7a124 fix: #70 下一首歌曲不存在时从播放列表中删除并继续找下一首 2024-06-12 01:18:08 +00:00
涵曦
0e1e412ee9 new version v0.1.37 2024-06-04 12:17:50 +00:00
涵曦
2e84f7c830 Update ci.yml 2024-06-04 18:46:17 +08:00
涵曦
61a0d68b6a Update release.yml 2024-06-04 18:45:53 +08:00
涵曦
ae90029d8e Update README.md 2024-06-04 15:14:14 +08:00
涵曦
ccc83a518c new version v0.1.36 2024-05-30 14:42:32 +00:00
涵曦
7884a5769f 继续修复启动失败问题 2024-05-30 14:42:12 +00:00
涵曦
a663bb330e new version v0.1.35 2024-05-30 13:49:52 +00:00
涵曦
346f0af543 fix: #67 没配置did时也允许启动 http 服务 2024-05-27 14:47:40 +00:00
涵曦
49ec1bb7c0 Update README.md 2024-05-20 00:03:27 +08:00
涵曦
fc0cc75dea new version v0.1.34 2024-05-19 15:53:50 +00:00
涵曦
6fc2be5d31 消除flask启动告警 2024-05-19 15:53:31 +00:00
涵曦
db680bf1ba update readme 2024-05-19 15:22:52 +00:00
13 changed files with 194 additions and 30 deletions

View File

@@ -25,6 +25,6 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}

View File

@@ -61,6 +61,6 @@ jobs:
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:${{ github.ref_name }}, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:latest, ${{ secrets.DOCKERHUB_USERNAME }}/xiaomusic:stable

View File

@@ -2,7 +2,28 @@
使用小爱/红米音箱播放音乐,音乐使用 yt-dlp 下载。
## 运行
## 最简配置运行
已经支持在 web 页面配置其他参数docker compose 配置如下:
```yaml
services:
xiaomusic:
image: hanxi/xiaomusic
container_name: xiaomusic
restart: unless-stopped
ports:
- 8090:8090
volumes:
- ./music:/app/music
environment:
MI_USER: '小米账号'
MI_PASS: '小米密码'
XIAOMUSIC_HOSTNAME: 'docker 主机 ip'
```
启动成功后,在 web 页面可以配置 MI_DID, MI_HARDWARE, XIAOMUSIC_SEARCH, XIAOMUSIC_PROXY 参数。
## 开发环境运行
- 使用 install_dependencies.sh 下载依赖
- 使用 pdm 安装环境
@@ -50,8 +71,17 @@ pdm run xiaomusic.py
## 在 Docker 里使用
```shell
docker run -e MI_USER=<your-xiaomi-account> -e MI_PASS=<your-xiaomi-password> -e MI_DID=<your-xiaomi-speaker-mid> -e MI_HARDWARE='L07A' -e XIAOMUSIC_PROXY=<proxy-for-yt-dlp> -e XIAOMUSIC_HOSTNAME=192.168.2.5 -e XIAOMUSIC_SEARCH='bilisearch:' -p 8090:8090 -v ./music:/app/music hanxi/xiaomusic
docker run -e MI_USER=<your-xiaomi-account> \
-e MI_PASS='your-xiaomi-password' \
-e MI_DID='your-xiaomi-speaker-mid' \
-e MI_HARDWARE='L07A' \
-e XIAOMUSIC_PROXY='proxy-for-yt-dlp' \
-e XIAOMUSIC_HOSTNAME=192.168.2.5 \
-e XIAOMUSIC_SEARCH='bilisearch:' \
-p 8090:8090 \
-v ./music:/app/music hanxi/xiaomusic
```
- XIAOMUSIC_SEARCH 可以配置为 'bilisearch:' 表示歌曲从哔哩哔哩下载;
- 配置为 'ytsearch:' 表示歌曲从 youtube 下载。
- XIAOMUSIC_PROXY 用于配置代理,默认为空;
@@ -137,6 +167,19 @@ services:
- 新功能
- 显示正在播放的歌曲
- 模糊搜索本地歌曲
- 设置页面
采用新的设置页面之后,必须在启动前配置的环境变量只剩下:
- MI_USER
- MI_PASS
- XIAOMUSIC_HOSTNAME
其他的这些可以在网页里配置:
- MI_DID
- MI_HARDWARE
- XIAOMUSIC_SEARCH
- XIAOMUSIC_PROXY
## 讨论区

12
pdm.lock generated
View File

@@ -5,7 +5,7 @@
groups = ["default"]
strategy = ["cross_platform"]
lock_version = "4.4.1"
content_hash = "sha256:3137b159cf24210912a6a3d16e6ae42511177b211996625fe6bdabebdaec230e"
content_hash = "sha256:d771311a452ca58665efe3b74af341cb202d75d83a250896c293ea9c696e5696"
[[package]]
name = "aiohttp"
@@ -668,6 +668,16 @@ files = [
{file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"},
]
[[package]]
name = "waitress"
version = "3.0.0"
requires_python = ">=3.8.0"
summary = "Waitress WSGI server"
files = [
{file = "waitress-3.0.0-py3-none-any.whl", hash = "sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669"},
{file = "waitress-3.0.0.tar.gz", hash = "sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1"},
]
[[package]]
name = "websockets"
version = "12.0"

View File

@@ -1,6 +1,6 @@
[project]
name = "xiaomusic"
version = "0.1.33"
version = "0.1.40"
description = "Play Music with xiaomi AI speaker"
authors = [
{name = "涵曦", email = "im.hanxi@gmail.com"},
@@ -13,6 +13,7 @@ dependencies = [
"mutagen>=1.47.0",
"yt-dlp>=2024.04.09",
"flask[async]>=3.0.1",
"waitress>=3.0.0",
]
requires-python = ">=3.10"
readme = "README.md"
@@ -21,3 +22,5 @@ license = {text = "MIT"}
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"
[tool.pdm]

View File

@@ -380,6 +380,9 @@ typing-extensions==4.9.0; python_version < "3.11" \
urllib3==2.0.6 \
--hash=sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2 \
--hash=sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564
waitress==3.0.0 \
--hash=sha256:005da479b04134cdd9dd602d1ee7c49d79de0537610d653674cc6cbde222b8a1 \
--hash=sha256:2a06f242f4ba0cc563444ca3d1998959447477363a2d7e9b8b4d75d35cfd1669
websockets==12.0 \
--hash=sha256:00700340c6c7ab788f176d118775202aadea7602c5cc6be6ae127761c16d6b0b \
--hash=sha256:0bee75f400895aef54157b36ed6d3b308fcab62e5260703add87f44cee9c82a6 \

View File

@@ -1 +1 @@
__version__ = "0.1.33"
__version__ = "0.1.40"

View File

@@ -43,6 +43,8 @@ KEY_WORD_DICT = {
"关机": "stop",
"停止播放": "stop",
"分钟后关机": "stop_after_minute",
"播放列表": "play_music_list",
"刷新列表": "gen_music_list",
"set_volume#": "set_volume",
"get_volume#": "get_volume",
}
@@ -65,6 +67,8 @@ KEY_MATCH_ORDER = [
"随机播放",
"关机",
"停止播放",
"刷新列表",
"播放列表",
]
SUPPORT_MUSIC_TYPE = [

View File

@@ -5,6 +5,7 @@ import traceback
import asyncio
from flask import Flask, request, send_from_directory
from waitress import serve
from threading import Thread
from xiaomusic.config import (
@@ -17,8 +18,8 @@ from xiaomusic import (
# 隐藏 flask 启动告警
# https://gist.github.com/jerblack/735b9953ba1ab6234abb43174210d356
cli = sys.modules['flask.cli']
cli.show_server_banner = lambda *x: None
#from flask import cli
#cli.show_server_banner = lambda *_: None
app = Flask(__name__)
host = "0.0.0.0"
@@ -94,6 +95,14 @@ async def savesetting():
await xiaomusic.saveconfig(data)
return "save success"
@app.route("/musiclist", methods=["GET"])
async def musiclist():
return xiaomusic.get_music_list()
@app.route("/curplaylist", methods=["GET"])
async def curplaylist():
return xiaomusic.get_cur_play_list()
def static_path_handler(filename):
log.debug(filename)
log.debug(static_path)
@@ -101,10 +110,8 @@ def static_path_handler(filename):
log.debug(absolute_path)
return send_from_directory(absolute_path, filename)
def run_app():
app.run(host=host, port=port)
serve(app, host=host, port=port)
def StartHTTPServer(_port, _static_path, _xiaomusic):
global port, static_path, xiaomusic, log

View File

@@ -1,11 +1,11 @@
$(function(){
$container=$("#cmds");
append_op_button_name("下一首");
append_op_button_name("全部循环");
append_op_button_name("关机");
append_op_button_name("单曲循环");
append_op_button_name("播放歌曲");
append_op_button_name("随机播放");
append_op_button_name("刷新列表");
append_op_button_name("下一首");
append_op_button_name("关机");
$container.append($("<hr>"));
@@ -26,6 +26,36 @@ $(function(){
$("#version").text(`(${data.version})`);
});
// 拉取播放列表
$.get("/musiclist", function(data, status) {
console.log(data, status);
$.each(data, function(key, value) {
$('#music_list').append($('<option></option>').val(key).text(key));
});
$('#music_list').change(function() {
const selectedValue = $(this).val();
$('#music_name').empty();
$.each(data[selectedValue], function(index, item) {
$('#music_name').append($('<option></option>').val(item).text(item));
});
});
$('#music_list').trigger('change');
// 获取当前播放列表
$.get("curplaylist", function(data, status) {
$('#music_list').val(data);
$('#music_list').trigger('change');
})
})
$("#play_music_list").on("click", () => {
var music_list = $("#music_list").val();
var music_name = $("#music_name").val();
let cmd = "播放列表" + music_list + "|" + music_name;
sendcmd(cmd);
})
function append_op_button_name(name) {
append_op_button(name, name);
@@ -48,8 +78,8 @@ $(function(){
$("#play").on("click", () => {
var search_key = $("#music-name").val();
var filename=$("#music-filename").val();
let cmd = "播放歌曲"+search_key+"|"+filename;
var filename = $("#music-filename").val();
let cmd = "播放歌曲" + search_key + "|" + filename;
sendcmd(cmd);
});
@@ -65,7 +95,9 @@ $(function(){
contentType: "application/json",
data: JSON.stringify({cmd: cmd}),
success: () => {
// 请求成功时执行的操作
if (cmd == "刷新列表") {
location.reload();
}
},
error: () => {
// 请求失败时执行的操作

View File

@@ -33,6 +33,15 @@
<div id="playering-music" class="text"></div>
</div>
<hr>
<div class="rows">
<label for="music_list">播放列表:</label>
<select id="music_list"></select>
<label for="music_name">歌曲:</label>
<select id="music_name"></select>
</div>
<button id="play_music_list">播放列表歌曲</button>
<footer>
<p>Powered by <a href="https://github.com/hanxi/xiaomusic" target="_blank">xiaomusic</a></p>
</footer>

View File

@@ -62,7 +62,6 @@ input,select {
}
footer {
position: fixed;
bottom: 0;
width: 100%;
text-align: center;

View File

@@ -75,6 +75,8 @@ class XiaoMusic:
self._volume = 0
self._all_music = {}
self._play_list = []
self._cur_play_list = ""
self._music_list = {} # 播放列表 key 为目录名, value 为 play_list
self._playing = False
# 关机定时器
@@ -94,7 +96,7 @@ class XiaoMusic:
self.try_init_setting()
# 启动时重新生成一次播放列表
self.gen_all_music_list()
self._gen_all_music_list()
# 启动时初始化获取声音
self.set_last_record("get_volume#")
@@ -154,7 +156,7 @@ class XiaoMusic:
self.device_id = h.get("deviceID")
break
else:
raise Exception(
self.log.error(
f"we have no hardware: {self.config.hardware} please use `micli mina` to check"
)
@@ -172,7 +174,7 @@ class XiaoMusic:
if d["model"].endswith(self.config.hardware.lower())
)
except StopIteration:
raise Exception(
self.log.error(
f"cannot find did for hardware: {self.config.hardware} "
"please set it via MI_DID env"
)
@@ -340,9 +342,16 @@ class XiaoMusic:
return f"http://{self.hostname}:{self.port}/{encoded_name}"
# 递归获取目录下所有歌曲,生成随机播放列表
def gen_all_music_list(self):
def _gen_all_music_list(self):
self._all_music = {}
all_music_by_dir = {}
for root, dirs, filenames in os.walk(self.music_path):
self.log.debug("root:%s dirs:%s music_path:%s", root, dirs, self.music_path)
dir_name = os.path.basename(root)
if self.music_path == root:
dir_name = "其他"
if dir_name not in all_music_by_dir:
all_music_by_dir[dir_name] = {}
for filename in filenames:
self.log.debug("gen_all_music_list. filename:%s", filename)
# 过滤隐藏文件
@@ -361,10 +370,20 @@ class XiaoMusic:
# 歌曲名字相同会覆盖
self._all_music[name] = os.path.join(root, filename)
all_music_by_dir[dir_name][name] = True
pass
self._play_list = list(self._all_music.keys())
self._cur_play_list = "全部"
random.shuffle(self._play_list)
self.log.debug(self._all_music)
self._music_list = {}
self._music_list["全部"] = self._play_list
for dir_name,musics in all_music_by_dir.items():
self._music_list[dir_name] = list(musics.keys())
self.log.debug("dir_name:%s, list:%s", dir_name, self._music_list[dir_name])
pass
# 把下载的音乐加入播放列表
def add_download_music(self, name):
self._all_music[name] = os.path.join(self.music_path, f"{name}.mp3")
@@ -388,8 +407,12 @@ class XiaoMusic:
next_index = index + 1
if next_index >= play_list_len:
next_index = 0
filename = self._play_list[next_index]
return filename
name = self._play_list[next_index]
filename = self.get_filename(name)
if len(filename) <= 0:
self._play_list.pop(next_index)
return self.get_next_music()
return name
# 获取文件播放时长
def get_file_duration(self, filename):
@@ -554,16 +577,38 @@ class XiaoMusic:
# 随机播放
async def random_play(self, **kwargs):
self.play_type = PLAY_TYPE_ALL
await self.do_tts(f"已经设置为全部循环并随机播放")
# 重新生成随机播放列表
self.gen_all_music_list()
await self.play_next()
random.shuffle(self._play_list)
await self.do_tts(f"已经设置为随机播放")
# 刷新列表
async def gen_music_list(self, **kwargs):
self._gen_all_music_list()
await self.do_tts(f"生成播放列表完毕")
# 播放一个播放列表
async def play_music_list(self, **kwargs):
parts = kwargs["arg1"].split("|")
list_name = parts[0]
if list_name not in self._music_list:
await self.do_tts(f"播放列表{list_name}不存在")
return
self._play_list = self._music_list[list_name]
self._cur_play_list = list_name
self.log.info(f"开始播放列表{list_name}")
music_name = ""
if len(parts) > 1:
music_name = parts[1]
else:
music_name = self.get_next_music()
await self.play(arg1=music_name)
async def stop(self, **kwargs):
self._playing = False
if self._next_timer:
self._next_timer.cancel()
self.log.info(f"定时器已取消")
self.cur_music = ""
await self.force_stop_xiaoai()
async def stop_after_minute(self, **kwargs):
@@ -597,10 +642,19 @@ class XiaoMusic:
# 搜索音乐
def searchmusic(self, name):
search_list = fuzzyfinder(name, self._play_list)
all_music_list = list(self._all_music.keys())
search_list = fuzzyfinder(name, all_music_list)
self.log.debug("searchmusic. name:%s search_list:%s", name, search_list)
return search_list
# 获取播放列表
def get_music_list(self):
return self._music_list
# 获取当前的播放列表
def get_cur_play_list(self):
return self._cur_play_list
# 正在播放中的音乐
def playingmusic(self):
self.log.debug("playingmusic. cur_music:%s", self.cur_music)