init
This commit is contained in:
+25
@@ -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
|
||||
*~
|
||||
@@ -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) |
|
||||
@@ -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)。
|
||||
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
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
}]
|
||||
@@ -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
@@ -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
|
||||
@@ -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' }
|
||||
@@ -0,0 +1,19 @@
|
||||
### 该文件夹下存放mpv脚本的对应设置文件
|
||||
|
||||
通常脚本设置文件名与所属脚本文件同名,注意脚本文件名中的`-`默认需转译成`_`。实际以脚本开发者设定为准。
|
||||
|
||||
脚本设置文件切勿美化格式(例如加入无意义的空格);切勿在参数后注释(应单独另起一行写注释)。
|
||||
|
||||
脚本及其设置文件可能不支持windows的CRLF换行(尝试更改为LF)。
|
||||
|
||||
以上所述情况在自行修改的过程中都可能导致脚本设置文件(部分)失效。
|
||||
|
||||
以下为mpv内置脚本所使用的设置文件:
|
||||
|
||||
```
|
||||
console.conf
|
||||
osc.conf
|
||||
stats.conf
|
||||
ytdl_hook.conf
|
||||
```
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# 设置自动保存文件播放进度及状态的时间间隔,单位为秒。默认值:60 秒
|
||||
save_interval=60
|
||||
# 设置文件播放进度的百分比,满足时自动删除文件播放进度及状态。默认值:99
|
||||
percent_pos=99
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
#指定命令面板的字体大小,默认值:16
|
||||
font_size=26
|
||||
#指定字体大小是否随窗口大小缩放,默认值:no
|
||||
scale_by_window=yes
|
||||
#指定命令面板的菜单项的显示数量,默认值:12
|
||||
lines_to_show=12
|
||||
#指定是否在打开命令面板时暂停播放,默认值:no
|
||||
pause_on_open=yes
|
||||
@@ -0,0 +1,6 @@
|
||||
###此配置不支持参数后注释,须另起一行
|
||||
|
||||
# 是否将命令历史记录保存到文件并加载它。默认:no
|
||||
persist_history=yes
|
||||
# 命令历史记录文件的路径。默认:~~state/command_history.txt
|
||||
history_path=~~/files/command_history.txt
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
##指定在file-browser文件浏览器中需隐藏的目录,以逗号分隔
|
||||
##示例:F:/$RECYCLE.BIN/
|
||||
paths=
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
#是否使用外部配置文件设置增强式键位动作,默认:no
|
||||
enable_external_config=yes
|
||||
#指定外部配置文件的路径,可以是 mpv 支持的相对路径或绝对路径
|
||||
#注意:启用外部配置文件功能时请确保该文件存在
|
||||
external_config=~~/inputevent_key.conf
|
||||
#指定键位事件的识别前缀,默认:event
|
||||
prefix=event
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
# options to pass to wget
|
||||
## 设置 mpv 需全局记忆的选项状态
|
||||
properties=volume
|
||||
## 保存文件路径
|
||||
properties_path=files/persistent_config.json
|
||||
@@ -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.0(0 表示透明(默认),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=▼
|
||||
@@ -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=
|
||||
@@ -0,0 +1,2 @@
|
||||
# options to pass to wget
|
||||
#wget_opts=
|
||||
@@ -0,0 +1,7 @@
|
||||
###此配置不支持参数后注释,须另起一行
|
||||
|
||||
# 指定历史记录条目的日期格式。这被传递给 Lua 的 os.date
|
||||
# 这使用与 strftime(3)相同的格式
|
||||
history_date_format=%Y-%m-%d %H:%M:%S
|
||||
# 是否仅显示具有相同路径的最后一个历史记录条目,默认:yes
|
||||
hide_history_duplicates=yes
|
||||
@@ -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-asc,time-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=[""]
|
||||
@@ -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
|
||||
@@ -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" ]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
# API token, 可以在 https://assrt.net 上注册账号后在个人界面获取
|
||||
#示例为脚本内预设的 key
|
||||
#api_token=tNjXZUnOJWcHznHDyalNMYqqP6IdDdpQ
|
||||
# 是否使用 https,默认 yes
|
||||
#use_https=no
|
||||
# 代理设置
|
||||
#proxy=
|
||||
@@ -0,0 +1,4 @@
|
||||
#ffmpeg 所在绝对路径,或者放入环境变量
|
||||
ffmpeg_path=ffmpeg
|
||||
#指定脚本在 OSD 和控制台显示的文本使用的语言,eng=English, chs=Chinese。默认值:eng
|
||||
language=chs
|
||||
@@ -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
|
||||
@@ -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=[]
|
||||
@@ -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=中日/中英/中上英下/双语/特效/简/繁/中
|
||||
@@ -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=
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
require('autosubsync')
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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())
|
||||
@@ -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.
|
||||
@@ -0,0 +1,229 @@
|
||||
# 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.
|
||||
|
||||
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.
|
||||
@@ -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"]
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user