This commit is contained in:
2026-03-27 07:06:16 +01:00
commit 1541961403
340 changed files with 151916 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
cache/
files/*.log
files/*.json
files/*.jsonl
files/command_history.txt
files/watch_history.jsonl
files/historybookmarks/
files/screen/
files/animated/
files/cutfragments/
files/chapters/
files/danmaku-history.json
historybookmarks
*.log
**/.git/
!/.git/
.claude/
.vscode/
*.swp
*.swo
*~
+19
View File
@@ -0,0 +1,19 @@
# 外部依赖
## 必需
| 依赖 | 用途 | 安装 |
| --------- | --------------------------------------- | --------------------- |
| mpv | 播放器本体(需 gpu-next + vulkan 支持) | `pacman -S mpv` |
| yt-dlp | URL 解析与流媒体播放 | `pacman -S yt-dlp` |
| ffmpeg | 字幕导出、缩略图生成、字幕同步 | `pacman -S ffmpeg` |
| curl | 字幕搜索下载、更新检查 | `pacman -S curl` |
| trash-cli | 安全删除文件(移至回收站) | `pacman -S trash-cli` |
## 可选
| 依赖 | 用途 | 安装 |
| ---------- | ------------------------------------- | ---------------------------------------------- |
| alass | 字幕自动同步autosubsync 脚本) | `paru -S alass` |
| ffsubsync | 字幕自动同步alass 的替代) | `pip install ffsubsync` |
| TorrServer | 种子流媒体播放mpv-torrserver 脚本) | [GitHub](https://github.com/YouROK/TorrServer) |
+54
View File
@@ -0,0 +1,54 @@
# .config/mpv
一套 mpv 播放器配置,集成 uosc 界面、多种着色器方案和自动更新机制。
## Credits
原始仓库为 [emoeem/mpv](https://github.com/emoeem/mpv)。根据我的需要精简了一些功能,同时做了一些修复。
## 结构
```
mpv.conf # 主配置(全局设置 + Profile 定义)
input.conf # 键位绑定与右键菜单
inputevent_key.conf # inputevent.lua 增强键位(单击/双击/长按)
profiles.conf # 场景预设(游戏/电影/动画/低功耗/直播等)
manager.json # 脚本和着色器的 git 自动更新源
scripts/ # Lua 脚本
script-opts/ # 脚本配置
shaders/ # GPU 着色器NNEDI3/FSRCNNX/Ani4K/Anime4K/ravu/igv 等)
fonts/ # 字体
icc/ # ICC 色彩配置文件
```
## 主要功能
- **界面**uosc 界面 + 右键菜单 + 缩略图预览 + 命令面板
- **色彩管理**ICC / Target 双模式HDR 直通与色调映射,动态峰值检测
- **着色器**NNEDI3、FSRCNNX、Ani4K、AniSD、Anime4K、ravu-zoom、SSIM
- **条件 Profile**:根据分辨率 / HDR / 帧率 / 刷新率 / 网络等自动调整参数
- **字幕**自动加载、编码检测GB18030、在线搜索assrt、自动同步autosubsync
- **音频**96kHz 重采样、独占模式、多声道自动下混调节、ReplayGain
- **播放管理**历史记录simplehistory、进度保存、文件浏览器、播放列表管理
- **网络**yt-dlp 集成、浏览器 Cookie、自动缓存优化、TorrServer 支持
- **更新**:按 M 键通过 manager.lua 从 git 自动更新所有脚本和着色器
## 着色器方案
`mpv.conf` 的 Profile 激活区切换(取消注释对应行):
| Profile | 适用场景 | GPU 开销 |
| --------- | --------------- | -------- |
| NNEDI3 | 通用 | 中 |
| NNEDI3+ | 通用64 变体) | 高 |
| ravu-zoom | 通用 | 中 |
| FSRCNNX | HD 内容 | 中 |
| FSRCNNX+ | SD 内容去伪影 | 中 |
| Ani4K | 动画 | 极高 |
| AniSD | SD 动画 | 极高 |
| Anime4K | 动画 | 低 |
| SSIM | 低性能需求 | 低 |
## 外部依赖
见 [DEPENDENCIES.md](DEPENDENCIES.md)。
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+323
View File
@@ -0,0 +1,323 @@
# 此文件定义 mpv 的快捷键绑定和 uosc 右键菜单项目
##⇘⇘uosc 一级菜单:打开
o script-message-to uosc open-file #menu: 打开 > 打开内置浏览器
TAB script-message-to file_browser browse-files;script-message-to file_browser dynamic/reload;show-text '' #menu: 打开 > 打开 OSD 浏览器
# script-message-to uosc playlist #menu: 打开 > 播放菜单
# script-message-to uosc chapters #menu: 打开 > 章节菜单
# script-message-to uosc editions #menu: 打开 > 版本菜单
# script-message-to uosc audio #menu: 打开 > 其他音轨
# script-message-to uosc subtitles #menu: 打开 > 其他字幕
# ignore #menu: 打开 > ---
` script-message-to simplehistory open-list;show-text '' #menu: 打开 > 历史 > 打开历史菜单
ALT+l script-message-to simplehistory history-incognito-mode #menu: 打开 > 历史 > 开/关 隐身历史
CTRL+L script-message-to simplehistory history-load-last #menu: 打开 > 历史 > 加载最后播放文件
CTRL+l script-message-to simplehistory history-resume #menu: 打开 > 历史 > 加载最后播放文件及进度
# ignore #menu: 打开 > ---
CTRL+c set clipboard/text ${path};show-text "已复制文件路径" #menu: 打开 > 复制 > 复制文件路径
CTRL+ALT+c set clipboard/text ${filename};show-text "已复制文件名" #menu: 打开 > 复制 > 复制文件名
# ignore #menu: 打开 > ---
# script-message-to webtorrent toggle-info;show-text '' #menu: 打开 > Youtube-dl > 开/关 磁链 OSD
CTRL+F script-message-to quality_menu video_formats_toggle #menu: 打开 > Youtube-dl > 开/关 ytdl 视频选择菜单
ALT+F script-message-to quality_menu audio_formats_toggle #menu: 打开 > Youtube-dl > 开/关 ytdl 音频选择菜单
# script-message-to quality_menu reload #menu: 打开 > Youtube-dl > ytdl 重新加载
##⇘⇘uosc 一级菜单:文件
SHIFT+F11 stop #menu: 文件 > 停止
ALT+t cycle ontop;show-text "置顶:${ontop}" #menu: 文件 > 开/关 置顶状态 #@state=(ontop and 'checked')
ALT+b cycle window-maximized #menu: 文件 > 开/关 最大化 #@state=(window_maximized and 'checked')
f cycle fullscreen #menu: 文件 > 开/关 全屏 #@state=(fullscreen and 'checked')
i script-binding stats/display-stats #menu: 文件 > 临时显示统计信息
I script-binding stats/display-stats-toggle #menu: 文件 > 常驻显示统计信息
l ab-loop #menu: 文件 > 设定/清除 片段循环
L cycle-values loop-file inf no;show-text "循环播放:${loop-file}" #menu: 文件 > 开/关 循环播放 #@state=(loop_file and 'checked')
n script-message playlistmanager shuffle #menu: 文件 > 随机播放
[ no-osd add speed -0.1; script-message-to uosc flash-speed #menu: 文件 > 速度 > 速度 -0.1
] no-osd add speed 0.1; script-message-to uosc flash-speed #menu: 文件 > 速度 > 速度 +0.1
{ no-osd multiply speed 0.5; script-message-to uosc flash-speed #menu: 文件 > 速度 > 半速
} no-osd multiply speed 2.0; script-message-to uosc flash-speed #menu: 文件 > 速度 > 倍速
BS no-osd set speed 1.0; script-message-to uosc flash-speed #menu: 文件 > 速度 > 重置速度
ALT+o script-message-to uosc show-in-directory #menu: 文件 > 定位当前文件
DEL script-message-to delete_current_file delete-file 1 "请按 1 确认删除" #menu: 文件 > 删除当前文件
##⇘⇘uosc 一级菜单:导航
# show-text ${track-list} 5000 #menu: 导航 > 打开 OSD 轨道信息
O no-osd cycle-values osd-level 3 1 #menu: 导航 > 开/关 显示 OSD 时间轴
F4 script-binding select/menu;show-text '' #menu: 导航 > OSD 交互菜单 > 综合菜单
F5 script-message-to playlistmanager showplaylist;show-text '' #menu: 导航 > OSD 交互菜单 > 播放列表
F6 script-binding select/select-audio-device;show-text '' #menu: 导航 > OSD 交互菜单 > 音频设备列表
# script-binding select/select-edition;show-text '' #menu: 导航 > OSD 交互菜单 > 版本列表
F7 script-binding select/select-chapter;show-text '' #menu: 导航 > OSD 交互菜单 > 章节列表
F8 script-binding select/select-track;show-text '' #menu: 导航 > OSD 交互菜单 > 轨道列表
F9 script-binding select/select-vid;show-text '' #menu: 导航 > OSD 交互菜单 > 视频轨列表
F10 script-binding select/select-aid;show-text '' #menu: 导航 > OSD 交互菜单 > 音频轨列表
F11 script-binding select/select-sid;show-text '' #menu: 导航 > OSD 交互菜单 > 字幕轨列表
ALT+c script-message-to chapter_make_read create_chapter #menu: 导航 > 章节制作 > 标记章节时间
ALT+e script-message-to chapter_make_read edit_chapter #menu: 导航 > 章节制作 > 编辑章节标题
ALT+r script-message-to chapter_make_read remove_chapter #menu: 导航 > 章节制作 > 删除当前章节
ALT+w script-message-to chapter_make_read write_chapter chp #menu: 导航 > 章节制作 > 创建 chp 章节文件
ALT+g script-message-to chapter_make_read write_chapter ogm #menu: 导航 > 章节制作 > 创建 ogm 章节文件
< playlist-prev;show-text "播放列表:${playlist-pos-1}/${playlist-count}" #menu: 导航 > 上个文件
> playlist-next;show-text "播放列表:${playlist-pos-1}/${playlist-count}" #menu: 导航 > 下个文件
PGDWN add chapter -1 #menu: 导航 > 上一章节
PGUP add chapter 1 #menu: 导航 > 下一章节
F3 script-message-to chapterskip skip-to-silence;show-text "跳到下一个静音位置" #menu: 导航 > 跳到下一个静音位置
ALT+q script-message-to chapterskip chapter-skip #menu: 导航 > 切换 章节跳过模式
ALT+n script-message-to chapterskip toggle-markskip #menu: 导航 > 标记片头片尾
, frame-back-step;show-text "当前帧:${estimated-frame-number}" #menu: 导航 > 前进后退 > 上一帧
. frame-step ;show-text "当前帧:${estimated-frame-number}" #menu: 导航 > 前进后退 > 下一帧
RIGHT seek 5 #menu: 导航 > 前进后退 > 前进 5 秒
LEFT seek -5 #menu: 导航 > 前进后退 > 后退 5 秒
UP no-osd add volume 5; script-message-to uosc flash-volume #menu: 音频 > 音量 > 音量 +5
DOWN no-osd add volume -5; script-message-to uosc flash-volume #menu: 音频 > 音量 > 音量 -5
SHIFT+RIGHT seek 1 exact #menu: 导航 > 前进后退 > 精准前进 1 秒
SHIFT+LEFT seek -1 exact #menu: 导航 > 前进后退 > 精准后退 1 秒
SHIFT+UP seek 80 exact #menu: 导航 > 前进后退 > 精准前进 80 秒
SHIFT+DOWN seek -80 exact #menu: 导航 > 前进后退 > 精准后退 80 秒
CTRL+z script-message-to undoredo undo #menu: 导航 > 跳转 > 撤消跳转
CTRL+x script-message-to undoredo redo #menu: 导航 > 跳转 > 重做跳转
CTRL+ALT+z script-message-to undoredo undoLoop #menu: 导航 > 跳转 > 循环跳转
##⇘⇘uosc 一级菜单:画面
# set video-aspect-override "-1";show-text "宽高比:${video-aspect-override}" #menu: 画面 > 切换 宽高比 > 默认值
# set video-aspect-override "16:9";show-text "宽高比:${video-aspect-override}" #menu: 画面 > 切换 宽高比 > 16:9
# set video-aspect-override "4:3";show-text "宽高比:${video-aspect-override}" #menu: 画面 > 切换 宽高比 > 4:3
# set video-aspect-override "2.35:1";show-text "宽高比:${video-aspect-override}" #menu: 画面 > 切换 宽高比 > 2.35:1
A cycle-values video-aspect-override 16:9 4:3 2.35:1 -1;show-text "宽高比:${video-aspect-override}" #menu: 画面 > 切换 宽高比 > 循环切换
CTRL+LEFT cycle-values video-rotate 0 270 180 90;show-text "视频旋转:${video-rotate}" #menu: 画面 > 左旋转
CTRL+RIGHT cycle-values video-rotate 0 90 180 270;show-text "视频旋转:${video-rotate}" #menu: 画面 > 右旋转
CTRL+- add window-scale -0.1;show-text "窗口缩小:${window-scale}" #menu: 画面 > 画面缩放 > 缩小窗口
CTRL+= add window-scale 0.1;show-text "窗口放大:${window-scale}" #menu: 画面 > 画面缩放 > 放大窗口
ALT+- add video-zoom -0.1;show-text "画面缩小:${video-zoom}" #menu: 画面 > 画面缩放 > 画面缩小
ALT+= add video-zoom 0.1;show-text "画面放大:${video-zoom}" #menu: 画面 > 画面缩放 > 画面放大
ALT+LEFT add video-pan-x -0.1;show-text "画面左移动:${video-pan-x}" #menu: 画面 > 画面缩放 > 画面左移动
ALT+RIGHT add video-pan-x 0.1;show-text "画面右移动:${video-pan-x}" #menu: 画面 > 画面缩放 > 画面右移动
ALT+UP add video-pan-y -0.1;show-text "画面上移动:${video-pan-y}" #menu: 画面 > 画面缩放 > 画面上移动
ALT+DOWN add video-pan-y 0.1;show-text "画面下移动:${video-pan-y}" #menu: 画面 > 画面缩放 > 画面下移动
ALT+p cycle-values panscan 0.0 1.0;show-text "视频画面缩放:${panscan}" #menu: 画面 > 开/关 裁切填充 #@state=(panscan and 'checked')
# ignore #menu: 画面 > ---
ALT+BS set video-zoom 0;set panscan 0;set video-rotate 0;set video-pan-x 0;set video-pan-y 0;set video-aspect-override -1;show-text "重置画面操作" #menu: 画面 > 重置以上画面操作
# ignore #menu: 画面 > ---
CTRL+I cycle icc-profile-auto ;show-text "ICC 自动校色:${icc-profile-auto}" #menu: 画面 > 开/关 自动 ICC 校色 #@state=(icc_profile_auto and 'checked')
# cycle sigmoid-upscaling;show-text "非线性色彩转换:${sigmoid-upscaling}" #menu: 画面 > 开/关 非线性色彩转换 #@state=(sigmoid_upscaling and 'checked')
1 add contrast -1;show-text "对比度:${contrast}" #menu: 画面 > 调色 > 对比度 -1
2 add contrast 1;show-text "对比度:${contrast}" #menu: 画面 > 调色 > 对比度 +1
3 add brightness -1;show-text "明度:${brightness}" #menu: 画面 > 调色 > 明度 -1
4 add brightness 1;show-text "明度:${brightness}" #menu: 画面 > 调色 > 明度 +1
5 add gamma -1;show-text "伽马:${gamma}" #menu: 画面 > 调色 > 伽马 -1
6 add gamma 1;show-text "伽马:${gamma}" #menu: 画面 > 调色 > 伽马 +1
7 add saturation -1;show-text "饱和度:${saturation}" #menu: 画面 > 调色 > 饱和度 -1
8 add saturation 1;show-text "饱和度:${saturation}" #menu: 画面 > 调色 > 饱和度 +1
- add hue -1;show-text "色相:${hue}" #menu: 画面 > 调色 > 色相 -1
= add hue 1;show-text "色相:${hue}" #menu: 画面 > 调色 > 色相 +1
CTRL+BS set contrast 0;set brightness 0;set gamma 0;set saturation 0;set hue 0;show-text "重置调色" #menu: 画面 > 调色 > 重置
D cycle deband;show-text "去色带:${deband}" #menu: 画面 > 去色带 > deband 开关 #@state=(deband and 'checked')
ALT+z add deband-iterations +1;show-text "增加去色带强度:${deband-iterations}" #menu: 画面 > 去色带 > deband 强度 +1
ALT+x add deband-iterations -1;show-text "降低去色带强度:${deband-iterations}" #menu: 画面 > 去色带 > deband 强度 -1
h cycle-values tone-mapping auto spline bt.2390 hable bt.2446a st2094-40 st2094-10;show-text "HDR 映射曲线:${tone-mapping}" #menu: 画面 > HDR 相关 > 切换 HDR 映射曲线
ALT+h cycle-values hdr-compute-peak yes no;show-text "HDR 动态映射:${hdr-compute-peak}" #menu: 画面 > HDR 相关 > 切换 HDR 动态映射 #@state=(hdr_compute_peak and 'checked')
CTRL+h cycle target-colorspace-hint;show-text "HDR 直通模式:${target-colorspace-hint}" #menu: 画面 > HDR 相关 > 切换 HDR 直通模式 #@state=(target_colorspace_hint and 'checked')
CTRL+t cycle-values target-trc auto pq gamma2.2;show-text "显示器传输特性:${target-trc}" #menu: 画面 > HDR 相关 > 切换 显示器传输特性
CTRL+T cycle-values target-peak 100 203;show-text "映射目标峰值:${target-peak}" #menu: 画面 > HDR 相关 > 切换 映射目标峰值
CTRL+g cycle gamut-mapping-mode ;show-text "色域映射模式:${gamut-mapping-mode}" #menu: 画面 > HDR 相关 > 切换 色域映射模式
# cycle tone-mapping-visualize;show-text "色调映射可视化模式:${tone-mapping-visualize}" #menu: 画面 > HDR 相关 > 切换 色调映射可视化模式
# ignore #menu: ---
##⇘⇘menu 一级菜单:轨道
# ignore #menu: 菜单 > 轨道 #@tracks
# ignore #menu: 菜单 > 次字幕 #@tracks/sub-secondary
# ignore #menu: 菜单 > 章节列表 #@chapters
# ignore #menu: 菜单 > 版本列表 #@editions
# ignore #menu: ---
##⇘⇘uosc 一级菜单:视频
# cycle video #menu: 视频 > 切换 视频轨
# set hwdec "no" #menu: 视频 > 切换 解码方式 > 软解
# set hwdec "auto-safe" #menu: 视频 > 切换 解码方式 > 自动选择硬解加速模式
# set hwdec "auto-copy-safe" #menu: 视频 > 切换 解码方式 > 自动选择 copy 硬解模式
# set hwdec "nvdec" #menu: 视频 > 切换 解码方式 > nvdec 硬解
# set hwdec "nvdec-copy" #menu: 视频 > 切换 解码方式 > nvdec-copy 硬解
# set video-sync audio;show-text "帧同步模式:${video-sync}" #menu: 视频 > 切换 帧同步模式 > audio
# set video-sync display-resample;show-text "帧同步模式:${video-sync}" #menu: 视频 > 切换 帧同步模式 > display-resample
# set video-sync display-tempo;show-text "帧同步模式:${video-sync}" #menu: 视频 > 切换 帧同步模式 > display-tempo
# set video-sync display-vdrop;show-text "帧同步模式:${video-sync}" #menu: 视频 > 切换 帧同步模式 > display-vdrop
# set video-sync display-resample-vdrop;show-text "帧同步模式:${video-sync}" #menu: 视频 > 切换 帧同步模式 > display-resample-vdrop
# cycle-values video-sync display-resample display-tempo audio display-vdrop display-resample-vdrop;show-text "帧同步模式:${video-sync}" #menu: 视频 > 切换 帧同步模式 > 循环切换
ALT+i cycle interpolation ;show-text "抖动补偿:${interpolation}" #menu: 视频 > 开/关 抖动补偿 #@state=(interpolation and 'checked')
d cycle deinterlace;show-text "去交错:${deinterlace}" #menu: 视频 > 开/关 反交错 #@state=(deinterlace and 'checked')
# cycle vd-lavc-assume-old-x264;show-text "兼容 x264 旧编码模式:${vd-lavc-assume-old-x264}" #menu: 视频 > 开/关 兼容 x264 旧编码模式 #@state=(vd_lavc_assume_old_x264 and 'checked')
# ignore #menu: 视频 > ---
s screenshot subtitles #menu: 视频 > 截屏 > 同源尺寸 - 有字幕 - 无 OSD-单帧
S screenshot video #menu: 视频 > 截屏 > 同源尺寸 - 无字幕 - 无 OSD-单帧
CTRL+s show-text "截屏" 400;script-message delay-command 0.5 screenshot window #menu: 视频 > 截屏 > 实际尺寸 - 有字幕 - 有 OSD-单帧
ALT+s screenshot subtitles+each-frame #menu: 视频 > 截屏 > 同源尺寸 - 有字幕 - 无 OSD-逐帧
ALT+S screenshot video+each-frame #menu: 视频 > 截屏 > 同源尺寸 - 无字幕 - 无 OSD-逐帧
CTRL+S show-text "逐帧截屏" 400;script-message delay-command 0.5 screenshot window+each-frame #menu: 视频 > 截屏 > 实际尺寸 - 有字幕 - 有 OSD-逐帧
##⇘⇘uosc 一级菜单:音频
# script-message-to uosc audio-device #menu: 音频 > 音频设备列表 #@audio-devices
# ignore #menu: 音频 > ---
y cycle audio;show-text "音轨切换为:${audio}" #menu: 音频 > 切换 音频轨
m cycle mute;show-text "静音:${mute}" #menu: 音频 > 切换 静音 #@state=(mute and 'checked')
# ignore #menu: 音频 > ---
CTRL+, add audio-delay -0.1;show-text "音频延迟:${audio-delay}" #menu: 音频 > 延迟 -0.1
CTRL+. add audio-delay 0.1;show-text "音频预载:${audio-delay}" #menu: 音频 > 延迟 +0.1
; set audio-delay 0 ;show-text "重置音频延迟:${audio-delay}" #menu: 音频 > 延迟 重置
# ignore #menu: 音频 > ---
# cycle audio-normalize-downmix;show-text "音频规格化:${audio-normalize-downmix}" #menu: 音频 > 切换 音频规格化 #@state=(audio_normalize_downmix and 'checked')
CTRL+y cycle audio-exclusive ;show-text "音频独占模式:${audio-exclusive}" #menu: 音频 > 切换 音频独占模式 #@state=(audio_exclusive and 'checked')
CTRL+Y cycle hr-seek-framedrop;show-text "音频同步模式:${hr-seek-framedrop}" #menu: 音频 > 切换 音频同步模式 #@state=(hr_seek_framedrop and 'checked')
# set audio-channels "7.1";show-text "音频通道输出方式:${audio-channels}" #menu: 音频 > 音频通道输出方式 > 7.1 声道输出
# set audio-channels "5.1";show-text "音频通道输出方式:${audio-channels}" #menu: 音频 > 音频通道输出方式 > 5.1 声道输出
# set audio-channels "stereo";show-text "音频通道输出方式:${audio-channels}" #menu: 音频 > 音频通道输出方式 > 双通道输出
# set audio-channels "7.1,5.1,stereo";show-text "音频通道输出方式:${audio-channels}" #menu: 音频 > 音频通道输出方式 > 自动选择以上输出方式
ALT+y cycle-values audio-channels "7.1,5.1,stereo" "7.1" "5.1" "stereo" "auto-safe" "auto";show-text "音频通道输出方式:${audio-channels}" #menu: 音频 > 音频通道输出方式 > 循环切换
F2 cycle-values af "@dynaudnorm:lavfi=[dynaudnorm=f=500:g=31:p=0.5:m=5:r=0.9]" "@loudnorm:lavfi=[loudnorm=I=-16:TP=-1.5:LRA=11]" "" #menu: 音频 > 切换 下混滤镜
ALT+` af clr "" #menu: 音频 > 清空 af 滤镜
##⇘⇘uosc 一级菜单:字幕
j cycle sub;show-text "字幕切换为:${sub}" #menu: 字幕 > 切换 字幕轨
k cycle secondary-sid;show-text "切换次字幕:${secondary-sid}" #menu: 字幕 > 切换 次字幕
v cycle sub-visibility;show-text "字幕可见性:${sub-visibility}" #menu: 字幕 > 切换 字幕可见性 #@state=(sub_visibility and 'checked')
ALT+V cycle secondary-sub-visibility;show-text "次字幕可见性:${secondary-sub-visibility}" #menu: 字幕 > 切换 次字幕可见性 #@state=(secondary_sub_visibility and 'checked')
u cycle sub-ass-override;show-text "字幕渲染样式:${sub-ass-override}" #menu: 字幕 > 切换 渲染样式
F cycle-values sub-font "Noto Sans CJK SC" "Noto Sans CJK KR" "Noto Serif CJK SC" "Noto Serif CJK KR";show-text "字幕字体:${sub-font}" #menu: 字幕 > 切换 默认字体
CTRL+r sub-reload;show-text "重载当前字幕" #menu: 字幕 > 重载当前字幕
# ignore #menu: 字幕 > ---
ALT+R cycle secondary-sub-ass-override;show-text "次字幕样式覆盖:${secondary-sub-ass-override}" #menu: 字幕 > 兼容性 > 切换 次字幕样式覆盖 #@state=(secondary_sub_ass_override and 'checked')
ALT+T cycle blend-subtitles;show-text "字幕混合视频帧:${blend-subtitles}" #menu: 字幕 > 兼容性 > 切换 字幕混合视频帧 #@state=(blend_subtitles and 'checked')
K cycle sub-fix-timing ;show-text "字幕时序修复:${sub-fix-timing}" #menu: 字幕 > 兼容性 > 切换 字幕时序修复 #@state=(sub_fix_timing and 'checked')
J cycle sub-ass-vsfilter-color-compat ;show-text "字幕颜色转换方式:${sub-ass-vsfilter-color-compat}" #menu: 字幕 > 兼容性 > 切换 字幕颜色转换方式
V cycle sub-ass-use-video-data ;show-text "使用视频信息:${sub-ass-use-video-data}" #menu: 字幕 > 兼容性 > 切换 使用视频信息
ALT+B cycle sub-vsfilter-bidi-compat ;show-text "bidi 双向检测兼容性:${sub-vsfilter-bidi-compat}" #menu: 字幕 > 兼容性 > 切换 bidi 双向检测兼容性 #@state=(sub_vsfilter_bidi_compat and 'checked')
ALT+X cycle-values sub-ass-style-overrides "ScaledBorderAndShadow=no" "ScaledBorderAndShadow=yes";show-text "强制替换 ass 样式:${sub-ass-style-overrides}" #menu: 字幕 > 兼容性 > 切换 ass 字幕阴影边框缩放
H cycle sub-ass-force-margins ;show-text "ass 字幕输出黑边:${sub-ass-force-margins}" #menu: 字幕 > 兼容性 > 切换 ass 字幕输出到黑边 #@state=(sub_ass_force_margins and 'checked')
ALT+Z cycle sub-use-margins ;show-text "srt 字幕输出黑边:${sub-use-margins}" #menu: 字幕 > 兼容性 > 切换 srt 字幕输出到黑边 #@state=(sub_use_margins and 'checked')
P cycle stretch-image-subs-to-screen ;show-text "pgs 字幕输出黑边:${stretch-image-subs-to-screen}" #menu: 字幕 > 兼容性 > 切换 pgs 字幕输出到黑边 #@state=(stretch_image_subs_to_screen and 'checked')
p cycle sub-gray;show-text "pgs 字幕灰度转换:${sub-gray}" #menu: 字幕 > 兼容性 > 切换 pgs 字幕灰度转换 #@state=(sub_gray and 'checked')
# ignore #menu: 字幕 > ---
Y script-message sub-select toggle #menu: 字幕 > 切换 字幕选择脚本
CTRL+f script-message-to sub_assrt sub-assrt #menu: 字幕 > 打开 字幕下载菜单
CTRL+m script-message-to autosubsync autosubsync-menu #menu: 字幕 > 打开 字幕同步菜单
CTRL+M script-binding select/select-subtitle-line #menu: 字幕 > 打开 字幕内容菜单
ALT+m script-message-to sub_export export-selected-subtitles #menu: 字幕 > 导出当前内封字幕
# ignore #menu: 字幕 > ---
r add sub-pos -1;show-text "字幕上移:${sub-pos}" #menu: 字幕 > 其他操作 > 字幕上移
t add sub-pos +1;show-text "字幕下移:${sub-pos}" #menu: 字幕 > 其他操作 > 字幕下移
R add secondary-sub-pos -1;show-text "次字幕上移:${secondary-sub-pos}" #menu: 字幕 > 其他操作 > 次字幕上移
T add secondary-sub-pos +1;show-text "次字幕下移:${secondary-sub-pos}" #menu: 字幕 > 其他操作 > 次字幕下移
z add sub-delay -0.1;show-text "字幕延迟:${sub-delay}" #menu: 字幕 > 其他操作 > 字幕延迟 -0.1
x add sub-delay 0.1;show-text "字幕预载:${sub-delay}" #menu: 字幕 > 其他操作 > 字幕延迟 +0.1
Z add secondary-sub-delay -0.1;show-text "次字幕延迟:${secondary-sub-delay}" #menu: 字幕 > 其他操作 > 次字幕延迟 -0.1
X add secondary-sub-delay 0.1;show-text "次字幕预载:${secondary-sub-delay}" #menu: 字幕 > 其他操作 > 次字幕延迟 +0.1
ALT+j add sub-scale -0.1;show-text "字幕缩小:${sub-scale}" #menu: 字幕 > 其他操作 > 字号 -0.1
ALT+k add sub-scale 0.1;show-text "字幕放大:${sub-scale}" #menu: 字幕 > 其他操作 > 字号 +0.1
CTRL+j sub-seek -1 #menu: 字幕 > 其他操作 > 跳转上一条字幕
CTRL+k sub-seek 1 #menu: 字幕 > 其他操作 > 跳转下一条字幕
SHIFT+BS set sub-pos 100;set sub-scale 1.0;set sub-delay 0;show-text "重置字幕状态" #menu: 字幕 > 其他操作 > 恢复初始
##⇘⇘uosc 一级菜单:视频滤镜
CTRL+` vf clr "" #menu: 视频滤镜 > 清空
ALT+v vf toggle deblock=filter=weak:block=4 #menu: 视频滤镜 > 开/关 去色块滤镜
! vf toggle format=colorlevels=limited #menu: 视频滤镜 > 开/关 动态范围限制
@ vf toggle vflip #menu: 视频滤镜 > 开/关 垂直翻转
SHARP vf toggle hflip #menu: 视频滤镜 > 开/关 水平翻转
$ vf toggle rotate=angle=180*PI/180 #menu: 视频滤镜 > 开/关 旋转 180
% vf toggle format:gamma=gamma2.2 #menu: 视频滤镜 > 开/关 伽马修正 2.2
^ vf toggle fps=fps=60/1.001 #menu: 视频滤镜 > 开/关 强制帧数 59.94
* vf toggle pad=aspect=16/9:x=-1:y=-1 #menu: 视频滤镜 > 开/关 填充 16:9 的黑边并居中
& vf toggle colortemperature=temperature=6500 #menu: 视频滤镜 > 开/关 色温修正 6500
##⇘⇘uosc 一级菜单:着色器
CTRL+0 change-list glsl-shaders clr "" #menu: 着色器 > 清空
CTRL+1 change-list glsl-shaders toggle "~~/shaders/igv/KrigBilateral.glsl" #menu: 着色器 > IGV > 开/关 KrigBilateral
CTRL+2 change-list glsl-shaders toggle "~~/shaders/igv/SSimSuperRes.glsl" #menu: 着色器 > IGV > 开/关 SSimSuperRes
CTRL+3 change-list glsl-shaders toggle "~~/shaders/igv/SSimDownscaler.glsl" #menu: 着色器 > IGV > 开/关 SSimDownscaler
CTRL+4 change-list glsl-shaders toggle "~~/shaders/igv/adaptive-sharpen.glsl" #menu: 着色器 > IGV > 开/关 自适应锐化
# change-list glsl-shaders toggle "~~/shaders/igv/adaptive-sharpen_luma.glsl" #menu: 着色器 > IGV > 开/关 自适应锐化-luma
CTRL+5 change-list glsl-shaders toggle "~~/shaders/igv/FSRCNNX_x2_8-0-4-1.glsl" #menu: 着色器 > FSRCNNX > 开/关 FSRCNNX
# change-list glsl-shaders toggle "~~/shaders/igv/FSRCNNX_x2_16-0-4-1.glsl" #menu: 着色器 > FSRCNNX > 开/关 FSRCNNX-16x
CTRL+6 change-list glsl-shaders toggle "~~/shaders/igv/FSRCNNX_x1_16-0-4-1_distort.glsl" #menu: 着色器 > FSRCNNX > 开/关 FSRCNNX-distort
CTRL+7 change-list glsl-shaders toggle "~~/shaders/nnedi3/nnedi3-nns32-win8x4.glsl" #menu: 着色器 > NNEDI3 > 开/关 nns32-4
# change-list glsl-shaders toggle "~~/shaders/nnedi3/nnedi3-nns32-win8x6.glsl" #menu: 着色器 > NNEDI3 > 开/关 nns32-6
# change-list glsl-shaders toggle "~~/shaders/nnedi3/nnedi3-nns64-win8x4.glsl" #menu: 着色器 > NNEDI3 > 开/关 nns64-4
# change-list glsl-shaders toggle "~~/shaders/nnedi3/nnedi3-nns64-win8x6.glsl" #menu: 着色器 > NNEDI3 > 开/关 nns64-6
# change-list glsl-shaders toggle "~~/shaders/nnedi3/nnedi3-nns128-win8x4.glsl" #menu: 着色器 > NNEDI3 > 开/关 nns128-4
# change-list glsl-shaders toggle "~~/shaders/nnedi3/nnedi3-nns128-win8x6.glsl" #menu: 着色器 > NNEDI3 > 开/关 nns128-6
CTRL+8 change-list glsl-shaders toggle "~~/shaders/ravu/ravu-zoom-ar-r3.glsl" #menu: 着色器 > RAVU > 开/关 ravu-zoom-ar
# change-list glsl-shaders toggle "~~/shaders/ravu/ravu-lite-ar-r3.glsl" #menu: 着色器 > RAVU > 开/关 ravu-lite-ar
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Restore/Anime4K_Clamp_Highlights.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 去伪影
CTRL+9 change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Restore/Anime4K_Restore_CNN_Soft_M.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 抗锯齿
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Restore/Anime4K_Restore_CNN_Soft_VL.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 中强度抗锯齿
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Restore/Anime4K_Restore_CNN_Soft_UL.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 高强度抗锯齿
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Experimental-Effects/Anime4K_Darken_HQ.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 深线
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Experimental-Effects/Anime4K_Thin_HQ.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 收线
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Denoise/Anime4K_Denoise_Bilateral_Mode.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 降噪
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Deblur/Anime4K_Deblur_DoG.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 去糊
# change-list glsl-shaders toggle "~~/shaders/Anime4K/glsl/Upscale/Anime4K_Upscale_CNN_x2_S.glsl" #menu: 着色器 > Anime4K > 开/关 Anime4K 放大
# change-list glsl-shaders toggle "~~/shaders/other/PixelClipper.glsl" #menu: 着色器 > Other > 开/关 抗振铃
# script-message cycle-commands "change-list glsl-shaders pre '~~/shaders/other/nlmeans_luma.glsl'" "change-list glsl-shaders remove '~~/shaders/other/nlmeans_luma.glsl'" #menu: 着色器 > Other > 开/关 nlmeans-luma 降噪
##⇘⇘uosc 一级菜单:其它
CTRL+P script-message cycle-commands "apply-profile FSRCNNX;show-text 配置组FSRCNNX" "apply-profile FSRCNNX+;show-text 配置组FSRCNNX+" "apply-profile NNEDI3;show-text 配置组NNEDI3" "apply-profile ravu-zoom;show-text 配置组ravu-zoom" "apply-profile Anime4K;show-text 配置组Anime4K" #menu: 其它 > 常规配置组 > 切换 指定配置组
ALT+1 apply-profile FSRCNNX;show-text "配置组FSRCNNX" #menu: 其它 > 常规配置组 > 切换 FSRCNNX 配置
ALT+2 apply-profile FSRCNNX+;show-text "配置组FSRCNNX+" #menu: 其它 > 常规配置组 > 切换 FSRCNNX+ 配置
ALT+3 apply-profile ravu-zoom;show-text "配置组ravu-zoom" #menu: 其它 > 常规配置组 > 切换 Ravu-zoom 配置
ALT+4 apply-profile Ani4K;show-text "配置组Ani4K" #menu: 其它 > 常规配置组 > 切换 Ani4K 配置
ALT+5 apply-profile AniSD;show-text "配置组AniSD" #menu: 其它 > 常规配置组 > 切换 AniSD 配置
ALT+6 apply-profile Anime4K;show-text "配置组Anime4K" #menu: 其它 > 常规配置组 > 切换 Anime4k 配置
ALT+7 apply-profile NNEDI3;show-text "配置组NNEDI3" #menu: 其它 > 常规配置组 > 切换 NNEDI3 配置
ALT+8 apply-profile NNEDI3+;show-text "配置组NNEDI3+" #menu: 其它 > 常规配置组 > 切换 NNEDI3+ 配置
# apply-profile SSIM;show-text "配置组SSIM" #menu: 其它 > 常规配置组 > 切换 SSIM 配置
# cycle border;show-text "显示边框:${border}" #menu: 其它 > 切换 边框模式 #@state=(border and 'checked')
CTRL+B cycle title-bar;show-text "显示标题栏:${title-bar}" #menu: 其它 > 切换 标题栏 #@state=(title_bar and 'checked')
# cycle-values title ${media-title} ${filename} #menu: 其它 > 切换 媒体标题
~ script-binding console/enable #menu: 其它 > 打开 控制台
CTRL+R cycle-values reset-on-next-file "all" "no" "vf,af,loop-file,deinterlace,border,contrast,brightness,gamma,saturation,hue,video-zoom,video-rotate,video-pan-x,video-pan-y,panscan,speed,audio,sub,audio-delay,sub-pos,sub-scale,sub-delay,sub-speed,sub-visibility";show-text "播放下一个文件时重置以下选项:${reset-on-next-file}" #menu: 其它 > 重置播放中更改项
##⇘⇘uosc 一级菜单:工具
Ctrl+p script-message-to command_palette show-command-palette "Command Palette" #menu: 工具 > 打开命令面板
# set clipboard/text ${filename} #menu: 工具 > 复制文件信息 > 复制当前文件名
# set clipboard/text ${path} #menu: 工具 > 复制文件信息 > 复制当前文件路径
CTRL+ALT+t set clipboard/text ${time-pos} #menu: 工具 > 复制文件信息 > 复制当前时间
CTRL+ALT+s set clipboard/text ${sub-text} #menu: 工具 > 复制文件信息 > 复制当前字幕内容
# script-message-to uosc open-config-directory #menu: 工具 > 定位配置文件
M script-message manager-update-all ;show-text "更新脚本着色器" #menu: 工具 > 一键更新脚本和着色器
CTRL+d script-message-to uosc_danmaku show_danmaku_keyboard #menu: 工具 > 弹幕功能 > 开/关 弹幕显示
CTRL+D script-message-to uosc_danmaku open_add_total_menu #menu: 工具 > 弹幕功能 > 打开弹幕综合菜单
CTRL+ALT+d script-message danmaku-delay 1 ${=time-pos} #menu: 工具 > 弹幕功能 > 弹幕延迟 +1 秒
CTRL+ALT+a script-message danmaku-delay -1 ${=time-pos} #menu: 工具 > 弹幕功能 > 弹幕延迟 -1 秒
_ script-message-to uosc_danmaku immediately_save_danmaku #menu: 工具 > 弹幕功能 > 保存当前弹幕
# ignore #menu: ---
##⇘⇘uosc 一级菜单
b cycle window-minimized #menu: 最小化
q quit #menu: 退出程序
Q quit-watch-later # 退出并保存当前状态
##⇘⇘以下键位不显示在 uosc 菜单中
MENU script-message-to uosc menu-blurred # 开/关 uosc 菜单
POWER quit
PLAY cycle pause;script-message-to uosc flash-pause-indicator
PAUSE cycle pause;script-message-to uosc flash-pause-indicator
PLAYPAUSE cycle pause;script-message-to uosc flash-pause-indicator
STOP quit
FORWARD seek 30
REWIND seek -30
NEXT playlist-next
PREV playlist-prev
SPACE cycle pause;script-message-to uosc flash-pause-indicator
MBTN_LEFT cycle pause;script-message-to uosc flash-pause-indicator
MBTN_LEFT_DBL ignore
MBTN_Right script-message-to uosc menu-blurred # 开/关 uosc 菜单
MBTN_FORWARD playlist-next;show-text "播放列表:${playlist-pos-1}/${playlist-count}" # 前进键 切换到列表中的下个文件
MBTN_BACK playlist-prev;show-text "播放列表:${playlist-pos-1}/${playlist-count}" # 后退键 切换到列表中的上个文件
MBTN_Right_DBL ignore
Wheel_Up no-osd add volume 10; script-message-to uosc flash-volume
Wheel_Down no-osd add volume -10; script-message-to uosc flash-volume
ESC set fullscreen no;set window-maximized no
9 no-osd add volume -1; script-message-to uosc flash-volume # 音量 -1
0 no-osd add volume 1; script-message-to uosc flash-volume # 音量 +1
+29
View File
@@ -0,0 +1,29 @@
##⇘⇘以下为基于 inputevent.lua 脚本实现的增强式功能键位绑定
## '#event:click'用于指定同键位单击时的操作,'#event:double_click'用于指定同键位双击时的操作,'#event:press'用于指定同键位长按时的操作,'#event:release'用于指定同键位长按释放时的操作
## 更多的键位动作请参考脚本上游说明https://github.com/zhongfly/InputEvent/tree/property-expansion
##! 注意:并非所有键位都具有以上不同的触发动作
## 同时脚本支持同一种动作根据条件触发不同的命令,条件的语法等同于 profile-cond自动 profile 的条件语句)
## 即脚本语法升级为 '#event:动作|条件'(原先的#event:动作视为无条件执行命令),命令执行的优先级是从下到上按顺序,执行第一个满足条件的命令
RIGHT seek 5 #event:click
RIGHT script-message-to evafast speedup #event:press
RIGHT script-message-to evafast slowdown #event:release
TAB script-message-to file_browser browse-files;script-message-to file_browser dynamic/reload;show-text '' #event:click
TAB script-message-to uosc toggle-ui #event:press
TAB script-message-to uosc toggle-ui #event:release
PGDWN add chapter -1 #event:click
PGDWN playlist-prev;show-text 播放列表:${playlist-pos-1}/${playlist-count} #event:click|chapter == 0
PGDWN playlist-prev;show-text 播放列表:${playlist-pos-1}/${playlist-count} #event:press
PGUP add chapter 1 #event:click
PGUP playlist-next;show-text 播放列表:${playlist-pos-1}/${playlist-count} #event:click|chapter+1 == chapters
PGUP playlist-next;show-text 播放列表:${playlist-pos-1}/${playlist-count} #event:press
SPACE cycle pause;script-message-to uosc flash-pause-indicator #event:click
SPACE script-binding simplehistory/history-load-last #event:click|idle_active
SPACE no-osd set speed 3; set pause no #event:press
SPACE ignore #event:release
MBTN_LEFT cycle pause;script-message-to uosc flash-pause-indicator #event:click
MBTN_LEFT cycle fullscreen #event:double_click
+111
View File
@@ -0,0 +1,111 @@
[
{
"git":"https://github.com/po5/evafast",
"branch":"rewrite",
"whitelist":"%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/stax76/mpv-scripts",
"branch":"main",
"whitelist":"delete_current_file%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/dyphire/mpv-scripts",
"branch":"main",
"blacklist":"license|%.md$|drcbox%.lua$|.-%-list%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/dyphire/mpv-playlistmanager",
"branch":"dev",
"whitelist":"playlistmanager%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/dyphire/mpv-sub-assrt",
"whitelist":"%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/dyphire/chapterskip",
"branch":"dev",
"whitelist":"%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/dyphire/Eisa01_mpv-scripts",
"branch":"dev",
"whitelist":"undoredo%.lua$|simplehistory%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/dyphire/autosubsync-mpv",
"branch":"v0.33_CM",
"whitelist":"readme%.md$|%.lua$",
"dest":"~~/scripts/autosubsync"
},
{
"git":"https://github.com/christoph-heinrich/mpv-quality-menu",
"whitelist":"quality%-menu%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/CogentRedTester/mpv-file-browser",
"whitelist":"main%.lua$|readme%.md$|doc|modules",
"dest":"~~/scripts/file-browser"
},
{
"git":"https://github.com/CogentRedTester/mpv-sub-select",
"whitelist":"sub%-select%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/CogentRedTester/mpv-scripts",
"whitelist":"cycle%-commands%.lua$",
"dest":"~~/scripts"
},
{
"git":"https://github.com/CogentRedTester/mpv-file-browser",
"whitelist":"favourites%.lua$|find%.lua$|home%-label%.lua$|url%-decode%.lua$|windir%.lua$|winroot%.lua$",
"dest":"~~/script-modules/file-browser-addons"
},
{
"git":"https://gist.github.com/igv/8a77e4eb8276753b54bb94c1c50c317e",
"whitelist":"%.glsl$",
"dest":"~~/shaders/igv"
},
{
"git":"https://gist.github.com/igv/a015fc885d5c22e6891820ad89555637",
"whitelist":"%.glsl$",
"dest":"~~/shaders/igv"
},
{
"git":"https://gist.github.com/igv/2364ffa6e81540f29cb7ab4c9bc05b6b",
"whitelist":"%.glsl$",
"dest":"~~/shaders/igv"
},
{
"git":"https://gist.github.com/igv/36508af3ffc84410fe39761d6969be10",
"whitelist":"%.glsl$",
"dest":"~~/shaders/igv"
},
{
"git":"https://github.com/Artoriuz/glsl-joint-bilateral",
"branch":"main",
"whitelist":"jointbilateral%.glsl$",
"dest":"~~/shaders/other"
},
{
"git":"https://github.com/Artoriuz/glsl-pixel-clipper",
"branch":"main",
"whitelist":"%.glsl$",
"dest":"~~/shaders/other"
},
{
"git":"https://github.com/bloc97/Anime4K",
"whitelist":"%.md$|%.glsl$",
"blacklist":"tensorflow",
"dest":"~~/shaders/Anime4K"
}]
+434
View File
@@ -0,0 +1,434 @@
# ===== 基础设置 =====
vo=gpu-next # 视频输出驱动
gpu-api=vulkan # 图形绘制接口
input-ime=no # 仅文本输入时激活 IME
autofit-smaller=40%x30% # 窗口最小占屏比例
idle=yes # 播放结束后保持运行
ontop # 窗口置顶
hr-seek=yes # 精确跳转
hr-seek-framedrop=no # 跳转时不丢帧,利于修正音频延迟
save-position-on-quit=yes # 退出时记住播放进度
write-filename-in-watch-later-config # 播放记录中写入文件名
resume-playback-check-mtime=yes # 校验文件修改时间防止误恢复
watch-later-dir="~~/cache/watch_later"
watch-later-options=start,vid,aid,sid
save-watch-history=yes # 保存播放历史(内置 select 脚本可浏览)
watch-history-path=~~/files/watch_history.jsonl
reset-on-next-file=vid,aid,sid,secondary-sid,vf,af,loop-file,deinterlace,contrast,brightness,gamma,saturation,hue,video-zoom,video-rotate,video-pan-x,video-pan-y,panscan,speed,audio-delay,sub-pos,sub-scale,sub-delay,sub-speed,sub-visibility,secondary-sub-visibility
input-ipc-server=/tmp/mpvsocket # IPC 套接字
directory-mode=ignore # 打开目录时忽略子目录
metadata-codepage=auto # 元数据编码自动检测
msg-level=all=info,auto_profiles=warn
title=${?pause==yes:⏸}${?mute==yes:🔇}${?ontop==yes:📌}${?demuxer-via-network==yes:${media-title}}${?demuxer-via-network==no:${filename}}
# ===== OSD =====
no-osd-bar
osd-on-seek=msg-bar
osd-bar-w=100
osd-bar-h=2
osd-bar-align-y=-1
osd-font="Noto Sans CJK SC;Noto Color Emoji"
osd-font-size=24
osd-color="#FFFFFF"
osd-outline-size=1.0
osd-outline-color="#1C1B1F"
osd-shadow-offset=0
osd-back-color="#1C1B1F"
osd-border-style=outline-and-shadow
osd-playlist-entry=filename
osd-status-msg=${playback-time/full} / ${duration/full} (${percent-pos}%)\nframe: ${estimated-frame-number} / ${estimated-frame-count}
osd-fractions=yes
osd-duration=2000
# ===== 色彩管理 =====
icc-profile-auto # 自动检测系统 ICC 配置文件
icc-intent=0 # 感知度映射意图
icc-force-contrast=1000 # 解决校色文件对比度问题
icc-3dlut-size=128x128x128 # 3D LUT 精度
icc-cache-dir="~~/cache/icc_cache"
use-embedded-icc-profile=yes # 使用嵌入式 ICC 配置文件
inverse-tone-mapping=yes # SDR→HDR 反向映射(仅 HDR 显示器有效)
target-colorspace-hint=auto # 自动配置显示器输出色彩空间
hdr-contrast-recovery=0.30 # HDR 对比度恢复强度
hdr-compute-peak=yes # 动态峰值检测
# ===== 音频 =====
audio-device=auto
audio-channels=7.1,5.1,stereo # 按优先级回退,避免多声道下混丢声道
ao=alsa
audio-exclusive=yes # 独占音频通道
audio-samplerate=96000 # 重采样至 96kHz
audio-format=s32 # 32位有符号整数
replaygain=album # 专辑增益优先,无专辑增益时回退到曲目增益
gapless-audio=no # 避免采样率锁定导致音质下降
audio-file-auto=fuzzy # 自动加载同名外部音轨
audio-file-paths=audio;audios;audio 5.1;audios 5.1;audio 7.1;audios 7.1;音轨
alang=japanese,jpn,jap,ja,jp,english,eng,en
# ===== 字幕 =====
sub-codepage=gb18030 # 非 UTF-8 字幕首先尝试 GB18030
sub-auto=fuzzy # 自动加载同名外部字幕
sub-file-paths=sub;subs;subtitles;字幕
slang=chs,sc,zh-Hans,zh-CN,cht,tc,zh-Hant,zh-HK,zh-TW,chi,zho,zh
sub-font="Noto Sans CJK SC;Noto Color Emoji"
sub-font-size=50
sub-bold=yes
sub-color="#FFFFFF"
sub-outline-size=0.5
sub-outline-color="#000000"
sub-shadow-offset=0.5
sub-back-color="#000000"
# ===== 截图 =====
screenshot-format=webp
screenshot-webp-quality=85
screenshot-webp-compression=6
screenshot-tag-colorspace=no
screenshot-template="~~/files/screen/%{media-title}-%P-%n"
# ===== 脚本与着色器 =====
gpu-shader-cache-dir="~~/cache/shaders_cache"
osc=no # 禁用内置 OSC使用 uosc
ytdl=yes
ytdl-format=bestvideo[height<=?2160][vcodec!=?vp9.2][vcodec!=?vp9]+bestaudio/best
ytdl-raw-options-append=cookies-from-browser=Firefox
input-default-bindings=no # 禁用内置键位,在 input.conf 中自定义
# ===== 外部配置 =====
include="~~/profiles.conf" # 场景预设(游戏/电影/动画等,手动激活)
# =========================================
# Profile 激活
# =========================================
# 色彩管理ICC 和 Target 二选一)
#profile=ICC # ICC 色彩管理,使用系统 ICC 配置文件
#profile=ICC+ # ICC 色彩管理,使用自定义 ICC 文件
profile=Target # Target 色彩管理,直接指定目标色彩空间
profile=Dither # 抖动算法fruit
#profile=Dither+ # 抖动算法error-diffusion更耗能
profile=Tscale # 时域插值oversample
#profile=Tscale+ # 时域插值sphinx更平滑但略模糊
profile=HQ # 高质量缩放算法组合
profile=DeBand-low # 去色带(低强度)
#profile=DeBand-medium
#profile=DeBand-high
profile=HDR2SDR # HDR 映射 SDR 参数
#profile=SDR2HDR # SDR 反向映射 HDR仅 HDR 显示器)
#profile=SWscaler # 软件缩放备选方案
# 着色器(选一)
#profile=NNEDI3 # 通用场景NNEDI3-32
profile=NNEDI3+ # 通用场景NNEDI3-64更耗能
#profile=ravu-zoom # 通用场景ravu-zoom
#profile=FSRCNNX # HD 场景
#profile=FSRCNNX+ # SD 场景FSRCNNX 去伪影)
#profile=Ani4K # 动画Ani4K V2高性能开销
#profile=AniSD # SD 动画AniSD高性能开销
#profile=Anime4K # 动画Anime4K 修复 + 去伪影)
#profile=SSIM # 低性能消耗
# =========================================
# Profile 定义:常规
# =========================================
[ICC]
# 使用系统 ICC 配置文件进行色彩管理
icc-profile=""
icc-profile-auto
icc-intent=0
icc-force-contrast=1000
icc-3dlut-size=auto
icc-cache-dir="~~/cache/icc_cache"
[ICC+]
# 使用自定义 ICC 文件(如 color.org 泛 ICC 或专业校色文件)
icc-profile="~~/icc/ITU-RBT709ReferenceDisplay.icc"
icc-intent=0
icc-force-contrast=no
icc-3dlut-size=auto
icc-cache-dir="~~/cache/icc_cache"
[Target]
# 不使用 ICC直接指定目标色彩空间参数
icc-profile=""
icc-profile-auto=no
target-prim=auto
target-contrast=auto
#target-trc=gamma2.2 # 非 OLED 显示器建议启用
#target-peak=203 # SDR 显示器默认 203 nit
[Dither]
dither-depth=auto
dither=fruit
dither-size-fruit=6
[Dither+]
dither-depth=auto
dither=error-diffusion
error-diffusion=floyd-steinberg
[Tscale]
video-sync=display-resample
interpolation
tscale=oversample
[Tscale+]
video-sync=display-resample
interpolation
tscale=sphinx
tscale-blur=0.65
[HQ]
scale=ewa_lanczossharp
cscale=bilinear
dscale=catmull_rom
scale-antiring=0.5
dscale-antiring=0.5
linear-upscaling=no
sigmoid-upscaling=yes
correct-downscaling=yes
linear-downscaling=no
deband=no
[DeBand-low]
deband-iterations=1
deband-threshold=48
deband-range=16
deband-grain=16
[DeBand-medium]
deband-iterations=2
deband-threshold=64
deband-range=16
deband-grain=24
[DeBand-high]
deband-iterations=3
deband-threshold=64
deband-range=16
deband-grain=24
[HDR2SDR]
tone-mapping=auto
gamut-mapping-mode=auto
hdr-contrast-recovery=0.30
hdr-compute-peak=auto
hdr-peak-percentile=99.995
hdr-peak-decay-rate=100
hdr-scene-threshold-low=5.5
hdr-scene-threshold-high=10
allow-delayed-peak-detect=no
[SDR2HDR]
# SDR 反向映射 HDR仅 HDR 显示器有效
icc-profile=""
icc-profile-auto=no
target-prim=auto
target-trc=auto
target-peak=auto
target-colorspace-hint=auto
inverse-tone-mapping=yes
[SWscaler]
sws-allow-zimg=no
sws-scaler=bicublin
zimg-scaler-chroma=bicubic
zimg-scaler=bicubic
zimg-scaler-param-a=1/3
zimg-scaler-param-b=1/3
zimg-fast=no
# --- 着色器 Profile ---
[NNEDI3]
profile-desc=通用场景NNEDI3-32 + 自适应锐化)
glsl-shaders-append="~~/shaders/nnedi3/nnedi3-nns32-win8x4.glsl"
glsl-shaders-append="~~/shaders/igv/adaptive-sharpen_luma.glsl"
[NNEDI3+]
profile-desc=通用场景NNEDI3-64 + 自适应锐化)
glsl-shaders-append="~~/shaders/nnedi3/nnedi3-nns64-win8x4.glsl"
glsl-shaders-append="~~/shaders/igv/adaptive-sharpen_luma.glsl"
[ravu-zoom]
profile-desc=通用场景ravu-zoom + 自适应锐化)
glsl-shaders-append="~~/shaders/ravu/ravu-zoom-ar-r3.glsl"
glsl-shaders-append="~~/shaders/igv/adaptive-sharpen_luma.glsl"
[FSRCNNX]
profile-desc=HD 场景FSRCNNX + 自适应锐化)
glsl-shaders-append="~~/shaders/igv/FSRCNNX_x2_8-0-4-1.glsl"
glsl-shaders-append="~~/shaders/igv/adaptive-sharpen_luma.glsl"
[FSRCNNX+]
profile-desc=SD 场景NNEDI3 + FSRCNNX 去伪影)
glsl-shaders-append="~~/shaders/nnedi3/nnedi3-nns32-win8x4.glsl"
glsl-shaders-append="~~/shaders/igv/FSRCNNX_x1_16-0-4-1_distort.glsl"
[Ani4K]
profile-desc=动画Ani4K V2高性能开销
glsl-shaders-append="~~/shaders/Ani4K/Ani4Kv2_ArtCNN_C4F32_i2.glsl"
[AniSD]
profile-desc=SD 动画AniSD高性能开销
glsl-shaders-append="~~/shaders/Ani4K/AniSD_ArtCNN_C4F32_i4.glsl"
[Anime4K]
profile-desc=动画Anime4K 修复 + 去伪影)
glsl-shaders-append="~~/shaders/igv/KrigBilateral.glsl"
glsl-shaders-append="~~/shaders/Anime4K/glsl/Restore/Anime4K_Restore_CNN_Soft_M.glsl"
glsl-shaders-append="~~/shaders/Anime4K/glsl/Restore/Anime4K_Clamp_Highlights.glsl"
[SSIM]
profile-desc=低性能消耗SSIM 超分 + 缩小)
glsl-shaders-append="~~/shaders/igv/SSimSuperRes.glsl"
glsl-shaders-append="~~/shaders/igv/SSimDownscaler.glsl"
# =========================================
# Profile 定义:条件触发
# =========================================
[SD]
# 低清源≤720p自动启用 FSRCNNX+ 增强
profile-cond=height <= 720 or width <= 720
profile-restore=copy
profile=FSRCNNX+
[Deband]
# YUV420P10 以下自动开启去色带
profile-cond=get("video-params/average-bpp") < 24
profile-restore=copy
deband=yes
[hdr-2390]
profile-cond=p.tone_mapping == "bt.2390" and p.current_vo == "gpu-next"
profile-restore=copy
tone-mapping-param=2.0
[peak-percentile]
profile-cond=get("video-params/avg-pq-y") < 0.21 and p.current_vo == "gpu-next"
profile-restore=copy
hdr-peak-percentile=99.8
[SDR-gamut]
# SDR 非 BT.709 内容的色域映射
profile-cond=get("video-params/primaries") ~= "bt.709" and get("video-params/gamma") == "bt.1886"
profile-restore=copy
gamut-mapping-mode=clip
[SDR-target]
# SDR 显示目标配置
profile-cond=not get("video-params/max-luma") and not get("inverse-tone-mapping") and p.current_vo == "gpu-next"
profile-restore=copy
target-colorspace-hint=no
[HDR]
# HDR 内容自动配置
profile-cond=get("video-params/max-luma") > 203
profile-restore=copy
target-peak=auto
target-colorspace-hint=auto
blend-subtitles=no
deband=no
vf=""
[HDR-PASS]
# HDR 直通(仅 HDR 显示模式下生效)
profile-cond=get("video-params/max-luma") > 203 and get("video-target-params/max-luma") > 203 and p.current_vo == "gpu-next"
profile-restore=copy
icc-profile=""
icc-profile-auto=no
target-colorspace-hint=auto
inverse-tone-mapping=no
target-prim=auto
target-trc=auto
target-peak=auto
target-contrast=auto
[video-sync]
# 音频调速超过阈值时切换同步模式,避免明显音高变化
profile-cond=(speed * audio_speed_correction) > 0 and ((speed * audio_speed_correction) < 0.96 or (speed * audio_speed_correction) > 1.04) and not (get("estimated-vf-fps") > 240 or get("display-fps") > 240)
profile-restore=copy-equal
video-sync=display-tempo
[fps-fix]
# 极高帧率/刷新率时使用 audio 同步避免异常
profile-cond=get("estimated-vf-fps") > 240 or get("display-fps") > 240
profile-restore=copy
video-sync=audio
[pgs-fix]
# 修复宽屏视频 PGS 字幕比例错位
profile-cond=get("video-params/aspect") > 16 / 9
profile-restore=copy
stretch-image-subs-to-screen=yes
[audio-filter]
# 多声道音频自动启用动态范围调节(适用于双声道设备)
profile-cond=get("audio-params/channel-count") > 2
profile-restore=copy-equal
af-pre=@dynaudnorm:lavfi=[dynaudnorm=f=500:g=31:p=0.5:m=5:r=0.9]
[pause]
# 暂停时取消置顶
profile-cond=pause
profile-restore=copy
ontop=no
[maximized]
# 最大化时禁止窗口自动调整大小
profile-cond=window_maximized and vid and not get("current-tracks/video/albumart")
profile-restore=copy-equal
auto-window-resize=no
[minimized]
# 最小化时自动暂停
profile-cond=window_minimized and vid and not get("current-tracks/video/albumart")
profile-restore=copy-equal
pause
[end]
# 播放列表结束后退出全屏
profile-cond=idle_active
no-fullscreen
no-window-maximized
[media-title]
# 网络/磁力链接显示媒体标题
profile-cond=path:find('://') ~= nil or path:find('^magnet:') ~= nil
profile-restore=copy
title=${?pause==yes:⏸}${?mute==yes:🔇}${?ontop==yes:📌}${media-title}
osd-playlist-entry=title
# =========================================
# Profile 定义:网络优化
# =========================================
[network-optimize]
# 网络播放时自动启用
profile-cond=demuxer_via_network
profile-restore=copy
cache=yes
cache-secs=600
demuxer-max-bytes=1GiB
demuxer-readahead-secs=120
network-timeout=60
stream-buffer-size=2MiB
prefetch-playlist=yes
stream-lavf-o-append=reconnect=1
stream-lavf-o-append=reconnect_at_eof=1
stream-lavf-o-append=reconnect_streamed=1
stream-lavf-o-append=multiple_requests=1
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
# 场景预设手动激活profile=game 等
[game]
profile-desc=低延迟,高性能
hwdec=auto-safe
video-sync=display-resample
interpolation=no
deband=no
video-latency-hacks=yes
opengl-pbo=yes
gpu-api=vulkan
gpu-context=wayland
cache=no
demuxer-max-bytes=8MiB
demuxer-max-back-bytes=4MiB
[movie]
profile-desc=高质量画质
hwdec=auto-copy-safe
video-sync=display-resample
interpolation=yes
deband=yes
deband-iterations=4
deband-threshold=35
deband-range=16
deband-grain=4
icc-profile-auto=yes
target-colorspace-hint=yes
hdr-compute-peak=yes
tone-mapping=hable
target-peak=100
[anime]
profile-desc=动画优化
hwdec=auto-safe
video-sync=display-resample
interpolation=yes
deband=yes
deband-iterations=3
deband-threshold=48
deband-range=16
deband-grain=6
icc-profile-auto=yes
glsl-shaders="~~/shaders/Anime4K/glsl/Restore/Anime4K_Restore_CNN_Soft_M.glsl;~~/shaders/Anime4K/glsl/Deblur/Anime4K_Deblur_DoG.glsl"
[lowpower]
profile-desc=省电模式
hwdec=no
video-sync=audio
interpolation=no
deband=no
icc-profile-auto=no
vd-lavc-threads=2
vd-lavc-skiploopfilter=all
vd-lavc-fast=yes
scale=bilinear
dscale=bilinear
cscale=bilinear
[stream]
profile-desc=网络流媒体优化
hwdec=auto-safe
video-sync=display-resample
cache=yes
cache-secs=300
demuxer-max-bytes=64MiB
demuxer-max-back-bytes=32MiB
network-timeout=60
ytdl=yes
ytdl-format=bestvideo[height<=?1080][vcodec!=?vp9]+bestaudio/best
[HDR-scene]
profile-desc=HDR 手动预设
hwdec=auto-copy-safe
target-colorspace-hint=yes
hdr-compute-peak=yes
tone-mapping=hable
target-peak=203
gamut-mapping-mode=relative
icc-profile-auto=yes
glsl-shaders="~~/shaders/other/PixelClipper.glsl"
[screenshot-hq]
profile-desc=高质量截图
screenshot-format=png
screenshot-png-compression=9
screenshot-png-filter=5
screenshot-tag-colorspace=yes
screenshot-high-bit-depth=yes
screenshot-sw=no
scale=lanczos
dscale=mitchell
cscale=mitchell
[live]
profile-desc=直播低延迟
hwdec=auto-safe
video-sync=display-resample
cache=no
demuxer-max-bytes=2MiB
demuxer-max-back-bytes=1MiB
video-latency-hacks=yes
opengl-pbo=yes
network-timeout=30
stream-lavf-o=reconnect_streamed=1,reconnect_delay_max=30
+930
View File
@@ -0,0 +1,930 @@
local mp = require 'mp'
local utils = require 'mp.utils'
local assdraw = require 'mp.assdraw'
-- create namespace with default values
local em = {
-- customisable values ------------------------------------------------------
loop_when_navigating = false, -- Loop when navigating through list
lines_to_show = 17, -- NOT including search line
pause_on_open = true,
resume_on_exit = "only-if-was-paused", -- another possible value is true
-- styles (earlyer it was a table, but required many more steps to pass def-s
-- here from .conf file)
font_size = 21,
--font size scales by window
scale_by_window = false,
-- cursor 'width', useful to change if you have hidpi monitor
cursor_x_border = 0.3,
line_bottom_margin = 1, -- basically space between lines
text_color = {
default = 'ffffff',
accent = 'd8a07b',
current = 'aaaaaa',
comment = '636363',
},
menu_x_padding = 5, -- this padding for now applies only to 'left', not x
menu_y_padding = 2, -- but this one applies to both - top & bottom
-- values that should be passed from main script ----------------------------
search_heading = 'Default search heading',
-- 'full' is required from main script, 'current_i' is optional
-- others are 'private'
list = {
full = {}, filtered = {}, current_i = nil, pointer_i = 1, show_from_to = {}
},
-- field to compare with when searching for 'current value' by 'current_i'
index_field = 'index',
-- fields to use when searching for string match / any other custom searching
-- if value has 0 length, then search list item itself
filter_by_fields = {},
-- 'private' values that are not supposed to be changed from the outside ----
is_active = false,
-- https://mpv.io/manual/master/#lua-scripting-mp-create-osd-overlay(format)
ass = mp.create_osd_overlay("ass-events"),
was_paused = false, -- flag that indicates that vid was paused by this script
line = '',
-- if there was no cursor it wouldn't have been needed, but for now we need
-- variable below only to compare it with 'line' and see if we need to filter
prev_line = '',
cursor = 1,
history = {},
history_pos = 1,
key_bindings = {},
insert_mode = false,
-- used only in 'update' func to get error text msgs
error_codes = {
no_match = 'Match required',
no_submit_provided = 'No submit function provided'
}
}
-- PRIVATE METHODS ------------------------------------------------------------
local ime_active = mp.get_property_native("input-ime")
-- declare constructor function
function em:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
-- some options might be customised by user in .conf file and read as strings
-- in that case parse those
if type(o.filter_by_fields) == 'string' then
o.filter_by_fields = utils.parse_json(o.filter_by_fields)
end
if type(o.text_color) == 'string' then
o.text_color = utils.parse_json(o.text_color)
end
return o
end
-- this func is just a getter of a current list depending on search line
function em:current()
return self.line == '' and self.list.full or self.list.filtered
end
-- REVIEW: how to get rid of this wrapper and handle filter func sideeffects
-- in a more elegant way?
function em:filter_wrapper()
-- handles sideeffect that are needed to be run on filtering list
-- cuz the filter func may be redefined in main script and therefore needs
-- to be straight forward - only doing filtering and returning the table
-- passing current query just in case, so ppl can use it in their custom funcs
self.list.filtered = self:filter(self.line)
self.prev_line = self.line
self.list.pointer_i = 1
self:set_from_to(true)
end
function em:set_from_to(reset_flag)
-- additional variables just for shorter var name
local i = self.list.pointer_i
local to_show = self.lines_to_show
local total = #self:current()
if reset_flag or to_show >= total then
self.list.show_from_to = { 1, math.min(to_show, total) }
return
end
-- If menu is opened with something already selected we want this 'selected'
-- to be displayed close to the middle of the menu. That's why 'show_from_to'
-- is not initially set, so we can know - if show_from_to length is 0 - it is
-- first call of this func in cur. init
if #self.list.show_from_to == 0 then
-- set show_from_to so chosen item will be displayed close to middle
local half_list = math.ceil(to_show / 2)
if i < half_list then
self.list.show_from_to = { 1, to_show }
elseif total - i < half_list then
self.list.show_from_to = { total - to_show + 1, total }
else
self.list.show_from_to = { i - half_list + 1, i - half_list + to_show }
end
else
table.unpack = table.unpack or unpack -- 5.1 compatibility
local first, last = table.unpack(self.list.show_from_to)
-- handle cursor moving towards start / end bondary
if first ~= 1 and i - first < 2 then
self.list.show_from_to = { first - 1, last - 1 }
end
if last ~= total and last - i < 2 then
self.list.show_from_to = { first + 1, last + 1 }
end
-- handle index jumps from beginning to end and backwards
if i > last then
self.list.show_from_to = { i - to_show + 1, i }
end
if i < first then self.list.show_from_to = { 1, to_show } end
end
end
function em:change_selected_index(num)
self.list.pointer_i = self.list.pointer_i + num
if self.loop_when_navigating then
if self.list.pointer_i < 1 then
self.list.pointer_i = #self:current()
elseif self.list.pointer_i > #self:current() then
self.list.pointer_i = 1
end
else
if self.list.pointer_i < 1 then
self.list.pointer_i = 1
elseif self.list.pointer_i > #self:current() then
self.list.pointer_i = #self:current()
end
end
self:set_from_to()
self:update()
end
-- Render the REPL and console as an ASS OSD
function em:update(err_code)
-- ASS tags documentation here - https://aegi.vmoe.info/docs/3.0/ASS_Tags/
-- do not bother if function was called to close the menu..
if not self.is_active then
em.ass:remove()
return
end
local line_height = self.font_size + self.line_bottom_margin
local _, h, aspect = mp.get_osd_size()
local wh = self.scale_by_window and 720 or h
local ww = wh * aspect
-- '+ 1' below is a search string
local menu_y_pos =
wh - (line_height * (self.lines_to_show + 1) + self.menu_y_padding * 2)
-- didn't find better place to handle filtered list update
if self.line ~= self.prev_line then self:filter_wrapper() end
local function get_background()
local a = self:ass_new_wrapper()
a:append('{\\1c&H1c1c1c\\1a&H19}') -- background color & opacity
a:pos(0, 0)
a:draw_start()
a:rect_cw(0, menu_y_pos, ww, wh)
a:draw_stop()
return a.text
end
local function get_search_header()
local a = self:ass_new_wrapper()
a:pos(self.menu_x_padding, menu_y_pos + self.menu_y_padding)
local search_prefix = table.concat({
self:get_font_color('accent'),
(#self:current() ~= 0 and self.list.pointer_i or '!'),
'/', #self:current(), '\\h\\h', self.search_heading, ':\\h'
});
a:append(search_prefix)
-- reset font color after search prefix
a:append(self:get_font_color 'default')
-- Create the cursor glyph as an ASS drawing. ASS will draw the cursor
-- inline with the surrounding text, but it sets the advance to the width
-- of the drawing. So the cursor doesn't affect layout too much, make it as
-- thin as possible and make it appear to be 1px wide by giving it 0.5px
-- horizontal borders.
local cheight = self.font_size * 8
-- TODO: maybe do it using draw_rect from ass?
local cglyph = '{\\r' .. -- styles reset
'\\1c&Hffffff&\\3c&Hffffff' .. -- font color and border color
'\\xbord' .. self.cursor_x_border .. '\\p4\\pbo24}' .. -- xborder, scale x8 and baseline offset
'm 0 0 l 0 ' .. cheight .. -- drawing just a line
'{\\p0\\r}' -- finish drawing and reset styles
local before_cur = self:ass_escape(self.line:sub(1, self.cursor - 1))
local after_cur = self:ass_escape(self.line:sub(self.cursor))
a:append(table.concat({
before_cur, cglyph, self:reset_styles(),
self:get_font_color('default'), after_cur,
(err_code and '\\h' .. self.error_codes[err_code] or "")
}))
return a.text
-- NOTE: perhaps this commented code will some day help me in coding cursor
-- like in M-x emacs menu:
-- Redraw the cursor with the REPL text invisible. This will make the
-- cursor appear in front of the text.
-- ass:new_event()
-- ass:an(1)
-- ass:append(style .. '{\\alpha&HFF&}> ' .. before_cur)
-- ass:append(cglyph)
-- ass:append(style .. '{\\alpha&HFF&}' .. after_cur)
end
local function get_list()
local a = assdraw.ass_new()
local function apply_highlighting(y)
a:new_event()
a:append(self:reset_styles())
a:append('{\\1c&Hffffff\\1a&HE6}') -- background color & opacity
a:pos(0, 0)
a:draw_start()
a:rect_cw(0, y, ww, y + self.font_size)
a:draw_stop()
end
-- REVIEW: maybe make another function 'get_line_str' and move there
-- everything from this for loop?
-- REVIEW: how to use something like table.unpack below?
for i = self.list.show_from_to[1], self.list.show_from_to[2] do
local value = assert(self:current()[i], 'no value with index ' .. i)
local y_offset = menu_y_pos + self.menu_y_padding +
(line_height * (i - self.list.show_from_to[1] + 1))
if i == self.list.pointer_i then apply_highlighting(y_offset) end
a:new_event()
a:append(self:reset_styles())
a:pos(self.menu_x_padding, y_offset)
a:append(self:get_line(i, value))
end
return a.text
end
em.ass.res_x = ww
em.ass.res_y = wh
em.ass.data = table.concat({
get_background(),
get_search_header(),
get_list()
}, "\n")
em.ass:update()
end
-- params:
-- - data : {list: {}, [current_i] : num}
function em:init(data)
self.list.full = data.list or {}
self.list.current_i = data.current_i or nil
self.list.pointer_i = data.current_i or 1
self:set_active(true)
end
function em:exit()
self:undefine_key_bindings()
collectgarbage()
end
-- TODO: write some idle func like this
-- function idle()
-- if pending_selection then
-- gallery:set_selection(pending_selection)
-- pending_selection = nil
-- end
-- if ass_changed or geometry_changed then
-- local ww, wh = mp.get_osd_size()
-- if geometry_changed then
-- geometry_changed = false
-- compute_geometry(ww, wh)
-- end
-- if ass_changed then
-- ass_changed = false
-- mp.set_osd_ass(ww, wh, ass)
-- end
-- end
-- end
-- ...
-- and handle it as follows
-- init():
-- mp.register_idle(idle)
-- idle()
-- exit():
-- mp.unregister_idle(idle)
-- idle()
-- And in these observers he is setting a flag, that's being checked in func above
-- mp.observe_property("osd-width", "native", mark_geometry_stale)
-- mp.observe_property("osd-height", "native", mark_geometry_stale)
-- PRIVATE METHODS END --------------------------------------------------------
-- PUBLIC METHODS -------------------------------------------------------------
function em:filter()
-- default filter func, might be redefined in main script
local result = {}
local function get_full_search_str(v)
local str = ''
for _, key in ipairs(self.filter_by_fields) do str = str .. (v[key] or '') end
return str
end
for _, v in ipairs(self.list.full) do
-- if filter_by_fields has 0 length, then search list item itself
if #self.filter_by_fields == 0 then
if self:search_method(v) then table.insert(result, v) end
else
-- NOTE: we might use search_method on fiels separately like this:
-- for _,key in ipairs(self.filter_by_fields) do
-- if self:search_method(v[key]) then table.insert(result, v) end
-- end
-- But since im planning to implement fuzzy search in future i need full
-- search string here
if self:search_method(get_full_search_str(v)) then
table.insert(result, v)
end
end
end
return result
end
-- TODO: implement fuzzy search and maybe match highlights
function em:search_method(str)
-- also might be redefined by main script
-- convert to string just to make sure..
return tostring(str):lower():find(self.line:lower(), 1, true)
end
-- this module requires submit function to be defined in main script
function em:submit() self:update('no_submit_provided') end
function em:update_list(list)
-- for now this func doesn't handle cases when we have 'current_i' to update
-- it
self.list.full = list
if self.line ~= self.prev_line then self:filter_wrapper() end
end
-- PUBLIC METHODS END ---------------------------------------------------------
-- HELPER METHODS -------------------------------------------------------------
function em:get_line(_, v) -- [i]ndex, [v]alue
-- this func might be redefined in main script to get a custom-formatted line
-- default implementation of this func supposes that value.content field is a
-- String
local a = assdraw.ass_new()
local style = (self.list.current_i == v[self.index_field])
and 'current' or 'default'
a:append(self:reset_styles())
a:append(self:get_font_color(style))
-- content as default field, which is holding string
-- no point in moving it to main object since content itself is being
-- composed in THIS function, that might (and most likely, should) be
-- redefined in main script
a:append(v.content or 'Something is off in `get_line` func')
return a.text
end
-- REVIEW: for now i don't see normal way of mergin this func with below one
-- but it's being used only once
function em:reset_styles()
local a = assdraw.ass_new()
-- alignment top left, no word wrapping, border 0, shadow 0
a:append('{\\an7\\q2\\bord0\\shad0}')
a:append('{\\fs' .. self.font_size .. '}')
return a.text
end
-- function to get rid of some copypaste
function em:ass_new_wrapper()
local a = assdraw.ass_new()
a:new_event()
a:append(self:reset_styles())
return a
end
function em:get_font_color(style)
return '{\\1c&H' .. self.text_color[style] .. '}'
end
-- HELPER METHODS END ---------------------------------------------------------
--[[
The below code is a modified implementation of text input from mpv's console.lua:
https://github.com/mpv-player/mpv/blob/87c9eefb2928252497f6141e847b74ad1158bc61/player/lua/console.lua
I was too lazy to list all modifications i've done to the script, but if u
rly need to see those - do diff with the original code
]]
--
-------------------------------------------------------------------------------
-- START ORIGINAL MPV CODE --
-------------------------------------------------------------------------------
-- Copyright (C) 2019 the mpv developers
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted, provided that the above
-- copyright notice and this permission notice appear in all copies.
--
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-- WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-- MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
-- SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-- WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
function em:detect_platform()
local o = {}
-- Kind of a dumb way of detecting the platform but whatever
if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then
return 'windows'
elseif mp.get_property_native('options/macos-force-dedicated-gpu', o) ~= o then
return 'macos'
elseif os.getenv('WAYLAND_DISPLAY') then
return 'wayland'
end
return 'x11'
end
-- Escape a string for verbatim display on the OSD
function em:ass_escape(str)
-- There is no escape for '\' in ASS (I think?) but '\' is used verbatim if
-- it isn't followed by a recognised character, so add a zero-width
-- non-breaking space
str = str:gsub('\\', '\\\239\187\191')
str = str:gsub('{', '\\{')
str = str:gsub('}', '\\}')
-- Precede newlines with a ZWNBSP to prevent ASS's weird collapsing of
-- consecutive newlines
str = str:gsub('\n', '\239\187\191\\N')
-- Turn leading spaces into hard spaces to prevent ASS from stripping them
str = str:gsub('\\N ', '\\N\\h')
str = str:gsub('^ ', '\\h')
return str
end
-- Set the REPL visibility ("enable", Esc)
function em:set_active(active)
if active == self.is_active then return end
if active then
if ime_active == false then
mp.set_property_bool("input-ime", true)
end
self.is_active = true
self.insert_mode = false
mp.enable_messages('terminal-default')
self:define_key_bindings()
-- set flag 'was_paused' only if vid wasn't paused before EM init
if self.pause_on_open and not mp.get_property_bool("pause", false) then
mp.set_property_bool("pause", true)
self.was_paused = true
end
self:set_from_to()
self:update()
else
-- no need to call 'update' in this block cuz 'clear' method is calling it
if ime_active == false then
mp.set_property_bool("input-ime", false)
end
self.is_active = false
self:undefine_key_bindings()
if self.resume_on_exit == true or
(self.resume_on_exit == "only-if-was-paused" and self.was_paused) then
mp.set_property_bool("pause", false)
end
self:clear()
collectgarbage()
end
end
-- Naive helper function to find the next UTF-8 character in 'str' after 'pos'
-- by skipping continuation bytes. Assumes 'str' contains valid UTF-8.
function em:next_utf8(str, pos)
if pos > str:len() then return pos end
repeat
pos = pos + 1
until pos > str:len() or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
return pos
end
-- As above, but finds the previous UTF-8 charcter in 'str' before 'pos'
function em:prev_utf8(str, pos)
if pos <= 1 then return pos end
repeat
pos = pos - 1
until pos <= 1 or str:byte(pos) < 0x80 or str:byte(pos) > 0xbf
return pos
end
-- Insert a character at the current cursor position (any_unicode)
function em:handle_char_input(c)
if self.insert_mode then
self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self:next_utf8(self.line, self.cursor))
else
self.line = self.line:sub(1, self.cursor - 1) .. c .. self.line:sub(self.cursor)
end
self.cursor = self.cursor + #c
self:update()
end
-- Remove the character behind the cursor (Backspace)
function em:handle_backspace()
if self.cursor <= 1 then return end
local prev = self:prev_utf8(self.line, self.cursor)
self.line = self.line:sub(1, prev - 1) .. self.line:sub(self.cursor)
self.cursor = prev
self:update()
end
-- Remove the character in front of the cursor (Del)
function em:handle_del()
if self.cursor > self.line:len() then return end
self.line = self.line:sub(1, self.cursor - 1) .. self.line:sub(self:next_utf8(self.line, self.cursor))
self:update()
end
-- Toggle insert mode (Ins)
function em:handle_ins()
self.insert_mode = not self.insert_mode
end
-- Move the cursor to the next character (Right)
function em:next_char()
self.cursor = self:next_utf8(self.line, self.cursor)
self:update()
end
-- Move the cursor to the previous character (Left)
function em:prev_char()
self.cursor = self:prev_utf8(self.line, self.cursor)
self:update()
end
-- Clear the current line (Ctrl+C)
function em:clear()
self.line = ''
self.prev_line = ''
self.list.current_i = nil
self.list.pointer_i = 1
self.list.filtered = {}
self.list.show_from_to = {}
self.was_paused = false
self.cursor = 1
self.insert_mode = false
self.history_pos = #self.history + 1
self:update()
end
-- Run the current command and clear the line (Enter)
function em:handle_enter()
if #self:current() == 0 then
self:update('no_match')
return
end
if self.history[#self.history] ~= self.line then
self.history[#self.history + 1] = self.line
end
self:submit(self:current()[self.list.pointer_i])
self:set_active(false)
end
-- Go to the specified position in the command history
function em:go_history(new_pos)
local old_pos = self.history_pos
self.history_pos = new_pos
-- Restrict the position to a legal value
if self.history_pos > #self.history + 1 then
self.history_pos = #self.history + 1
elseif self.history_pos < 1 then
self.history_pos = 1
end
-- Do nothing if the history position didn't actually change
if self.history_pos == old_pos then
return
end
-- If the user was editing a non-history line, save it as the last history
-- entry. This makes it much less frustrating to accidentally hit Up/Down
-- while editing a line.
if old_pos == #self.history + 1 and self.line ~= '' and self.history[#self.history] ~= self.line then
self.history[#self.history + 1] = self.line
end
-- Now show the history line (or a blank line for #history + 1)
if self.history_pos <= #self.history then
self.line = self.history[self.history_pos]
else
self.line = ''
end
self.cursor = self.line:len() + 1
self.insert_mode = false
self:update()
end
-- Go to the specified relative position in the command history (Up, Down)
function em:move_history(amount)
self:go_history(self.history_pos + amount)
end
-- Go to the first command in the command history (PgUp)
function em:handle_pgup()
-- Determine the number of items to move up (half a page)
local half_page = math.ceil(self.lines_to_show / 2)
-- Move the history position up by half a page
self:change_selected_index(-half_page)
end
-- Stop browsing history and start editing a blank line (PgDown)
function em:handle_pgdown()
-- Determine the number of items to move down (half a page)
local half_page = math.ceil(self.lines_to_show / 2)
-- Move the history position down by half a page
self:change_selected_index(half_page)
end
-- Move to the start of the current word, or if already at the start, the start
-- of the previous word. (Ctrl+Left)
function em:prev_word()
-- This is basically the same as next_word() but backwards, so reverse the
-- string in order to do a "backwards" find. This wouldn't be as annoying
-- to do if Lua didn't insist on 1-based indexing.
self.cursor = self.line:len() - select(2, self.line:reverse():find('%s*[^%s]*', self.line:len() - self.cursor + 2)) + 1
self:update()
end
-- Move to the end of the current word, or if already at the end, the end of
-- the next word. (Ctrl+Right)
function em:next_word()
self.cursor = select(2, self.line:find('%s*[^%s]*', self.cursor)) + 1
self:update()
end
-- Move the cursor to the beginning of the line (HOME)
function em:go_home()
self.cursor = 1
self:update()
end
-- Move the cursor to the end of the line (END)
function em:go_end()
self.cursor = self.line:len() + 1
self:update()
end
-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
function em:del_word()
local before_cur = self.line:sub(1, self.cursor - 1)
local after_cur = self.line:sub(self.cursor)
before_cur = before_cur:gsub('[^%s]+%s*$', '', 1)
self.line = before_cur .. after_cur
self.cursor = before_cur:len() + 1
self:update()
end
-- Delete from the cursor to the end of the word (Ctrl+Del)
function em:del_next_word()
if self.cursor > self.line:len() then return end
local before_cur = self.line:sub(1, self.cursor - 1)
local after_cur = self.line:sub(self.cursor)
after_cur = after_cur:gsub('^%s*[^%s]+', '', 1)
self.line = before_cur .. after_cur
self:update()
end
-- Delete from the cursor to the end of the line (Ctrl+K)
function em:del_to_eol()
self.line = self.line:sub(1, self.cursor - 1)
self:update()
end
-- Delete from the cursor back to the start of the line (Ctrl+U)
function em:del_to_start()
self.line = self.line:sub(self.cursor)
self.cursor = 1
self:update()
end
-- Returns a string of UTF-8 text from the clipboard (or the primary selection)
function em:get_clipboard(clip)
-- Pick a better default font for Windows and macOS
local platform = self:detect_platform()
if platform == 'x11' then
local res = utils.subprocess({
args = { 'xclip', '-selection', clip and 'clipboard' or 'primary', '-out' },
playback_only = false,
})
if not res.error then
return res.stdout
end
elseif platform == 'wayland' then
local res = utils.subprocess({
args = { 'wl-paste', clip and '-n' or '-np' },
playback_only = false,
})
if not res.error then
return res.stdout
end
elseif platform == 'windows' then
local res = utils.subprocess({
args = { 'powershell', '-NoProfile', '-Command', [[& {
Trap {
Write-Error -ErrorRecord $_
Exit 1
}
$clip = ""
if (Get-Command "Get-Clipboard" -errorAction SilentlyContinue) {
$clip = Get-Clipboard -Raw -Format Text -TextFormatType UnicodeText
} else {
Add-Type -AssemblyName PresentationCore
$clip = [Windows.Clipboard]::GetText()
}
$clip = $clip -Replace "`r",""
$u8clip = [System.Text.Encoding]::UTF8.GetBytes($clip)
[Console]::OpenStandardOutput().Write($u8clip, 0, $u8clip.Length)
}]] },
playback_only = false,
})
if not res.error then
return res.stdout
end
elseif platform == 'macos' then
local res = utils.subprocess({
args = { 'pbpaste' },
playback_only = false,
})
if not res.error then
return res.stdout
end
end
return ''
end
-- Paste text from the window-system's clipboard. 'clip' determines whether the
-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
function em:paste(clip)
local text = self:get_clipboard(clip)
local before_cur = self.line:sub(1, self.cursor - 1)
local after_cur = self.line:sub(self.cursor)
self.line = before_cur .. text .. after_cur
self.cursor = self.cursor + text:len()
self:update()
end
-- List of input bindings. This is a weird mashup between common GUI text-input
-- bindings and readline bindings.
function em:get_bindings()
local bindings = {
{ 'ctrl+[', function() self:set_active(false) end },
{ 'ctrl+g', function() self:set_active(false) end },
{ 'esc', function() self:set_active(false) end },
{ 'enter', function() self:handle_enter() end },
{ 'kp_enter', function() self:handle_enter() end },
{ 'ctrl+m', function() self:handle_enter() end },
{ 'bs', function() self:handle_backspace() end },
{ 'shift+bs', function() self:handle_backspace() end },
{ 'ctrl+h', function() self:handle_backspace() end },
{ 'del', function() self:handle_del() end },
{ 'shift+del', function() self:handle_del() end },
{ 'ins', function() self:handle_ins() end },
{ 'shift+ins', function() self:paste(false) end },
{ 'mbtn_mid', function() self:paste(false) end },
{ 'left', function() self:prev_char() end },
{ 'ctrl+b', function() self:prev_char() end },
{ 'right', function() self:next_char() end },
{ 'ctrl+f', function() self:next_char() end },
{ 'ctrl+k', function() self:change_selected_index(-1) end },
{ 'ctrl+p', function() self:change_selected_index(-1) end },
{ 'ctrl+j', function() self:change_selected_index(1) end },
{ 'ctrl+n', function() self:change_selected_index(1) end },
{ 'up', function() self:move_history(-1) end },
{ 'alt+p', function() self:move_history(-1) end },
{ 'wheel_up', function() self:move_history(-1) end },
{ 'down', function() self:move_history(1) end },
{ 'alt+n', function() self:move_history(1) end },
{ 'wheel_down', function() self:move_history(1) end },
{ 'wheel_left', function() end },
{ 'wheel_right', function() end },
{ 'ctrl+left', function() self:prev_word() end },
{ 'alt+b', function() self:prev_word() end },
{ 'ctrl+right', function() self:next_word() end },
{ 'alt+f', function() self:next_word() end },
{ 'ctrl+a', function() self:go_home() end },
{ 'home', function() self:go_home() end },
{ 'ctrl+e', function() self:go_end() end },
{ 'end', function() self:go_end() end },
{ 'ctrl+shift+f',function() self:handle_pgdown() end },
{ 'ctrl+shift+b',function() self:handle_pgup() end },
{ 'pgdwn', function() self:handle_pgdown() end },
{ 'pgup', function() self:handle_pgup() end },
{ 'ctrl+c', function() self:clear() end },
{ 'ctrl+d', function() self:handle_del() end },
{ 'ctrl+u', function() self:del_to_start() end },
{ 'ctrl+v', function() self:paste(true) end },
{ 'meta+v', function() self:paste(true) end },
{ 'ctrl+bs', function() self:del_word() end },
{ 'ctrl+w', function() self:del_word() end },
{ 'ctrl+del', function() self:del_next_word() end },
{ 'alt+d', function() self:del_next_word() end },
{ 'kp_dec', function() self:handle_char_input('.') end },
}
for i = 0, 9 do
bindings[#bindings + 1] =
{ 'kp' .. i, function() self:handle_char_input('' .. i) end }
end
return bindings
end
function em:text_input(info)
if info.key_text and (info.event == "press" or info.event == "down"
or info.event == "repeat")
then
self:handle_char_input(info.key_text)
end
end
function em:define_key_bindings()
if #self.key_bindings > 0 then
return
end
for _, bind in ipairs(self:get_bindings()) do
-- Generate arbitrary name for removing the bindings later.
local name = "search_" .. (#self.key_bindings + 1)
self.key_bindings[#self.key_bindings + 1] = name
mp.add_forced_key_binding(bind[1], name, bind[2], { repeatable = true })
end
mp.add_forced_key_binding("any_unicode", "search_input", function(...)
self:text_input(...)
end, { repeatable = true, complex = true })
self.key_bindings[#self.key_bindings + 1] = "search_input"
end
function em:undefine_key_bindings()
for _, name in ipairs(self.key_bindings) do
mp.remove_key_binding(name)
end
self.key_bindings = {}
end
-------------------------------------------------------------------------------
-- END ORIGINAL MPV CODE --
-------------------------------------------------------------------------------
return em
@@ -0,0 +1,206 @@
--[[
An addon for mpv-file-browser which adds a Favourites path that can be loaded from the ROOT
]]--
local mp = require "mp"
local msg = require "mp.msg"
local fb = require 'file-browser'
local save_path = mp.command_native({"expand-path", "~~/script-opts/file_browser_favourites.txt"}) --[[@as string]]
do
local file = io.open(save_path, "a+")
if not file then
msg.error("cannot access file", ("%q"):format(save_path), "make sure that the directory exists")
return {}
end
file:close()
end
---@type Item[]
local favourites = {}
local favourites_loaded = false
---@type ParserConfig
local favs = {
api_version = "1.8.0",
priority = 30,
cursor = 1
}
local use_virtual_directory = true
---@type table<string,string>
local full_paths = {}
---@param str string
---@return Item
local function create_favourite_object(str)
local item = {
type = str:sub(-1) == "/" and "dir" or "file",
path = str,
redirect = not use_virtual_directory,
name = str:match("([^/]+/?)$")
}
full_paths[str:match("([^/]+)/?$")] = str
return item
end
---@param self Parser
function favs:setup()
self:register_root_item('Favourites/')
end
local function update_favourites()
local file = io.open(save_path, "r")
if not file then return end
favourites = {}
for str in file:lines() do
table.insert(favourites, create_favourite_object(str))
end
file:close()
favourites_loaded = true
end
function favs:can_parse(directory)
return directory:find("Favourites/") == 1
end
---@async
---@param self Parser
---@param directory string
---@return List?
---@return Opts?
function favs:parse(directory)
if not favourites_loaded then update_favourites() end
if directory == "Favourites/" then
local opts = {
filtered = true,
sorted = true
}
return favourites, opts
end
if use_virtual_directory then
-- converts the relative favourite path into a full path
local name = directory:match("Favourites/([^/]+)/?")
local _, finish = directory:find("Favourites/([^/]+/?)")
local full_path = (full_paths[name] or "")..directory:sub(finish+1)
local list, opts = self:defer(full_path or "")
if not list then return nil end
opts = opts or {}
opts.id = self:get_id()
if opts.directory_label then
opts.directory_label = opts.directory_label:gsub(full_paths[name], "Favourites/"..name..'/')
if opts.directory_label:find("Favourites/") ~= 1 then opts.directory_label = nil end
end
for _, item in ipairs(list) do
if not item.path then item.redirect = false end
item.path = item.path or full_path..item.name
end
return list, opts
end
local path = full_paths[ directory:match("([^/]+/?)$") or "" ]
local list, opts = self:defer(path)
if not list then return nil end
opts = opts or {}
opts.directory = opts.directory or path
return list, opts
end
---@param path string
---@return integer?
---@return Item?
local function get_favourite(path)
for index, value in ipairs(favourites) do
if value.path == path then return index, value end
end
end
--update the browser with new contents of the file
---@async
local function update_browser()
if favs.get_directory():find("^[fF]avourites/$") then
local cursor = favs.get_selected_index()
fb.rescan_await()
fb.set_selected_index(cursor)
else
fb.clear_cache({'favourites/', 'Favourites/'})
end
end
--write the contents of favourites to the file
local function write_to_file()
local file = io.open(save_path, "w+")
if not file then return msg.error(file, "could not open favourites file") end
for _, item in ipairs(favourites) do
file:write(string.format("%s\n", item.path))
end
file:close()
end
local function add_favourite(path)
if get_favourite(path) then return end
update_favourites()
table.insert(favourites, create_favourite_object(path))
write_to_file()
end
local function remove_favourite(path)
update_favourites()
local index = get_favourite(path)
if not index then return end
table.remove(favourites, index)
write_to_file()
end
local function move_favourite(path, direction)
update_favourites()
local index, item = get_favourite(path)
if not index or not favourites[index + direction] then return end
favourites[index] = favourites[index + direction]
favourites[index + direction] = item
write_to_file()
end
---@async
local function toggle_favourite(cmd, state, co)
local path = fb.get_full_path(state.list[state.selected], state.directory)
if state.directory:find("[fF]avourites/$") then remove_favourite(path)
else add_favourite(path) end
update_browser()
end
---@async
local function move_key(cmd, state, co)
if not state.directory:find("[fF]avourites/") then return false end
local path = fb.get_full_path(state.list[state.selected], state.directory)
local cursor = fb.get_selected_index()
if cmd.name == favs:get_id().."/move_up" then
move_favourite(path, -1)
fb.set_selected_index(cursor-1)
else
move_favourite(path, 1)
fb.set_selected_index(cursor+1)
end
update_browser()
end
update_favourites()
favs.keybinds = {
{ "F", "toggle_favourite", toggle_favourite, {}, },
{ "Ctrl+UP", "move_up", move_key, {repeatable = true} },
{ "Ctrl+DOWN", "move_down", move_key, {repeatable = true} },
}
return favs
@@ -0,0 +1,39 @@
--[[
An addon for file-browser which decodes URLs so that they are more readable
]]
---@type ParserConfig
local urldecode = {
priority = 5,
api_version = "1.0.0"
}
--decodes a URL address
--this piece of code was taken from: https://stackoverflow.com/questions/20405985/lua-decodeuri-luvit/20406960#20406960
---@type fun(s: string): string
local decodeURI
do
local char, gsub, tonumber = string.char, string.gsub, tonumber
local function _(hex) return char(tonumber(hex, 16)) end
function decodeURI(s)
s = gsub(s, '%%(%x%x)', _)
return s
end
end
function urldecode:can_parse(directory)
return self.get_protocol(directory) ~= nil
end
---@async
function urldecode:parse(directory)
local list, opts = self:defer(directory)
opts = opts or {}
if opts.directory and not self.get_protocol(opts.directory) then return list, opts end
opts.directory_label = decodeURI(opts.directory_label or (opts.directory or directory))
return list, opts
end
return urldecode
@@ -0,0 +1,206 @@
--[[
An addon for mpv-file-browser which adds a Favourites path that can be loaded from the ROOT
]]--
local mp = require "mp"
local msg = require "mp.msg"
local fb = require 'file-browser'
local save_path = mp.command_native({"expand-path", "~~/script-opts/file_browser_favourites.txt"}) --[[@as string]]
do
local file = io.open(save_path, "a+")
if not file then
msg.error("cannot access file", ("%q"):format(save_path), "make sure that the directory exists")
return {}
end
file:close()
end
---@type Item[]
local favourites = {}
local favourites_loaded = false
---@type ParserConfig
local favs = {
api_version = "1.8.0",
priority = 30,
cursor = 1
}
local use_virtual_directory = true
---@type table<string,string>
local full_paths = {}
---@param str string
---@return Item
local function create_favourite_object(str)
local item = {
type = str:sub(-1) == "/" and "dir" or "file",
path = str,
redirect = not use_virtual_directory,
name = str:match("([^/]+/?)$")
}
full_paths[str:match("([^/]+)/?$")] = str
return item
end
---@param self Parser
function favs:setup()
self:register_root_item('Favourites/')
end
local function update_favourites()
local file = io.open(save_path, "r")
if not file then return end
favourites = {}
for str in file:lines() do
table.insert(favourites, create_favourite_object(str))
end
file:close()
favourites_loaded = true
end
function favs:can_parse(directory)
return directory:find("Favourites/") == 1
end
---@async
---@param self Parser
---@param directory string
---@return List?
---@return Opts?
function favs:parse(directory)
if not favourites_loaded then update_favourites() end
if directory == "Favourites/" then
local opts = {
filtered = true,
sorted = true
}
return favourites, opts
end
if use_virtual_directory then
-- converts the relative favourite path into a full path
local name = directory:match("Favourites/([^/]+)/?")
local _, finish = directory:find("Favourites/([^/]+/?)")
local full_path = (full_paths[name] or "")..directory:sub(finish+1)
local list, opts = self:defer(full_path or "")
if not list then return nil end
opts = opts or {}
opts.id = self:get_id()
if opts.directory_label then
opts.directory_label = opts.directory_label:gsub(full_paths[name], "Favourites/"..name..'/')
if opts.directory_label:find("Favourites/") ~= 1 then opts.directory_label = nil end
end
for _, item in ipairs(list) do
if not item.path then item.redirect = false end
item.path = item.path or full_path..item.name
end
return list, opts
end
local path = full_paths[ directory:match("([^/]+/?)$") or "" ]
local list, opts = self:defer(path)
if not list then return nil end
opts = opts or {}
opts.directory = opts.directory or path
return list, opts
end
---@param path string
---@return integer?
---@return Item?
local function get_favourite(path)
for index, value in ipairs(favourites) do
if value.path == path then return index, value end
end
end
--update the browser with new contents of the file
---@async
local function update_browser()
if favs.get_directory():find("^[fF]avourites/$") then
local cursor = favs.get_selected_index()
fb.rescan_await()
fb.set_selected_index(cursor)
else
fb.clear_cache({'favourites/', 'Favourites/'})
end
end
--write the contents of favourites to the file
local function write_to_file()
local file = io.open(save_path, "w+")
if not file then return msg.error(file, "could not open favourites file") end
for _, item in ipairs(favourites) do
file:write(string.format("%s\n", item.path))
end
file:close()
end
local function add_favourite(path)
if get_favourite(path) then return end
update_favourites()
table.insert(favourites, create_favourite_object(path))
write_to_file()
end
local function remove_favourite(path)
update_favourites()
local index = get_favourite(path)
if not index then return end
table.remove(favourites, index)
write_to_file()
end
local function move_favourite(path, direction)
update_favourites()
local index, item = get_favourite(path)
if not index or not favourites[index + direction] then return end
favourites[index] = favourites[index + direction]
favourites[index + direction] = item
write_to_file()
end
---@async
local function toggle_favourite(cmd, state, co)
local path = fb.get_full_path(state.list[state.selected], state.directory)
if state.directory:find("[fF]avourites/$") then remove_favourite(path)
else add_favourite(path) end
update_browser()
end
---@async
local function move_key(cmd, state, co)
if not state.directory:find("[fF]avourites/") then return false end
local path = fb.get_full_path(state.list[state.selected], state.directory)
local cursor = fb.get_selected_index()
if cmd.name == favs:get_id().."/move_up" then
move_favourite(path, -1)
fb.set_selected_index(cursor-1)
else
move_favourite(path, 1)
fb.set_selected_index(cursor+1)
end
update_browser()
end
update_favourites()
favs.keybinds = {
{ "F", "toggle_favourite", toggle_favourite, {}, },
{ "Ctrl+UP", "move_up", move_key, {repeatable = true} },
{ "Ctrl+DOWN", "move_down", move_key, {repeatable = true} },
}
return favs
@@ -0,0 +1,45 @@
local fb = require "file-browser"
local opt = require "mp.options"
local o = {
--list of absolute paths separated by the root separators
paths = ""
}
--config file stored in ~~/script-opts/file-browser/filter.conf
opt.read_options(o, "file-browser/filter")
local parser = {
priority = 10,
api_version = "1.3.0"
}
local paths = {}
for str in fb.iterate_opt(o.paths) do
paths[str] = true
end
local function filter(path)
return paths[path]
end
function parser:can_parse()
return true
end
function parser:parse(directory)
local list, opts = self:defer(directory)
if not list then return list, opts end
directory = opts.directory or directory
for i=#list, 1, -1 do
if filter( fb.get_full_path(list[i], directory) ) then
table.remove(list, i)
end
end
return list, opts
end
return parser
@@ -0,0 +1,41 @@
local mp = require 'mp'
local msg = require 'mp.msg'
local fb = require 'file-browser'
local isos = {
name = 'iso-loader',
priority = 20,
api_version = '1.5'
}
function isos:setup()
fb.add_default_extension('iso')
end
function isos:can_parse()
return true
end
function isos:parse(directory, parse_state)
local list, opts = self:defer(directory, parse_state)
if not list or #list == 0 then return list, opts end
for _, item in ipairs(list) do
local path = fb.get_full_path(item, opts.directory or directory)
if fb.get_extension(path) == 'iso' then
item.mpv_options = { ['bluray-device'] = path, ['dvd-device'] = path }
item.path = 'bd://'
end
end
return list, opts
end
mp.add_hook('on_load_fail', 50, function()
if mp.get_property('stream-open-filename') == 'bd://' then
msg.info('failed to load bluray-device, attempting dvd-device')
mp.set_property('stream-open-filename', 'dvd://')
end
end)
return isos
@@ -0,0 +1,124 @@
--[[
This file is an internal file-browser addon.
It should not be imported like a normal module.
Allows searching the current directory.
]]--
local msg = require "mp.msg"
local fb = require "file-browser"
local input_loaded, input = pcall(require, "mp.input")
local user_input_loaded, user_input = pcall(require, "user-input-module")
---@type ParserConfig
local find = {
api_version = "1.3.0"
}
---@type thread|nil
local latest_coroutine = nil
---@type State
local global_fb_state = getmetatable(fb.get_state()).__original
---@param name string
---@param query string
---@return boolean
local function compare(name, query)
if name:find(query) then return true end
if name:lower():find(query) then return true end
if name:upper():find(query) then return true end
return false
end
---@async
---@param key Keybind
---@param state State
---@param co thread
---@return boolean?
local function main(key, state, co)
if not state.list then return false end
---@type string
local text
if key.name == "find/find" then text = "Find: enter search string"
else text = "Find: enter advanced search string" end
if input_loaded then
input.get({
prompt = text .. "\n>",
id = "file-browser/find",
submit = fb.coroutine.callback(),
})
elseif user_input_loaded then
user_input.get_user_input( fb.coroutine.callback(), { text = text, id = "find", replace = true } )
end
local query, error = coroutine.yield()
if input_loaded then input.terminate() end
if not query then return msg.debug(error) end
-- allow the directory to be changed before this point
local list = fb.get_list()
local parse_id = global_fb_state.co
if key.name == "find/find" then
query = fb.pattern_escape(query)
end
local results = {}
for index, item in ipairs(list) do
if compare(item.label or item.name, query) then
table.insert(results, index)
end
end
if (#results < 1) then
msg.warn("No matching items for '"..query.."'")
return
end
--keep cycling through the search results if any are found
--putting this into a separate coroutine removes any passthrough ambiguity
--the final return statement should return to `step_find` not any other function
---@async
fb.coroutine.run(function()
latest_coroutine = coroutine.running()
---@type number
local rindex = 1
while (true) do
if rindex == 0 then rindex = #results
elseif rindex == #results + 1 then rindex = 1 end
fb.set_selected_index(results[rindex])
local direction = coroutine.yield(true) --[[@as number]]
rindex = rindex + direction
if parse_id ~= global_fb_state.co then
latest_coroutine = nil
return
end
end
end)
end
local function step_find(key)
if not latest_coroutine then return false end
---@type number
local direction = 0
if key.name == "find/next" then direction = 1
elseif key.name == "find/prev" then direction = -1 end
return fb.coroutine.resume_err(latest_coroutine, direction)
end
find.keybinds = {
{"Ctrl+f", "find", main, {}},
{"Ctrl+F", "find_advanced", main, {}},
{"n", "next", step_find, {}},
{"N", "prev", step_find, {}},
}
return find
@@ -0,0 +1,31 @@
--[[
An addon for mpv-file-browser which displays ~/ for the home directory instead of the full path
]]--
local mp = require "mp"
local fb = require "file-browser"
local home = fb.fix_path(mp.command_native({"expand-path", "~/"}) --[[@as string]], true)
---@type ParserConfig
local home_label = {
priority = 100,
api_version = "1.0.0"
}
function home_label:can_parse(directory)
if not fb.get_opt('home_label') then return false end
return directory:sub(1, home:len()) == home
end
---@async
function home_label:parse(directory)
local list, opts = self:defer(directory)
if not opts then opts = {} end
if (not opts.directory or opts.directory == directory) and not opts.directory_label then
opts.directory_label = "~/"..(directory:sub(home:len()+1) or "")
end
return list, opts
end
return home_label
@@ -0,0 +1,218 @@
--[[
An addon for mpv-file-browser which uses the Windows dir command to parse native directories
This behaves near identically to the native parser, but IO is done asynchronously.
Available at: https://github.com/CogentRedTester/mpv-file-browser/tree/master/addons
]]--
local mp = require "mp"
local msg = require "mp.msg"
local fb = require "file-browser"
local PLATFORM = fb.get_platform()
---@param bytes string
---@return fun(): number, number
local function byte_iterator(bytes)
---@async
---@return number?
local function iter()
for i = 1, #bytes do
coroutine.yield(bytes:byte(i), i)
end
error('malformed utf16le string - expected byte but found end of string')
end
return coroutine.wrap(iter)
end
---@param bits number
---@param by number
---@return number
local function lshift(bits, by)
return bits * 2^by
end
---@param bits number
---@param by number
---@return integer
local function rshift(bits, by)
return math.floor(bits / 2^by)
end
---@param bits number
---@param i number
---@return number
local function bits_below(bits, i)
return bits % 2^i
end
---@param bits number
---@param i number exclusive
---@param j number inclusive
---@return integer
local function bits_between(bits, i, j)
return rshift(bits_below(bits, j), i)
end
---@param bytes string
---@return number[]
local function utf16le_to_unicode(bytes)
msg.trace('converting from utf16-le to unicode codepoints')
---@type number[]
local codepoints = {}
local get_byte = byte_iterator(bytes)
while true do
-- start of a char
local success, little, i = pcall(get_byte)
if not success then break end
local big = get_byte()
local codepoint = little + lshift(big, 8)
if codepoint < 0xd800 or codepoint > 0xdfff then
table.insert(codepoints, codepoint)
else
-- handling surrogate pairs
-- grab the next two bytes to grab the low surrogate
local high_pair = codepoint
local low_pair = get_byte() + lshift(get_byte(), 8)
if high_pair >= 0xdc00 then
error(('malformed utf16le string at byte #%d (0x%04X) - high surrogate pair should be < 0xDC00'):format(i, high_pair))
elseif low_pair < 0xdc00 then
error(('malformed utf16le string at byte #%d (0x%04X) - low surrogate pair should be >= 0xDC00'):format(i+2, low_pair))
end
-- The last 10 bits of each surrogate are the two halves of the codepoint
-- https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF
local high_bits = bits_below(high_pair, 10)
local low_bits = bits_below(low_pair, 10)
local surrogate_par = (low_bits + lshift(high_bits, 10)) + 0x10000
table.insert(codepoints, surrogate_par)
end
end
return codepoints
end
---@param codepoints number[]
---@return string
local function unicode_to_utf8(codepoints)
---@type number[]
local bytes = {}
-- https://en.wikipedia.org/wiki/UTF-8#Description
for i, codepoint in ipairs(codepoints) do
if codepoint >= 0xd800 and codepoint <= 0xdfff then
error(('codepoint %d (U+%05X) is within the reserved surrogate pair range (U+D800-U+DFFF)'):format(i, codepoint))
elseif codepoint <= 0x7f then
table.insert(bytes, codepoint)
elseif codepoint <= 0x7ff then
table.insert(bytes, 0xC0 + rshift(codepoint, 6))
table.insert(bytes, 0x80 + bits_below(codepoint, 6))
elseif codepoint <= 0xffff then
table.insert(bytes, 0xE0 + rshift(codepoint, 12))
table.insert(bytes, 0x80 + bits_between(codepoint, 6, 12))
table.insert(bytes, 0x80 + bits_below(codepoint, 6))
elseif codepoint <= 0x10ffff then
table.insert(bytes, 0xF0 + rshift(codepoint, 18))
table.insert(bytes, 0x80 + bits_between(codepoint, 12, 18))
table.insert(bytes, 0x80 + bits_between(codepoint, 6, 12))
table.insert(bytes, 0x80 + bits_below(codepoint, 6))
else
error(('codepoint %d (U+%05X) is larger than U+10FFFF'):format(i, codepoint))
end
end
return string.char(table.unpack(bytes))
end
local function utf8(text)
return unicode_to_utf8(utf16le_to_unicode(text))
end
---@type ParserConfig
local dir = {
priority = 109,
api_version = "1.9.0",
name = "cmd-dir",
keybind_name = "file"
}
---@async
---@param args string[]
---@param parse_state ParseState
---@return string|nil
local function command(args, parse_state)
local async = mp.command_native_async({
name = "subprocess",
playback_only = false,
capture_stdout = true,
capture_stderr = true,
args = args,
}, fb.coroutine.callback(30) )
---@type boolean, boolean, MPVSubprocessResult
local completed, _, cmd = parse_state:yield()
if not completed then
msg.warn('read timed out for:', table.unpack(args))
mp.abort_async_command(async)
return nil
end
local success = xpcall(function()
cmd.stdout = utf8(cmd.stdout) or ''
cmd.stderr = utf8(cmd.stderr) or ''
end, fb.traceback)
if not success then return msg.error('failed to convert utf16-le string to utf8') end
--dir returns this exact error message if the directory is empty
if cmd.status == 1 and cmd.stderr == "File Not Found\r\n" then cmd.status = 0 end
if cmd.status ~= 0 then return msg.error(cmd.stderr) end
return cmd.status == 0 and cmd.stdout or nil
end
function dir:can_parse(directory)
if not fb.get_opt('windir_parser') then return false end
return PLATFORM == 'windows' and directory ~= '' and not fb.get_protocol(directory)
end
---@async
function dir:parse(directory, parse_state)
local list = {}
-- the dir command expects backslashes for our paths
directory = string.gsub(directory, "/", "\\")
local dirs = command({ "cmd", "/U", "/c", "dir", "/b", "/ad", directory }, parse_state)
if not dirs then return end
local files = command({ "cmd", "/U", "/c", "dir", "/b", "/a-d", directory }, parse_state)
if not files then return end
for name in dirs:gmatch("[^\n\r]+") do
name = name.."/"
if fb.valid_dir(name) then
table.insert(list, { name = name, type = "dir" })
msg.trace(name)
end
end
for name in files:gmatch("[^\n\r]+") do
if fb.valid_file(name) then
table.insert(list, { name = name, type = "file" })
msg.trace(name)
end
end
return list, { filtered = true }
end
return dir
@@ -0,0 +1,62 @@
--[[
This file is an internal file-browser addon.
It should not be imported like a normal module.
Automatically populates the root with windows drives on startup.
Ctrl+r will add new drives mounted since startup.
Drives will only be added if they are not already present in the root.
]]
local mp = require 'mp'
local msg = require 'mp.msg'
local fb = require 'file-browser'
local PLATFORM = fb.get_platform()
---returns a list of windows drives
---@return string[]?
local function get_drives()
---@type MPVSubprocessResult?, string?
local result, err = mp.command_native({
name = 'subprocess',
playback_only = false,
capture_stdout = true,
args = {'fsutil', 'fsinfo', 'drives'}
})
if not result then return msg.error(err) end
if result.status ~= 0 then return msg.error('could not read windows root') end
local root = {}
for drive in result.stdout:gmatch("(%a:)\\") do
table.insert(root, drive..'/')
end
return root
end
-- adds windows drives to the root if they are not already present
local function import_drives()
if fb.get_opt('auto_detect_windows_drives') and PLATFORM ~= 'windows' then return end
local drives = get_drives()
if not drives then return end
for _, drive in ipairs(drives) do
fb.register_root_item(drive)
end
end
local keybind = {
key = 'Ctrl+r',
name = 'import_root_drives',
command = import_drives,
parser = 'root',
passthrough = true
}
---@type ParserConfig
return {
api_version = '1.9.0',
setup = import_drives,
keybinds = { keybind }
}
@@ -0,0 +1,86 @@
local msg = require 'mp.msg'
local utils = require 'mp.utils'
local fb = require 'file-browser'
local parser = {
priority = 105,
api_version = '1.2.0'
}
-- stores a table of the parsers loaded by file-browser
-- we will use this to check if a parser is for a local file system
local parsers
local sort_mode = 0
function parser:setup()
parsers = fb.get_parsers()
end
function parser:parse(directory)
if sort_mode == 0 or fb.get_protocol(directory) then return end
local list, opts = self:defer(directory)
if not list then return list, opts end
-- Only run this on parsers that are for the local filesystem.
-- We assume that custom addons for the local filesystem are setting the keybind_name field to 'file'
-- for compatability.
if parsers[opts.id] then
if parsers[opts.id].keybind_name ~= 'file' and parsers[opts.id].name ~= 'file' then
return list, opts
end
end
directory = opts.directory or directory
local cache = {}
-- gets the file info of an item
-- uses memoisation to speed things up
function get_file_info(item)
if cache[item] then return cache[item] end
local path = fb.get_full_path(item, directory)
local file_info = utils.file_info(path)
if not file_info then
msg.warn('failed to read file info for', path)
return {}
end
cache[item] = file_info
return file_info
end
-- sorts the items based on the latest modification time
-- if mtime is undefined due to a file read failure then use 0
table.sort(list, function(a, b)
-- `dir` will compare as less than `file`
if a.type ~= b.type then return a.type < b.type end
if sort_mode == 1 then
return (get_file_info(a).mtime or 0) < (get_file_info(b).mtime or 0)
elseif sort_mode == 2 then
return (get_file_info(a).mtime or 0) > (get_file_info(b).mtime or 0)
elseif sort_mode == 3 then
return (get_file_info(a).size or 0) < (get_file_info(b).size or 0)
elseif sort_mode == 4 then
return (get_file_info(a).size or 0) > (get_file_info(b).size or 0)
end
end)
opts.sorted = true
return list, opts
end
-- adds the keybind to toggle sorting
parser.keybinds = {
{
key = '^',
name = 'toggle_sort',
command = function()
sort_mode = sort_mode + 1
if sort_mode > 4 then sort_mode = 0 end
fb.rescan()
end
}
}
return parser
@@ -0,0 +1,39 @@
--[[
An addon for file-browser which decodes URLs so that they are more readable
]]
---@type ParserConfig
local urldecode = {
priority = 5,
api_version = "1.0.0"
}
--decodes a URL address
--this piece of code was taken from: https://stackoverflow.com/questions/20405985/lua-decodeuri-luvit/20406960#20406960
---@type fun(s: string): string
local decodeURI
do
local char, gsub, tonumber = string.char, string.gsub, tonumber
local function _(hex) return char(tonumber(hex, 16)) end
function decodeURI(s)
s = gsub(s, '%%(%x%x)', _)
return s
end
end
function urldecode:can_parse(directory)
return self.get_protocol(directory) ~= nil
end
---@async
function urldecode:parse(directory)
local list, opts = self:defer(directory)
opts = opts or {}
if opts.directory and not self.get_protocol(opts.directory) then return list, opts end
opts.directory_label = decodeURI(opts.directory_label or (opts.directory or directory))
return list, opts
end
return urldecode
@@ -0,0 +1,51 @@
local fb = require "file-browser"
local fb_utils = require 'modules.utils'
local PLATFORM = fb.get_platform()
-- Only enable Windows-specific sorting on Windows platforms
if PLATFORM == 'windows' then
-- this code is based on https://github.com/mpvnet-player/mpv.net/issues/575#issuecomment-1817413401
local ffi = require "ffi"
local winapi = {
ffi = ffi,
C = ffi.C,
CP_UTF8 = 65001,
shlwapi = ffi.load("shlwapi"),
}
-- ffi code from https://github.com/po5/thumbfast, Mozilla Public License Version 2.0
ffi.cdef[[
int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr,
int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar);
int __stdcall StrCmpLogicalW(wchar_t *psz1, wchar_t *psz2);
]]
winapi.utf8_to_wide = function(utf8_str)
if utf8_str then
local utf16_len = winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, utf8_str, -1, nil, 0)
if utf16_len > 0 then
local utf16_str = winapi.ffi.new("wchar_t[?]", utf16_len)
if winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, utf8_str, -1, utf16_str, utf16_len) > 0 then
return utf16_str
end
end
end
return ""
end
fb_utils.sort = function (t)
table.sort(t, function(a, b)
local a_wide = winapi.utf8_to_wide(a.type:sub(1, 1) .. (a.label or a.name))
local b_wide = winapi.utf8_to_wide(b.type:sub(1, 1) .. (b.label or b.name))
return winapi.shlwapi.StrCmpLogicalW(a_wide, b_wide) == -1
end)
return t
end
end
return { api_version = '1.2.0' }
+19
View File
@@ -0,0 +1,19 @@
### 该文件夹下存放mpv脚本的对应设置文件
通常脚本设置文件名与所属脚本文件同名,注意脚本文件名中的`-`默认需转译成`_`。实际以脚本开发者设定为准。
脚本设置文件切勿美化格式(例如加入无意义的空格);切勿在参数后注释(应单独另起一行写注释)。
脚本及其设置文件可能不支持windows的CRLF换行尝试更改为LF
以上所述情况在自行修改的过程中都可能导致脚本设置文件(部分)失效。
以下为mpv内置脚本所使用的设置文件
```
console.conf
osc.conf
stats.conf
ytdl_hook.conf
```
+4
View File
@@ -0,0 +1,4 @@
# 设置自动保存文件播放进度及状态的时间间隔单位为秒。默认值60 秒
save_interval=60
# 设置文件播放进度的百分比满足时自动删除文件播放进度及状态。默认值99
percent_pos=99
+34
View File
@@ -0,0 +1,34 @@
###不支持参数后注释,须另起一行
##禁用所有自动加载相关功能默认no
#disabled=yes
##是否自动加载当前目录不含子目录所有图片到播放列表默认yes
#images=no
##是否自动加载当前目录不含子目录所有视频到播放列表默认yes
#videos=no
##是否自动加载当前目录不含子目录所有音频到播放列表默认yes
#audio=no
##指定额外需要用于加载的图片、视频和音频扩展白名单
additional_image_exts=jfif
additional_video_exts=asf,f4v,rm,ts,vob
additional_audio_exts=dsf,spx
##<auto|recursive|lazy|默认 ignore> 打开文件时,选择递归、懒惰或忽略全部子目录
directory_mode=ignore
##是否掠过隐藏文件默认yes
#ignore_hidden=no
##指定需要略过的文件名模式,多个模式之间用逗号分隔。默认值为空
##支持 lua 模式写法,可使用 % 转义 ,
#ignore_patterns=^~,^bak-,%.bak$
#是否只自动载入相同类型的文件视频、音频、图片默认no
same_type=yes
#是否只自动载入相似系列的文件默认no
#same_series=yes
+28
View File
@@ -0,0 +1,28 @@
# 手动指定可执行文件的绝对路径,如果以下程序不存在于环境变量中
# 1. ffmpeg
#ffmpeg_path=C:/Program Files/ffmpeg/bin/ffmpeg.exe
ffmpeg_path=ffmpeg
# 2. ffsubsync
#ffsubsync_path=C:/Program Files/ffsubsync/ffsubsync.exe
#ffsubsync_path=/home/user/.local/bin/ffsubsync
# 3. alass
#alass_path=C:/Program Files/ffmpeg/bin/alass.exe
#alass_path=/usr/bin/alass
##⇘⇘以下路径设置为在 mpv 程序所在的根目录下查找指定程序
alass_path=alass
# 首选的字幕同步工具。允许选项:'ffsubsync','alass','ask'.
# 如果设置为“ask”脚本每次都会要求选择工具
# 1. 用于与音频同步的首选工具。('ffsubsync','alass','ask')
audio_subsync_tool=alass
# 2. 用于与字幕同步的首选工具。('ffsubsync','alass','ask')
altsub_subsync_tool=alass
# 禁用原字幕 (yes,no)
# 尝试字幕同步操作完成后,告诉 mpv 忽略原来的字幕轨道
unload_old_sub=no
+12
View File
@@ -0,0 +1,12 @@
# 黑名单或白名单只需设置其中一种
# 白名单,只允许视频格式
#whitelist=3gp,amr,amv,asf,avi,avi,bdmv,f4v,flv,ifo,iso,m2ts,m4v,mkv,mov,mp4,mpeg,mpg,ogv,rm,rmvb,ts,vob,webm,wmv
# 或者,视频附近常见的黑名单格式
blacklist=mpls,mks,mka,weba,aqt,ass,gsub,idx,jss,lrc,mks,pgs,pjs,psb,rt,slt,smi,sub,sup,sbv,srt,ssa,ssf,ttxt,txt,usf,vt,vtt
remove_files_without_extension=yes
# 脚本仅在播放开始时生效,禁用则在播放列表更改时也会生效
oneshot=no
+29
View File
@@ -0,0 +1,29 @@
#是否启用自动读取并加载外部章节文件。默认yes
autoload=yes
#是否启用自动导出章节文件 (当章节信息更改后)。默认no
autosave=no
#是否使用外部章节信息覆盖视频内部章节信息。默认no
force_overwrite=no
#指定外部章节文件的标识及扩展名
chapter_file_ext=.chp
#选择外部章节文件是否需要匹配源文件的扩展名。默认yes
basename_with_ext=yes
#从视频文件同目录下的指定子目录读取外部章节文件
#注意:脚本优先从指定的子目录读取外部章节文件
#当子目录的文件不存在时会继续尝试在视频文件同目录中读取外部章节文件
external_chapter_subpath=chapters
#是否将章节文件统一存储在配置的全局目录中网络文件将始终使用全局目录。默认no
global_chapters=no
#指定章节文件的全局目录的路径。可以是 mpv 支持的相对路径或绝对路径
global_chapters_dir=~~/files/chapters
#是否在全局目录中使用哈希值保存章节文件名。默认no
##如果设置为'no',章节文件将以相应的媒体文件命名,可能会导致冲突
##使用哈希可防止同名但位于不同目录中的媒体文件获取相同的章节文件
##但如果您将文件移动到不同的目录,哈希值将更改导致无法加载章节文件
hash=no
#设置创建新章节时是否默认打开重命名输入功能。默认yes
ask_for_title=yes
#设置询问新章节标题时的占位符名称
placeholder_title=Chapter
#设置询问章节标题时是否暂停播放。默认yes
pause_on_input=yes
+8
View File
@@ -0,0 +1,8 @@
# 是否启用自动跳过。默认no
enabled=no
# 每个章节名仅跳过一次。默认yes
skip_once=yes
# 章节名匹配规则Lua 正则)
categories=opening>^OP/ OP$/^[Oo]pening/[Oo]pening$/^Intro%s*Start/オープニング$/^片头$/片头开始$; ending>^ED/ ED$/^[Ee]nding/[Ee]nding$/エンディング$; credits>^[Cc]redits/[Cc]redits$; prologue>^[Pp]rologue/^[Ii]ntro$; preview>[Pp]review$/^[Pp]review/予告$/預告$; PartAB>Part [AB]/Ending 1; PartC>Part C
# 需要跳过的章节类别
skip=opening;ending;credits;prologue;preview
+8
View File
@@ -0,0 +1,8 @@
#指定命令面板的字体大小默认值16
font_size=26
#指定字体大小是否随窗口大小缩放默认值no
scale_by_window=yes
#指定命令面板的菜单项的显示数量默认值12
lines_to_show=12
#指定是否在打开命令面板时暂停播放默认值no
pause_on_open=yes
+6
View File
@@ -0,0 +1,6 @@
###此配置不支持参数后注释,须另起一行
# 是否将命令历史记录保存到文件并加载它。默认no
persist_history=yes
# 命令历史记录文件的路径。默认:~~state/command_history.txt
history_path=~~/files/command_history.txt
+37
View File
@@ -0,0 +1,37 @@
###此配置不支持参数后注释,须另起一行
# 指定控制台显示补全时使用的等宽字体。其他情况使用 --osd-font
#monospace_font=Noto Sans Mono CJK SC
# 字体大小默认 24。最终大小将与缩放率相乘
#font_size=24
# 设置用于 REPL 和控制台的字体边框大小。默认值1.32
border_size=1.3
# 菜单背景的透明度。范围从 0不透明到 255完全透明。默认值80
background_alpha=50
# 菜单的内边距。默认值10
#padding=10
# 菜单边框的大小。默认值0
#menu_outline_size=0
# 菜单边框的颜色。默认值:#FFFFFF
#menu_outline_color=#FFFFFF
# 菜单的圆角半径。默认值8
#orner_radius=8
# 菜单所选项的颜色。默认值:#222222
#selected_color=#222222
# 菜单所选项的背景颜色。默认值:#FFFFFF
#selected_back_color=#FFFFFF
# 与搜索字符串匹配的字符的颜色。默认值:#0088FF
#match_color=#0088FF
# 是否使用窗口高度缩放控制台。可以是 yes、no 或 auto取决于--osd-scale-by-window 选项。默认值auto
#scale_with_window=auto
# 查询时是否使用精确搜索而非模糊搜索默认值no
# 在查询前加上 ' 字符可强制启用精确匹配
#exact_match=no
# 设置 Tab 自动补全是否区分大小写。仅适用于 ASCII 字符
## 默认值:在 Windows 上为 no在其他平台上为 yes
#case_sensitive=no
# 删除历史记录中的重复条目以便仅保留最新的条目。默认yes
history_dedup=yes
# 设置字体高度与字体宽度的比率调整完成建议的表格宽度。默认值auto
## 1.8-2.5 范围内的值对于常见的等宽字体有用
#font_hw_ratio=auto
+14
View File
@@ -0,0 +1,14 @@
##! 注意这个菜单脚本只支持 windows 系统
## 指定是否使用 mpv 内部的上下文菜单实现。默认值为 yes
## 需使用包含上游提交 https://github.com/mpv-player/mpv/commit/3c1e983 的 mpv 版本
use_mpv_impl=no
### 指定是否启用 uosc 的菜单语法支持。与默认支持的 mpv.net 菜单语法不兼容,但是可以使用 uosc 的菜单语法。默认值为 no
uosc_syntax=yes
## 指定是否启用菜单标题转义。默认值为 yes
escape_title=yes
## 指定菜单标题的最大长度。默认值为 80。如果标题长度超过这个值将会被截断
## 设为 0 表示不限制标题长度
max_title_length=80
## 指定播放列表菜单项的最大数量。默认值为 20
## 设为 0 表示禁用播放列表菜单
max_playlist_items=20
+35
View File
@@ -0,0 +1,35 @@
# 脚本键位按下时要跳多远
seek_distance=5
# 播放速度调整量,每 'speed_interval' 应用一次,直到达到上限
speed_increase=0.1
speed_decrease=0.1
# 以什么间隔应用速度调整量
speed_interval=0.05
# 播放速度上限
speed_cap=3
# 显示字幕时的播放速度上限,'no' 表示与 'speed_cap' 相同
subs_speed_cap=1.5
# 调整前将当前速度乘以速度调整量指数加速。默认no
# 使用比默认值低得多的值,例如 speed_increase=0.05, speed_decrease=0.025
multiply_modifier=no
# 在 OSD 上显示当前速度(如果使用 uosc则闪烁显示速度。默认yes
show_speed=yes
# 速度切换时在 OSD 上显示当前速度(如果使用 uosc则闪烁显示速度。默认yes
show_speed_toggled=yes
# 在 osd 上显示搜索操作(如果使用 uosc则闪烁显示时间线。默认yes
show_seek=yes
# 设置 'subs_speed_cap' 项时提前查看以实现更平滑的过渡。默认no
subs_lookahead=no
# 设置 osd 消息显示的符号,示例即默认值
#osd_symbol={\fnmpv-osd-symbols} {\r}
#osd_rewind={\fnmpv-osd-symbols} {\r}
+193
View File
@@ -0,0 +1,193 @@
[
{
"key": "WHEEL_UP",
"command": ["script-binding", "file_browser/dynamic/scroll_up"]
},
{
"key": "WHEEL_DOWN",
"command": ["script-binding", "file_browser/dynamic/scroll_down"]
},
{
"key": "MBTN_LEFT",
"command": ["script-binding", "file_browser/dynamic/down_dir"]
},
{
"key": "MBTN_RIGHT",
"command": ["script-binding", "file_browser/dynamic/up_dir"]
},
{
"key": "MBTN_MID",
"command": ["script-binding", "file_browser/dynamic/play"]
},
{
"key": "KP1",
"command": ["print-text", "files: %n"],
"filter": "file",
"multiselect": true,
"multi-type": "concat",
"concat-string": "\n"
},
{
"key": "KP1",
"command": ["print-text", "directories: %n"],
"filter": "dir",
"multiselect": true,
"multi-type": "concat",
"concat-string": "\n"
},
{
"key": "KP1",
"command": ["print-text", "%f"],
"passthrough": true,
"name": "thing"
},
{
"key": "KP2",
"command": ["print-text", "name: %n"],
"multiselect": true
},
{
"key": "KP3",
"command": ["print-text", "open directory: %p"]
},
{
"key": "KP4",
"command": ["print-text", "directory name: %d"]
},
{
"key": "KP5",
"command": ["print-text", "escape the code: %%f"],
"multiselect": true
},
{
"key": "KP6",
"command": ["print-text", "full filepath via concatenation: %p%n"],
"multiselect": true
},
{
"key": "KP7",
"command": ["print-text", "quote/escape filepath: %F"],
"multiselect": true
},
{
"key": "KP8",
"command": ["print-text", "%r"]
},
{
"key": "Alt+DEL",
"command": ["run", "powershell", "-command", "rm", "%F"],
"filter": "file",
"multiselect": true,
"multi-type": "concat"
},
{
"key": "Ctrl+ENTER",
"command": ["run", "powershell", "-command", "mpv.exe", "%F"],
"multiselect": true,
"multi-type": "concat"
},
{
"key": "Ctrl+c",
"command": [
["run", "powershell", "-command", "Set-Clipboard", "%F"],
["print-text", "copied filepath to clipboard"]
],
"multiselect": true,
"delay": 0.3
},
{
"key": "Ctrl+v",
"command": ["run", "powershell", "-command", "cp", "-LiteralPath", "(Get-Clipboard)", "%P"],
"multiselect": false
},
{
"key": "Ctrl+x",
"command": ["run", "powershell", "-command", "mv", "-LiteralPath", "(Get-Clipboard)", "%P"],
"multiselect": false
},
{
"key": "INS",
"command": ["run", "powershell", "-command", "Set-Content", "-LiteralPath", "( %P + '/.ordered-chapters.m3u' )", "-Value", "( %N )"],
"multiselect": true,
"multi-type": "concat",
"concat-string": "+'\n'+"
},
{
"key": "Ctrl+INS",
"command": ["run", "powershell", "-command", "rm", "-LiteralPath", "( %P + '/.ordered-chapters.m3u' )", "-Force"],
"multiselect": false
},
{
"key": "Ctrl+o",
"command": ["run", "powershell", "-command", "explorer.exe", "(( %P ).TrimEnd('/') -replace '/', '\\' )"],
"multiselect": false
},
{
"key": "Ctrl+O",
"command": ["run", "powershell", "-command", "explorer.exe", "(( %F ).TrimEnd('/') -replace '/', '\\' )"],
"filter": "dir",
"multiselect": true
},
{
"key": "Ctrl+O",
"command": ["run", "powershell", "-command", "explorer.exe", "'/select,'", "( %F -replace '/', '\\' )"],
"filter": "file",
"multiselect": true
},
{
"key": "Ctrl+o",
"command": ["run", "powershell", "-command", "& 'C:/Program Files/Mozilla Firefox/firefox.exe' %P"],
"multiselect": false,
"parser": "ftp"
},
{
"key": "Ctrl+O",
"command": ["run", "powershell", "-command", "& 'C:/Program Files/Mozilla Firefox/firefox.exe' %F"],
"filter": "dir",
"multiselect": true,
"parser": "ftp"
},
{
"key": "DEL",
"command": [
["run", "powershell", "-command", "(New-Object -ComObject 'Shell.Application').NameSpace(0).ParseName((%F -replace '/', '\\' )).InvokeVerb('delete')"],
["script-message", "delay-command", "4", "script-binding", "file_browser/dynamic/reload"],
["show-text", "删除 %f"]
],
"multiselect": true,
"multi-type": "repeat"
},
{
"key": "F",
"command": ["script-message", "favourites/add_favourite", "%f"]
},
{
"key": "F",
"command": ["script-message", "favourites/remove_favourite", "%f"],
"parser": "favourites"
},
{
"key": "Ctrl+UP",
"command": [
["script-message", "favourites/move_up", "%f"]
],
"parser": "favourites"
},
{
"key": "Ctrl+DOWN",
"command": [
["script-message", "favourites/move_down", "%f"]
],
"parser": "favourites"
},
{
"key": "Ctrl+r",
"command": [
["script-message", "winroot/import_root_drives"]
],
"parser": "root"
}
]
+3
View File
@@ -0,0 +1,3 @@
##指定在file-browser文件浏览器中需隐藏的目录以逗号分隔
##示例F:/$RECYCLE.BIN/
paths=
+238
View File
@@ -0,0 +1,238 @@
#######################################################
# This is the default config file for mpv-file-browser
# https://github.com/CogentRedTester/mpv-file-browser
#######################################################
# root 目录,以逗号分隔
# on linux你可能想要添加"/"
# on windows这应该用于添加不同的驱动器号
# Examples
# linux:
# root=~/,/
# windows:
# root=~/,C:/
root=~/
# characters 单独的根目录,每个字符单独工作
# 以防万一个人使用具有奇怪名称的目录
root_separators=,
# 要同时显示在屏幕上的条目数量
num_entries=20
# 要保留历史记录的目录数,大小为 0 时禁用历史记录
history_size=100
# 目录是否循环滚动,默认 yes
wrap=yes
# 是否启用插件默认no
addons=yes
# 启用自定义键绑定
# he keybind json 文件必须位于 ~~/script-opts
custom_keybinds=yes
# 自动检测 Windows 驱动器并将其添加到根目录
# 在根目录下使用 Ctrl+r 会运行另一次扫描
auto_detect_windows_drives=yes
# 当空闲模式下打开浏览器时,首选当前工作目录而不是根目录
# 工作目录无论如何都被设置为"当前"目录,因此播放时浏览器将自动定位至当前工作目录,即使此选项设置为 no
default_to_working_directory=no
# 打开浏览器时,更喜欢由文件浏览器的先前 MPV 实例打开的目录
# 覆盖`default_to_working_directory`选项
# 需要`save_last_opened_directory`为 yes
# 使用内部开放的 `last-opened-directory` 插件
default_to_last_opened_directory=no
# 是否保存最后一个打开的目录
save_last_opened_directory=no
# 播放文件更改时,将光标移至当前播放项目(如果有)
cursor_follows_playing_item=no
####################################
########## filter settings #########
####################################
# 只在浏览器中显示与 mpv 兼容的文件
filter_files=yes
# file 浏览器仅显示默认情况下与 mpv 兼容的文件
# 加入此列表中的文件扩展名将将其添加到扩展名白名单中
# 用根分隔符分隔,请勿使用任何空格
extension_whitelist=amv;bdmv;ifo;iso
# 加入此列表的文件扩展名以禁用默认文件类型
# 这将覆盖上面以及下面所有的白名单选项
#extension_blacklist=mpls
# 加入此列表中的文件扩展名将会添加到外挂音轨扩展名白名单中
# 用根分隔符分隔,请勿使用任何空格
audio_extensions=mka,dts,dtshd,dts-hd,truehd,true-hd,flac
# 加入此列表中的文件扩展名将会添加到字幕扩展名白名单中
# 用根分隔符分隔,请勿使用任何空格
subtitle_extensions=etf,etf8,utf-8,idx,sub,srt,rt,ssa,ass,mks,vtt,sup,scc,smi,lrc,pgs
# 过滤 .config 等以 '.' 开头的目录或文件
# 用于 linux 系统
#filter_dot_dirs=no
#filter_dot_files=no
####################################
###### file loading settings #######
####################################
# 这个选项可反转 alt+ENTER 键绑定的行为
# 当禁用密钥绑定,则需要为文件启用自动加载
# 当启用键绑定将禁用文件的自动加载
autoload=no
# 启用在将项目追加到播放列表时同时递归目录的功能实验性默认值no
# 此功能在将插件与异步 IO 结合使用时具有巨大的性能改进潜力
concurrent_recursion=yes
# 可以并发运行的最大递归数量
# 如果此数字太高,则可能会使 mpv 事件队列溢出从而导致某些目录被完全丢弃默认值16
max_concurrency=16
# 将本地文件追加到播放列表时,用正斜杠代替反斜杠
# 在 Windows 系统上可能有用默认值no
substitute_backslash=no
# 如果通过选择当前播放的文件触发自动加载,则当前文件在关闭和重新打开之前将保存其稍后观看的配置
# 禁用时当前文件将不会重新启动
autoload_save_current=yes
####################################
### directory parsing settings #####
####################################
# 目录缓存用于提高目录读取速度,
# 如果加载目录需要较长时间,可以启用此功能。
# 但可能会导致显示“幽灵”文件(已删除但仍然存在)
# 或者无法显示最近创建的文件。
# 使用 Ctrl+r 重新加载目录时不会使用缓存。
# 使用 Ctrl+Shift+r 可强制清除缓存。
cache=no
# 启用内部 `ls` 插件,该插件使用 `ls` 命令解析目录。
# 允许目录解析并行运行,从而防止浏览器卡顿。
# 在 Windows 系统上会自动禁用此功能。
ls_parser=yes
# 启用内部 `windir` 插件,该插件使用 cmd.exe 中的 `dir` 命令解析目录。
# 允许目录解析并行运行,从而防止浏览器卡顿。
# 在非 Windows 系统上会自动禁用此功能。
windir_parser=no
# 向上移动目录时,不要停止在空协议方案上,例如 `ftp://`
# 例如从 `ftp://localhost/` 向上移动将直接移动到根目录,而不是 `ftp://`
skip_protocol_schemes=yes
# 将光盘的驱动路径映射到它们各自的文件路径
# 例如,将 bd:// 映射到 bluray-device 属性的值
map_bd_device=yes
map_dvd_device=yes
map_cdda_device=yes
####################################
########## misc settings ###########
####################################
# 是否启用脚本信息来控制空闲屏幕上的徽标文字的显示
toggle_idlescreen=no
# 将路径中的反斜杠 '\' 解释为正斜杠 '/'
# 这在 Windows 上很有用,因为 Windows 本身使用反斜杠。
# 由于反斜杠是 Unix 系统中有效的文件名字符,因此可能导致路径损坏,但此类文件名很少见
# 使用"yes"和"no"启用/禁用。"auto"尝试使用 MPV 的 "platform" 该属性mpv v0.36+)来决定
# 如果该属性不可用,则默认为 "yes"
normalise_backslash=auto
# 在`user-data`属性的`file_browser/open`字段中设置浏览器当前的打开状态
# 此属性仅在 mpv v0.36+ 中可用
set_user_data=yes
# 在`shared-script-properties`属性的`file_browser-open`字段中设置浏览器当前的打开状态
# 该属性已被弃用
set_shared_script_properties=no
####################################
########## file overrides #########
####################################
# directory 加载外部模块
module_directory=~~/script-modules
addon_directory=~~/script-modules/file-browser-addons
custom_keybinds_file=~~/script-opts/file-browser-keybinds.json
last_opened_directory_file=~~/files/file_browser-last_opened_directory
####################################
######### style settings ###########
####################################
# 用"~/"在标题中替换用户的主目录,使用内部标签插件实现
home_label=yes
# 设置文件浏览器以使用特定的文本对齐方式(默认:左上角)
# 使用 ASS 标签对齐编号https://aegi.vmoe.info/docs/3.0/ASS_Tags/#index23h3
# 设置为 'auto' 以使用默认的 mpv osd 对齐选项
# 选项:'auto'|'top'|'center'|'bottom'
align_y=top
# 选项: 'auto'|'left'|'center'|'right'
align_x=left
# 用于标头的格式字符串。使用自定义键绑定替换代码
# 动态更改标头的内容。请参阅docs/custom-keybinds.md#codes
# 例如,要添加文件编号请将其设置为: {\fnMonospace}[%i/%x]{\fn<font_name_header or blank>} %q\N----------------------------------------------------
format_string_header=%q\N----------------------------------------------------
# 用于包装器的格式字符串。支持自定义键绑定替换代码,以及支持两个附加代码:'%<'和'%>',分别显示可见列表前后的项数
# 将这些选项设置为空字符串将禁用包装器
format_string_topwrapper=%< 项 覆盖\N
format_string_bottomwrapper=\N%> 项 剩余
# 允许为光标和文件夹自定义图标,可以为矢量图形或 Unicode 字形。示例即为默认设置(矢量图形)
#folder_icon={\p1}m 6.52 0 l 1.63 0 b 0.73 0 0.01 0.73 0.01 1.63 l 0 11.41 b 0 12.32 0.73 13.05 1.63 13.05 l 14.68 13.05 b 15.58 13.05 16.31 12.32 16.31 11.41 l 16.31 3.26 b 16.31 2.36 15.58 1.63 14.68 1.63 l 8.15 1.63{\p0}\h
#cursor_icon={\p1}m 14.11 6.86 l 0.34 0.02 b 0.25 -0.02 0.13 -0 0.06 0.08 b -0.01 0.16 -0.02 0.28 0.04 0.36 l 3.38 5.55 l 3.38 5.55 3.67 6.15 3.81 6.79 3.79 7.45 3.61 8.08 3.39 8.5l 0.04 13.77 b -0.02 13.86 -0.01 13.98 0.06 14.06 b 0.11 14.11 0.17 14.13 0.24 14.13 b 0.27 14.13 0.31 14.13 0.34 14.11 l 14.11 7.28 b 14.2 7.24 14.25 7.16 14.25 7.07 b 14.25 6.98 14.2 6.9 14.11 6.86{\p0}\h
#cursor_icon_flipped={\p1}m 0.13 6.86 l 13.9 0.02 b 14 -0.02 14.11 -0 14.19 0.08 b 14.26 0.16 14.27 0.28 14.21 0.36 l 10.87 5.55 l 10.87 5.55 10.44 6.79 10.64 8.08 10.86 8.5l 14.21 13.77 b 14.27 13.86 14.26 13.98 14.19 14.06 b 14.14 14.11 14.07 14.13 14.01 14.13 b 13.97 14.13 13.94 14.13 13.9 14.11 l 0.13 7.28 b 0.05 7.24 0 7.16 0 7.07 b 0 6.98 0.05 6.9 0.13 6.86{\p0}\h
# 设置字体的不透明度(十六进制),从 00不透明到 FF透明
font_opacity_selection_marker=99
# 页眉使用粗体
font_bold_header=yes
# 指定缩放浏览器的大小2 会使大小增加一倍0.5 会将其减半,依此类推。
# header 和 wrappers 相对于 base 的大小进行缩放
scaling_factor_base=1
scaling_factor_header=1.4
scaling_factor_wrappers=0.64
# 自定义字体名称,默认值为空白
# 设置文件夹/光标的自定义字体可以修复损坏或丢失的图标
#font_name_header=
font_name_body=Noto Sans CJK SC,Noto Color Emoji
#font_name_wrappers=
#font_name_folder=
#font_name_cursor=
# 自定义字体颜色
# colours 采用十六进制格式,按蓝绿色红色顺序排列
# 这与大多数 RGB 颜色代码的顺序相反
font_colour_header=00ccff
font_colour_body=ffffff
font_colour_wrappers=00ccff
font_colour_cursor=00ccff
font_colour_escape_chars=413eff
# 以下选项是应用于不同状态的列表项的颜色
font_colour_selected=fce788
font_colour_multiselect=fcad88
font_colour_playing=33ff66
font_colour_playing_multiselected=22b547
+15
View File
@@ -0,0 +1,15 @@
# 指定脚本的工作方式有三种模式可选noth、pass、switch。默认值noth
## noth什么也不做
## pass当显示器处于 HDR 模式时,为 HDR 内容传递 HDR 信号
## switch根据 mpv 播放的视频内容在显示器的 HDR 模式和 SDR 模式之间自动切换,在 Windows 10 及更高版本的系统上可用
hdr_mode=noth
# 指定是否仅在全屏或窗口最大化时自动切换 HDR 模式。仅在 hdr_mode=switch 时生效默认值no
fullscreen_only=no
# 用于指定你的 HDR 显示器的目标峰值默认值203
#! 提醒:此项必须指定显示器的真实峰值亮度,否则会导致 HDR 内容显示不正确。默认值 203 会视为 SDR 显示器处理
target_peak=203
# 用于指定你的 HDR 显示器的最大对比度数值默认值auto即无限对比度OLED
#! 提醒:此项必须指定为显示器的最大对比度,否则无法正确实施黑位补偿
## 例如 100000 表示显示器最大对比度为 100000:1
## OLED 显示器无需更改此项,使用默认值即可
target_contrast=auto
+7
View File
@@ -0,0 +1,7 @@
#是否使用外部配置文件设置增强式键位动作默认no
enable_external_config=yes
#指定外部配置文件的路径,可以是 mpv 支持的相对路径或绝对路径
#注意:启用外部配置文件功能时请确保该文件存在
external_config=~~/inputevent_key.conf
#指定键位事件的识别前缀默认event
prefix=event
+83
View File
@@ -0,0 +1,83 @@
#choose a layout(reduced/original/mid)
#layout=original
#show OSC when windowed? yes/no
#showwindowed=
#show OSC when fullscreen? yes/no
#showfullscreen=
#scaling of the controller when windowed
scalewindowed=1
#scaling of the controller when fullscreen
scalefullscreen=1
#scaling when rendered on a forced window
scaleforcedwindow=1.5
#scale the controller with the video? yes/no
#vidscale=
#duration in ms until the OSC hides if no mouse movement. enforced non-negative for the user but internally negative is 'always-on'.
#hidetimeout=500
#duration of fade out in ms 0=no fade
#fadeduration=200
#minimum amount of pixels the mouse has to move between ticks to make the OSC show up
#minmousemove=3
#use native mpv values and disable OSC internal track list management (and some functions that depend on it) yes/no
#amaprogrammer=
#default osc font
#font='mpv-osd-symbols'
#show seekrange overlay yes/no
#seekrange=
#color of seekbar and knot,there is no # before value
#seekbarfg_color="7FFFD4"
#transparency of seekranges
seekrangealpha=128
#use keyframes when dragging the seekbar yes/no
#seekbarkeyframes=
#string compatible with property-expansion to be shown as OSC title
#title='${media-title}'
#show osc and no hide timeout on pause yes/no
showonpause=no
#display timecodes with milliseconds yes/no
#timems=false
#display total time instead of remaining time? yes/no
#timetotal=no
#how mpv logo on idle
#idlescreen=yes
#only used at init to set visibility_mode(...) auto/always/never
#visibility=always
#whether to show window controls auto/yes/no
#windowcontrols=
#whether to show mute button and volumne slider yes/no
#volumecontrol=
#volume bar show processd volume yes/no
#processvolume=
#eng=English chs=Chinese eng/chs
#language=chs
#alpha of the background box,0 (opaque) to 255 (fully transparent)
boxalpha=128
#hight of deadzone,from bottom to top
#deadzone=200
+11
View File
@@ -0,0 +1,11 @@
##指定 TorrServer HTTP 地址,示例为默认值
server=http://localhost:8090
##指定是否启用 TorrServer 初始化。默认值no
#! 仅当 TorrServer 位于同一台设备上且未设置自启动时,才需要启用此选项
torrserver_init=no
##指定 TorrServer 路径,示例为默认值。可以为相对路径(已设置环境变量)或绝对路径
##需自行安装 TorrServer详见https://github.com/YouROK/TorrServer
#! 仅当 torrserver_init=yes 时,才需要设置此选项
torrserver_path=TorrServer
##指定是否查找可能的外部轨道音轨、字幕。默认值yes
search_for_external_tracks=yes
+5
View File
@@ -0,0 +1,5 @@
# options to pass to wget
## 设置 mpv 需全局记忆的选项状态
properties=volume
## 保存文件路径
properties_path=files/persistent_config.json
+157
View File
@@ -0,0 +1,157 @@
###此配置不支持在激活的参数后进行注释,如有注释需求应另起一行
#### ------- Mpv-Playlistmanager configuration ------- ####
#### ------- FUNCTIONAL ------- ####
#navigation 键绑定仅在播放列表可见时强制覆盖
#设置"no",则可以通过任何导航键显示播放列表
dynamic_binds=yes
#主菜单键位绑定
key_showplaylist=
#按住键位时显示播放列表
key_peek_at_playlist=
## 动态绑定键位 不应在 input.conf 中设置(不与静态绑定的键位冲突)
## 可以绑定多个键位,用空格分离
key_moveup=UP WHEEL_UP
key_movedown=DOWN WHEEL_DOWN
key_movepageup=PGUP MBTN_BACK
key_movepagedown=PGDWN MBTN_FORWARD
key_movebegin=HOME
key_moveend=END
key_selectfile=RIGHT
key_unselectfile=LEFT
key_playfile=ENTER MBTN_LEFT
key_removefile=DEL BS
key_closeplaylist=ESC MBTN_RIGHT
## 额外的功能键位动态绑定
## 可以绑定多个键位,用空格分离
key_sortplaylist=s
key_shuffleplaylist=r R
key_reverseplaylist=S
key_loadfiles=l L
key_saveplaylist=p P
# json 替换格式,请查看.lua 以获取说明
# example json=[{"ext":{"all":true},"rules":[{"_":" "}]},{"ext":{"mp4":true,"mkv":true},"rules":[{"^(.+)%..+$":"%1"},{"%s*[%[%(].-[%]%)]%s*":""},{"(%w)%.(%w)":"%1 %2"}]},{"protocol":{"http":true,"https":true},"rules":[{"^%a+://w*%.?":""}]}]
# 空值,无需更换
filename_replace=[{"protocol":{"all":true},"rules":[{"%%(%x%x)":"hex_to_char"}]},{"protocol":{"http":true,"https":true},"rules":[{"^%a[%a%d]+://localhost:.*/":""}]}]
## 指定从目录中搜索和加载的文件类型。注:此项与 autoload.lua 脚本功能重复
#loadfiles_filetypes=["3gp","amr","amv","asf","avi","avi","bdmv","f4v","flv","ifo","iso","m2ts","m4v","mkv","mov","mp4","mpeg","mpg","ogv","rm","rmvb","ts","vob","webm","wmv"]
#在启动时加载 1 个或多个文件到播放列表。注:此项与 autoload.lua 脚本功能重复
loadfiles_on_start=no
#空闲启动时从工作目录加载文件
loadfiles_on_idle_start=no
#总是在当前播放文件后放置加载的文件
loadfiles_always_append=no
#指定在初始加载后将任何文件添加到播放列表时是否进行自然排序
sortplaylist_on_file_add=no
#指定使用排序功能时需使用的排序方法,必须是其中之一:"name-asc", "name-desc", "date-asc", "date-desc", "size-asc", "size-desc".
default_sort=name-asc
#linux | windows | auto
system=auto
#Use ~ 用于主目录。留空以使用 mpv/playlists
playlist_savepath=
#播放列表打开时同步当前播放文件所在位置pos
sync_cursor_on_load=yes
#每次加载新文件时显示文件标题
show_title_on_file_load=no
#每次加载新文件时显示播放列表
show_playlist_on_file_load=no
#选择播放文件后关闭播放列表
close_playlist_on_playfile=no
##是否光标移动到末尾自动切换到首位条目
loop_cursor=yes
#当播放列表不可见时重置光标导航
reset_cursor_on_close=yes
#允许播放列表管理器在文件之间导航时编写稍后观看配置
allow_write_watch_later_config=no
#在保存、随机播放、反转播放列表时向 OSD 输出视觉反馈
display_osd_feedback=no
#prefer 以显示以下文件的标题:"all""url""none"。排序仍使用文件名
prefer_titles=url
#指定要用于标题解析的 ytdl 可执行文件可以是绝对路径。默认yt-dlp
youtube_dl_executable=yt-dlp
#使用 youtube-dl/yt-dlp 解析播放列表中网址的标题默认no
#prefer_titles 必须设置为 "url" 或"all" 才能正常工作
resolve_url_titles=yes
#指定播放列表中 url 标题解析的超时时长默认值15s
resolve_title_timeout=15
#指定同时解析 url 媒体标题的数量。较高的数字可能会导致卡明显的卡顿
concurrent_title_resolve_limit=10
##播放列表在 OSD 显示的时长默认值5s
playlist_display_timeout=5
##播放列表将呈现的最大行数。特殊值 -1 将自动计算,默认值:-1
showamount=13
##播放列表的字体样式
#example {\fnUbuntu\fs10\b0\bord1} equals: font=Ubuntu, size=10, bold=no, border=1
#read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
#no 值默认为 mpv.conf 中的 OSD 设置
style_ass_tags={\fnNoto Sans CJK SC\fs26\b1\bord0.5}
##左上方的边距
text_padding_x=10
text_padding_y=15
#菜指定单打开时屏幕的不透明度0.0 - 1.00 表示透明默认1 表示不透明)
curtain_opacity=0.0
##是否修改 MPV 的窗口标题
set_title_stripped=no
title_prefix=
title_suffix= - mpv
##是否切断长文件名,最大显示字符数
slice_longfilenames=yes
slice_longfilenames_amount=100
##播放列表标题
#%mediatitle or %filename = 播放文件的标题或名称
#%pos = 播放文件的位置
#%cursor = 导航的位置
#%plen = 播放列表长度
#%N = 换行符
playlist_header=播放列表 [%cursor/%plen]
##播放列表模板
#%pos = 播放文件的位置
#%name = 文件的标题或名称
#%N = 换行符
#也可以使用上面提到的 ASS 标签。例如:
# selected_file={\c&HFF00FF&}➔ %name | 为所选文件添加颜色
#使用 ASS 标签,你需要为每一行重置它们 (see https://github.com/jonniek/mpv-playlistmanager/issues/20)
normal_file={\c&HFFFFFF&}□ %name
hovered_file={\c&H33FFFF&}■ %name
selected_file={\c&C1C1FF&}☑ %name
playing_file={\c&HAAAAAA&}▷ %name
playing_hovered_file={\c&H00FF00&}▶ %name
playing_selected_file={\c&C1C1FF&}☑ %name
##播放列表被截断时的显示
playlist_sliced_prefix=▲
playlist_sliced_suffix=▼
+81
View File
@@ -0,0 +1,81 @@
###### 键绑定 ######
# 向上移动菜单光标
up_binding=UP WHEEL_UP
# 向下移动菜单光标
down_binding=DOWN WHEEL_DOWN
# 选择菜单项
select_binding=ENTER MBTN_LEFT
# 关闭质量菜单
close_menu_binding=ESC MBTN_RIGHT CTRL+F ALT+F
###### 键绑定 ######
# 字体大小按窗口缩放,如果为否需要更大的字体和填充大小
scale_playlist_by_window=yes
# 播放列表屁股样式覆盖大括号内。\keyvalue 是一个字段,额外 \ 用于 lua 中的转义
# example {\\fnUbuntu\\fs10\\b0\\bord1} equals: font=Ubuntu, size=10, bold=no, border=1
# 标签参考 https://aegi.vmoe.info/docs/3.0/ASS_Tags/
# 未声明的标签将使用默认的 osd 设置
# 这些样式将用于整个播放列表。更具体的样式实现方式并不优雅
# 建议使用等宽字体,保持样式美观
style_ass_tags={\\fnNoto Sans CJK SC,Noto Color Emoji\\fs25\\bord0.5}
# 自定义游标
# 也可以使用上面提到的 ASS 标签。例如:
# selected_and_inactive={\c&H33FFFF&}● - | 为所选格式添加颜色
# 使用 ASS 标签,你需要为每一行重置它们
selected_and_active={\c&H00FF00&}▶ -
selected_and_inactive={\c&H33FFFF&}● -
unselected_and_active={\c&HAAAAAA&}▷ -
unselected_and_inactive={\c&HFFFFFF&}○ -
# 移位绘图坐标。mpv.net 兼容性所必需的
shift_x=0
shift_y=0
# 左上角的填充
text_padding_x=5
text_padding_y=10
# 菜单打开时屏幕变暗
curtain_opacity=0.7
# 质量菜单超时的秒数
menu_timeout=6
# 使用 youtube-dl 获取可用格式的列表(覆盖 quality_strings默认yes
fetch_formats=yes
# 可供选择的 ytdl 格式字符串列表
quality_strings_video=[ {"4320p" : "bestvideo[height<=?4320p]"}, {"2160p" : "bestvideo[height<=?2160]"}, {"1440p" : "bestvideo[height<=?1440]"}, {"1080p" : "bestvideo[height<=?1080]"}, {"720p" : "bestvideo[height<=?720]"}, {"480p" : "bestvideo[height<=?480]"}, {"360p" : "bestvideo[height<=?360]"}, {"240p" : "bestvideo[height<=?240]"}, {"144p" : "bestvideo[height<=?144]"} ]
quality_strings_audio=[ {"default" : "bestaudio"} ]
# 打开网络视频后显示视频格式菜单默认yes
start_with_menu=no
# 在列表中包含未知格式,不幸的是,选择视频或音频格式并不总是完美的
# 设置为 yes 以确保您不会错过任何格式,但随后列表还可能包括实际上不是视频或音频的格式。已知不是视频或音频的格式仍会被过滤掉
include_unknown=no
# 隐藏所有格式都相同的列默认yes
hide_identical_columns=yes
# 指定列的属性以什么顺序显示,使用','分隔列表,属性前加'-'可使该列左对齐
# 对于 uosc 集成,可以将文本分成标题和提示
## 这是通过用'|'而不是逗号分隔两列来实现的
##可用属性有:
#resolution, width, height, fps, dynamic_range, tbr, vbr, abr, asr,
#filesize, filesize_approx, vcodec, acodec, ext, video_ext, audio_ext,
#language, format, format_note, quality
##以下属性经过特殊处理
#size, frame_rate, bitrate_total, bitrate_video, bitrate_audio,
#codec_video, codec_audio, audio_sample_rate
columns_video=-resolution,frame_rate|dynamic_range,bitrate_video,size,-codec_video,-ext
columns_audio=language,audio_sample_rate,bitrate_audio|size,-codec_audio,-ext
# 用于排序的列,有关可用列,请参阅'columns_video'
# 逗号分隔列表,前缀列带“-”以反转排序顺序
# 将此内容留空可保留 yt-dlp/youtube-dl 的顺序
# 注:拼写错误的列不会导致错误,但它们可能会影响结果
sort_video=
sort_audio=
+2
View File
@@ -0,0 +1,2 @@
# options to pass to wget
#wget_opts=
+7
View File
@@ -0,0 +1,7 @@
###此配置不支持参数后注释,须另起一行
# 指定历史记录条目的日期格式。这被传递给 Lua 的 os.date
# 这使用与 strftime3相同的格式
history_date_format=%Y-%m-%d %H:%M:%S
# 是否仅显示具有相同路径的最后一个历史记录条目默认yes
hide_history_duplicates=yes
+305
View File
@@ -0,0 +1,305 @@
####------脚本设置-----####
#--打开 mpv 且没有加载视频/文件时自动运行列表。'none'表示禁用。或者选择all, recents, distinct, protocols, fileonly, titleonly, timeonly, keywords.
auto_run_list_idle=none
#--mpv 启动且未加载任何内容时的行为。'none'表示禁用。'resume'以自动恢复您上次播放的项目。'resume-notime'以恢复您上次播放的项目,但从头开始
startup_idle_behavior=none
#--在打开和关闭菜单时隐藏 OSC 空闲屏幕消息(如果多个脚本触发 osc-空闲屏幕关闭,可能会导致意外行为)
toggle_idlescreen=no
#--更改为 0以便项目从确切位置恢复或减小值以便在加载恢复点之前为您提供一些预览
resume_offset=-10
#--yes 用于在发生操作时显示 OSD 消息。更改为 no 将禁用从此脚本生成的所有 osd 消息
osd_messages=yes
#--none: 用于禁用。notification将触发一条消息以恢复上一个到达的时间。force根据阈值强制恢复上次播放
resume_option=none
#--0 在之前播放过同一视频时始终触发恢复选项,如果上次播放时间在视频的 5% 之后开始并在完成 5% 之前结束,则值(如 5将仅触发恢复选项
resume_option_threshold=5
#--yes 用于将历史记录时间标记为章节。no 禁用标记为章节的行为
mark_history_as_chapter=no
#--yes: 反转黑名单为白名单,以便将诸如路径/网址之类的东西添加到 history_blacklist 中以保存到历史记录中
invert_history_blacklist=no
#--设置黑名单Paths / URLs / Websites / Files / Protocols / Extensions, 黑名单中的类型或路径将不会添加到历史记录中
##例如:["c:\\users\\eisa01\\desktop", "c:\\users\\eisa01\\desktop\\*", "c:\\temp\\naruto-01.mp4", "youtube.com", "https://dailymotion.com/", "avi", "https://www.youtube.com/watch?v=e8YBesRKq_U", ".jpeg", "magnet:", "https://", "ftp"]
history_blacklist=[""]
#--键绑定,用于在没有视频播放时立即加载和恢复最后一项。如果视频正在播放,它将恢复到上次找到的位置
##! 注意:该绑定将覆盖 input.conf 中的同键位,推荐留空在 input.conf 中绑定该功能
history_resume_keybind=[""]
#--键绑定,用于在没有播放视频时立即加载最后一项而不恢复。如果视频正在播放,那么它将添加到播放列表中
##! 注意:该绑定将覆盖 input.conf 中的同键位,推荐留空在 input.conf 中绑定该功能
history_load_last_keybind=[""]
#--键绑定,将用于打开列表以及指定的筛选器。可用的过滤器:"all", "recents", "distinct", "protocols", "fileonly", "titleonly", "timeonly", "keywords".
##! 注意:该绑定将覆盖 input.conf 中的同键位,推荐留空在 input.conf 中绑定该功能
open_list_keybind=[ ["", "distinct"], ["", "recents"] ]
#--动态键绑定,在列表打开时使用以跳转到特定筛选器(它还允许按两次筛选器键绑定以关闭列表)。可用的过滤器:"all", "recents", "distinct", "protocols", "fileonly", "titleonly", "timeonly", "keywords".
list_filter_jump_keybind=[ ["a", "all"], ["r", "recents"], ["d", "distinct"], ["f", "fileonly"], ["p", "protocols"], ["t", "titleonly"], ["l", "playing"] ]
####------隐身设置-----####
#--指定是否在 MPV 启动时自动启动隐身模式
auto_run_incognito_mode=no
#--yes以便自动从历史记录中删除触发隐身模式的文件no将文件保留在隐身模式触发的历史记录中
delete_incognito_entry=yes
#--"none"表示禁用,"deleted-restore"以便自动恢复进入隐身时删除的文件,"always"表示退出隐身模式后始终立即更新历史记录中的条目
restore_incognito_entry=always
#--键绑定,触发隐身模式。启用后播放的文件不会添加到历史记录中,直到禁用此模式
##! 注意:该绑定将覆盖 input.conf 中的同键位,推荐留空在 input.conf 中绑定该功能
history_incognito_mode_keybind=[""]
####------日志记录设置------####
#--指定书签日志文件的保存路径。更改为'/:dir%script%'以将其放置在脚本的同一目录中,或者更改为'/:dir%mpvconf%'以将其放置在 mpv portable_config 目录中。
##或者使用'/:var'写入任何变量,然后使用变量'/:var%APPDATA%',您也可以使用路径,例如:'/:var%APPDATA%\mpv'或'/:var%HOME%/mpv'或指定绝对路径,例如:'C\Users\Eisa01\Desktop\'
log_path=/:dir%mpvconf%/files
#--名称 + 将用于存储日志数据的文件的扩展名
log_file=mpvHistory.log
#--日志中的日期格式(请参阅 lua 日期格式),例如:"%d/%m/%y %X" or "%d/%b/%y %X"
date_format=%A/%B %d/%m/%Y %X
#--在 all, protocols, none 中选择保存媒体标题的类型。此选项会将媒体标题存储在日志文件中,这对于网站/协议很有用,因为标题无法仅从链接中解析
file_title_logging=protocols
#--在下面(逗号后)添加您希望将其标题存储在日志文件中的任何协议。这仅对 (file_title_logging = "protocols" or file_title_logging = "all") 有效
logging_protocols=["://", "magnet:"]
#--指定显示文件名而不是标题的范围。在 local, protocols, all, 和 none 之间进行选择
## "local"更喜欢非协议视频的文件名。"protocols"将仅首选协议的文件名。"all"将始终使用文件名而不是标题。"none"将始终使用标题而不是文件名
prefer_filename_over_title=local
#--限制保存具有相同路径的条目:-1 表示无限制0 将始终更新相同路径的条目,例如值 3 将限制为 3然后它将在第 4 个条目开始更新旧值
same_entry_limit=2
####------列表设置-------####
#--设置是否启用光标循环滚动
loop_through_list=yes
#--设置是否在到达列表中间后更新显示新项目
list_middle_loader=yes
#--显示文件路径而不是媒体标题
show_paths=no
#--在显示其名称和值之前显示每个项目的编号
show_item_number=yes
#--设置是否按下面指定的字符数量对长文件名进行切片
slice_longfilenames=yes
#--用于切片长文件名的字符数量
slice_longfilenames_amount=80
#--更改最大数量以在当前列表显示更多项目
list_show_amount=10
#--是否启用动态选择键绑定,条目从 0 到 9用于在列表打开时快速选择list_show_amount = 10 是此功能工作的最大值)
quickselect_0to9_keybind=yes
#--是否启用双击主列表时退出列表的功能,即使列表是通过其他过滤器访问的
main_list_keybind_twice_exits=yes
#--巧妙地将搜索设置为不键入(当搜索框打开时),而无需按 ctrl+enter 键
search_not_typing_smartly=yes
#--"specific"查找日期、标题、路径/URL、时间的匹配项。"any"以根据日期,标题,路径/ URL 和时间的组合查找任何键入的搜索。"any-notime"根据日期、标题和路径/URL 的组合查找任何键入的搜索,但不查找时间(这是为了减少不需要的结果)
search_behavior=any
####------过滤器设置-------####
##--可用过滤器:"all"以显示所有项目。或"keybinds"以显示使用键绑定插槽过滤的列表。或"recents"以显示最近添加的要记录的项目而不重复。或"distinct"以显示不同路径中文件的最近保存条目。
##或"fileonly"以显示没有时间保存的文件。或"timeonly"以显示只有时间的文件。或"keywords"以显示具有配置中指定的匹配关键字的文件。或"playing"以显示当前播放文件的列表。
#--跳转到以下过滤器,并在通过左右键导航时按显示的顺序跳转。您可以更改顺序并删除不需要的筛选器
filters_and_sequence=["all", "recents", "distinct", "protocols", "playing", "fileonly", "titleonly", "keywords"]
#--键绑定,将用于根据 filters_and_sequence 跳转到下一个可用筛选器
next_filter_sequence_keybind=["RIGHT", "MBTN_FORWARD"]
#--键绑定,将用于根据 filters_and_sequence 跳转到上一个可用筛选器
previous_filter_sequence_keybind=["LEFT", "MBTN_BACK"]
#--是否启用循环访问过滤器的行为
loop_through_filters=yes
#--为您想要的"keywords"中创建一个过滤器例如youtube.com 将过滤掉 YouTube 上的视频。您还可以插入文件名或标题的一部分,或扩展名或路径的完整路径/部分。例如: ["youtube.com", "mp4", "naruto", "c:\\users\\eisa01\\desktop"]. 留空已禁用关键词过滤器
keywords_filter_list=["youtube.com"]
####------排序设置-------####
##--可用排序added-asc 用于首先显示最新添加的项目。或者 added-desc 用于显示添加顺序。或者 alphanum-asc 用于 A 到 Z 方法,文件名和集数先降低。或者 alphanum-desc 是它的 Z 到 A 方法。或 time-asctime-desc 根据时间对列表进行排序
#--指定列表中所有不同筛选器的默认排序方法。选择范围added-asc, added-desc, time-asc, time-desc, alphanum-asc, alphanum-desc
list_default_sort=added-asc
#--指定特定过滤器的默认排序,例如:[ ["all", "alphanum-asc"], ["playing", "added-desc"] ]
list_filters_sort=[ ["keybinds", "keybind-asc"], ["fileonly", "alphanum-asc"], ["playing", "time-asc"] ]
#--键绑定,用于在列表打开时循环浏览不同的可用排序
list_cycle_sort_keybind=["alt+s"]
####------列表设计设置------####
#--指定列表的对齐方式,使用数字键盘位置从 1-9 中选择,或 0 以禁用。例如7 左上对齐8 中上对齐9 右上角对齐
list_alignment=7
#--列表中项目显示的时间类型。选择duration, length, remaining.
text_time_type=duration
#--指定在保存的时间之前显示的时间分隔符样式
time_seperator= 🕒
#--指定表示上面有更多项目的文本时的样式。\n 用于换行。\h 代表空格
list_sliced_prefix=...\h\N
#--指定表示下面有更多项目的文本时的样式
list_sliced_suffix=...
#--yes 启用前文本用于在列表之前显示快速选择键绑定。no 禁用
quickselect_0to9_pre_text=no
#--指定列表的文本颜色BGR 十六进制
text_color=ffffff
#--列表文本的字体大小
text_scale=80
#--列表文本的黑色边框大小
text_border=0.5
#--前光标位置的文本颜色BGR 十六进制
text_cursor_color=ffbf7f
#--列表中当前光标位置的文本的字体大小
text_cursor_scale=90
#--列表中当前光标位置的文本的黑色边框大小
text_cursor_border=0.7
#--突出显示的多选项目的前置文本
text_highlight_pre_text=✅
#--在打字模式下搜索框的颜色
search_color_typing=ffffaa
#--处于打字模式且处于活动状态时搜索框的颜色
search_color_not_typing=00bfff
#--列表标题颜色BGR 十六进制
header_color=00bfff
#--列表的标题文本大小
header_scale=100
#--列表标题的黑色边框大小
header_border=0.6
#--要显示为列表标题的文本
#--可用标头变量:%cursor%, %total%, %highlight%, %filter%, %search%, %listduration%, %listlength%, %listremaining%
#--仅在触发变量时显示的用户定义文本:%prefilter%, %afterfilter%, %prehighlight%, %afterhighlight% %presearch%, %aftersearch%, %prelistduration%, %afterlistduration%, %prelistlength%, %afterlistlength%, %prelistremaining%, %afterlistremaining%
#--变量说明:%cursor显示列表中光标位置的数量。%total当前列表中的项目总数。%highlight%:突出显示的项目总数。%filter显示筛选器名称%search显示键入的搜索。仅在触发用户变量时才显示的用户定义文本示例%prefilter显示筛选器之前的用户定义文本%afterfilter显示筛选器后的用户定义文本
header_text=⌛ 历史菜单 [%cursor%/%total%]%prehighlight%%highlight%%afterhighlight%%prelistduration%%listduration%%afterlistduration%%prefilter%%filter%%afterfilter%%presort%%sort%%aftersort%%presearch%%search%%aftersearch%
#--指定使用 %sort% 变量时从标头中隐藏的排序方法
header_sort_hide_text=added-asc
#--设置在标头中触发变量之前或之后显示的文本
header_sort_pre_text= \{
header_sort_after_text=}
header_filter_pre_text= [Filter:
header_filter_after_text=]
header_search_pre_text=\h\N[Search=
header_search_after_text=..]
header_highlight_pre_text=✅
header_highlight_after_text=
header_list_duration_pre_text= 🕒
header_list_duration_after_text=
header_list_length_pre_text= 🕒
header_list_length_after_text=
header_list_remaining_pre_text= 🕒
header_list_remaining_after_text=
####-----时间格式设置-----####
##--在第一个参数中您可以从可用样式中定义default, hms, hms-full, timestamp, timestamp-concise。"default"以 HH:MM:SS.sss 格式显示。"hms"以 1h 2m 3.4s 格式显示。"hms-full"与 hms 相同,但当小时和分钟为 0 时保持恒定。"timestamp"将总时间显示为时间戳 123456.700 格式。"timestamp-concise"以 123456.7 格式显示总时间(根据可用性显示和隐藏小数)。
##--在第二个参数中,您可以定义是显示毫秒、舍入毫秒还是截断毫秒。可用选项:'truncate'以删除毫秒并保留秒数。0 删除毫秒并将秒舍入。1 或大于是要显示的毫秒数。默认值为 3 毫秒。
##--在第三个参数中,您可以在 hour:minute:second(小时:分钟:秒) 之间定义分隔符。"default"样式自动设置为":""hms""hms-full"自动设置为" "。您可以定义自己的。一些例子: ["default"3 "-"]["hms-full"5 "."]["hms" "truncate" ""]["timestamp-concise"]["timestamp" ["timestamp"0]["timestamp" "truncate"]["timestamp"5]
osd_time_format=["default", "truncate"]
list_time_format=["default", "truncate"]
header_duration_time_format=["hms", "truncate", ":"]
header_length_time_format=["hms", "truncate", ":"]
header_remaining_time_format=["hms", "truncate", ":"]
####------列出键绑定设置------####
#--在下面(逗号后)添加要绑定的任何其他键绑定。或者更改引号内的字母以更改键绑定
#--更改和添加键绑定的示例:--从 ["b", "B"] 到 ["b"]. --从 [""] 到 ["alt+b"]. --从 [""] 到 ["a" "ctrl+a", "alt+a"]
#--键绑定,将用于在列表中向上导航
list_move_up_keybind=["UP", "WHEEL_UP"]
#--键绑定,将用于在列表中向下导航
list_move_down_keybind=["DOWN", "WHEEL_DOWN"]
#--键绑定,将用于转到列表上显示的页面的第一项
list_page_up_keybind=["PGUP"]
#--键绑定,将用于转到列表上显示的页面的最后一项
list_page_down_keybind=["PGDWN"]
#--键绑定,将用于导航到列表中的第一项
list_move_first_keybind=["HOME"]
#--密钥绑定,将用于导航到列表中的最后一项
list_move_last_keybind=["END"]
#--按键绑定,用于在按下导航键绑定时突出显示,按住 shift然后按任何导航键绑定例如up, down, home, pgdwn 等。
list_highlight_move_keybind=["SHIFT"]
#--键绑定,将用于突出显示列表中所有显示的项目
list_highlight_all_keybind=["ctrl+a"]
#--键绑定,将用于从列表中删除所有当前突出显示的项目
list_unhighlight_all_keybind=["ctrl+d"]
#--键绑定,将用于根据光标位置加载条目
list_select_keybind=["ENTER", "MBTN_MID"]
#--键绑定,将用于根据光标位置向播放列表添加条目
list_add_playlist_keybind=["CTRL+ENTER"]
#--键绑定,将用于将所有突出显示的条目添加到播放列表
list_add_playlist_highlighted_keybind=["SHIFT+ENTER"]
#--将用于关闭列表的键绑定(如果搜索打开,则首先关闭搜索)
list_close_keybind=["ESC", "MBTN_RIGHT"]
#--键绑定,将用于根据光标位置删除条目
list_delete_keybind=["DEL"]
#--密钥绑定,将用于从列表中删除所有突出显示的条目
list_delete_highlighted_keybind=["SHIFT+DEL"]
#--将用于触发搜索的密钥绑定
list_search_activate_keybind=["ctrl+f"]
#--键绑定,将用于在保持搜索打开的同时退出搜索的键入模式
list_search_not_typing_mode_keybind=["ALT+ENTER"]
#--列表打开时忽略的键绑定
list_ignored_keybind=[""]
+91
View File
@@ -0,0 +1,91 @@
###不支持参数后注释,须另起一行
# -- 动态键位绑定(对应五个不同的信息页)
#key_page_1=1
#key_page_2=2
#key_page_3=3
#key_page_4=4
#key_page_0=0
# -- 动态键位绑定(部分页面支持上下翻页)
#key_scroll_up=UP
#key_scroll_down=DOWN
##设置滚动一次的行数。默认值1
#scroll_lines=1
##短暂显示的持续时间。默认值4
#duration=4
##常驻显示的数据刷新间隔(秒),设为 0 会有 bug。默认值1
#redraw_delay=1
##文本格式化ASS。默认值yes
##ass_formatting=yes
##禁止其它 OSD 文本覆盖 stats 信息。默认值no
#persistent_overlay=yes
##设为 yes 将输出传递的完整信息。默认值no
#print_perfdata_passes=no
##如果过滤器列表的长度超过这个数目则每行显示一个过滤器。默认值128
#filter_params_max_length=128
##启用调试输出。默认值100
#debug=no
# -- 图形选项和样式
##显示性能数据的图表。默认值no
#plot_perfdata=yes
##显示垂直同步和抖动值的图形仅在统计信息常驻显示时。默认值no
#plot_vsync_ratio=yes
#plot_vsync_jitter=yes
##显示缓存值图表(第 3 页仅在切换时显示。默认值no
#plot_cache=no
##自动启用色调映射 LUT 可视化仅在统计信息常驻显示时。默认值no
#plot_tonemapping_lut=no
#skip_frames=5
#global_max=yes
##切换时清除数据缓冲区。默认值yes
#flush_graph_data=yes
#plot_bg_border_width=1.25
#plot_bg_border_color=0000FF
#plot_bg_color=262626
#plot_color=FFFFFF
##指定是否使用视频缩放文本和图形。no 尝试保持大小不变auto 使用 OSD 缩放文本和图形
##而 OSD 使用 window 或保持恒定大小,具体取决于 --osd-scale-by-window 选项。默认值auto
#vidscale=auto
# -- 字体相关设定
font=Noto Sans CJK SC,Noto Color Emoji
font_mono=Noto Sans CJK SC,Noto Color Emoji
##字体大小,默认 20
#font_size=20
##字体颜色
font_color=FFFFFF
##字体边框粗细,默认 1.2
border_size=1.5
##字体边框颜色,默认 262626
border_color=000000
shadow_x_offset=0.1
shadow_y_offset=0.1
shadow_color=000000
##<0-99> 字体透明度,似乎是百分比,默认 11
alpha=0
# -- 自定义标头,用于设置 ASS 标签的文本输出样式
# -- 指定此参数将忽略上面的文本样式值并使用这个字符串代替
#custom_header=
# -- 文本格式ASS
#ass_nl=\\N
#ass_indent=\\h\\h\\h\\h\\h
#ass_prefix_sep=\\h\\h
#ass_b1={\\b1}
#ass_b0={\\b0}
#ass_it1={\\i1}
#ass_it0={\\i0}
# -- Without ASS
#no_ass_nl=\n
#no_ass_indent=\t
#no_ass_prefix_sep=
#no_ass_b1=\027[1m
#no_ass_b0=\027[0m
#no_ass_it1=\027[3m
#no_ass_it0=\027[0m
+208
View File
@@ -0,0 +1,208 @@
[
{
"alang": "*",
"slang": ["chs", "sc", "zho?%-cn", "zho?%-hans", "cht", "tc", "zho?%-hant", "zho?%-tw", "zho?%-hk", "zho?%-", "chi", "zho?", "und"],
"whitelist": ["chs&j[ap]n?"],
"condition": "sub.codec ~= 'null'"
},
{
"inherit": "^",
"whitelist": ["sc&j[ap]n?"]
},
{
"inherit": "^",
"whitelist": ["cht&j[ap]n?"]
},
{
"inherit": "^",
"whitelist": ["ch&j[ap]n?"]
},
{
"inherit": "^",
"whitelist": ["tc&j[ap]n?"]
},
{
"inherit": "^",
"whitelist": ["zh&j[ap]n?"]
},
{
"inherit": "^",
"whitelist": ["chs&eng?"]
},
{
"inherit": "^",
"whitelist": ["sc&eng?"]
},
{
"inherit": "^",
"whitelist": ["cht&eng?"]
},
{
"inherit": "^",
"whitelist": ["ch&eng?"]
},
{
"inherit": "^",
"whitelist": ["tc&eng?"]
},
{
"inherit": "^",
"whitelist": ["zh&eng?"]
},
{
"inherit": "^",
"whitelist": ["中日"]
},
{
"inherit": "^",
"whitelist": ["中英"]
},
{
"inherit": "^",
"whitelist": ["中上英下"]
},
{
"inherit": "^",
"whitelist": ["简英"]
},
{
"inherit": "^",
"whitelist": ["双语"]
},
{
"inherit": "^",
"whitelist": ["特效"]
},
{
"inherit": "^",
"whitelist": ["简体&英文"]
},
{
"inherit": "^",
"whitelist": ["繁英"]
},
{
"inherit": "^",
"whitelist": ["繁体&英文"]
},
{
"inherit": "^",
"whitelist": ["繁體&英文"]
},
{
"inherit": "^",
"whitelist": ["chs"]
},
{
"inherit": "^",
"whitelist": ["sc"]
},
{
"inherit": "^",
"whitelist": ["cn"]
},
{
"inherit": "^",
"whitelist": ["hans"]
},
{
"inherit": "^",
"whitelist": ["cht"]
},
{
"inherit": "^",
"blacklist": ["dutch"],
"whitelist": ["tc"]
},
{
"inherit": "^",
"whitelist": ["hant"]
},
{
"inherit": "^",
"whitelist": ["hk"]
},
{
"inherit": "^",
"whitelist": ["tw"]
},
{
"inherit": "^",
"whitelist": ["简"]
},
{
"inherit": "^",
"whitelist": ["中"]
},
{
"inherit": "^",
"whitelist": ["繁"]
},
{
"alang": "*",
"slang": "und",
"blacklist": [ "sign", "song", "comment", "danmaku", "danmu", "xml" ],
"condition": "sub.codec ~= 'null'"
},
{
"alang": "*",
"slang": ["chi", "zho?", "und"],
"whitelist": ["simplified"],
"condition": "sub.codec ~= 'null'"
},
{
"inherit": "^",
"whitelist": ["traditional"]
},
{
"alang": "*",
"slang": "zho?%-cn",
"condition": "sub.codec ~= 'null'"
},
{
"inherit": "^",
"slang": "zho?%-hans"
},
{
"inherit": "^",
"slang": "zho?%-hant"
},
{
"inherit": "^",
"slang": "zho?%-hk"
},
{
"inherit": "^",
"slang": "zho?%-tw"
},
{
"inherit": "^",
"slang": "zho?%-"
},
{
"inherit": "^",
"slang": "chi"
},
{
"inherit": "^",
"slang": "zho?"
},
{
"inherit": "^",
"slang": "default"
},
{
"inherit": "^",
"slang": "forced"
},
{
"inherit": "^",
"slang": "j[ap]n?",
"blacklist": [ "sign", "song" ]
},
{
"inherit": "^",
"slang": "eng?",
"blacklist": [ "sign", "song" ]
}
]
+7
View File
@@ -0,0 +1,7 @@
# API token, 可以在 https://assrt.net 上注册账号后在个人界面获取
#示例为脚本内预设的 key
#api_token=tNjXZUnOJWcHznHDyalNMYqqP6IdDdpQ
# 是否使用 https默认 yes
#use_https=no
# 代理设置
#proxy=
+4
View File
@@ -0,0 +1,4 @@
#ffmpeg 所在绝对路径,或者放入环境变量
ffmpeg_path=ffmpeg
#指定脚本在 OSD 和控制台显示的文本使用的语言eng=English, chs=Chinese。默认值eng
language=chs
+24
View File
@@ -0,0 +1,24 @@
#######################################################
## Default configuration file for mpv-sub-select ##
## https://github.com/CogentRedTester/mpv-sub-select ##
#######################################################
# 强制启用脚本
#! 注意:这不会覆盖 sid 选项的显式指定
force_enable=yes
# 基于偏好 json 文件的实验性音轨选择
select_audio=no
#observe 音频开关,并在 alang 更改时重新选择字幕
observe_audio_switches=yes
# 仅选择用字幕轨道中明确说明的强制字幕。
# 默认情况下,在搜索特定语言的字幕轨道时,
# 强制字幕将包含在搜索结果中,并与其他曲目相同。
# 这意味着没有办法编写专门排除强制字幕轨道的规则
# 通过启用强制字幕,除非有规则在`slang`中明确包含"forced",否则永远不会选择强制字幕
explicit_forced_subs=no
# 指定包含 "sub-select.json" 文件的文件夹
config=~~/script-opts
+42
View File
@@ -0,0 +1,42 @@
# Socket 路径 (留空自动设置)
socket=
# 缩略图缓存路径 (留空自动设置)
thumbnail=
# 最大缩略图大小(以像素为单位)(缩小以适合)
# 当启用 hidpi 时此值会自适应缩放
max_height=200
max_width=200
# 叠加 ID
overlay_id=42
# 在文件加载时生成缩略图器,以更快地获得初始缩略图。默认禁用
spawn_first=no
# 是否退出超时未活动的缩略图进程(秒),默认 0 即禁用
quit_after_inactivity=0
# 在网络播放时启用。默认禁用
network=no
# 在音频播放时启用。默认禁用
audio=no
# 启用硬件解码生成缩略图。默认禁用
# 注意:硬解在高端显卡上可以加速生成缩略图,但在低端显卡上可能会出问题
hwdec=yes
# 仅限 Windows使用原生 Windows API 写入管道(需要 LuaJIT。默认禁用
direct_io=yes
# 指定 mpv 可执行文件的自定义路径。默认mpv
mpv_path=mpv
# 指定需要忽略的视频扩展名黑名单,这些文件无法正常生成缩略图
blacklist_ext=bdmv,ifo
## 指定需忽略的共享盘(挂载盘)的路径/目录(缩略图生成性能差)
## windows 示例excluded_dir=["X:", "Z:", "F:/Download/", "Download"]
#excluded_dir=[]
+30
View File
@@ -0,0 +1,30 @@
# Options are slash-separated lists of words and languages
#是否启用脚本,默认 yes
enabled=yes
#同步选择曲目,与其他脚本一起更好地工作,默认 yes
hook=yes
#模仿 mpv 的曲目列表信息,以保留用户在文件中选择的曲目,默认 no
#注意此项必须设为 no否则脚本功能无法真正生效
fingerprint=no
#覆盖用户的显式曲目选择,默认 no
force=no
#如果 mpv 无法做到这一点,请尝试重新选择最后一个轨道,例如当信息更改时,默认 no
smart_keep=no
## 指定需忽略的特殊协议
special_protocols=["://", "^magnet:"]
#视频轨道筛选项设置
#preferred_video_lang=
#excluded_video_words=
#expected_video_words=
#音频轨道筛选项设置
preferred_audio_lang=japanese/jpn/jap/ja/jp/english/eng/en
preferred_audio_channels=8/6/3/2
excluded_audio_words=commentary/cast/staff/dub/guide
expected_audio_words=
#字幕轨道筛选项设置
#preferred_sub_lang=chs/sc/zh-CN/zh-Hans/cht/tc/zh-Hant/zh-HK/zh-TW/chi/zh
#excluded_sub_words=
#expected_sub_words=中日/中英/中上英下/双语/特效/简/繁/中
+241
View File
@@ -0,0 +1,241 @@
# 时间轴中当前位置的显示样式。可用line, bar
timeline_style=bar
# 时间线line宽度窗口/全屏模式)
timeline_line_width=2
# 进度条完全展开时的时间轴大小以像素为单位0 表示禁用
timeline_size=30
# 背景颜色的顶部边框,有助于在视觉上将时间轴与视频分开
timeline_border=1
# 指定在时间线上使用鼠标滚轮时跳转的步进秒数。默认5
# 默认使用快速查找。添加 '!' 后缀以启用精确查找。示例:'5!'
timeline_step=5
# 是否在时间轴上显示网络内容的渲染缓存指标默认yes
timeline_cache=yes
# 设置时间轴应始终可见的状态。使用逗号分隔可用paused, audio, image, video, idle, windowed, fullscreen
timeline_persistency=
# 设置何时显示始终可见的进度条最小化时间线。可以是windowed(默认值), fullscreen, always, never
# 也可以使用 `toggle-progress` 命令按需切换
progress=windowed
progress_size=2
progress_line_width=10
# 以逗号分隔的项列表,用于构造时间轴上方的控制栏。设置为`never`以禁用
# 参数规范:括在`{}`中表示值,括在`[]`中表示可选
# 完整的条目语法:`[<[!]{disposition1}[,[!]{dispositionN}]>]{element}[:{paramN}][#{badge}[>{limit}]][?{tooltip}]`
#
# 常用属性参考:
# `{icon}` 指定图标名称的参数(例如 face这里查询所有可用的值 https://fonts.google.com/icons?icon.platform=web&icon.set=Material+Icons&icon.style=Rounded
# `{element}`的参数及介绍:
# `{shorthand}` - 以下的可用值都是预配置好的快捷指令,可作为按钮:
# play-pause播放/暂停menu菜单subtitles字幕轨列表audio音轨列表video视频轨列表playlist播放列表chapters章节列表editions版本列表
# stream-quality流式传输品质偏好open-file文件浏览器items播放列表/文件浏览器)
# next跳转下一个prev上一个first首位last末位audio-device音频输出设备列表
# fullscreen切换全屏loop-playlist切换列表循环loop-file切换单曲循环shuffle切换乱序播放autoload自动加载文件
#
# speed[:{scale}] (速度滑块 其中控件系数的尺寸,默认 1.3
# command:{icon}:{command} (按下该按钮时执行的指令)
# toggle:{icon}:{prop} (切换 mpv 属性的按钮)
#
# cycle:{default_icon}:{prop}:{value1}[={icon1}][!]/{valueN}[={iconN}][!]
# 在不同 mpv 属性的值之间循环的按钮,每个值都可以选择不同的 {icon} 和激活标记。结尾处赋予可选的半角感叹号,将使该按钮成为可激活的样式。
#
# gap[:{scale}] (留出一个间隔 其中系数的尺寸,默认 0.3
# space填补上两个控件之间的所有空间对于将条目右对齐非常有用。用多个 space 可在它们之间分配空间,可用于居中对齐)
#
# 控件条目的可见性控制:
## `<[!]{disposition1}[+[!]{dispositionN}][,{more_dispositions}]>` - 可选的前缀是用于控制 {element} 的可见性
# - `+` 创建 AND 条件,`,` 拆分为 OR 组。示例:`<foo,bar+baz>` -> `foo OR (bar AND baz)`
# - `{disposition}` 的可用值:
## idle 如果 mpv 处于空闲状态则为 true
## image 如果当前文件为单帧图片则为 true
## audio 如果当前文件为纯音频则为 true
## video 如果当前文件存在视频轨则为 true
## has_many_video 如果当前文件存在多个视频轨则为 true
## has_image 如果当前文件带有封面或其他图像轨道则为 true
## has_audio 如果当前文件存在音轨则为 true
## has_many_audio 如果当前文件存在多个音轨则为 true
## has_sub 如果当前文件存在字幕轨则为 true
## has_many_sub 如果当前文件存在多个字幕轨则为 true
## has_many_edition 如果当前文件存在多个版本则为 true
## has_chapter 如果当前文件存在章节则为 true
## stream 如果当前文件为流则为 true
## has_playlist 如果当前列表存在多个文件则为 true
## {mpv_prop} 任何 mpv 属性(注意:可以在脚本中设置 `user-data/foo` 以添加自定义属性)
## 可选的`!`前缀可用来反转所需的处理方式
## 示例:
## - `<stream>stream-quality` - 仅对流媒体显示“流式传输品质偏好”按钮
## - `<has_audio,!audio>audio` - 对所有存在音轨的文件显示“音轨列表”按钮,但不包括纯音频的文件
#
# 将 `#{badge}[>{limit}]` 放在 `{element}` 参数后,可赋予它一个徽章标记。可用的 `badge` 值:
## `sub`, `audio`, `video` - 轨道计数值
## `{mpv_prop}` - 如果 mpv 的属性值是一个数组将显示其大小。可用的属性参见https://mpv.io/manual/master/#property-list
## `>{limit}` 只有当它的数值高于此阈值时,才会显示徽章标记
## 示例:`#audio>1`
#
# 将 `?{tooltip}` 放在 `{element}` 的设置后面,赋予它一个工具提示
## 示例:`<stream>stream-quality?Stream quality`
#
# 一些可用的快捷指令的实现示范:
## menu = command:menu:script-binding uosc/menu-blurred?Menu
## subtitles = command:subtitles:script-binding uosc/subtitles#sub>1?Subtitles
## fullscreen = cycle:crop_free:fullscreen:no/yes=fullscreen_exit!?Fullscreen
## loop-playlist = cycle:repeat:loop-playlist:no/inf!?Loop playlist
## toggle:{icon}:{prop} = cycle:{icon}:{prop}:no/yes!
controls=menu,open-file,<idle>command:history:script-binding recentmenu/open?最近播放,<idle>command:bookmarks:script-binding simplebookmark/open-list?书签菜单,<idle>command:file_copy:script-binding smartcopypaste_II/open-list?剪贴菜单,gap,<!idle>command:analytics:script-binding stats/display-stats-toggle?统计,<stream>stream-quality,<audio,has_image>command:image:script-binding uosc/video#video?封面,<has_many_edition>editions,<video>video,<has_audio+!audio>audio,<video,audio>subtitles,<has_chapter>chapters,space,<has_chapter>command:skip_previous:add chapter -1?上一章节,<video,audio>speed,<has_chapter>command:skip_next:add chapter 1?下一章节,space,loop-file,shuffle,loop-playlist,gap,<has_playlist>prev,<has_playlist>playlist,<has_playlist>next,gap,audio-device,gap,fullscreen
controls_size=32
controls_margin=8
controls_spacing=2
controls_persistency=idle
# 显示音量控制的位置none, left, right
# 设置音量控制条的样式
volume=right
volume_size=40
volume_border=1
volume_step=1
volume_persistency=
# 播放速度小部件:鼠标拖动或滚轮更改,单击以重置
speed_step=0.1
speed_step_is_factor=no
speed_persistency=
# 控制所有菜单,如上下文菜单,字幕加载器/选择器等
menu_item_height=36
menu_min_width=260
menu_padding=1
# 确定激活搜索需要`/`或`ctrl+f`或者是否键入任何文本就足够了
# 启用后,如果菜单是 unicode 字符,则无法再使用打开菜单的相同键关闭菜单
menu_type_to_search=no
# 顶栏的显隐逻辑(仅在无边框和全屏模式下显示),默认 no-border 其它可用的值never, always
top_bar=no-border
top_bar_size=40
# 启用顶栏的右侧控制按钮,示例即默认值
top_bar_controls=yes
# 可以是:`no` (隐藏), `yes` (从 mpv.conf 继承标题), 或自定义模板字符串
top_bar_title=yes
# 使用模板字符串以启用替代顶部栏标题。如果替代标题与主标题匹配,它将被隐藏
# 提示:使用 '${media-title}' 表示主标题,使用 '${filename}' 表示替代标题
top_bar_alt_title=${?media-title:${media-title}}
# 可以是:
# `below` => 在主标题下方显示替代标题
# `toggle` => 通过单击顶部栏在主标题和 alt 之间切换顶部栏标题文本,或调用`toggle-title`绑定
top_bar_alt_title_place=below
# 加载以下类型的内容时闪烁顶部栏。可用audio,image,video,chapter。默认video,audio特殊值 none 禁用闪烁
top_bar_flash_on=video,audio
top_bar_persistency=
# 边框模式下绘制的内边框和透明度
window_border_size=1
# 如果没有播放列表和文件结束,加载目录中符合 load_types 选项指定的下一个文件默认no
# 当该选项被启用时 uosc 将主动设置 mpv 选项状态:`keep-open=yes``keep-open-pause=no`
autoload=no
# 启用播放列表/目录导航随机播放默认no
# 这是实现按预期工作的洗牌所必需的,包括目录导航,因为 mpv 内置的“随机播放”选项基本上无法使用
shuffle=no
# 指定 UI 的缩放比例,请参考显示器的 HIDPI 缩放比例
scale=1
# 指定 UI 在全屏时的缩放比例。默认值1.3
scale_fullscreen=1.3
# 自定义 UI 要使用的字体,推荐使用等宽字体以获得更好的显示效果。默认留空以使用'osd-font'
# font=Noto Sans CJK SC,Noto Color Emoji
# 调整文字缩放以适合您的字体
font_scale=1
# 指定直接在视频顶部绘制时文本和图标的边框。默认值1.2
text_border=1.2
# 指定按钮、菜单和所有其他矩形的边框半径。默认值4
border_radius=2
# 设置以逗号分隔的颜色覆盖列表,采用 RGB 十六进制格式:`rrggbb`
# 默认值: foreground=ffffff,foreground_text=000000,background=000000,background_text=ffffff,curtain=111111,match=69c5ff
color=foreground=FFFBFE,foreground_text=1C1B1F,background=1C1B1F,background_text=FFFBFE
# 设置以逗号分隔的不透明度覆盖列表,用于各种 UI 元素背景和形状。文本始终为 100%
# 示例: opacity=timeline=0.5,title=0.5
# 默认: timeline=0.9,position=1,chapters=0.8,slider=0.9,slider_gauge=1,controls=0,speed=0.6,menu=1,submenu=0.4,border=1,title=1,
# tooltip=1,thumbnail=1,curtain=0.8,idle_indicator=0.8,audio_indicator=0.5,buffering_indicator=0.3,playlist_position=0.8
opacity=menu=0.9,submenu=0.7,curtain=0.5
# 以逗号分隔的功能列表,以牺牲一些性能影响为代价进行优化。默认:空白
# text_width - 使用更准确的文本宽度测量,单独测量每个文本字符串而不是只测量一次已知字母的宽度并将它们相加
# sorting - 使用文件名排序可以更好地处理非英语语言,尤其是亚洲语言。目前,此功能仅在 Windows 上可用,对其他平台没有影响
refine=sorting
# 指定过渡动画的持续时间以毫秒为单位。默认值100
animation_duration=100
# `flash-{element}`命令使用的闪存持续时间(以毫秒为单位)
flash_duration=1000
# 以像素为单位的距离,低于该像素的元素完全淡入/淡出
proximity_in=40
proximity_out=120
# 是否在整个 UI 中仅使用粗体字体粗细默认no
font_bold=no
# 指定按何种方式显示时间轴右侧时间:`total`, `playtime-remaining`(按当前速度缩放), `time-remaining` (文件的剩余长度)
destination_time=playtime-remaining
# 指定显示时间戳的亚秒级精度,精确等效到秒的小数点后的位数。默认 0
time_precision=3
# 在时间轴中显示流的缓冲时间如果它低于此秒数0 表示禁用
buffered_time_threshold=60
# 是否在 mpv 自动隐藏光标时隐藏 UI默认no
autohide=no
# 指定切换暂停状态时显示的指示器样式
# 可以是none, flash, static, manual (由 flash-pause-indicator 和 decide-pause-indicator 命令控制)
pause_indicator=manual
# 指定在流质量菜单中列出的大小
stream_quality_options=4320,2160,1440,1080,720,480,360,240,144
# 指定导航媒体文件时要查找的文件类型
video_types=3g2,3gp,asf,avi,bdmv,f4v,flv,h264,h265,iso,ifo,m2ts,m4v,mkv,mov,mp4,mp4v,mpeg,mpg,ogm,ogv,rm,rmvb,ts,vob,webm,wmv,y4m
audio_types=aac,ac3,aiff,ape,au,dsf,dts,flac,m4a,mid,midi,mka,mp3,mp4a,oga,ogg,opus,spx,tak,tta,wav,weba,wma,wv
image_types=apng,avif,bmp,gif,j2k,jp2,jfif,jpeg,jpg,jxl,mj2,png,svg,tga,tif,tiff,webp
playlist_types=m3u,m3u8,pls,url,cue
# 指定加载外部字幕时要查找的文件类型
subtitle_types=aqt,ass,gsub,idx,jss,lrc,mks,pgs,pjs,psb,rt,slt,smi,sub,sup,sbv,srt,ssa,ssf,ttxt,txt,usf,vt,vtt
# 自动加载或请求播放下一个文件时接受哪些类型作为下一个条目
# 可选video,audio,image,playlist,same。特殊值 same 意味着只加载和当前文件属于同一类型的媒体文件
load_types=same
# 指定打开文件菜单时的默认目录,默认值:~/。使用特殊值 '{drives}' 打开 Windows 上的驱动器菜单(在 unix 上默认为 '/'
default_directory={drives}
# 读取目录时是否列出隐藏文件默认no。由于环境限制这目前仅隐藏以'.'点开头的文件
# 不会隐藏 Windows 上的隐藏文件(我们无法判断它们是隐藏的)
show_hidden_files=no
# 删除文件时移动到回收站默认no
use_trash=yes
# 根据 UI 元素的可见性调整了 OSD 边距默认yes
adjust_osd_margins=no
# 将一些常见的章节类型转换为章节范围指标,该章节拥有的时间轴部分基于彩色
# 章节范围指标语法是逗号分隔的“{type}:{color}”
# `{type}` - 范围类型。目前支持有:
# - `openings` - 开场白和动画开场
# - `endings` - 外传和动画结局
# - `ads` - 由脚本创建的赞助商块https://github.com/po5/mpv_sponsorblock
# `{color}` - 颜色代码,格式为 RRGGBB(AA)
#
# 要不转换任何范围类型,只需将其从列表中删除即可,全部留空即为不启用此特性
# 示例chapter_ranges=openings:38869680,endings:38869680,ads:a5353580
chapter_ranges=openings:30abf964,endings:30abf964,intros:3fb95080,outros:3fb95080,ads:c54e4e80
# 补充额外的 lua 模式来识别章节范围的起始点(除`ads`外的所有章节)
# 语法:`{type}:{pattern}[,{patternN}][;{type}:{pattern}[,{patternN}]]`
# 目前可定义的章节范围`type`有openings;endings;intros;outros
chapter_range_patterns=openings:^Intro%s*Start,オープニング$,^片头$,片头开始$;endings:^end$,^End$,エンディング$;intros:preview$,预告$,予告$;outros:credits$
# 指定本地化语言优先级,从高到低
# 内置语言可以在 'uosc/intl' 中找到。
# 'slang' 是从 '--slang' 属性继承值的关键字
# 支持自定义 json 文件的路径:`languages=~~/custom.json,slang,en`
languages=slang,en
# 默认情况下,将字幕下载到当前打开的文件的目录中
# 如果从 URL 播放文件,我们将使用此目录(扩展到 `{mpv_config_dir}/subtitles`
# 在路径前加上`!`以强制将所有字幕保存在那里,示例:`!~~/subtitles`
subtitles_directory=~~/subtitles
# 指定要禁用的元素 ID 的列表,使用逗号分隔,默认留空。可用 ID
# window_border, top_bar, timeline, controls, volume,
# idle_indicator, audio_indicator, buffering_indicator, pause_indicator
disable_elements=idle_indicator,audio_indicator
# 指定`ziggy`的可执行文件的路径。默认default, 即 `~~/scripts/uosc/bin`
# 留空将在系统 PATH 中查找可执行文件Windows 上还会在 mpv.exe 所在目录中查找
# ziggy_path=
+62
View File
@@ -0,0 +1,62 @@
## 指定弹幕服务器地址,自定义服务需兼容 dandanplay 的 api
api_server=https://api.dandanplay.net
## 指定 b 站和爱腾优的弹幕获取的兜底服务器地址,主要用于获取非动画弹幕
## 服务器可以自托管https://github.com/lyz05/danmaku
#fallback_server=https://fc.lyz05.cn
## 设置 tmdb 的 API Key用于获取非动画条目的中文信息(当搜索内容非中文时)
## 可以在 https://www.themoviedb.org 注册后去个人账号设置界面获取
## 注意:自定义此参数时还需要对获取到的 API Key 进行 base64 编码
#tmdb_api_key=NmJmYjIxOTZkNzIyN2UyMTIzMGM3Y2YzZjQ4MDNkZGM=
## 加载更多来自弹幕服务器上第三方的弹幕
load_more_danmaku=yes
## 自动加载网络弹幕
auto_load=no
## 自动加载本地弹幕
autoload_local_danmaku=yes
## 为 URL 串流播放场景自动加载弹幕
autoload_for_url=yes
## 自动保存网络弹幕到本地(视频同目录下同名 xml 文件)
save_danmaku=yes
## 启用 fps 视频滤镜让弹幕滚动更平滑
##! 性能消耗较大
vf_fps=yes
## 设置要使用的 fps 滤镜参数
fps=60/1.001
## 指定合并重复弹幕的时间间隔的容差值,单位为秒。默认值:-1表示禁用
merge_tolerance=0
## 限制同屏显示的最大弹幕数量,默认值 0 表示不限制
max_screen_danmaku=0
## 指定弹幕关联历史记录文件的路径,支持绝对路径和相对路径
history_path=~~/files/danmaku-history.json
## 中文简繁转换。0-不转换1-转换为简体2-转换为繁体
chConvert=1
## 滚动弹幕的显示时间。这会左右滚动速度
scrolltime=15
## 固定弹幕的显示时间
fixtime=5
## 字体
fontname=Noto Sans CJK SC
## 字体大小
fontsize=30
## 透明度
opacity=0.6
## 粗体
bold=yes
## 描边 0-4
outline=1
## 阴影
shadow=0
## 全部弹幕的显示范围0.0-1.0
displayarea=0.85
## 指定弹幕屏蔽词文件路径black.txt支持绝对路径和相对路径。文件内容以换行分隔
## 支持 lua 的正则表达式写法
blacklist_path=~~/files/danmaku-blacklist.txt
## 自定义标题解析中的额外替换规则,内容格式为 JSON 字符串,替换模式为 lua 的 string.gsub 函数
##! 注意:由于 mpv 的 lua 版本限制,自定义规则只支持形如 %n 的捕获组写法,即示例用法,不支持直接替换字符的写法
title_replace=[{"rules":[{ "^(.-)": "%1"},{ "^.*《(.-)》": "%1" }]}]
## 指定哈希匹配中需忽略的共享盘(挂载盘)的路径/目录。支持绝对路径和相对路径,多个路径用逗号分隔
## 示例:["X:", "Z:", "F:/Download/", "Download"]
#excluded_path=
+23
View File
@@ -0,0 +1,23 @@
###不支持参数后注释,须另起一行
##首选尝试用 ytdl 解析 url默认 no。取决于 url 播放场景是否大多需要 ytdl 解析
try_ytdl_first=yes
##url 解析地址黑名单,格式解释见 https://mpv.io/manual/master/#options-exclude
##推荐在 try_ytdl_first=yes 的情况下使用,可合理加速网络地址的解析
exclude=%.avi$|%.flac$|%.flv$|%.mp3$|%.m3u$|%.m3u8$|%.m4a$|%.m4v$|%.mkv$|%.mp4$|%.ts$|%.VOB$|%.wav$|%.webm$|%.wmw$
##当 try_ytdl_first 为 no 时 mpv 应尝试优先使用 ytdl 解析的 URL 模式列表。匹配方式与 exclude 相同,示例为默认值
include=^%w+%.youtube%.com/|^youtube%.com/|^youtu%.be/|^%w+%.twitch%.tv/|^twitch%.tv/
##https://mpv.io/manual/master/#options-all-formats
#all_formats=no
#force_all_formats=yes
##允许切换源的轨道(出于性能原因默认禁用)
#use_manifests=no
##youtube-dl 最活跃的分支 yt-dlp 大有取代上游的趋势且开发活跃 https://github.com/yt-dlp/yt-dlp/releases
##须要 youtube-dl.exe / yt-dlp.exe 在环境变量 PATH 中,或位于 mpv.exe 所在路径的目录下
##yt-dlp.exe 更新命令yt-dlp -U
#ytdl_path=yt-dlp
+70
View File
@@ -0,0 +1,70 @@
-- Runs write-watch-later-config periodically
local options = require 'mp.options'
local msg = require 'mp.msg'
o = {
save_interval = 60,
percent_pos = 99,
}
options.read_options(o)
local can_delete = true
local can_save = true
local path = nil -- only set after file success load, reset to nil when file unload.
local function reset()
path = nil
end
-- set vars when file success load
local function init()
path = mp.get_property("path")
end
local function save()
if not can_save then return end
local watch_later_list = mp.get_property("watch-later-options", {})
if mp.get_property_bool("save-position-on-quit") then
msg.debug("saving state")
if not watch_later_list:find("start") then
mp.commandv("change-list", "watch-later-options", "append", "start")
end
mp.command("write-watch-later-config")
end
end
local function save_if_pause(_, pause)
if pause then save() end
end
local function pause_timer_while_paused(_, pause)
if pause then timer:stop() else timer:resume() end
end
-- save watch-later-config when file unloading
local function save_or_delete()
if not can_delete then return end
local eof = mp.get_property_bool("eof-reached")
local percent_pos = mp.get_property_number("percent-pos")
if eof or percent_pos and (percent_pos == 0 or percent_pos >= o.percent_pos) then
can_delete = true
if path ~= nil then
msg.debug("deleting state: percent_pos=0 or eof")
mp.commandv("delete-watch-later-config", path)
end
elseif path ~= nil then
save()
end
reset()
end
mp.register_script_message("skip-delete-state", function() can_delete = false end)
timer = mp.add_periodic_timer(o.save_interval, save)
mp.observe_property("pause", "bool", pause_timer_while_paused)
mp.observe_property("pause", "bool", save_if_pause)
mp.register_event("file-loaded", init)
mp.add_hook("on_unload", 50, save_or_delete) -- after mpv saving state
+610
View File
@@ -0,0 +1,610 @@
-- This script automatically loads playlist entries before and after the
-- the currently played file. It does so by scanning the directory a file is
-- located in when starting playback. It sorts the directory entries
-- alphabetically, and adds entries before and after the current file to
-- the internal playlist. (It stops if it would add an already existing
-- playlist entry at the same position - this makes it "stable".)
-- Add at most 5000 * 2 files when starting a file (before + after).
--[[
To configure this script use file autoload.conf in directory script-opts (the "script-opts"
directory must be in the mpv configuration directory, typically ~/.config/mpv/).
Option `ignore_patterns` is a comma-separated list of patterns (see lua.org/pil/20.2.html).
Additionally to the standard lua patterns, you can also escape commas with `%`,
for example, the option `bak%,x%,,another` will be resolved as patterns `bak,x,` and `another`.
But it does not mean you need to escape all lua patterns twice,
so the option `bak%%,%。mp4,` will be resolved as two patterns `bak%%` and `%.mp4`.
Example configuration would be:
disabled=no
images=no
videos=yes
audio=yes
additional_image_exts=list,of,ext
additional_video_exts=list,of,ext
additional_audio_exts=list,of,ext
ignore_hidden=yes
same_type=yes
same_series=yes
directory_mode=recursive
ignore_patterns=^~,^bak-,%.bak$
--]]
MAXENTRIES = 5000
MAXDIRSTACK = 20
local msg = require 'mp.msg'
local options = require 'mp.options'
local utils = require 'mp.utils'
o = {
disabled = false,
images = true,
videos = true,
audio = true,
additional_image_exts = "",
additional_video_exts = "",
additional_audio_exts = "",
ignore_hidden = true,
same_type = false,
same_series = false,
directory_mode = "ignore",
ignore_patterns = ""
}
options.read_options(o, nil, function(list)
split_option_exts(list.additional_video_exts, list.additional_audio_exts, list.additional_image_exts)
if list.videos or list.additional_video_exts or
list.audio or list.additional_audio_exts or
list.images or list.additional_image_exts then
create_extensions()
end
if list.directory_mode then
validate_directory_mode()
end
end)
function Set (t)
local set = {}
for _, v in pairs(t) do set[v] = true end
return set
end
function SetUnion (a,b)
for k in pairs(b) do a[k] = true end
return a
end
-- Returns first and last positions in string or past-to-end indices
function FindOrPastTheEnd (string, pattern, start_at)
local pos1, pos2 = string.find(string, pattern, start_at)
return pos1 or #string + 1,
pos2 or #string + 1
end
function Split (list)
local set = {}
local item_pos = 1
local item = ""
while item_pos <= #list do
local pos1, pos2 = FindOrPastTheEnd(list, "%%*,", item_pos)
local pattern_length = pos2 - pos1
local is_comma_escaped = pattern_length % 2
local pos_before_escape = pos1 - 1
local item_escape_count = pattern_length - is_comma_escaped
item = item .. string.sub(list, item_pos, pos_before_escape + item_escape_count)
if is_comma_escaped == 1 then
item = item .. ","
else
set[item] = true
item = ""
end
item_pos = pos2 + 1
end
set[item] = true
-- exclude empty items
set[""] = nil
return set
end
EXTENSIONS_VIDEO_DEFAULT = Set {
'3g2', '3gp', 'avi', 'flv', 'm2ts', 'm4v', 'mj2', 'mkv', 'mov',
'mp4', 'mpeg', 'mpg', 'ogv', 'rmvb', 'webm', 'wmv', 'y4m'
}
EXTENSIONS_AUDIO_DEFAULT = Set {
'aiff', 'ape', 'au', 'flac', 'm4a', 'mka', 'mp3', 'oga', 'ogg',
'ogm', 'opus', 'wav', 'wma'
}
EXTENSIONS_IMAGES_DEFAULT = Set {
'avif', 'bmp', 'gif', 'j2k', 'jp2', 'jpeg', 'jpg', 'jxl', 'png',
'svg', 'tga', 'tif', 'tiff', 'webp'
}
function split_option_exts(video, audio, image)
if video then o.additional_video_exts = Split(o.additional_video_exts) end
if audio then o.additional_audio_exts = Split(o.additional_audio_exts) end
if image then o.additional_image_exts = Split(o.additional_image_exts) end
end
split_option_exts(true, true, true)
function split_patterns()
o.ignore_patterns = Split(o.ignore_patterns)
end
split_patterns()
function create_extensions()
EXTENSIONS = {}
EXTENSIONS_VIDEO = {}
EXTENSIONS_AUDIO = {}
EXTENSIONS_IMAGES = {}
if o.videos then
SetUnion(SetUnion(EXTENSIONS_VIDEO, EXTENSIONS_VIDEO_DEFAULT), o.additional_video_exts)
SetUnion(EXTENSIONS, EXTENSIONS_VIDEO)
end
if o.audio then
SetUnion(SetUnion(EXTENSIONS_AUDIO, EXTENSIONS_AUDIO_DEFAULT), o.additional_audio_exts)
SetUnion(EXTENSIONS, EXTENSIONS_AUDIO)
end
if o.images then
SetUnion(SetUnion(EXTENSIONS_IMAGES, EXTENSIONS_IMAGES_DEFAULT), o.additional_image_exts)
SetUnion(EXTENSIONS, EXTENSIONS_IMAGES)
end
end
create_extensions()
function validate_directory_mode()
if o.directory_mode ~= "recursive" and o.directory_mode ~= "lazy" and o.directory_mode ~= "ignore" then
o.directory_mode = nil
end
end
validate_directory_mode()
function add_files(files)
local oldcount = mp.get_property_number("playlist-count", 1)
for i = 1, #files do
mp.commandv("loadfile", files[i][1], "append")
mp.commandv("playlist-move", oldcount + i - 1, files[i][2])
end
end
function get_extension(path)
match = string.match(path, "%.([^%.]+)$" )
if match == nil then
return "nomatch"
else
return match
end
end
function get_filename_without_ext(filename)
local idx = filename:match(".+()%.%w+$")
if idx then
filename = filename:sub(1, idx - 1)
end
return filename
end
function utf8_char_bytes(str, i)
local char_byte = str:byte(i)
if char_byte < 0xC0 then
return 1
elseif char_byte < 0xE0 then
return 2
elseif char_byte < 0xF0 then
return 3
elseif char_byte < 0xF8 then
return 4
else
return 1
end
end
function utf8_iter(str)
local byte_start = 1
return function()
local start = byte_start
if #str < start then return nil end
local byte_count = utf8_char_bytes(str, start)
byte_start = start + byte_count
return start, str:sub(start, start + byte_count - 1)
end
end
function utf8_to_table(str)
local t = {}
for _, ch in utf8_iter(str) do
t[#t + 1] = ch
end
return t
end
function jaro(s1, s2)
local match_window = math.floor(math.max(#s1, #s2) / 2.0) - 1
local matches1 = {}
local matches2 = {}
local m = 0;
local t = 0;
for i = 0, #s1, 1 do
local start = math.max(0, i - match_window)
local final = math.min(i + match_window + 1, #s2)
for k = start, final, 1 do
if not (matches2[k] or s1[i] ~= s2[k]) then
matches1[i] = true
matches2[k] = true
m = m + 1
break
end
end
end
if m == 0 then
return 0.0
end
local k = 0
for i = 0, #s1, 1 do
if matches1[i] then
while not matches2[k] do
k = k + 1
end
if s1[i] ~= s2[k] then
t = t + 1
end
k = k + 1
end
end
t = t / 2.0
return (m / #s1 + m / #s2 + (m - t) / m) / 3.0
end
function jaro_winkler_distance(s1, s2)
if #s1 + #s2 == 0 then
return 0.0
end
if s1 == s2 then
return 1.0
end
s1 = utf8_to_table(s1)
s2 = utf8_to_table(s2)
local d = jaro(s1, s2)
local p = 0.1
local l = 0;
while (s1[l] == s2[l] and l < 4) do
l = l + 1
end
return d + l * p * (1 - d)
end
function is_same_series(f1, f2)
local f1, f2 = get_filename_without_ext(f1), get_filename_without_ext(f2)
if f1 ~= f2 then
-- by episode
local sub1 = f1:gsub("^[%[%(]+.-[%]%)]+[%s%[]*", ""):match("(.-%D+)0*%d+")
local sub2 = f2:gsub("^[%[%(]+.-[%]%)]+[%s%[]*", ""):match("(.-%D+)0*%d+")
if sub1 and sub2 and sub1 == sub2 then
return true
end
-- by similarity
local threshold = 0.8
local similarity = jaro_winkler_distance(f1, f2)
if similarity > threshold then
return true
end
end
return false
end
function is_ignored(file)
for pattern, _ in pairs(o.ignore_patterns) do
if string.match(file, pattern) then
return true
end
end
return false
end
table.filter = function(t, iter)
for i = #t, 1, -1 do
if not iter(t[i]) then
table.remove(t, i)
end
end
end
table.append = function(t1, t2)
local t1_size = #t1
for i = 1, #t2 do
t1[t1_size + i] = t2[i]
end
end
----- winapi start -----
-- in windows system, we can use the sorting function provided by the win32 API
-- see https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw
-- this function was taken from https://github.com/mpvnet-player/mpv.net/issues/575#issuecomment-1817413401
local winapi = {}
local is_windows = mp.get_property_native("platform") == "windows"
if is_windows then
-- is_ffi_loaded is false usually means the mpv builds without luajit
local is_ffi_loaded, ffi = pcall(require, "ffi")
if is_ffi_loaded then
winapi = {
ffi = ffi,
C = ffi.C,
CP_UTF8 = 65001,
shlwapi = ffi.load("shlwapi"),
}
-- ffi code from https://github.com/po5/thumbfast, Mozilla Public License Version 2.0
ffi.cdef[[
int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr,
int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar);
int __stdcall StrCmpLogicalW(wchar_t *psz1, wchar_t *psz2);
]]
winapi.utf8_to_wide = function(utf8_str)
if utf8_str then
local utf16_len = winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, utf8_str, -1, nil, 0)
if utf16_len > 0 then
local utf16_str = winapi.ffi.new("wchar_t[?]", utf16_len)
if winapi.C.MultiByteToWideChar(winapi.CP_UTF8, 0, utf8_str, -1, utf16_str, utf16_len) > 0 then
return utf16_str
end
end
end
return ""
end
end
end
----- winapi end -----
function alphanumsort_windows(filenames)
table.sort(filenames, function(a, b)
local a_wide = winapi.utf8_to_wide(a)
local b_wide = winapi.utf8_to_wide(b)
return winapi.shlwapi.StrCmpLogicalW(a_wide, b_wide) == -1
end)
return filenames
end
-- alphanum sorting for humans in Lua
-- http://notebook.kulchenko.com/algorithms/alphanumeric-natural-sorting-for-humans-in-lua
function alphanumsort_lua(filenames)
local function padnum(n, d)
return #d > 0 and ("%03d%s%.12f"):format(#n, n, tonumber(d) / (10 ^ #d))
or ("%03d%s"):format(#n, n)
end
local tuples = {}
for i, f in ipairs(filenames) do
tuples[i] = {f:lower():gsub("0*(%d+)%.?(%d*)", padnum), f}
end
table.sort(tuples, function(a, b)
return a[1] == b[1] and #b[2] < #a[2] or a[1] < b[1]
end)
for i, tuple in ipairs(tuples) do filenames[i] = tuple[2] end
return filenames
end
function alphanumsort(filenames)
local is_ffi_loaded = pcall(require, "ffi")
if is_windows and is_ffi_loaded then
alphanumsort_windows(filenames)
else
alphanumsort_lua(filenames)
end
end
local autoloaded = nil
local added_entries = {}
local autoloaded_dir = nil
function scan_dir(path, current_file, dir_mode, separator, dir_depth, total_files, extensions)
if dir_depth == MAXDIRSTACK then
return
end
msg.trace("scanning: " .. path)
local files = utils.readdir(path, "files") or {}
local dirs = dir_mode ~= "ignore" and utils.readdir(path, "dirs") or {}
local prefix = path == "." and "" or path
table.filter(files, function (v)
-- The current file could be a hidden file, ignoring it doesn't load other
-- files from the current directory.
local current = prefix .. v == current_file
if o.ignore_hidden and not current and string.match(v, "^%.") then
return false
end
if not current and is_ignored(v) then
return false
end
local ext = get_extension(v)
if ext == nil then
return false
end
local name = mp.get_property("filename")
if o.same_series then
local name = mp.get_property("filename")
for ext, _ in pairs(extensions) do
if name:match(ext .. "$") ~= nil and v ~= name and
not is_same_series(name, v)
then
return false
end
end
end
return extensions[string.lower(ext)]
end)
table.filter(dirs, function(d)
return not ((o.ignore_hidden and string.match(d, "^%.")))
end)
alphanumsort(files)
alphanumsort(dirs)
for i, file in ipairs(files) do
files[i] = prefix .. file
end
table.append(total_files, files)
if dir_mode == "recursive" then
for _, dir in ipairs(dirs) do
scan_dir(prefix .. dir .. separator, current_file, dir_mode,
separator, dir_depth + 1, total_files, extensions)
end
else
for i, dir in ipairs(dirs) do
dirs[i] = prefix .. dir
end
table.append(total_files, dirs)
end
end
function find_and_add_entries()
local path = mp.get_property("path", "")
local dir, filename = utils.split_path(path)
msg.trace(("dir: %s, filename: %s"):format(dir, filename))
if o.disabled then
msg.debug("stopping: autoload disabled")
return
elseif #dir == 0 then
msg.debug("stopping: not a local path")
return
end
local pl_count = mp.get_property_number("playlist-count", 1)
this_ext = get_extension(filename)
-- check if this is a manually made playlist
if (pl_count > 1 and autoloaded == nil) or
(pl_count == 1 and EXTENSIONS[string.lower(this_ext)] == nil) then
msg.debug("stopping: manually made playlist")
return
else
if pl_count == 1 then
autoloaded = true
autoloaded_dir = dir
added_entries = {}
end
end
local extensions = {}
if o.same_type then
if EXTENSIONS_VIDEO[string.lower(this_ext)] ~= nil then
extensions = EXTENSIONS_VIDEO
elseif EXTENSIONS_AUDIO[string.lower(this_ext)] ~= nil then
extensions = EXTENSIONS_AUDIO
else
extensions = EXTENSIONS_IMAGES
end
else
extensions = EXTENSIONS
end
local pl = mp.get_property_native("playlist", {})
local pl_current = mp.get_property_number("playlist-pos-1", 1)
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
utils.to_string(pl)))
local files = {}
do
local dir_mode = o.directory_mode or mp.get_property("directory-mode", "lazy")
local separator = mp.get_property_native("platform") == "windows" and "\\" or "/"
scan_dir(autoloaded_dir, path, dir_mode, separator, 0, files, extensions)
end
if next(files) == nil then
msg.debug("no other files or directories in directory")
return
end
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
local current
for i = 1, #files do
if files[i] == path then
current = i
break
end
end
if current == nil then
return
end
msg.trace("current file position in files: "..current)
-- treat already existing playlist entries, independent of how they got added
-- as if they got added by autoload
for _, entry in ipairs(pl) do
added_entries[entry.filename] = true
end
local append = {[-1] = {}, [1] = {}}
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
for i = 1, MAXENTRIES do
local pos = current + i * direction
local file = files[pos]
if file == nil or file[1] == "." then
break
end
-- skip files that are/were already in the playlist
if not added_entries[file] then
if direction == -1 then
msg.verbose("Prepending " .. file)
table.insert(append[-1], 1, {file, pl_current + i * direction + 1})
else
msg.verbose("Adding " .. file)
if pl_count > 1 then
table.insert(append[1], {file, pl_current + i * direction - 1})
else
mp.commandv("loadfile", file, "append")
end
end
end
added_entries[file] = true
end
if pl_count == 1 and direction == -1 and #append[-1] > 0 then
for i = 1, #append[-1] do
mp.commandv("loadfile", append[-1][i][1], "append")
end
mp.commandv("playlist-move", 0, current)
end
end
if pl_count > 1 then
add_files(append[1])
add_files(append[-1])
end
end
mp.register_event("start-file", find_and_add_entries)
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 joaquintorres
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+130
View File
@@ -0,0 +1,130 @@
# autosubsync-mpv
Automatic subtitle synchronization script for [mpv](https://wiki.archlinux.org/index.php/Mpv).
A demo can be viewed on <a target="_blank" href="https://www.youtube.com/watch?v=w1vwnUiF6Bc"><img src="https://user-images.githubusercontent.com/69171671/115097010-4bd13c80-9f17-11eb-83e9-2583658f73bc.png" width="80px"></a>
Supported backends:
* [ffsubsync](https://github.com/smacke/ffsubsync)
* [alass](https://github.com/kaegi/alass)
## Installation
0. Make sure you have mpv v0.33 or higher installed.
```
$ mpv --version
```
1. Install [FFmpeg](https://wiki.archlinux.org/index.php/FFmpeg):
```
$ pacman -S ffmpeg
```
Windows users have to manually install FFmpeg from [here](https://ffmpeg.zeranoe.com/builds/).
2. Install your retiming program of choice,
[ffsubsync](https://github.com/smacke/ffsubsync), [alass](https://github.com/kaegi/alass) or both:
```
$ pip install ffsubsync
```
```
$ trizen -S alass-git # for Arch-based distros
```
3. Download the add-on and save it to your mpv scripts folder.
| GNU/Linux | Windows |
|---|---|
| `~/.config/mpv/scripts` | `%AppData%\mpv\scripts\` |
To do it in one command:
```
$ git clone 'https://github.com/Ajatt-Tools/autosubsync-mpv' ~/.config/mpv/scripts/autosubsync
```
## Configuration
You can skip this step if the add-on works out of the box.
Create a config file:
| GNU/Linux | Windows |
|---|---|
| `~/.config/mpv/script-opts/autosubsync.conf` | `%AppData%\mpv\script-opts\autosubsync.conf` |
Example config:
```
# Absolute paths to the executables, if needed:
# 1. ffmpeg
ffmpeg_path=C:/Program Files/ffmpeg/bin/ffmpeg.exe
ffmpeg_path=/usr/bin/ffmpeg
# 2. ffsubsync
ffsubsync_path=C:/Program Files/ffsubsync/ffsubsync.exe
ffsubsync_path=/home/user/.local/bin/ffsubsync
# 3. alass
alass_path=C:/Program Files/ffmpeg/bin/alass.exe
alass_path=/usr/bin/alass
# Preferred retiming tool. Allowed options: 'ffsubsync', 'alass', 'ask'.
# If set to 'ask', the add-on will ask to choose the tool every time:
# 1. Preferred tool for syncing to audio.
audio_subsync_tool=ask
audio_subsync_tool=ffsubsync
audio_subsync_tool=alass
# 2. Preferred tool for syncing to another subtitle.
altsub_subsync_tool=ask
altsub_subsync_tool=ffsubsync
altsub_subsync_tool=alass
# Unload old subs (yes,no)
# After retiming, tell mpv to forget the original subtitle track.
unload_old_sub=yes
unload_old_sub=no
```
## Notes
* On Windows, you need to use forward slashes or double backslashes for your path.
For example, `"C:\\Users\\YourPath\\Scripts\\ffsubsync"`
or `"C:/Users/YourPath/Scripts/ffsubsync"`,
or it might not work.
* On GNU/Linux you can use `which ffsubsync` to find out where it is.
## Usage
When you have an out of sync sub, press `n` to synchronize it.
`ffsubsync` can typically take up to about 20-30 seconds
to synchronize (I've seen it take as much as 2 minutes
with a very large file on a lower end computer), so it
would probably be faster to find another, properly
synchronized subtitle with `autosub` or `trueautosub`.
Many times this is just not possible, as all available
subs for your specific language are out of sync.
Take into account that using this script has the
same limitations as `ffsubsync`, so subtitles that have
a lot of extra text or are meant for an entirely different
version of the video might not sync properly. `alass` is supposed
to handle some edge cases better, but I haven't fully tested it yet,
obtaining similar results with both.
Note that the script will create a new subtitle file, in the same folder
as the original, with the `_retimed` suffix at the end.
## Issues and feedback
If you are having trouble getting it to work or you've found a bug,
feel free to [join our community](https://tatsumoto-ren.github.io/blog/join-our-community.html) to ask directly.
Try to check if
[ffsubsync](https://github.com/smacke/ffsubsync)
or
[alass](https://github.com/kaegi/alass)
works properly outside of `mpv` first.
If the retiming tool of choice isn't working, `autosubsync` will likely fail.
+559
View File
@@ -0,0 +1,559 @@
-- Usage:
-- default keybinding: n
-- add the following to your input.conf to change the default keybinding:
-- keyname script_binding autosubsync-menu
local mp = require('mp')
local utils = require('mp.utils')
local mpopt = require('mp.options')
local menu = require('menu')
local sub = require('subtitle')
local ref_selector
local engine_selector
local track_selector
-- Config
-- Options can be changed here or in a separate config file.
-- Config path: ~/.config/mpv/script-opts/autosubsync.conf
local config = {
-- Change the following lines if the locations of executables differ from the defaults
-- If set to empty, the path will be guessed.
ffmpeg_path = "",
ffsubsync_path = "",
alass_path = "",
-- Choose what tool to use. Allowed options: ffsubsync, alass, ask.
-- If set to ask, the add-on will ask to choose the tool every time.
audio_subsync_tool = "ask",
altsub_subsync_tool = "ask",
-- After retiming, tell mpv to forget the original subtitle track.
unload_old_sub = true,
}
mpopt.read_options(config, 'autosubsync')
local function is_empty(var)
return var == nil or var == '' or (type(var) == 'table' and next(var) == nil)
end
----- string
local function replace(str, what, with)
if is_empty(str) then return "" end
if is_empty(what) then return str end
if with == nil then with = "" end
what = string.gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1")
with = string.gsub(with, "[%%]", "%%%%")
return string.gsub(str, what, with)
end
local function esc_for_title(string)
string = string:gsub('^[%._%-%s]*', '')
:gsub('%.%w+$', '')
return string
end
local function esc_for_code(trackCode)
if trackCode:find("PGS") then trackCode = "PGS"
elseif trackCode:find("SUBRIP") then trackCode = "SRT"
elseif trackCode:find("VTT") then trackCode = "VTT"
elseif trackCode:find("DVD_SUB") then trackCode = "VOB_SUB"
elseif trackCode:find("DVB_SUB") then trackCode = "DVB_SUB"
elseif trackCode:find("DVB_TELE") then trackCode = "TELETEXT"
elseif trackCode:find("ARIB") then trackCode = "ARIB"
end
return trackCode
end
-- Snippet borrowed from stackoverflow to get the operating system
-- originally found at: https://stackoverflow.com/a/30960054
local os_name = (function()
if os.getenv("HOME") == nil then
return function()
return "Windows"
end
else
return function()
return "*nix"
end
end
end)()
local os_temp = (function()
if os_name() == "Windows" then
return function()
return os.getenv('TEMP')
end
else
return function()
return '/tmp/'
end
end
end)()
-- Courtesy of https://stackoverflow.com/questions/4990990/check-if-a-file-exists-with-lua
local function file_exists(filepath)
if not filepath then
return false
end
local f = io.open(filepath, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
local function find_executable(name)
local os_path = os.getenv("PATH") or ""
local fallback_path = utils.join_path("/usr/bin", name)
local exec_path
for path in os_path:gmatch("[^:]+") do
exec_path = utils.join_path(path, name)
if file_exists(exec_path) then
return exec_path
end
end
return fallback_path
end
local function notify(message, level, duration)
level = level or 'info'
duration = duration or 1
mp.msg[level](message)
mp.osd_message(message, duration)
end
local function subprocess(args)
return mp.command_native {
name = "subprocess",
playback_only = false,
capture_stdout = true,
args = args
}
end
local url_decode = function(url)
local function hex_to_char(x)
return string.char(tonumber(x, 16))
end
if url ~= nil then
url = url:gsub("^file://", "")
url = url:gsub("+", " ")
url = url:gsub("%%(%x%x)", hex_to_char)
return url
else
return
end
end
local function get_loaded_tracks(track_type)
local result = {}
local track_list = mp.get_property_native('track-list')
for _, track in pairs(track_list) do
if track.type == track_type then
track['external-filename'] = track.external and url_decode(track['external-filename'])
table.insert(result, track)
end
end
return result
end
local function get_active_track(track_type)
local track_list = mp.get_property_native('track-list')
for num, track in ipairs(track_list) do
if track.type == track_type and track.selected == true then
if track.external then
track['external-filename'] = url_decode(track['external-filename'])
end
if not (track_type == 'sub' and track.id == mp.get_property_native('secondary-sid')) then
return num, track
end
end
end
return notify(string.format("错误: 没有选择类型为 '%s' 的轨道", track_type), "error", 3)
end
local function remove_extension(filename)
return filename:gsub('%.%w+$', '')
end
local function get_extension(filename)
return filename:match("^.+(%.%w+)$")
end
local function startswith(str, prefix)
return string.sub(str, 1, string.len(prefix)) == prefix
end
local function mkfp_retimed(sub_path)
if not startswith(sub_path, os_temp()) then
return table.concat { remove_extension(sub_path), '_retimed', get_extension(sub_path) }
else
return table.concat { remove_extension(mp.get_property("path")), '_retimed', get_extension(sub_path) }
end
end
local function engine_is_set()
local subsync_tool = ref_selector:get_subsync_tool()
if is_empty(subsync_tool) or subsync_tool == "ask" then
return false
else
return true
end
end
local function extract_to_file(subtitle_track)
local codec_ext_map = { subrip = "srt", ass = "ass" }
local ext = codec_ext_map[subtitle_track['codec']]
if ext == nil then
return notify(string.format("错误: 不支持的格式: %s", subtitle_track['codec']), "error", 3)
end
local temp_sub_fp = utils.join_path(os_temp(), 'autosubsync_extracted.' .. ext)
notify("提取内封字幕...", nil, 3)
local screenx, screeny, aspect = mp.get_osd_size()
mp.set_osd_ass(screenx, screeny, "{\\an9}● ")
local ret = subprocess {
config.ffmpeg_path,
"-hide_banner",
"-nostdin",
"-y",
"-loglevel", "quiet",
"-an",
"-vn",
"-i", mp.get_property("path"),
"-map", "0:" .. (subtitle_track and subtitle_track['ff-index'] or 's'),
"-f", ext,
temp_sub_fp
}
mp.set_osd_ass(screenx, screeny, "")
if ret == nil or ret.status ~= 0 then
return notify("无法提取内封字幕.\n请先确保在脚本配置文件中为 ffmpeg 指定了正确的路径\n并确保视频有内封字幕.", "error", 7)
end
return temp_sub_fp
end
local function sync_subtitles(ref_sub_path)
local reference_file_path = ref_sub_path or mp.get_property("path")
local _, sub_track = get_active_track('sub')
if sub_track == nil then
return
end
local subtitle_path = sub_track.external and sub_track['external-filename'] or extract_to_file(sub_track)
local engine_name = engine_selector:get_engine_name()
local engine_path = config[engine_name .. '_path']
if not file_exists(subtitle_path) then
return notify(
table.concat {
"字幕同步失败:\n无法找到 ",
subtitle_path or "外部字幕文件."
},
"error",
3
)
end
local retimed_subtitle_path = mkfp_retimed(subtitle_path)
notify(string.format("开始 %s...", engine_name), nil, 2)
local ret
local screenx, screeny, aspect = mp.get_osd_size()
if engine_name == "ffsubsync" then
local args = { config.ffsubsync_path, reference_file_path, "-i", subtitle_path, "-o", retimed_subtitle_path }
if not ref_sub_path then
table.insert(args, '--reference-stream')
table.insert(args, '0:' .. get_active_track('audio'))
end
mp.set_osd_ass(screenx, screeny, "{\\an9}● ")
ret = subprocess(args)
mp.set_osd_ass(screenx, screeny, "")
else
mp.set_osd_ass(screenx, screeny, "{\\an9}● ")
ret = subprocess { config.alass_path, reference_file_path, subtitle_path, retimed_subtitle_path }
mp.set_osd_ass(screenx, screeny, "")
end
if ret == nil then
return notify("解析失败或没有传递参数.", "fatal", 3)
end
if ret.status == 0 then
local old_sid = mp.get_property("sid")
if mp.commandv("sub_add", retimed_subtitle_path) then
notify("字幕同步.", nil, 2)
mp.set_property("sub-delay", 0)
if config.unload_old_sub then
mp.commandv("sub_remove", old_sid)
end
else
notify("错误: 不能添加同步字幕.", "error", 3)
end
else
notify(string.format("字幕同步失败.\n请确保在脚本配置文件中为 %s 指定了正确的路径.\n或音轨提取失败", engine_name), "error", 3)
end
end
local function sync_to_subtitle()
local selected_track = track_selector:get_selected_track()
if selected_track and selected_track.external then
sync_subtitles(selected_track['external-filename'])
else
local temp_sub_fp = extract_to_file(selected_track)
if temp_sub_fp then
sync_subtitles(temp_sub_fp)
os.remove(temp_sub_fp)
end
end
end
local function sync_to_manual_offset()
local _, track = get_active_track('sub')
local sub_delay = tonumber(mp.get_property("sub-delay"))
if tonumber(sub_delay) == 0 then
return notify("没有手动调整时轴,什么都做不了!", "error", 7)
end
local file_path = track.external and track['external-filename'] or extract_to_file(track)
if file_path == nil then
return
end
local ext = get_extension(file_path)
local codec_parser_map = { ass = sub.ASS, subrip = sub.SRT }
local parser = codec_parser_map[track['codec']]
if parser == nil then
return notify(string.format("错误: 不支持的格式: %s", track['codec']), "error", 3)
end
local s = parser:populate(file_path)
s:shift_timing(sub_delay)
if track.external == false then
os.remove(file_path)
s.filename = mp.get_property("filename/no-ext") .. "_manual_timing" .. ext
else
s.filename = remove_extension(s.filename) .. '_manual_timing' .. ext
end
s:save()
mp.commandv("sub_add", s.filename)
if config.unload_old_sub then
mp.commandv("sub_remove", track.id)
end
mp.set_property("sub-delay", 0)
return notify(string.format("手动同步保存,加载 '%s'", s.filename), "info", 7)
end
------------------------------------------------------------
-- Menu actions & bindings
ref_selector = menu:new {
items = { '与音频同步', '与其他字幕同步', '保存当前时轴', '退出' },
last_choice = 'audio',
pos_x = 50,
pos_y = 50,
rect_width = 400,
text_color = 'fff5da',
border_color = '2f1728',
active_color = 'ff6b71',
inactive_color = 'fff5da',
}
function ref_selector:get_keybindings()
return {
{ key = 'h', fn = function() self:close() end },
{ key = 'j', fn = function() self:down() end },
{ key = 'k', fn = function() self:up() end },
{ key = 'l', fn = function() self:act() end },
{ key = 'down', fn = function() self:down() end },
{ key = 'up', fn = function() self:up() end },
{ key = 'Enter', fn = function() self:act() end },
{ key = 'ESC', fn = function() self:close() end },
{ key = 'n', fn = function() self:close() end },
{ key = 'WHEEL_DOWN', fn = function() self:down() end },
{ key = 'WHEEL_UP', fn = function() self:up() end },
{ key = 'MBTN_LEFT', fn = function() self:act() end },
{ key = 'MBTN_RIGHT', fn = function() self:close() end },
}
end
function ref_selector:new(o)
self.__index = self
o = o or {}
return setmetatable(o, self)
end
function ref_selector:get_ref()
if self.selected == 1 then
return 'audio'
elseif self.selected == 2 then
return 'sub'
else
return nil
end
end
function ref_selector:get_subsync_tool()
if self.selected == 1 then
return config.audio_subsync_tool
elseif self.selected == 2 then
return config.altsub_subsync_tool
end
end
function ref_selector:act()
self:close()
if self.selected == 3 then
return sync_to_manual_offset()
end
if self.selected == 4 then
return
end
engine_selector:init()
end
function ref_selector:call_subsync()
if self.selected == 1 then
sync_subtitles()
elseif self.selected == 2 then
sync_to_subtitle()
elseif self.selected == 3 then
sync_to_manual_offset()
end
end
function ref_selector:open()
self.selected = 1
for _, val in pairs(self:get_keybindings()) do
mp.add_forced_key_binding(val.key, val.key, val.fn)
end
self:draw()
end
function ref_selector:close()
for _, val in pairs(self:get_keybindings()) do
mp.remove_key_binding(val.key)
end
self:erase()
end
------------------------------------------------------------
-- Engine selector
engine_selector = ref_selector:new {
items = { 'ffsubsync', 'alass', '退出' },
last_choice = 'ffsubsync',
}
function engine_selector:init()
if not engine_is_set() then
engine_selector:open()
else
track_selector:init()
end
end
function engine_selector:get_engine_name()
return engine_is_set() and ref_selector:get_subsync_tool() or self.last_choice
end
function engine_selector:act()
self:close()
if self.selected == 1 then
self.last_choice = 'ffsubsync'
elseif self.selected == 2 then
self.last_choice = 'alass'
elseif self.selected == 3 then
return
end
track_selector:init()
end
------------------------------------------------------------
-- Track selector
track_selector = ref_selector:new { }
function track_selector:init()
self.selected = 0
if ref_selector:get_ref() == 'audio' then
return ref_selector:call_subsync()
end
self.all_sub_tracks = get_loaded_tracks(ref_selector:get_ref())
self.tracks = {}
self.items = {}
local filename = mp.get_property_native('filename/no-ext')
for _, track in ipairs(self.all_sub_tracks) do
local supported_format = true
if track.external then
local ext = get_extension(track['external-filename'])
if ext ~= '.srt' and ext ~= '.ass' then
supported_format = false
end
end
if not track.selected and supported_format then
table.insert(self.tracks, track)
table.insert(
self.items,
string.format(
"%s #%s - %s%s%s",
(track.external and 'External' or 'Internal'),
track['id'],
(track.lang or (track.title and
esc_for_title(replace(track.title, filename, '')) or 'unknown')),
(track.codec and '[' .. esc_for_code(track.codec:upper()) .. ']' or ''),
(track.selected and ' (active)' or '')
)
)
end
end
if #self.items == 0 then
notify("没有找到受支持的字幕轨道.", "warn", 5)
return
end
table.insert(self.items, "退出")
self:open()
end
function track_selector:get_selected_track()
if self.selected < 1 then
return nil
end
return self.tracks[self.selected]
end
function track_selector:act()
self:close()
if self.selected == #self.items then
return
end
ref_selector:call_subsync()
end
------------------------------------------------------------
-- Initialize the addon
local function init()
for _, executable in pairs { 'ffmpeg', 'ffsubsync', 'alass' } do
local config_key = executable .. '_path'
config[config_key] = is_empty(config[config_key]) and find_executable(executable) or config[config_key]
end
end
------------------------------------------------------------
-- Entry point
init()
mp.add_key_binding("n", "autosubsync-menu", function() ref_selector:open() end)
+1
View File
@@ -0,0 +1 @@
require('autosubsync')
+107
View File
@@ -0,0 +1,107 @@
------------------------------------------------------------
-- Menu visuals
local mp = require('mp')
local assdraw = require('mp.assdraw')
local Menu = assdraw.ass_new()
function Menu:new(o)
self.__index = self
o = o or {}
o.selected = o.selected or 1
o.canvas_width = o.canvas_width or 1280
o.canvas_height = o.canvas_height or 720
o.pos_x = o.pos_x or 0
o.pos_y = o.pos_y or 0
o.rect_width = o.rect_width or 320
o.rect_height = o.rect_height or 40
o.active_color = o.active_color or 'ffffff'
o.inactive_color = o.inactive_color or 'aaaaaa'
o.border_color = o.border_color or '000000'
o.text_color = o.text_color or 'ffffff'
return setmetatable(o, self)
end
function Menu:set_position(x, y)
self.pos_x = x
self.pos_y = y
end
function Menu:font_size(size)
self:append(string.format([[{\fs%s}]], size))
end
function Menu:set_text_color(code)
self:append(string.format("{\\1c&H%s%s%s&\\1a&H05&}", code:sub(5, 6), code:sub(3, 4), code:sub(1, 2)))
end
function Menu:set_border_color(code)
self:append(string.format("{\\3c&H%s%s%s&}", code:sub(5, 6), code:sub(3, 4), code:sub(1, 2)))
end
function Menu:apply_text_color()
self:set_border_color(self.border_color)
self:set_text_color(self.text_color)
end
function Menu:apply_rect_color(i)
self:set_border_color(self.border_color)
if i == self.selected then
self:set_text_color(self.active_color)
else
self:set_text_color(self.inactive_color)
end
end
function Menu:draw_text(i)
local padding = 5
local font_size = 25
self:new_event()
self:pos(self.pos_x + padding, self.pos_y + self.rect_height * (i - 1) + padding)
self:font_size(font_size)
self:apply_text_color(i)
self:append(self.items[i])
end
function Menu:draw_item(i)
self:new_event()
self:pos(self.pos_x, self.pos_y)
self:apply_rect_color(i)
self:draw_start()
self:rect_cw(0, 0 + (i - 1) * self.rect_height, self.rect_width, i * self.rect_height)
self:draw_stop()
self:draw_text(i)
end
function Menu:draw()
self.text = ''
for i, _ in ipairs(self.items) do
self:draw_item(i)
end
mp.set_osd_ass(self.canvas_width, self.canvas_height, self.text)
end
function Menu:erase()
mp.set_osd_ass(self.canvas_width, self.canvas_height, '')
end
function Menu:up()
self.selected = self.selected - 1
if self.selected == 0 then
self.selected = #self.items
end
self:draw()
end
function Menu:down()
self.selected = self.selected + 1
if self.selected > #self.items then
self.selected = 1
end
self:draw()
end
return Menu
+276
View File
@@ -0,0 +1,276 @@
local P = {}
local TimeStamp = {}
local TimeStamp_mt = { __index = TimeStamp }
function TimeStamp:new(hours, minutes, seconds)
local new = {}
new.hours = hours
new.minutes = minutes
new.seconds = seconds
return setmetatable(new, TimeStamp_mt)
end
function TimeStamp.toTimeStamp(seconds)
local diff, h, m, s = seconds, 0, 0, 0
h = math.floor(diff / 3600)
diff = diff - (h * 3600)
m = math.floor(diff / 60)
diff = diff - (m * 60)
s = diff
return TimeStamp:new(h, m, s)
end
function TimeStamp:toSeconds()
return (3600 * self.hours) + (60 * self.minutes) + self.seconds
end
function TimeStamp:adjustTime(seconds)
return self.toTimeStamp(self:toSeconds() + seconds)
end
function TimeStamp:toString(decimal_symbol)
local seconds_fmt = string.format("%06.3f", self.seconds):gsub("%.", decimal_symbol)
return string.format("%02d:%02d:%s", self.hours, self.minutes, seconds_fmt)
end
function TimeStamp.to_seconds(seconds, milliseconds)
return tonumber(string.format("%s.%s", seconds, milliseconds))
end
local AbstractSubtitle = {}
local AbstractSubtitle_mt = { __index = AbstractSubtitle }
function AbstractSubtitle:create()
local new = {}
return setmetatable(new, AbstractSubtitle_mt)
end
function AbstractSubtitle:save()
print(string.format("Writing '%s' to file..", self.filename))
local f = io.open(self.filename, 'w')
f:write(self:toString())
f:close()
end
-- strip Byte Order Mark from file, if it's present
function AbstractSubtitle:sanitize(line)
local bom_table = { 0xEF, 0xBB, 0xBF } -- TODO maybe add other ones (like UTF-16)
local function has_bom()
for i = 1, #bom_table do
if i > #line then return false end
local ch, byte = line:sub(i, i), line:byte(i, i)
if byte ~= bom_table[i] then return false end
end
return true
end
return has_bom() and string.sub(line, #bom_table + 1) or line
end
local function trim(s)
return s:match "^%s*(.-)%s*$"
end
function AbstractSubtitle:parse_file(filename)
local lines = {}
for line in io.lines(filename) do
if #lines == 0 then line = self:sanitize(line) end
line = line:gsub('\r\n?', '') -- make sure there's no carriage return
line = trim(line)
table.insert(lines, line)
end
return lines
end
function AbstractSubtitle:shift_timing(diff_seconds)
for _, entry in pairs(self.entries) do
if self.valid_entry(entry) then
entry.start_time = entry.start_time:adjustTime(diff_seconds)
entry.end_time = entry.end_time:adjustTime(diff_seconds)
end
end
end
function AbstractSubtitle.valid_entry(entry)
return entry ~= nil
end
local function inheritsFrom (baseClass)
local new_class = {}
local class_mt = { __index = new_class }
function new_class:create(filename)
local instance = {
filename = filename,
language = nil,
header = nil, -- will be empty for srt, some stuff for ass
entries = {} -- list of entries
}
setmetatable(instance, class_mt)
return instance
end
if baseClass then
setmetatable(new_class, { __index = baseClass })
end
return new_class
end
local SRT = inheritsFrom(AbstractSubtitle)
function SRT.entry()
return { index = nil, start_time = nil, end_time = nil, text = {} }
end
function SRT:populate(filename)
local timestamp_fmt = "^(%d+):(%d+):(%d+),(%d+) %-%-> (%d+):(%d+):(%d+),(%d+)$"
local function parse_timestamp(timestamp)
local function to_seconds(seconds, milliseconds)
return tonumber(string.format("%s.%s", seconds, milliseconds))
end
local _, _, from_h, from_m, from_s, from_ms, to_h, to_m, to_s, to_ms = timestamp:find(timestamp_fmt)
return TimeStamp:new(from_h, from_m, to_seconds(from_s, from_ms)), TimeStamp:new(to_h, to_m, to_seconds(to_s, to_ms))
end
local new = self:create(filename)
local entry = self.entry()
local f_idx, idx = 1, 1
for _, line in pairs(self:parse_file(filename)) do
if idx == 1 and #line > 0 then
assert(line:match("^%d+$"), string.format("SRT FORMAT ERROR (line %d): expected a number but got '%s'", f_idx, line))
entry.index = line
elseif idx == 2 then
assert(line:match("^%d+:%d+:%d+,%d+ %-%-> %d+:%d+:%d+,%d+$"), string.format("SRT FORMAT ERROR (line %d): expected a timecode string but got '%s'", f_idx, line))
local t_start, t_end = parse_timestamp(line)
entry.start_time, entry.end_time = t_start, t_end
else
if #line == 0 then
-- end of text
if entry.index ~= nil then
table.insert(new.entries, entry)
end
entry = SRT.entry()
idx = 0
else
table.insert(entry.text, line)
end
end
idx = idx + 1
f_idx = f_idx + 1
end
return new
end
function SRT:toString()
local stringbuilder = {}
local function append(s)
table.insert(stringbuilder, s)
end
for _, entry in pairs(self.entries) do
append(entry.index)
local timestamp_string = string.format("%s --> %s", entry.start_time:toString(","), entry.end_time:toString(","))
append(timestamp_string)
if type(entry.text) == 'table' then
append(table.concat(entry.text, "\n"))
else append(entry.text) end
append('')
end
return table.concat(stringbuilder, '\n')
end
local ASS = inheritsFrom(AbstractSubtitle)
ASS.header_mapper = { ["Start"] = "start_time", ["End"] = "end_time" }
function ASS.valid_entry(entry)
return entry['type'] ~= nil
end
function ASS:toString()
local stringbuilder = {}
local function append(s) table.insert(stringbuilder, s) end
append(self.header)
append('[Events]')
for i = 1, #self.entries do
if i == 1 then
-- stringbuilder for events header
local event_sb = {};
for _, v in pairs(self.event_header) do table.insert(event_sb, v) end
append(string.format("Format: %s", table.concat(event_sb, ", ")))
end
local entry = self.entries[i]
local entry_sb = {}
for _, col in pairs(self.event_header) do
local value = entry[col]
local timestamp_entry_column = self.header_mapper[col]
if timestamp_entry_column then
value = entry[timestamp_entry_column]:toString(".")
end
table.insert(entry_sb, value)
end
append(string.format("%s: %s", entry['type'], table.concat(entry_sb, ",")))
end
return table.concat(stringbuilder, '\n')
end
function ASS:populate(filename, language)
local header, events, parser = {}, {}, nil
for _, line in pairs(self:parse_file(filename)) do
local _, _, event = string.find(line, "^%[([^%]]+)%]%s*$")
if event then
if event == "Events" then
parser = function(x) table.insert(events, x) end
else
parser = function(x) table.insert(header, x) end
parser(line)
end
else
parser(line)
end
end
-- create subtitle instance
local ev_regex = "^(%a+):%s(.+)$"
local function parse_event(header_columns, ev)
local function create_timestamp(timestamp_str)
local timestamp_fmt = "^(%d+):(%d+):(%d+).(%d+)"
local _, _, h, m, s, ms = timestamp_str:find(timestamp_fmt)
return TimeStamp:new(h, m, TimeStamp.to_seconds(s, ms))
end
local new_event = {}
local _, _, ev_type, ev_values = string.find(ev, ev_regex)
new_event['type'] = ev_type
-- skipping last column, since that's the text, which can contain commas
local last_idx = 0;
for i = 1, #header_columns - 1 do
local col = header_columns[i]
local idx = string.find(ev_values, ",", last_idx + 1)
local val = ev_values:sub(last_idx + 1, idx - 1)
local timestamp_entry_column = self.header_mapper[col]
if timestamp_entry_column then
new_event[timestamp_entry_column] = create_timestamp(val)
else
new_event[col] = val
end
last_idx = idx
end
new_event[header_columns[#header_columns]] = ev_values:sub(last_idx + 1)
return new_event
end
local sub = self:create(filename)
sub.header = table.concat(header, "\n")
sub.language = language
-- remove and process first entry in events, which is a header
local _, _, colstring = string.find(table.remove(events, 1), "^%a+:%s(.+)$")
local columns = {};
for i in colstring:gmatch("[^%,%s]+") do table.insert(columns, i) end
sub.event_header = columns
for _, event in pairs(events) do
if #event > 0 then
table.insert(sub.entries, parse_event(columns, event))
end
end
return sub
end
P.AbstractSubtitle = AbstractSubtitle
P.ASS = ASS
P.SRT = SRT
return P
+78
View File
@@ -0,0 +1,78 @@
opts = {
blacklist="",
whitelist="",
remove_files_without_extension = false,
oneshot = true,
}
(require 'mp.options').read_options(opts)
local msg = require 'mp.msg'
function split(input)
local ret = {}
for str in string.gmatch(input, "([^,]+)") do
ret[#ret + 1] = str
end
return ret
end
opts.blacklist = split(opts.blacklist)
opts.whitelist = split(opts.whitelist)
local exclude
if #opts.whitelist > 0 then
exclude = function(extension)
for _, ext in pairs(opts.whitelist) do
if extension == ext then
return false
end
end
return true
end
elseif #opts.blacklist > 0 then
exclude = function(extension)
for _, ext in pairs(opts.blacklist) do
if extension == ext then
return true
end
end
return false
end
else
return
end
function should_remove(filename)
if string.find(filename, "://") then
return false
end
local extension = string.match(filename, "%.([^%.]+)$")
if not extension and opts.remove_file_without_extension then
return true
end
if extension and exclude(string.lower(extension)) then
return true
end
return false
end
function process(playlist_count)
if playlist_count < 2 then return end
if opts.oneshot then
mp.unobserve_property(observe)
end
local playlist = mp.get_property_native("playlist")
local removed = 0
for i = #playlist, 1, -1 do
if should_remove(playlist[i].filename) then
mp.commandv("playlist-remove", i-1)
removed = removed + 1
end
end
if removed == #playlist then
msg.warn("Removed eveything from the playlist")
end
end
function observe(k,v) process(v) end
mp.observe_property("playlist-count", "number", observe)
+618
View File
@@ -0,0 +1,618 @@
--[[
* chapter-make-read.lua v.2025-03-01
*
* AUTHORS: dyphire
* License: MIT
* link: https://github.com/dyphire/mpv-scripts
--]]
--[[
Copyright (c) 2023 dyphire <qimoge@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]]
-- Implementation read and automatically load the namesake external chapter file.
-- The external chapter files should conform to the following formats.
-- Note: The Timestamps should use the 12-bit format of 'hh:mm:ss.sss'.
-- Note: The file encoding should be UTF-8 and the linebreak should be Unix(LF).
-- Note: The script also supports reading OGM format and MediaInfo format in addition to the following formats.
--[[
00:00:00.000 A part
00:00:40.312 OP
00:02:00.873 B part
00:10:44.269 C part
00:22:40.146 ED
--]]
-- This script also supports manually load/refresh,marks,edits,remove and creates external chapter files, usage:
-- Note: It can also be used to export the existing chapter information of the playback file.
-- add bindings to input.conf:
-- key script-message-to chapter_make_read load_chapter
-- key script-message-to chapter_make_read create_chapter
-- key script-message-to chapter_make_read edit_chapter
-- key script-message-to chapter_make_read remove_chapter
-- key script-message-to chapter_make_read write_chapter chp
-- key script-message-to chapter_make_read write_chapter ogm
local msg = require 'mp.msg'
local utils = require 'mp.utils'
local options = require "mp.options"
local o = {
autoload = true,
autosave = false,
force_overwrite = false,
-- Specifies the extension of the external chapter file.
chapter_file_ext = ".chp",
-- Select whether the external chapter file needs to match the extension of the source file.
basename_with_ext = true,
-- Specifies the subpath of the same directory as the playback file as the external chapter file path.
-- Note: The external chapter file is read from the subdirectory first.
-- If the file does not exist, it will next be read from the same directory as the playback file.
external_chapter_subpath = "chapters",
-- save all chapter files in a single global directory
global_chapters = false,
global_chapters_dir = "~~/chapters",
-- hash works only in global_chapters_dir
hash = false,
-- ask for title or leave it empty
ask_for_title = true,
-- placeholder when asking for title of a new chapter
placeholder_title = "Chapter ",
-- pause the playback when asking for chapter title
pause_on_input = true,
}
options.read_options(o)
local input_loaded, input = pcall(require, "mp.input")
-- Requires: https://github.com/CogentRedTester/mpv-user-input
local user_input_loaded, user_input = pcall(require, "user-input-module")
local path = nil
local dir = nil
local fname = nil
local chapter_fullpath = nil
local all_chapters = {}
local chapter_count = 0
local chapters_modified = false
local paused = false
local protocol = false
local function is_protocol(path)
return type(path) == 'string' and (path:find('^%a[%w.+-]-://') ~= nil or path:find('^%a[%w.+-]-:%?') ~= nil)
end
function url_decode(str)
local function hex_to_char(x)
return string.char(tonumber(x, 16))
end
if str ~= nil then
str = str:gsub('^%a[%a%d-_]+://', '')
:gsub('^%a[%a%d-_]+:\\?', '')
:gsub('%%(%x%x)', hex_to_char)
if str:find('://localhost:?') then
str = str:gsub('^.*/', '')
end
str = str:gsub('[\\/:%?]*', '')
return str
end
end
--create global_chapters_dir if it doesn't exist
local global_chapters_dir = mp.command_native({ "expand-path", o.global_chapters_dir })
if global_chapters_dir and global_chapters_dir ~= '' then
local meta = utils.file_info(global_chapters_dir)
if not meta or not meta.is_dir then
local is_windows = package.config:sub(1, 1) == "\\"
local windows_args = { 'powershell', '-NoProfile', '-Command', 'mkdir', string.format("\"%s\"", global_chapters_dir) }
local unix_args = { 'mkdir', '-p', global_chapters_dir }
local args = is_windows and windows_args or unix_args
local res = mp.command_native({ name = "subprocess", capture_stdout = true, playback_only = false, args = args })
if res.status ~= 0 then
msg.error("Failed to create global_chapters_dir save directory " .. global_chapters_dir ..
". Error: " .. (res.error or "unknown"))
return
end
end
end
local function read_chapter(func)
local meta = utils.file_info(chapter_fullpath)
if not meta or not meta.is_file then return end
local f = io.open(chapter_fullpath, "r")
if not f then return end
local contents = {}
for line in f:lines() do
table.insert(contents, (func(line)))
end
f:close()
return contents
end
local function read_chapter_table()
local line_pos = 0
return read_chapter(function(line)
local h, m, s, t, n, l
local thin_space = string.char(0xE2, 0x80, 0x89)
local line = line:gsub(thin_space, " ")
if line:match("^%d+:%d+:%d+") ~= nil then
h, m, s = line:match("^(%d+):(%d+):(%d+[,%.]?%d+)")
s = s:gsub(',', '.')
t = h * 3600 + m * 60 + s
if line:match("^%d+:%d+:%d+[,%.]?%d+[,%s].*") ~= nil then
n = line:match("^%d+:%d+:%d+[,%.]?%d+[,%s](.*)")
n = n:gsub(":%s%a?%a?:", "")
:gsub("^%s*(.-)%s*$", "%1")
end
l = line
line_pos = line_pos + 1
elseif line:match("^%d+:%d+[,%.]?%d+[,%s].*") ~= nil then
m, s = line:match("^(%d+):(%d+[,%.]?%d+)")
s = s:gsub(',', '.')
t = m * 60 + s
if line:match("^%d+:%d+[,%.]?%d+[,%s].*") ~= nil then
n = line:match("^%d+:%d+[,%.]?%d+[,%s](.*)")
n = n:gsub(":%s%a?%a?:", "")
:gsub("^%s*(.-)%s*$", "%1")
end
l = line
line_pos = line_pos + 1
elseif line:match("^CHAPTER%d+=%d+:%d+:%d+") ~= nil then
h, m, s = line:match("^CHAPTER%d+=(%d+):(%d+):(%d+[,%.]?%d+)")
s = s:gsub(',', '.')
t = h * 3600 + m * 60 + s
l = line
line_pos = line_pos + 1
elseif line:match("^CHAPTER%d+NAME=.*") ~= nil then
n = line:gsub("^CHAPTER%d+NAME=", "")
n = n:gsub("^%s*(.-)%s*$", "%1")
l = line
line_pos = line_pos + 1
else
return
end
return { found_title = n, found_time = t, found_line = l }
end)
end
local function refresh_globals()
path = mp.get_property("path")
if path then
protocol = is_protocol(path)
dir = utils.split_path(path)
end
if protocol then
fname = url_decode(mp.get_property("media-title"))
elseif o.basename_with_ext then
fname = mp.get_property("filename")
else
fname = mp.get_property("filename/no-ext")
end
all_chapters = mp.get_property_native("chapter-list")
chapter_count = mp.get_property_number("chapter-list/count")
end
local function format_time(seconds)
local result = ""
local hours, mins, secs, msecs
if seconds <= 0 then
return "00:00:00.000";
else
hours = string.format("%02.f", math.floor(seconds / 3600))
mins = string.format("%02.f", math.floor(seconds / 60 - (hours * 60)))
secs = string.format("%02.f", math.floor(seconds - hours * 60 * 60 - mins * 60))
msecs = string.format("%03.f", seconds * 1000 - hours * 60 * 60 * 1000 - mins * 60 * 1000 - secs * 1000)
result = hours .. ":" .. mins .. ":" .. secs .. "." .. msecs
end
return result
end
-- for unix use only
-- returns a table of command path and varargs, or nil if command was not found
local function command_exists(command, ...)
msg.debug("looking for command:", command)
-- msg.debug("args:", )
local process = mp.command_native({
name = "subprocess",
capture_stdout = true,
capture_stderr = true,
playback_only = false,
args = {"sh", "-c", "command -v -- " .. command}
})
if process.status == 0 then
local command_path = process.stdout:gsub("\n", "")
msg.debug("command found:", command_path)
return {command_path, ...}
else
msg.debug("command not found:", command)
return nil
end
end
-- returns md5 hash of the full path of the current media file
local function hash(path)
if path == nil then
msg.debug("something is wrong with the path, can't get full_path, can't hash it")
return
end
msg.debug("hashing:", path)
local cmd = {
name = 'subprocess',
capture_stdout = true,
playback_only = false,
}
local args = nil
local is_unix = package.config:sub(1,1) == "/"
if is_unix then
local md5 = command_exists("md5sum") or command_exists("md5") or command_exists("openssl", "md5 | cut -d ' ' -f 2")
if md5 == nil then
msg.warn("no md5 command found, can't generate hash")
return
end
md5 = table.concat(md5, " ")
cmd["stdin_data"] = path
args = {"sh", "-c", md5 .. " | cut -d ' ' -f 1 | tr '[:lower:]' '[:upper:]'" }
else --windows
-- https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash?view=powershell-7.3
local hash_command = [[
$s = [System.IO.MemoryStream]::new();
$w = [System.IO.StreamWriter]::new($s);
$w.write(']] .. path .. [[');
$w.Flush();
$s.Position = 0;
Get-FileHash -Algorithm MD5 -InputStream $s | Select-Object -ExpandProperty Hash
]]
args = {"powershell", "-NoProfile", "-Command", hash_command}
end
cmd["args"] = args
msg.debug("hash cmd:", utils.to_string(cmd))
local process = mp.command_native(cmd)
if process.status == 0 then
local hash = process.stdout:gsub("%s+", "")
msg.debug("hash:", hash)
return hash
else
msg.warn("hash function failed")
return
end
end
local function get_chapter_filename(path)
name = hash(path)
if name == nil then
msg.warn("hash function failed, fallback to filename")
name = fname
end
return name
end
local function mark_chapter(force_overwrite)
refresh_globals()
if not path then return end
local chapter_index = 0
local chapters_time = {}
local chapters_title = {}
local fpath = dir
if protocol then
fpath = global_chapters_dir
if o.hash then fname = get_chapter_filename(path) end
elseif o.external_chapter_subpath ~= '' then
fpath = utils.join_path(dir, o.external_chapter_subpath)
local meta = utils.file_info(fpath)
if not meta or not meta.is_dir then
fpath = dir
end
end
if o.global_chapters and global_chapters_dir and global_chapters_dir ~= '' and not protocol then
fpath = global_chapters_dir
local meta = utils.file_info(fpath)
if meta and meta.is_dir then
if o.hash then
fname = get_chapter_filename(path)
end
end
end
local chapter_filename = fname .. o.chapter_file_ext
chapter_fullpath = utils.join_path(fpath, chapter_filename)
local fmeta = utils.file_info(chapter_fullpath)
if (not fmeta or not fmeta.is_file) and fpath ~= dir and not protocol then
if o.basename_with_ext then
fname = mp.get_property("filename")
else
fname = mp.get_property("filename/no-ext")
end
chapter_filename = fname .. o.chapter_file_ext
chapter_fullpath = utils.join_path(dir, chapter_filename)
end
local list_contents = read_chapter_table()
if not list_contents then return end
for i = 1, #list_contents do
local chapter_time = tonumber(list_contents[i].found_time)
if chapter_time ~= nil and chapter_time >= 0 then
table.insert(chapters_time, chapter_time)
end
if list_contents[i].found_title ~= nil then
table.insert(chapters_title, list_contents[i].found_title)
end
end
if not chapters_time[1] then return end
table.sort(chapters_time, function(a, b) return a < b end)
if force_overwrite then all_chapters = {} end
for i = 1, #chapters_time do
chapter_index = chapter_index + 1
all_chapters[chapter_index] = {
title = chapters_title[i] or ("Chapter " .. string.format("%02.f", chapter_index)),
time = chapters_time[i]
}
end
table.sort(all_chapters, function(a, b) return a['time'] < b['time'] end)
mp.set_property_native("chapter-list", all_chapters)
msg.info("load external chapter file successful: " .. chapter_filename)
end
local function change_chapter_list(chapter_tltle, chapter_index)
local chapter_list = mp.get_property_native("chapter-list")
if chapter_index > mp.get_property_number("chapter-list/count") then
msg.warn("can't set chapter title")
return
end
chapter_list[chapter_index].title = chapter_tltle
mp.set_property_native("chapter-list", chapter_list)
end
local function change_title_callback(user_input, err, chapter_index)
if user_input == nil or err ~= nil then
if paused then return elseif o.pause_on_input then mp.set_property_native("pause", false) end
msg.warn("no chapter title provided:", err)
return
end
change_chapter_list(user_input, chapter_index)
if paused then return elseif o.pause_on_input then mp.set_property_native("pause", false) end
chapters_modified = true
end
local function input_title(default_input, cursor_pos, chapter_index)
input.get({
prompt = 'Chapter title:',
default_text = default_input,
cursor_position = cursor_pos,
submit = function(text)
input.terminate()
change_chapter_list(text, chapter_index)
end,
closed = function()
if paused then return elseif o.pause_on_input then mp.set_property_native("pause", false) end
end
})
end
local function input_choice(title, chapter_index)
if not input_loaded and not user_input_loaded then
msg.error("no mpv-user-input, can't get user input, install: https://github.com/CogentRedTester/mpv-user-input")
return
end
if input_loaded then
input_title(title, #title + 1, chapter_index)
elseif user_input_loaded then
-- ask user for chapter title
-- (+1 because mpv indexes from 0, lua from 1)
user_input.get_user_input(change_title_callback, {
request_text = "Chapter title:",
default_input = title,
cursor_pos = #title + 1,
}, chapter_index)
end
end
local function create_chapter()
refresh_globals()
if not path then return end
local time_pos = mp.get_property_number("time-pos")
local time_pos_osd = mp.get_property_osd("time-pos/full")
local current_chapter = mp.get_property_number("chapter")
mp.osd_message(time_pos_osd, 1)
if chapter_count == 0 then
all_chapters[1] = {
title = o.placeholder_title .. "01",
time = time_pos
}
-- We just set it to zero here so when we add 1 later it ends up as 1
-- otherwise it's probably "nil"
current_chapter = 0
-- note that mpv will treat the beginning of the file as all_chapters[0] when using pageup/pagedown
-- so we don't actually have to worry if the file doesn't start with a chapter
else
-- to insert a chapter we have to increase the index on all subsequent chapters
-- otherwise we'll end up with duplicate chapter IDs which will confuse mpv
-- +2 looks weird, but remember mpv indexes at 0 and lua indexes at 1
-- adding two will turn "current chapter" from mpv notation into "next chapter" from lua's notation
-- count down because these areas of memory overlap
for i = chapter_count, current_chapter + 2, -1 do
all_chapters[i + 1] = all_chapters[i]
end
all_chapters[current_chapter + 2] = {
title = o.placeholder_title .. string.format("%02.f", current_chapter + 2),
time = time_pos
}
end
mp.set_property_native("chapter-list", all_chapters)
mp.set_property_number("chapter", current_chapter + 1)
chapters_modified = true
if o.ask_for_title then
local chapter_index = mp.get_property_number("chapter") + 1
local title = o.placeholder_title .. string.format("%02.f", chapter_index)
input_choice(title, chapter_index)
if o.pause_on_input then
paused = mp.get_property_native("pause")
mp.set_property_bool("pause", true)
-- FIXME: for whatever reason osd gets hidden when we pause the
-- playback like that, workaround to make input prompt appear
-- right away without requiring mouse or keyboard action
mp.osd_message(" ", 0.1)
end
end
end
local function edit_chapter()
local chapter_index = mp.get_property_number("chapter") + 1
local chapter_list = mp.get_property_native("chapter-list")
local title = chapter_list[chapter_index + 1].title
if chapter_index == nil or chapter_index == -1 then
msg.verbose("no chapter selected, nothing to edit")
return
end
input_choice(title, chapter_index)
if o.pause_on_input then
paused = mp.get_property_native("pause")
mp.set_property_bool("pause", true)
-- FIXME: for whatever reason osd gets hidden when we pause the
-- playback like that, workaround to make input prompt appear
-- right away without requiring mouse or keyboard action
mp.osd_message(" ", 0.1)
end
end
local function remove_chapter()
local chapter_count = mp.get_property_number("chapter-list/count")
if chapter_count < 1 then
msg.verbose("no chapters to remove")
return
end
local chapter_list = mp.get_property_native("chapter-list")
-- +1 because mpv indexes from 0, lua from 1
local current_chapter = mp.get_property_number("chapter") + 1
table.remove(chapter_list, current_chapter)
msg.debug("removing chapter", current_chapter)
mp.set_property_native("chapter-list", chapter_list)
chapters_modified = true
end
local function write_chapter(format, force_write)
refresh_globals()
if not path or chapter_count == 0 or (not chapters_modified and not force_write) then
msg.debug("nothing to write")
return
end
if o.global_chapters then dir = global_chapters_dir end
if o.hash and o.global_chapters then fname = get_chapter_filename(path) end
local out_path = utils.join_path(dir, fname .. o.chapter_file_ext)
local chapters = ""
local next_chapter = nil
for i = 1, chapter_count, 1 do
local current_chapter = all_chapters[i]
local time_pos = format_time(current_chapter.time)
if format == "ogm" then
next_chapter = "CHAPTER" .. string.format("%02.f", i) .. "=" .. time_pos .. "\n" ..
"CHAPTER" .. string.format("%02.f", i) .. "NAME=" .. current_chapter.title .. "\n"
elseif format == "chp" then
next_chapter = time_pos .. " " .. current_chapter.title .. "\n"
else
msg.warn("please specify the correct chapter format: chp/ogm.")
return
end
if i == 1 and (o.global_chapters or protocol) then
chapters = "# " .. path .. "\n\n" .. next_chapter
else
chapters = chapters .. next_chapter
end
end
local file = io.open(out_path, "w")
if file == nil then
dir = global_chapters_dir
fname = url_decode(mp.get_property("media-title"))
if o.hash then fname = get_chapter_filename(path) end
out_path = utils.join_path(dir, fname .. o.chapter_file_ext)
file = io.open(out_path, "w")
end
if file == nil then
mp.error("Could not open chapter file for writing.")
return
end
file:write(chapters)
file:close()
if not o.autosave then
mp.osd_message("Export chapter file to: " .. out_path, 3)
end
msg.info("Export chapter file to: " .. out_path)
end
-- HOOKS -----------------------------------------------------------------------
if o.autoload then
mp.add_hook("on_preloaded", 50, function()
if o.force_overwrite then
mark_chapter(true)
else
mark_chapter(false)
end
end)
end
if o.autosave then
mp.add_hook("on_unload", 50, function()
write_chapter("chp", false)
end)
end
if user_input_loaded and not input_loaded then
mp.add_hook("on_unload", 50, function() user_input.cancel_user_input() end)
end
mp.register_script_message("load_chapter", function() mark_chapter(true) end)
mp.register_script_message("create_chapter", create_chapter, { repeatable = true })
mp.register_script_message("remove_chapter", remove_chapter)
mp.register_script_message("edit_chapter", edit_chapter)
mp.register_script_message("write_chapter", function(format)
write_chapter(format, true)
end)
+91
View File
@@ -0,0 +1,91 @@
-- chapterskip.lua
--
-- Ain't Nobody Got Time for That
--
-- This script skips chapters based on their title.
local categories = {
prologue = "^[Pp]rologue/^[Ii]ntro",
opening = "^OP/ OP$/^[Oo]pening/[Oo]pening$",
ending = "^ED/ ED$/^[Ee]nding/[Ee]nding$",
credits = "^[Cc]redits/[Cc]redits$",
preview = "[Pp]review$"
}
local options = {
enabled = false,
skip_once = true,
categories = "",
skip = ""
}
mp.options = require "mp.options"
function matches(i, title)
for category in string.gmatch(options.skip, " *([^;]*[^; ]) *") do
if categories[category:lower()] then
if string.find(category:lower(), "^idx%-") == nil then
if title then
for pattern in string.gmatch(categories[category:lower()], "([^/]+)") do
if string.match(title, pattern) then
return true
end
end
end
else
for pattern in string.gmatch(categories[category:lower()], "([^/]+)") do
if tonumber(pattern) == i then
return true
end
end
end
end
end
end
local skipped = {}
local parsed = {}
local function toggle_chapterskip()
options.enabled = not options.enabled
end
function chapterskip(_, current)
mp.options.read_options(options, "chapterskip")
if not options.enabled then return end
for category in string.gmatch(options.categories, "([^;]+)") do
name, patterns = string.match(category, " *([^+>]*[^+> ]) *[+>](.*)")
if name then
categories[name:lower()] = patterns
elseif not parsed[category] then
mp.msg.warn("Improper category definition: " .. category)
end
parsed[category] = true
end
local chapters = mp.get_property_native("chapter-list")
local skip = false
for i, chapter in ipairs(chapters) do
if (not options.skip_once or not skipped[i]) and matches(i, chapter.title) then
if i == current + 1 or skip == i - 1 then
if skip then
skipped[skip] = true
end
skip = i
end
elseif skip then
mp.set_property("time-pos", chapter.time)
skipped[skip] = true
return
end
end
if skip then
if mp.get_property_native("playlist-count") == mp.get_property_native("playlist-pos-1") then
return mp.set_property("time-pos", mp.get_property_native("duration"))
end
mp.commandv("playlist-next")
end
end
mp.observe_property("chapter", "number", chapterskip)
mp.register_event("file-loaded", function() skipped = {} end)
mp.register_script_message("chapter-skip", toggle_chapterskip)
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
--[[
script to cycle commands with a keybind, accomplished through script messages
available at: https://github.com/CogentRedTester/mpv-scripts
syntax:
script-message cycle-commands "command1 args" "command2 args" "command3 args"
The syntax of each command is identical to the standard input.conf syntax, but each command must be
a quoted string. Note that this may require you to nest (and potentially escape) quotes for the arguments.
Read the mpv documentation for how to do this: https://mpv.io/manual/master/#flat-command-syntax.
Semicolons also work exactly like they do normally, so you can easily send multiple commands each cycle.
Here are some examples of the same command using different quotes:
script-message cycle-commands "show-text one 1000 ; print-text two" "show-text \"three four\""
script-message cycle-commands 'show-text one 1000 ; print-text two' 'show-text "three four"'
script-message cycle-commands ``show-text one 1000 ; print-text two`` ``show-text "three four"``
This would, on keypress one, print 'one' to the OSD for 1 second and 'two' to the console,
and on keypress two 'three four' would be printed to the OSD.
Note that single (') and backtick (`) quoting was only added in mpv v0.34.
There are no limits to the number of commands, and the script message can be used as often as one wants.
The script stores the current iteration position for each unique set of command strings,
so there should be no overlap unless one binds the exact same set of strings (including spacing).
If the first command is `!reverse`, then the commands are cycled in the opposite direction.
If every subsequent command string is identical to a non-reversed cycle, then they share
their iteration position, making it possible to 'seek' forwards or backwards in the cycle:
script-message cycle-commands 'apply-profile profile1' 'apply-profile profile2' 'apply-profile profile3'
script-message cycle-commands !reverse 'apply-profile profile1' 'apply-profile profile2' 'apply-profile profile3'
Most commands should print messages to the OSD automatically, this can be controlled
by adding input prefixes to the commands: https://mpv.io/manual/master/#input-command-prefixes.
Some commands will not print an osd message even when told to, in this case you have two options:
you can add a show-text command to the cycle, or you can use the cycle-commands/osd script message
which will print the command string to the osd. For example:
script-message cycle-commands 'apply-profile profile1;show-text "applying profile1"' 'apply-profile profile2;show-text "applying profile2"'
script-message cycle-commands/osd 'apply-profile profile1' 'apply-profile profile2'
Any osd messages printed by the command will override the message sent by cycle-commands/osd.
]]--
local mp = require 'mp'
local msg = require 'mp.msg'
--keeps track of the current position for a specific cycle
local iterators = {}
--main function to identify and run the cycles
local function main(osd, ...)
local commands = {...}
local reverse = commands[1] == '!reverse'
if reverse then table.remove(commands, 1) end
--to identify the specific cycle we'll concatenate all the strings together to use as our table key
local str = ("%d> %s"):format(#commands, table.concat(commands, '|'))
msg.trace('recieved:', str)
-- we'll initialise the iterator at 0 (an invalid position) to support forward or backwards iteration
if iterators[str] == nil then
msg.debug('unknown cycle, creating iterator')
iterators[str] = 0
end
iterators[str] = iterators[str] + (reverse and -1 or 1)
if iterators[str] > #commands then iterators[str] = 1 end
if iterators[str] < 1 then iterators[str] = #commands end
--mp.command should run the commands exactly as if they were entered in input.conf.
--This should provide universal support for all input.conf command syntax
local cmd = commands[ iterators[str] ]
msg.verbose('sending command:', cmd)
if osd then mp.osd_message(cmd) end
mp.command(cmd)
end
mp.register_script_message('cycle-commands', function(...) main(false, ...) end)
mp.register_script_message('cycle-commands/osd', function(...) main(true, ...) end)
+169
View File
@@ -0,0 +1,169 @@
--[[
https://github.com/stax76/mpv-scripts
This script instantly deletes the file that is currently playing
via keyboard shortcut, the file is moved to the recycle bin and
removed from the playlist.
On Linux the app trash-cli must be installed first.
On Ubuntu: sudo apt install trash-cli
Usage:
Add bindings to input.conf:
# delete directly
KP0 script-message-to delete_current_file delete-file
# delete with confirmation
KP0 script-message-to delete_current_file delete-file KP1 "Press 1 to delete file"
Press KP0 to initiate the delete operation,
the script will ask to confirm by pressing KP1.
You may customize the the init and confirm key and the confirm message.
Confirm key and confirm message are optional.
Similar scripts:
https://github.com/zenyd/mpv-scripts#delete-file
]]--
key_bindings = {}
function file_exists(name)
if not name or name == '' then
return false
end
local f = io.open(name, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
function is_protocol(path)
return type(path) == 'string' and (path:match('^%a[%a%d_-]+://'))
end
function delete_file(path)
local is_windows = package.config:sub(1,1) == "\\"
if is_protocol(path) or not file_exists(path) then
return
end
if is_windows then
local ps_code = [[
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('__path__', 'OnlyErrorDialogs', 'SendToRecycleBin')
]]
local escaped_path = string.gsub(path, "'", "''")
escaped_path = string.gsub(escaped_path, "", "")
escaped_path = string.gsub(escaped_path, "%%", "%%%%")
ps_code = string.gsub(ps_code, "__path__", escaped_path)
mp.command_native({
name = "subprocess",
playback_only = false,
detach = true,
args = { 'powershell', '-NoProfile', '-Command', ps_code },
})
else
mp.command_native({
name = "subprocess",
playback_only = false,
args = { 'trash', path },
})
end
end
function remove_current_file()
local count = mp.get_property_number("playlist-count")
local pos = mp.get_property_number("playlist-pos")
local new_pos = 0
if pos == count - 1 then
new_pos = pos - 1
else
new_pos = pos + 1
end
mp.set_property_number("playlist-pos", new_pos)
if pos > -1 then
mp.command("playlist-remove " .. pos)
end
end
function handle_confirm_key()
local path = mp.get_property("path")
if file_to_delete == path then
mp.commandv("show-text", "")
delete_file(file_to_delete)
remove_current_file()
remove_bindings()
file_to_delete = ""
end
end
function cleanup()
remove_bindings()
file_to_delete = ""
mp.commandv("show-text", "")
end
function get_bindings()
return {
{ confirm_key, handle_confirm_key },
}
end
function add_bindings()
if #key_bindings > 0 then
return
end
local script_name = mp.get_script_name()
for _, bind in ipairs(get_bindings()) do
local name = script_name .. "_key_" .. (#key_bindings + 1)
key_bindings[#key_bindings + 1] = name
mp.add_forced_key_binding(bind[1], name, bind[2])
end
end
function remove_bindings()
if #key_bindings == 0 then
return
end
for _, name in ipairs(key_bindings) do
mp.remove_key_binding(name)
end
key_bindings = {}
end
function client_message(event)
local path = mp.get_property("path")
if event.args[1] == "delete-file" and #event.args == 1 then
delete_file(path)
remove_current_file()
elseif event.args[1] == "delete-file" and #event.args == 3 and #key_bindings == 0 then
confirm_key = event.args[2]
mp.add_timeout(10, cleanup)
add_bindings()
file_to_delete = path
mp.commandv("show-text", event.args[3], "10000")
end
end
mp.register_event("client-message", client_message)
+169
View File
@@ -0,0 +1,169 @@
--[[
https://github.com/stax76/mpv-scripts
This script instantly deletes the file that is currently playing
via keyboard shortcut, the file is moved to the recycle bin and
removed from the playlist.
On Linux the app trash-cli must be installed first.
On Ubuntu: sudo apt install trash-cli
Usage:
Add bindings to input.conf:
# delete directly
KP0 script-message-to delete_current_file delete-file
# delete with confirmation
KP0 script-message-to delete_current_file delete-file KP1 "Press 1 to delete file"
Press KP0 to initiate the delete operation,
the script will ask to confirm by pressing KP1.
You may customize the the init and confirm key and the confirm message.
Confirm key and confirm message are optional.
Similar scripts:
https://github.com/zenyd/mpv-scripts#delete-file
]]--
key_bindings = {}
function file_exists(name)
if not name or name == '' then
return false
end
local f = io.open(name, "r")
if f ~= nil then
io.close(f)
return true
else
return false
end
end
function is_protocol(path)
return type(path) == 'string' and (path:match('^%a[%a%d_-]+://'))
end
function delete_file(path)
local is_windows = package.config:sub(1,1) == "\\"
if is_protocol(path) or not file_exists(path) then
return
end
if is_windows then
local ps_code = [[
Add-Type -AssemblyName Microsoft.VisualBasic
[Microsoft.VisualBasic.FileIO.FileSystem]::DeleteFile('__path__', 'OnlyErrorDialogs', 'SendToRecycleBin')
]]
local escaped_path = string.gsub(path, "'", "''")
escaped_path = string.gsub(escaped_path, "", "")
escaped_path = string.gsub(escaped_path, "%%", "%%%%")
ps_code = string.gsub(ps_code, "__path__", escaped_path)
mp.command_native({
name = "subprocess",
playback_only = false,
detach = true,
args = { 'powershell', '-NoProfile', '-Command', ps_code },
})
else
mp.command_native({
name = "subprocess",
playback_only = false,
args = { 'trash', path },
})
end
end
function remove_current_file()
local count = mp.get_property_number("playlist-count")
local pos = mp.get_property_number("playlist-pos")
local new_pos = 0
if pos == count - 1 then
new_pos = pos - 1
else
new_pos = pos + 1
end
mp.set_property_number("playlist-pos", new_pos)
if pos > -1 then
mp.command("playlist-remove " .. pos)
end
end
function handle_confirm_key()
local path = mp.get_property("path")
if file_to_delete == path then
mp.commandv("show-text", "")
delete_file(file_to_delete)
remove_current_file()
remove_bindings()
file_to_delete = ""
end
end
function cleanup()
remove_bindings()
file_to_delete = ""
mp.commandv("show-text", "")
end
function get_bindings()
return {
{ confirm_key, handle_confirm_key },
}
end
function add_bindings()
if #key_bindings > 0 then
return
end
local script_name = mp.get_script_name()
for _, bind in ipairs(get_bindings()) do
local name = script_name .. "_key_" .. (#key_bindings + 1)
key_bindings[#key_bindings + 1] = name
mp.add_forced_key_binding(bind[1], name, bind[2])
end
end
function remove_bindings()
if #key_bindings == 0 then
return
end
for _, name in ipairs(key_bindings) do
mp.remove_key_binding(name)
end
key_bindings = {}
end
function client_message(event)
local path = mp.get_property("path")
if event.args[1] == "delete-file" and #event.args == 1 then
delete_file(path)
remove_current_file()
elseif event.args[1] == "delete-file" and #event.args == 3 and #key_bindings == 0 then
confirm_key = event.args[2]
mp.add_timeout(10, cleanup)
add_bindings()
file_to_delete = path
mp.commandv("show-text", event.args[3], "10000")
end
end
mp.register_event("client-message", client_message)
+396
View File
@@ -0,0 +1,396 @@
-- evafast.lua
--
-- Much speed.
--
-- Jumps forwards when right arrow is tapped, speeds up when it's held.
-- Inspired by bilibili.com's player. Allows you to have both seeking and fast-forwarding on the same key.
-- Also supports toggling fastforward mode with a keypress.
-- Adjust --input-ar-delay to define when to start fastforwarding.
-- Define --hr-seek if you want accurate seeking.
-- If you just want a nicer fastforward.lua without hybrid key behavior, set seek_distance to 0.
-- Consider setting --sub-filter-regex="\`\s*\'" (on Linux) to ignore empty lines.
local options = {
-- How far to jump on press, set to 0 to disable seeking and force fastforward
seek_distance = 5,
-- Playback speed modifier, applied once every speed_interval until cap is reached
speed_increase = 0.1,
speed_decrease = 0.1,
-- At what interval to apply speed modifiers
speed_interval = 0.05,
-- Playback speed cap
speed_cap = 2,
-- Playback speed cap when subtitles are displayed, ignored when equal to speed_cap
subs_speed_cap = 1.6,
-- Multiply current speed by modifier before adjustment (exponential speedup)
-- Use much lower values than default e.g. speed_increase=0.05, speed_decrease=0.025
multiply_modifier = false,
-- Show current speed on the osd (or flash speed if using uosc)
show_speed = true,
-- Show current speed on the osd when toggled (or flash speed if using uosc)
show_speed_toggled = true,
-- Show current speed on the osd when speeding up towards a target time (or flash speed if using uosc)
show_speed_target = false,
-- Show seek actions on the osd (or flash timeline if using uosc)
show_seek = true,
-- Look ahead for smoother transition when subs_speed_cap is set
subs_lookahead = true,
-- Symbols prepended to the osd message
osd_symbol = "{\\fnmpv-osd-symbols} {\\r}",
osd_rewind = "{\\fnmpv-osd-symbols} {\\r}"
}
mp.options = require "mp.options"
mp.options.read_options(options, "evafast", function() end)
local uosc_available = false
local has_subtitle = true
local speedup_target = nil
local toggled_display = true
local toggled = false
local toggled_rewind = false
local speedup = false
local original_speed = 1
local next_sub_at = -1
local rewinding = false
local forced_slowdown = false
local file_duration = 0
local last_key_state = "up"
local was_rewinding = false
local ass_start = mp.get_property_osd("osd-ass-cc/0")
local ass_stop = mp.get_property_osd("osd-ass-cc/1")
local function speed_transition(current_speed, target_speed)
local speed_correction = current_speed >= target_speed and -options.speed_decrease or options.speed_increase
local time_for_correction = 0
local adjusted_speed = current_speed
while adjusted_speed ~= target_speed do
time_for_correction = time_for_correction + options.speed_interval * adjusted_speed
if options.multiply_modifier then
adjusted_speed = adjusted_speed + adjusted_speed * speed_correction
else
adjusted_speed = adjusted_speed + speed_correction
end
if (current_speed < target_speed and adjusted_speed > target_speed) or (current_speed > target_speed and adjusted_speed < target_speed) then
adjusted_speed = target_speed
end
end
return time_for_correction
end
local function next_sub(current_time)
local sub_delay = mp.get_property_native("sub-delay", 0)
local sub_visible = mp.get_property_bool("sub-visibility")
if sub_visible then
mp.set_property_bool("sub-visibility", false)
end
mp.command("no-osd sub-step 1")
local sub_next_delay = mp.get_property_native("sub-delay", 0)
mp.set_property("sub-delay", sub_delay)
if sub_visible then
mp.set_property_bool("sub-visibility", sub_visible)
end
if sub_delay - sub_next_delay == 0 then
return -2
end
local sub_next = current_time + sub_delay - sub_next_delay
normalized = math.floor(sub_next * 1000 + 0.5) / 1000
return normalized
end
local function flash_state(current_speed, display, forced)
local uosc_show = uosc_available and (display == nil or display == "uosc")
local osd_show = not uosc_available and (display == nil or display == "osd")
local show_special = (not speedup_target and options.show_speed_toggled) or (speedup_target and options.show_speed_target)
local show_toggled = show_special and (toggled or not speedup)
local show_regular = not toggled and toggled_display and options.show_speed
if current_speed and (show_regular or show_toggled or forced) then
if uosc_show then
mp.command("script-binding uosc/flash-speed")
elseif osd_show then
if current_speed == true then
current_speed = mp.get_property_number("speed", 1)
end
mp.osd_message(ass_start .. (was_rewinding and options.osd_rewind or options.osd_symbol) .. ass_stop .. string.format("x%.1f", current_speed))
end
elseif not current_speed and options.show_seek then
if uosc_show then
mp.command("script-binding uosc/flash-timeline")
elseif osd_show then
mp.osd_message(ass_start .. (was_rewinding and options.osd_rewind or options.osd_symbol))
end
end
end
local function ensure_timer(reset)
if not reset and speed_timer:is_enabled() then return end
speed_timer.timeout = 0
speed_timer:resume()
speed_timer.timeout = options.speed_interval
end
local function evafast_speedup(toggle)
if not toggled and not speedup_target and not speed_timer:is_enabled() then
original_speed = mp.get_property_number("speed", 1)
end
speedup = true
if toggle then
toggled = true
end
ensure_timer()
end
local function evafast_slowdown(display)
forced_slowdown = false
if not display then
toggled_display = false
end
toggled = false
speedup = false
ensure_timer()
end
local function evafast_toggle()
if toggled_rewind then
mp.set_property("play-dir", "+")
end
toggled_rewind = false
if speedup then
evafast_slowdown()
else
evafast_speedup(true)
end
end
local function evafast_toggle_rewind()
rewinding = not speedup
mp.set_property("play-dir", rewinding and "-" or "+")
evafast_toggle()
toggled_rewind = rewinding
end
local function adjust_speed()
local current_time = mp.get_property_number("time-pos", 0)
local current_speed = mp.get_property_number("speed", 1)
local target_speed = original_speed
if speedup then
target_speed = options.speed_cap
if has_subtitle and target_speed ~= options.subs_speed_cap then
local sub_displayed = mp.get_property("sub-start") ~= nil
if sub_displayed then
target_speed = options.subs_speed_cap
elseif options.subs_lookahead then
if next_sub_at < current_time and next_sub_at ~= -2 then
next_sub_at = next_sub(current_time)
end
if target_speed ~= options.subs_speed_cap and next_sub_at > current_time then
local time_for_correction = speed_transition(options.speed_cap, options.subs_speed_cap)
if current_time + time_for_correction >= next_sub_at then
target_speed = options.subs_speed_cap
end
end
end
end
if speedup_target ~= nil then
local effective_speedup_target = speedup_target >= 0 and speedup_target or (file_duration + speedup_target)
if current_time >= effective_speedup_target then
evafast_slowdown()
else
local time_for_correction = speed_transition(current_speed, original_speed)
if current_time + time_for_correction > effective_speedup_target or forced_slowdown then
forced_slowdown = true
speedup = false
target_speed = original_speed
end
end
end
end
if math.floor(target_speed * 1000 + 0.5) == math.floor(current_speed * 1000 + 0.5) then
if forced_slowdown or (not toggled and (not speedup or options.subs_speed_cap == options.speed_cap or (not has_subtitle and not speedup_target))) then
speed_timer:kill()
toggled_display = true
if speedup_target ~= nil then
evafast_slowdown()
end
speedup_target = nil
end
return
end
local new_speed = current_speed
local speed_correction = 0
if options.multiply_modifier then
speed_correction = current_speed * options.speed_increase
else
speed_correction = options.speed_increase
end
if current_speed > target_speed then
new_speed = math.max(current_speed - speed_correction, target_speed)
else
new_speed = math.min(current_speed + speed_correction, target_speed)
end
mp.set_property("speed", new_speed)
flash_state(new_speed)
end
speed_timer = mp.add_periodic_timer(100, adjust_speed)
speed_timer:kill()
local function evafast(keypress, rewind)
was_rewinding = false
if rewinding and not toggled_rewind and (not rewind or (keypress["event"] == "up" and last_key_state ~= "down")) then
rewinding = false
was_rewinding = true
mp.set_property("play-dir", "+")
end
if rewind then
was_rewinding = true
end
if keypress["event"] == "down" then
if not speed_timer:is_enabled() then
if not toggled and not speedup_target then
original_speed = mp.get_property_number("speed", 1)
end
flash_state(nil, "osd")
flash_state(1, "uosc", true)
end
toggled_display = true
speed_timer:stop()
if options.seek_distance == 0 then
keypress["event"] = "repeat"
end
end
if keypress["event"] == "press" or keypress["event"] == "up" and last_key_state ~= "repeat" then
if not toggled and not speedup_target then
speed_timer:kill()
mp.set_property("speed", original_speed)
end
flash_state()
ensure_timer()
if rewind then
if not toggled_rewind then
rewinding = false
mp.set_property("play-dir", "+") -- unnecessary in some cases
end
mp.commandv("seek", -options.seek_distance)
else
mp.commandv("seek", options.seek_distance)
end
elseif keypress["event"] == "repeat" and last_key_state ~= "repeat" then
speedup = true
ensure_timer()
if rewind then
mp.set_property("play-dir", "-")
rewinding = true
end
elseif keypress["event"] == "up" and not toggled and not speedup_target then
evafast_slowdown(true)
ensure_timer(true)
end
last_key_state = keypress["event"]
end
local function evafast_rewind(keypress)
evafast(keypress, true)
end
mp.observe_property("duration", "native", function(prop, val)
file_duration = val or 0
end)
mp.observe_property("sid", "native", function(prop, val)
has_subtitle = (val or 0) ~= 0
next_sub_at = -1
end)
mp.observe_property("sub-start", "native", function(prop, val)
next_sub_at = -1
end)
mp.register_event("file-loaded", function()
next_sub_at = -1
end)
mp.register_event("seek", function()
next_sub_at = -1
end)
mp.register_script_message("uosc-version", function(version)
uosc_available = true
end)
mp.register_script_message("speedup-target", function(time)
local current_time = mp.get_property_number("time-pos", 0)
sign = string.sub(time, 1, 1)
time = tonumber(time) or 0
if sign == "+" then
time = current_time + time
end
if current_time >= time and time >= 0 then
speedup_target = nil
evafast_slowdown()
return
end
speedup_target = time
evafast_speedup()
end)
mp.register_script_message("get-version", function(script)
mp.commandv("script-message-to", script, "evafast-version", "2.0")
end)
mp.add_key_binding("RIGHT", "evafast", evafast, {repeatable = true, complex = true})
mp.add_key_binding(nil, "evafast-rewind", evafast_rewind, {repeatable = true, complex = true})
mp.add_key_binding(nil, "flash-speed", function() flash_state(true, nil, true) end)
mp.add_key_binding(nil, "speedup", evafast_speedup)
mp.add_key_binding(nil, "slowdown", evafast_slowdown)
mp.add_key_binding(nil, "toggle", evafast_toggle)
mp.add_key_binding(nil, "toggle-rewind", evafast_toggle_rewind)
mp.commandv("script-message-to", "uosc", "get-version", mp.get_script_name())
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Oscar Manglaras
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+229
View File
@@ -0,0 +1,229 @@
# mpv-file-browser
![cover](screenshots/bunny.png)
This script allows users to browse and open files and folders entirely from within mpv. The script uses nothing outside the mpv API, so should work identically on all platforms. The browser can move up and down directories, start playing files and folders, or add them to the queue.
By default only file types compatible with mpv will be shown, but this can be changed in the config file.
This script requires at least **mpv v0.33**.
Originally, file-browser worked with versions of mpv going back to
v0.31, you can find those older versions of file-browser in the
[mpv-v0.31 branch](https://github.com/CogentRedTester/mpv-file-browser/tree/mpv-v0.31).
That branch will no longer be receiving any feature updates,
but I will try to fix any bugs that are reported on the issue
tracker.
## Installation
### Basic
Clone this git repository into the mpv `~~/scripts` directory and
change the name of the folder from `mpv-file-browser` to `file-browser`.
You can then pull to receive updates.
Alternatively, you can download the zip and extract the contents to `~~/scripts/file-browser`.
`~~/` is the mpv config directory which is typically `~/.config/mpv/` on linux and `%APPDATA%/mpv/` on windows.
### Configuration
Create a `file_browser.conf` file in the `~~/script-opts/` directory to configure the script.
See [docs/file_browser.conf](docs/file_browser.conf) for the full list of options and their default values.
The [`root` option](#root-directory) may be worth tweaking for your system.
### Addons
To use [addons](addons/README.md) place addon files in the `~~/script-modules/file-browser-addons/` directory.
### Custom Keybinds
To setup [custom keybinds](docs/custom-keybinds.md) create a `~~/script-opts/file-browser-keybinds.json` file.
Do **not** copy the `file-browser-keybinds.json` file
stored in this repository, that file is a collection of random examples, many of which are for completely different
operating systems. Use them and the [docs](docs/custom-keybinds.md) to create your own collection of keybinds.
### File Structure
<details>
<summary>Expected directory tree (basic):</summary>
```
~~/
├── script-opts
│   └── file_browser.conf
└── scripts
   └── file-browser
      ├── addons/
      ├── docs/
      ├── modules/
      ├── screenshots/
      ├── LICENSE
      ├── main.lua
      └── README.md
```
</details>
<details>
<summary>Expected directory tree (full):</summary>
```
~~/
├── script-modules
│   └── file-browser-addons
│   ├── addon1.lua
│   ├── addon2.lua
│   └── etc.lua
├── script-opts
│   ├── file_browser.conf
│   └── file-browser-keybinds.json
└── scripts
   └── file-browser
      ├── addons/
      ├── docs/
      ├── modules/
      ├── screenshots/
      ├── LICENSE
      ├── main.lua
      └── README.md
```
</details>
## Keybinds
The following keybinds are set by default
| Key | Name | Description |
|-------------|----------------------------------|-------------------------------------------------------------------------------|
| MENU | browse-files | toggles the browser |
| Ctrl+o | open-browser | opens the browser |
| Alt+o | browse-directory/get-user-input | opens a dialogue box to type in a directory - requires [mpv-user-input](#mpv-user-input) when mpv < v0.38 |
The following dynamic keybinds are only set while the browser is open:
| Key | Name | Description |
|-------------|---------------|-------------------------------------------------------------------------------|
| ESC | close | closes the browser or clears the selection |
| ENTER | play | plays the currently selected file or folder |
| Shift+ENTER | play_append | appends the current file or folder to the playlist |
| Alt+ENTER | play_autoload | loads playlist entries before and after the selected file (like autoload.lua) |
| RIGHT | down_dir | enter the currently selected directory |
| LEFT | up_dir | move to the parent directory |
| DOWN | scroll_down | move selector down the list |
| UP | scroll_up | move selector up the list |
| PGDWN | page_down | move selector down the list by a page (the num_entries option) |
| PGUP | page_up | move selector up the list by a page (the num_entries option) |
| Shift+PGDWN | list_bottom | move selector to the bottom of the list |
| Shift+PGUP | list_top | move selector to the top of the list |
| HOME | goto_current | move to the directory of the currently playing file |
| Shift+HOME | goto_root | move to the root directory |
| Alt+LEFT | history_back | move to previously open directory |
| Alt+RIGHT | history_forward| move forwards again in history to the next directory |
| Ctrl+r | reload | reload current directory |
| Ctrl+Shift+r| cache/clear | clears the directory cache (disabled by default) |
| s | select_mode | toggles multiselect mode |
| S | select_item | toggles selection for the current item |
| Ctrl+a | select_all | select all items in the current directory |
| Ctrl+f | find/find | Opens a text input to search the contents of the folder - requires [mpv-user-input](#mpv-user-input) when mpv < v0.38|
| Ctrl+F | find/find_advanced| Allows using [Lua Patterns](https://www.lua.org/manual/5.1/manual.html#5.4.1) in the search input|
| n | find/next | Jumps to the next matching entry for the latest search term |
| N | find/prev | Jumps to the previous matching entry for the latest search term |
When attempting to play or append a subtitle file the script will instead load the subtitle track into the existing video.
The behaviour of the autoload keybind can be reversed with the `autoload` script-opt.
By default the playlist will only be autoloaded if `Alt+ENTER` is used on a single file, however when the option is switched autoload will always be used on single files *unless* `Alt+ENTER` is used. Using autoload on a directory, or while appending an item, will not work.
## Root Directory
To accomodate for both windows and linux this script has its own virtual root directory where drives and file folders can be manually added. The root directory can only contain folders.
The root directory is set using the `root` option, which is a comma separated list of directories. Entries are sent through mpv's `expand-path` command. By default `~/` and `C:/` are set on Windows
and `~/` and `/` are set on non-Windows systems.
Extra locations can be added manually, for example, my Windows root looks like:
`root=~/,C:/,D:/,E:/,Z:/`
## Multi-Select
By default file-browser only opens/appends the single item that the cursor has selected.
However, using the `s` keybinds specified above, it is possible to select multiple items to open all at once. Selected items are shown in a different colour to the cursor.
When in multiselect mode the cursor changes colour and scrolling up and down the list will drag the current selection. If the original item was unselected, then dragging will select items, if the original item was selected, then dragging will unselect items.
When multiple items are selected using the open or append commands all selected files will be added to the playlist in the order they appear on the screen.
The currently selected (with the cursor) file will be ignored, instead the first multi-selected item in the folder will follow replace/append behaviour as normal, and following selected items will be appended to the playlist afterwards in the order that they appear on the screen.
## Custom Keybinds
File-browser also supports custom keybinds. These keybinds send normal input commands, but the script will substitute characters in the command strings for specific values depending on the currently open directory, and currently selected item.
This allows for a wide range of customised behaviour, such as loading additional audio tracks from the browser, or copying the path of the selected item to the clipboard.
To see how to enable and use custom keybinds, see [custom-keybinds.md](docs/custom-keybinds.md).
## Add-ons
Add-ons are ways to add extra features to file-browser, for example adding support for network file servers like ftp, or implementing virtual directories in the root like recently opened files.
They can be enabled by setting `addon` script-opt to yes, and placing the addon file into the `~~/script-modules/file-browser-addons/` directory.
For a list of existing addons see the [wiki](https://github.com/CogentRedTester/mpv-file-browser/wiki/Addon-List).
For instructions on writing your own addons see [addons.md](docs/addons.md).
## Script Messages
File-browser supports a small number of script messages that allow the user or other scripts to talk with the browser.
### `browse-directory`
`script-message browse-directory [directory]`
Opens the given directory in the browser. If the browser is currently closed it will be opened.
### `get-directory-contents`
`script-message get-directory-contents [directory] [response-string]`
Reads the given directory, and sends the resulting tables to the specified script-message in the format:
`script-message [response-string] [list] [opts]`
The [list](docs/addons.md#the-list-array)
and [opts](docs/addons.md#the-opts-table)
tables are formatted as json strings through the `mp.utils.format_json` function.
See [addons.md](docs/addons.md) for how the tables are structured, and what each field means.
The API_VERSION field of the `opts` table refers to what version of the addon API file browser is using.
The `response-string` refers to an arbitrary script-message that the tables should be sent to.
This script-message allows other scripts to utilise file-browser's directory parsing capabilities, as well as those of the file-browser addons.
## Conditional Auto-Profiles
file-browser provides a property that can be used with [conditional auto-profiles](https://mpv.io/manual/master/#conditional-auto-profiles)
to detect when the browser is open.
On mpv v0.36+ you should use the `user-data` property with the `file_browser/open` boolean.
Here is an example of an auto-profile that hides the OSC logo when using file-browser in an idle window:
```properties
[hide-logo]
profile-cond= idle_active and user_data.file_browser.open
profile-restore=copy
osc=no
```
On older versions of mpv you can use the `file_browser-open` field of the `shared-script-properties` property:
```properties
[hide-logo]
profile-cond= idle_active and shared_script_properties["file_browser-open"] == "yes"
profile-restore=copy
osc=no
```
See [#55](https://github.com/CogentRedTester/mpv-file-browser/issues/55) for more details on this.
## [mpv-user-input](https://github.com/CogentRedTester/mpv-user-input)
mpv-user-input is a script that provides an API to request text input from the user over the OSD.
It was built using `console.lua` as a base, so supports almost all the same text input commands.
If `user-input.lua` is loaded by mpv, and `user-input-module` is in the `~~/script-modules/` directory,
then using `Alt+o` will open an input box that can be used to directly enter directories for file-browser to open.
Mpv v0.38 added the `mp.input` module, which means `mpv-user-input` is no-longer necessary from that version onwards.
+12
View File
@@ -0,0 +1,12 @@
# addons
Add-ons are ways to add extra features to file-browser, for example adding support for network file servers like ftp, or implementing virtual directories in the root like recently opened files.
They can be enabled by setting `addon` script-opt to yes, and placing the addon file into the `~~/script-modules/file-browser-addons/` directory.
Browsing filesystems provided by add-ons should feel identical to the normal handling of the script,
but they may require extra commandline tools be installed.
Since addons are loaded programatically from the addon directory it is possible for anyone to write their own addon.
Instructions on how to do this are available [here](../docs/addons.md).
For a list of available addons see the [wiki](https://github.com/CogentRedTester/mpv-file-browser/wiki/Addon-List).
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,330 @@
# Custom Keybinds
File-browser also supports custom keybinds. These keybinds send normal input commands, but the script will substitute characters in the command strings for specific values depending on the currently open directory, and currently selected item.
This allows for a wide range of customised behaviour, such as loading additional audio tracks from the browser, or copying the path of the selected item to the clipboard.
The feature is disabled by default, but is enabled with the `custom_keybinds` script-opt.
Keybinds are declared in the `~~/script-opts/file-browser-keybinds.json` file, the config takes the form of an array of json objects, with the following keys:
| option | required | default | description |
|---------------|----------|------------|--------------------------------------------------------------------------------------------|
| key | yes | - | the key to bind the command to - same syntax as input.conf |
| command | yes | - | json array of commands and arguments |
| name | no | numeric id | name of the script-binding - see [modifying default keybinds](#modifying-default-keybinds) |
| condition | no | - | a Lua [expression](#expressions) - the keybind will only run if this evaluates to true |
| flags | no | - | flags to send to the mpv add_keybind function - see [here](https://mpv.io/manual/master/#lua-scripting-[,flags]]\)) |
| filter | no | - | run the command on just a file (`file`) or folder (`dir`) |
| parser | no | - | run the command only in directories provided by the specified parser. |
| multiselect | no | `false` | command is run on all selected items |
| multi-type | no | `repeat` | which multiselect mode to use - `repeat` or `concat` |
| delay | no | `0` | time to wait between sending repeated multi commands |
| concat-string | no | `' '` (space) | string to insert between items when concatenating multi commands |
| passthrough | no | - | force or ban passthrough behaviour - see [passthrough](#passthrough-keybinds) |
| api_version | no | - | tie the keybind to a particular [addon API version](./addons.md#api-version), printing warnings and throwing errors if the keybind is used with wrong versions |
Example:
```json
{
"key": "KP1",
"command": ["print-text", "example"],
}
```
The command can also be an array of arrays, in order to send multiple commands at once:
```json
{
"key": "KP2",
"command": [
["print-text", "example2"],
["show-text", "example2"]
]
}
```
Filter should not be included unless one wants to limit what types of list entries the command should be run on.
To only run the command for directories use `dir`, to only run the command for files use `file`.
The parser filter is for filtering keybinds to only work inside directories loaded by specific parsers.
There are two parsers in the base script, the default parser for native filesystems is called `file`, while the root parser is called `root`.
Other parsers can be supplied by addons, and use the addon's filename with `-browser.lua` or just `.lua` stripped unless otherwise stated.
For example `ftp-browser.lua` would have a parser called `ftp`.
You can set the filter to match multiple parsers by separating the names with spaces.
```json
{
"key": "KP2",
"command": [ ["print-text", "example3"] ],
"parser": "ftp file"
}
```
The `flags` field is mostly only useful for addons, but can also be useful if one wants a key to be repeatable.
In this case the the keybind would look like the following:
```json
{
"key": "p",
"command": ["print-text", "spam-text"],
"flags": { "repeatable": true }
}
```
## Codes
The script will scan every string in the command for the special substitution strings, they are:
| code | description |
|--------|---------------------------------------------------------------------|
| `%%` | escape code for `%` |
| `%f` | filepath of the selected item |
| `%n` | filename of the selected item |
| `%p` | currently open directory |
| `%q` | currently open directory but preferring the directory label |
| `%d` | name of the current directory (characters between the last two '/') |
| `%r` | name of the parser for the currently open directory |
| `%x` | number of items in the currently open directory |
| `%i` | the 1-based index of the selected item in the list |
| `%j` | the 1-based index of the item in a multiselection - returns 1 for single selections |
Additionally, using the uppercase forms of those codes will send the substituted string through the `string.format("%q", str)` function.
This adds double quotes around the string and automatically escapes any characters which would break the string encapsulation.
This is not necessary for most mpv commands, but can be very useful when sending commands to the OS with the `run` command,
or when passing values into [expressions](#conditional-command-condition-command).
Example of a command to add an audio track:
```json
{
"key": "Ctrl+a",
"command": ["audio-add", "%f"],
"filter": "file"
}
```
Any commands that contain codes representing specific items (`%f`, `%n`, `%i` etc) will
not be run if no item is selected (for example in an empty directory).
In these cases [passthrough](#passthrough-keybinds) rules will apply.
## Multiselect Commands
When multiple items are selected the command can be run for all items in the order they appear on the screen.
This can be controlled by the `multiselect` flag, which takes a boolean value.
When not set the flag defaults to `false`.
There are two different multiselect modes, controlled by the `multi-type` option. There are two options:
### `repeat`
The default mode that sends the commands once for each item that is selected.
If time is needed between running commands of multiple selected items (for example, due to file handlers) then the `delay` option can be used to set a duration (in seconds) between commands.
### `concat`
Run a single command, but replace item specific codes with a concatenated string made from each selected item.
For example `["print-text", "%n" ]` would print the name of each item selected separated by `' '` (space).
The string inserted between each item is determined by the `concat-string` option, but `' '` is the default.
## Passthrough Keybinds
When loading keybinds from the json file file-browser will move down the list and overwrite any existing bindings with the same key.
This means the lower an item on the list, the higher preference it has.
However, file-browser implements a layered passthrough system for its keybinds; if a keybind is blocked from running by user filters, then the next highest preference command will be sent, continuing until a command is sent or there are no more keybinds.
The default dynamic keybinds are considered the lowest priority.
The `filter`, `parser`, and `condition` options can all trigger passthrough, as well as some [codes](#codes).
If a multi-select command is run on multiple items then passthrough will occur if any of the selected items fail the filters.
Passthrough can be forcibly disabled or enabled using the passthrough option.
When set to `true` passthrough will always be activate regardless of the state of the filters.
## Modifying Default Keybinds
Since the custom keybinds are applied after the default dynamic keybinds they can be used to overwrite the default bindings.
Setting new keys for the existing binds can be done with the `script-binding [binding-name]` command, where `binding-name` is the full name of the keybinding.
For this script the names of the dynamic keybinds are in the format `file_browser/dynamic/[name]` where `name` is a unique identifier documented in the [keybinds](README.md#keybinds) table.
For example to change the scroll buttons from the arrows to the scroll wheel:
```json
[
{
"key": "WHEEL_UP",
"command": ["script-binding", "file_browser/dynamic/scroll_up"]
},
{
"key": "WHEEL_DOWN",
"command": ["script-binding", "file_browser/dynamic/scroll_down"]
},
{
"key": "UP",
"command": ["osd-auto", "add", "volume", "2"]
},
{
"key": "DOWN",
"command": ["osd-auto", "add", "volume", "-2"]
}
]
```
Custom keybinds can be called using the same method, but users must set the `name` value inside the `file-browser-keybinds.json` file.
To avoid conflicts custom keybinds use the format: `file_browser/dynamic/custom/[name]`.
## Expressions
Expressions are used to evaluate Lua code into a string that can be used for commands.
These behave similarly to those used for [`profile-cond`](https://mpv.io/manual/master/#conditional-auto-profiles)
values. In an expression the `mp`, `mp.msg`, and `mp.utils` modules are available as `mp`, `msg`, and `utils` respectively.
Additionally, in mpv v0.38+ the `mp.input` module is available as `input`.
The file-browser [addon API](addons/addons.md#the-api) is available as `fb` and if [mpv-user-input](https://github.com/CogentRedTester/mpv-user-input)
is installed then user-input API will be available in `user_input`.
This example only runs the keybind if the browser is in the Windows C drive or if
the selected item is a matroska file:
```json
[
{
"key": "KP1",
"command": ["print-text", "in my C:/ drive!"],
"condition": "(%P):find('C:/') == 1"
},
{
"key": "KP2",
"command": ["print-text", "Matroska File!"],
"condition": "fb.get_extension(%N) == 'mkv'"
}
]
```
If the `condition` expression contains any item specific codes (`%F`, `%I`, etc) then it will be
evaluated on each individual item, otherwise it will evaluated once for the whole keybind.
If a code is invalid (for example using `%i` in empty directories) then the expression returns false.
There are some utility script messages that extend the power of expressions.
[`conditional-command`](#conditional-command-condition-command) allows one to specify conditions that
can apply to individual items or commands. The tradeoff is that you lose the automated passthrough behaviour.
There is also [`evaluate-expressions`](#evaluate-expressions-command) which allows one to evaluate expressions inside commands.
## Utility Script Messages
There are a small number of custom script messages defined by file-browser to support custom keybinds.
### `=> <command...>`
A basic script message that makes it easier to chain multiple utility script messages together.
Any `=>` string will be substituted for `script-message`.
```json
{
"key": "KP1",
"command": ["script-message", "=>", "delay-command", "%j * 2", "=>", "evaluate-expressions", "print-text", "!{%j * 2}"],
"multiselect": true
}
```
### `conditional-command [condition] <command...>`
Runs the following command only if the condition [expression](#expressions) is `true`.
This example command will only run if the player is currently paused:
```json
{
"key": "KP1",
"command": ["script-message", "conditional-command", "mp.get_property_bool('pause')", "print-text", "is paused"],
}
```
Custom keybind codes are evaluated before the expressions.
This example only runs if the currently selected item in the browser has a `.mkv` extension:
```json
{
"key": "KP1",
"command": ["script-message", "conditional-command", "fb.get_extension(%N) == 'mkv'", "print-text", "a matroska file"],
}
```
### `delay-command [delay] <command...>`
Delays the following command by `[delay]` seconds.
Delay is an [expression](#expressions).
The following example will send the `print-text` command after 5 seconds:
```json
{
"key": "KP1",
"command": ["script-message", "delay-command", "5", "print-text", "example"],
}
```
### `evaluate-expressions <command...>`
Evaluates embedded Lua expressions in the following command.
Expressions have the same behaviour as the [`conditional-command`](#conditional-command-condition-command) script-message.
Expressions must be surrounded by `!{}` characters.
Additional `!` characters can be placed at the start of the expression to
escape the evaluation.
For example the following keybind will print 3 to the console:
```json
{
"key": "KP1",
"command": ["script-message", "evaluate-expressions", "print-text", "!{1 + 2}"],
}
```
This example replaces all `/` characters in the path with `\`
(note that the `\` needs to be escaped twice, once for the json file, and once for the string in the lua expression):
```json
{
"key": "KP1",
"command": ["script-message", "evaluate-expressions", "print-text", "!{ string.gsub(%F, '/', '\\\\') }"],
}
```
### `run-statement <statement...>`
Runs the following string a as a Lua statement. This is similar to an [expression](#expressions),
but instead of the code evaluating to a value it must run a series of statements. Basically it allows
for function bodies to be embedded into custom keybinds. All the same modules are available.
If multiple strings are sent to the script-message then they will be concatenated together with newlines.
The following keybind will use [mpv-user-input](https://github.com/CogentRedTester/mpv-user-input) to
rename items in file-browser:
```json
{
"key": "KP1",
"command": ["script-message", "run-statement",
"assert(user_input, 'install mpv-user-input!')",
"local line, err = user_input.get_user_input_co({",
"id = 'rename-file',",
"source = 'custom-keybind',",
"request_text = 'rename file:',",
"queueable = true,",
"default_input = %N,",
"cursor_pos = #(%N) - #fb.get_extension(%N, '')",
"})",
"if not line then return end",
"os.rename(%F, utils.join_path(%P, line))",
"fb.rescan()"
],
"parser": "file",
"multiselect": true
}
```
## Examples
See [here](file-browser-keybinds.json).
@@ -0,0 +1,51 @@
[
{
"comment": "deletes the currently selected file",
"key": "Alt+DEL",
"command": ["script-message", "run-statement", "os.remove(%F) ; fb.rescan()"],
"multiselect": true,
"multi-type": "repeat"
},
{
"comment": "opens the currently selected items in a new mpv window",
"key": "Ctrl+ENTER",
"command": ["run", "mpv", "%F"],
"multiselect": true,
"multi-type": "concat"
},
{
"key": "Ctrl+c",
"command": [
["run", "powershell", "-command", "Set-Clipboard", "%F"],
["print-text", "copied filepath to clipboard"]
],
"condition": "fb.get_platform() == 'windows'",
"api_version": "1.9.0",
"multiselect": true,
"delay": 0.3
},
{
"key": "WHEEL_UP",
"command": ["script-binding", "file_browser/dynamic/scroll_up"],
"flags": { "repeat": true }
},
{
"key": "WHEEL_DOWN",
"command": ["script-binding", "file_browser/dynamic/scroll_down"],
"flags": { "repeat": true }
},
{
"key": "MBTN_LEFT",
"command": ["script-binding", "file_browser/dynamic/down_dir"]
},
{
"key": "MBTN_RIGHT",
"command": ["script-binding", "file_browser/dynamic/up_dir"]
},
{
"key": "MBTN_MID",
"command": ["script-binding", "file_browser/dynamic/play"]
}
]
+247
View File
@@ -0,0 +1,247 @@
#######################################################
# This is the default config file for mpv-file-browser
# https://github.com/CogentRedTester/mpv-file-browser
#######################################################
####################################
######## browser settings ##########
####################################
# Root directories, separated by commas.
# `C:/` and `/` are automatically added on Windows and non-windows systems, respectively.
# The order of automatically added items can be changed by entering them here manually.
root=~/
# characters to separate root directories, each character works individually
root_separators=,
# number of entries to show on the screen at once
num_entries=20
# number of directories to keep in the history.
# A size of 0 disables the history.
history_size=100
# wrap the cursor around the top and bottom of the list
wrap=no
# enables loading external addons
addons=yes
# enable custom keybinds
# the keybind json file must go in ~~/script-opts
custom_keybinds=yes
# Automatically detect windows drives and adds them to the root.
# Using Ctrl+r in the root will run another scan.
auto_detect_windows_drives=yes
# when opening the browser in idle mode prefer the current working directory over the root
# note that the working directory is set as the 'current' directory regardless, so `home` will
# move the browser there even if this option is set to false
default_to_working_directory=no
# When opening the browser prefer the directory last opened by a previous mpv instance of file-browser.
# Overrides the `default_to_working_directory` option.
# Requires `save_last_opened_directory` to be `yes`.
# Uses the internal `last-opened-directory` addon.
default_to_last_opened_directory=no
# Whether to save the last opened directory.
save_last_opened_directory=no
# Move the cursor to the currently playing item (if available) when the playing file changes.
cursor_follows_playing_item=no
####################################
########## filter settings #########
####################################
# only show files compatible with mpv in the browser
filter_files=yes
# file-browser only shows files that are compatible with mpv by default
# adding a file extension to this list will add it to the extension whitelist
# extensions are separated with commas, do not use any spaces
extension_whitelist=
# add file extensions to this list to disable default filetypes
# note that this will also override audio/subtitle_extension options below
extension_blacklist=
# files with these extensions will be added as additional audio tracks for the current file instead of appended to the playlist
# items on this list are automatically added to the extension whitelist
audio_extensions=mka,dts,dtshd,dts-hd,truehd,true-hd
# files with these extensions will be added as additional subtitle tracks for the current file instead of appended to the playlist
# items on this list are automatically added to the extension whitelist
subtitle_extensions=etf,etf8,utf-8,idx,sub,srt,rt,ssa,ass,mks,vtt,sup,scc,smi,lrc,pgs
# filter directories or files starting with a period like .config for linux systems
# auto will show dot entries on windows and hide them otherwise
filter_dot_dirs=auto
filter_dot_files=auto
####################################
###### file loading settings #######
####################################
# this option reverses the behaviour of the alt+ENTER keybind
# when disabled the keybind is required to enable autoload for the file
# when enabled the keybind disables autoload for the file
autoload=no
# experimental feature that recurses directories concurrently when appending items to the playlist
# this feature has the potential for massive performance improvements when using addons with asynchronous IO
concurrent_recursion=yes
# maximum number of recursions that can run concurrently
# if this number is too high it risks overflowing the mpv event queue, which will cause some directories to be dropped entirely
max_concurrency=16
# substitute forward slashes for backslashes when appending a local file to the playlist
# may be useful on windows systems
substitute_backslash=no
# if autoload is triggered by selecting the currently playing file, then
# the current file will have it's watch-later config saved before being closed and re-opened
# essentially the current file will not be restarted
autoload_save_current=yes
####################################
### directory parsing settings #####
####################################
# a directory cache to improve directory reading time,
# enable if it takes a long time to load directories.
# May cause 'ghost' files to be shown that no-longer exist or
# fail to show files that have recently been created.
# Reloading the directory with Ctrl+r will never use the cache.
# Use Ctrl+Shift+r to forcibly clear the cache.
cache=no
# Enables the internal `ls` addon that parses directories using the `ls` commandline tool.
# Allows directory parsing to run concurrently, which prevents the browser from locking up.
# Automatically disables itself on Windows systems.
ls_parser=yes
# Enables the internal `windir` addon that parses directories using the `dir` command in cmd.exe.
# Allows directory parsing to run concurrently, which prevents the browser from locking up.
# Automatically disables itself on non-Windows systems.
windir_parser=yes
# when moving up a directory do not stop on empty protocol schemes like `ftp://`
# e.g. moving up from `ftp://localhost/` will move straight to the root instead of `ftp://`
skip_protocol_schemes=yes
# map optical device paths to their respective file paths,
# e.g. mapping bd:// to the value of the bluray-device property
map_bd_device=yes
map_dvd_device=yes
map_cdda_device=yes
####################################
########## misc settings ###########
####################################
# turn the OSC idle screen off and on when opening and closing the browser
# this should only be enabled if file-browser is the only thing controlling the idle-screen,
# if multiple sources attempt to control the idle-screen at the same time it can cause unexpected behaviour.
toggle_idlescreen=no
# interpret backslashes `\` in paths as forward slashes `/`
# this is useful on Windows, which natively uses backslashes.
# As backslashes are valid filename characters in Unix systems this could
# cause mangled paths, though such filenames are rare.
# Use `yes` and `no` to enable/disable. `auto` tries to use the mpv `platform`
# property (mpv v0.36+) to decide. If the property is unavailable it defaults to `yes`.
normalise_backslash=auto
# Set the current open status of the browser in the `file_browser/open` field of the `user-data` property.
# This property is only available in mpv v0.36+.
set_user_data=yes
# Set the current open status of the browser in the `file_browser-open` field of the `shared-script-properties` property.
# This property is deprecated. When it is removed in mpv v0.37 file-browser will automatically disable this option.
set_shared_script_properties=no
####################################
########## file overrides #########
####################################
# directory to load external modules - currently just user-input-module
module_directory=~~/script-modules
addon_directory=~~/script-modules/file-browser-addons
custom_keybinds_file=~~/script-opts/file-browser-keybinds.json
last_opened_directory_file=~~state/file_browser-last_opened_directory
####################################
######### style settings ###########
####################################
# Replace the user's home directory with `~/` in the header.
# Uses the internal home-label addon.
home_label=yes
# force file-browser to use a specific alignment (default: top-left)
# set to auto to use the default mpv osd-align options
# Options: 'auto'|'top'|'center'|'bottom'
align_y=top
# Options: 'auto'|'left'|'center'|'right'
align_x=left
# The format string used for the header. Uses custom-keybind substitution codes to
# dynamically change the contents of the header (see: docs/custom-keybinds.md#codes)
# and supports the additional code `%^`which re-applies the default header ass style.
# The original style used before the current one was: %q\N----------------------------------------------------
format_string_header={\fnMonospace}[%i/%x]%^ %q\N------------------------------------------------------------------
# The format strings used for the wrappers. Supports custom-keybind substitution codes, and
# supports two additional codes: `%<` and `%>` to show the number of items before and after the visible list, respectively.
# Setting these options to empty strings will disable the wrappers.
# Original styles used before the current ones were:
# top: %< item(s) above\N
# bottom: \N%> item(s) remaining
format_string_topwrapper=...
format_string_bottomwrapper=...
# allows custom icons be set for the folder and cursor
# the `\h` character is a hard space to add padding
folder_icon={\p1}m 6.52 0 l 1.63 0 b 0.73 0 0.01 0.73 0.01 1.63 l 0 11.41 b 0 12.32 0.73 13.05 1.63 13.05 l 14.68 13.05 b 15.58 13.05 16.31 12.32 16.31 11.41 l 16.31 3.26 b 16.31 2.36 15.58 1.63 14.68 1.63 l 8.15 1.63{\p0}\h
cursor_icon={\p1}m 14.11 6.86 l 0.34 0.02 b 0.25 -0.02 0.13 -0 0.06 0.08 b -0.01 0.16 -0.02 0.28 0.04 0.36 l 3.38 5.55 l 3.38 5.55 3.67 6.15 3.81 6.79 3.79 7.45 3.61 8.08 3.39 8.5l 0.04 13.77 b -0.02 13.86 -0.01 13.98 0.06 14.06 b 0.11 14.11 0.17 14.13 0.24 14.13 b 0.27 14.13 0.31 14.13 0.34 14.11 l 14.11 7.28 b 14.2 7.24 14.25 7.16 14.25 7.07 b 14.25 6.98 14.2 6.9 14.11 6.86{\p0}\h
cursor_icon_flipped={\p1}m 0.13 6.86 l 13.9 0.02 b 14 -0.02 14.11 -0 14.19 0.08 b 14.26 0.16 14.27 0.28 14.21 0.36 l 10.87 5.55 l 10.87 5.55 10.44 6.79 10.64 8.08 10.86 8.5l 14.21 13.77 b 14.27 13.86 14.26 13.98 14.19 14.06 b 14.14 14.11 14.07 14.13 14.01 14.13 b 13.97 14.13 13.94 14.13 13.9 14.11 l 0.13 7.28 b 0.05 7.24 0 7.16 0 7.07 b 0 6.98 0.05 6.9 0.13 6.86{\p0}\h
# set the opacity of fonts in hexadecimal from 00 (opaque) to FF (transparent)
font_opacity_selection_marker=99
# print the header in bold font
font_bold_header=yes
# scale the size of the browser; 2 would double the size, 0.5 would halve it, etc.
# the header and wrapper scaling is relative to the base scaling
scaling_factor_base=1
scaling_factor_header=1.4
scaling_factor_wrappers=1
# set custom font names, blank is the default
# setting custom fonts for the folder/cursor can fix broken or missing icons
font_name_header=
font_name_body=
font_name_wrappers=
font_name_folder=
font_name_cursor=
# set custom font colours
# colours are in hexadecimal format in Blue Green Red order
# note that this is the opposite order to RGB colour codes
font_colour_header=00ccff
font_colour_body=ffffff
font_colour_wrappers=00ccff
font_colour_cursor=00ccff
font_colour_escape_chars=413eff
# these are colours applied to list items in different states
font_colour_selected=fce788
font_colour_multiselect=fcad88
font_colour_playing=33ff66
font_colour_playing_multiselected=22b547
+76
View File
@@ -0,0 +1,76 @@
--[[
mpv-file-browser
This script allows users to browse and open files and folders entirely from within mpv.
The script uses nothing outside the mpv API, so should work identically on all platforms.
The browser can move up and down directories, start playing files and folders, or add them to the queue.
For full documentation see: https://github.com/CogentRedTester/mpv-file-browser
]]--
local mp = require 'mp'
local o = require 'modules.options'
-- setting the package paths
package.path = mp.command_native({"expand-path", o.module_directory}).."/?.lua;"..package.path
local addons = require 'modules.addons'
local keybinds = require 'modules.keybinds'
local setup = require 'modules.setup'
local controls = require 'modules.controls'
local observers = require 'modules.observers'
local script_messages = require 'modules.script-messages'
local input_loaded, input = pcall(require, "mp.input")
local user_input_loaded, user_input = pcall(require, "user-input-module")
-- root and addon setup
setup.root()
addons.load_internal_addons()
if o.addons then addons.load_external_addons() end
addons.setup_addons()
--these need to be below the addon setup in case any parsers add custom entries
setup.extensions_list()
keybinds.setup_keybinds()
-- property observers
mp.observe_property('path', 'string', observers.current_directory)
if o.map_dvd_device then mp.observe_property('dvd-device', 'string', observers.dvd_device) end
if o.map_bd_device then mp.observe_property('bluray-device', 'string', observers.bd_device) end
if o.map_cdda_device then mp.observe_property('cdda-device', 'string', observers.cd_device) end
if o.align_x == 'auto' then mp.observe_property('osd-align-x', 'string', observers.osd_align) end
if o.align_y == 'auto' then mp.observe_property('osd-align-y', 'string', observers.osd_align) end
-- scripts messages
mp.register_script_message('=>', script_messages.chain)
mp.register_script_message('delay-command', script_messages.delay_command)
mp.register_script_message('conditional-command', script_messages.conditional_command)
mp.register_script_message('evaluate-expressions', script_messages.evaluate_expressions)
mp.register_script_message('run-statement', script_messages.run_statement)
mp.register_script_message('browse-directory', controls.browse_directory)
mp.register_script_message("get-directory-contents", script_messages.get_directory_contents)
--declares the keybind to open the browser
mp.add_key_binding('MENU','browse-files', controls.toggle)
mp.add_key_binding('Ctrl+o','open-browser', controls.open)
if input_loaded then
mp.add_key_binding("Alt+o", "browse-directory/get-user-input", function()
input.get({
prompt = "open directory:",
id = "file-browser/browse-directory",
submit = function(text)
controls.browse_directory(text)
input.terminate()
end
})
end)
elseif user_input_loaded then
mp.add_key_binding("Alt+o", "browse-directory/get-user-input", function()
user_input.get_user_input(controls.browse_directory, {request_text = "open directory:"})
end)
end

Some files were not shown because too many files have changed in this diff Show More