-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchapter-converter.lua
222 lines (191 loc) · 7.55 KB
/
chapter-converter.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
-- chapter-converter.lua
-- src: https://github.com/VimWei/mpv-config
-- * Function:
-- - Converts chapter format between YouTube and mpv.
-- - YouTube Chapter: "videoname.chapter" (e.g., "00:10 chapter title").
-- - mpv Chapter: "videoname.ext.ffmetadata" (FFmpeg metadata standard).
-- * Hotkey customize:
-- - Ctrl+y script-binding youtube-to-mpv
-- - Ctrl+Alt+y script-binding mpv-to-youtube
-- * Ref:
-- - adding/editing/removing/saving/loading chapters
-- - https://github.com/mar04/chapters_for_mpv
local utils = require 'mp.utils'
function log(message)
mp.msg.info(message)
-- 是否在OSD上显示消息
mp.osd_message(message, 3)
end
function parse_time(time_str)
local hours, minutes, seconds, milliseconds = 0, 0, 0, 0
-- 匹配时间格式
local parts = {}
for part in string.gmatch(time_str, "([^:%.]+)") do
table.insert(parts, part)
end
-- 处理不同的时间格式
if #parts == 4 then
-- 格式为 1:27:45.200
hours = tonumber(parts[1]) or 0
minutes = tonumber(parts[2]) or 0
seconds = tonumber(parts[3]) or 0
milliseconds = tonumber(parts[4]) or 0
elseif #parts == 3 then
if string.find(time_str, "%.") then
-- 格式为 1:27.200
minutes = tonumber(parts[1]) or 0
seconds = tonumber(parts[2]) or 0
milliseconds = tonumber(parts[3]) or 0
else
-- 格式为 1:27:45
hours = tonumber(parts[1]) or 0
minutes = tonumber(parts[2]) or 0
seconds = tonumber(parts[3]) or 0
end
elseif #parts == 2 then
if string.find(time_str, "%.") then
-- 格式为 27.200
seconds = tonumber(parts[1]) or 0
milliseconds = tonumber(parts[2]) or 0
else
-- 格式为 1:27
minutes = tonumber(parts[1]) or 0
seconds = tonumber(parts[2]) or 0
end
elseif #parts == 1 then
-- 格式为 27
seconds = tonumber(parts[1]) or 0
end
-- 计算总纳秒数
local total_nanoseconds = (hours * 3600 + minutes * 60 + seconds) * 1e9 + milliseconds * 1e6
return total_nanoseconds
end
function convert_youtube_to_mpv()
local video_path = mp.get_property("path")
local video_dir, video_name = utils.split_path(video_path)
local name_without_ext = string.gsub(video_name, "%.%w+$", "")
local chapter_file = utils.join_path(video_dir, name_without_ext .. ".chapter")
local ffmetadata_file = video_path .. ".ffmetadata"
-- 检查 .chapter 文件是否存在
local chapter_file_info = utils.file_info(chapter_file)
if not chapter_file_info then
mp.msg.warn("Chapter file not found: " .. chapter_file)
return
end
-- 读取 .chapter 文件
local chapter_content = io.open(chapter_file, "r")
if not chapter_content then
mp.msg.error("Could not open chapter file: " .. chapter_file)
return
end
-- 创建 .ffmetadata 文件
local ffmetadata = io.open(ffmetadata_file, "w")
if not ffmetadata then
mp.msg.error("Could not create ffmetadata file: " .. ffmetadata_file)
chapter_content:close()
return
end
-- 写入 ffmetadata 头部
ffmetadata:write(";FFMETADATA1\n")
local chapters = {}
-- 解析 .chapter 文件并写入 .ffmetadata 文件
for line in chapter_content:lines() do
local time, title = line:match("(%d+:%d+:%d+%.?%d*)%s+(.*)")
if not time then
time, title = line:match("(%d+:%d+%.?%d*)%s+(.*)")
if not time then
-- 这种情况通常是格式为 '1:27.123' 或 '1:27'
time, title = line:match("(%d+:%d+%.?%d*)%s+(.*)")
if not time then
mp.msg.error("Invalid time format: " .. line)
return
end
end
end
if time and title then
local start_time = parse_time(time)
table.insert(chapters, {start_time = start_time, title = title})
end
end
-- 写入章节信息
for i, chapter in ipairs(chapters) do
ffmetadata:write("[CHAPTER]\n")
ffmetadata:write(string.format("START=%d\n", chapter.start_time))
if i < #chapters then
ffmetadata:write(string.format("END=%d\n", chapters[i+1].start_time))
else
-- 对于最后一个章节,使用视频总时长作为结束时间
local video_duration_ns = mp.get_property_number("duration") * 1e9
ffmetadata:write(string.format("END=%d\n", video_duration_ns))
end
ffmetadata:write(string.format("title=Chapter %d %s\n", i, chapter.title))
end
chapter_content:close()
ffmetadata:close()
log("Successfully created ffmetadata file: " .. ffmetadata_file)
end
function convert_mpv_to_youtube()
local video_path = mp.get_property("path")
local video_dir, video_name = utils.split_path(video_path)
local name_without_ext = string.gsub(video_name, "%.%w+$", "")
local chapter_file = utils.join_path(video_dir, name_without_ext .. ".chapter")
local ffmetadata_file = video_path .. ".ffmetadata"
-- 检查 .ffmetadata 文件是否存在
local ffmetadata_file_info = utils.file_info(ffmetadata_file)
if not ffmetadata_file_info then
mp.msg.warn("FFmetadata file not found: " .. ffmetadata_file)
return
end
-- 读取 .ffmetadata 文件
local ffmetadata_content = io.open(ffmetadata_file, "r")
if not ffmetadata_content then
mp.msg.error("Could not open ffmetadata file: " .. ffmetadata_file)
return
end
-- 创建 .chapter 文件
local chapter_content = io.open(chapter_file, "w")
if not chapter_content then
mp.msg.error("Could not create chapter file: " .. chapter_file)
ffmetadata_content:close()
return
end
-- 解析 .ffmetadata 文件并写入 .chapter 文件
local in_chapter_section = false
local chapter_start_time = 0
local chapter_title = ""
for line in ffmetadata_content:lines() do
if line:match("%[CHAPTER%]") then
in_chapter_section = true
elseif in_chapter_section then
local start_time_str = line:match("START=(%d+)")
local title_str = line:match("title=(.*)")
if start_time_str then
chapter_start_time = tonumber(start_time_str)
elseif title_str then
chapter_title = title_str:match("Chapter %d+ (.+)")
if chapter_title then
local formatted_start_time = format_time(chapter_start_time)
chapter_content:write(formatted_start_time .. " " .. chapter_title .. "\n")
end
in_chapter_section = false
end
end
end
ffmetadata_content:close()
chapter_content:close()
log("Successfully created chapter file: " .. chapter_file)
end
function format_time(nanoseconds)
local total_seconds = math.floor(nanoseconds / 1e9)
local hours = math.floor(total_seconds / 3600)
local minutes = math.floor((total_seconds % 3600) / 60)
local seconds = total_seconds % 60
local milliseconds = math.floor((nanoseconds % 1e9) / 1e6)
if hours > 0 then
return string.format("%d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds)
else
return string.format("%d:%02d.%03d", minutes, seconds, milliseconds)
end
end
mp.add_key_binding(nil, "youtube-to-mpv", convert_youtube_to_mpv)
mp.add_key_binding(nil, "mpv-to-youtube", convert_mpv_to_youtube)