同步上游 zip:合并任务后台化 + 合并整剧可直接播放;保留 ultrafast+4线程 转码优化与 .gitignore 本地规则

This commit is contained in:
yvan
2026-09-18 10:16:15 +08:00
parent 80485e9dba
commit b96363d787
21 changed files with 1173 additions and 172 deletions
+44 -26
View File
@@ -67,6 +67,8 @@ type UIApp struct {
address string
runningCancels map[string]context.CancelFunc
mergeMu sync.Mutex
mergeJobs map[string]*uiMergeJob
mergeStopped bool
parsingInFlight map[string]bool
parsingCancels map[string]context.CancelFunc
closed bool
@@ -103,6 +105,7 @@ type uiState struct {
}
type UIMergeState struct {
PlaybackTaskID string `json:"playbackTaskId,omitempty"`
DramaID string `json:"dramaId"`
DramaTitle string `json:"dramaTitle,omitempty"`
Status string `json:"status"`
@@ -240,6 +243,7 @@ func (a *UIApp) ListenAndServe(addr string) error {
a.mu.Unlock()
defer a.closePlaybacks()
defer a.stopMergeJobs()
server := &http.Server{Addr: addr, Handler: a.routes(), ReadHeaderTimeout: 10 * time.Second}
return server.ListenAndServe()
@@ -277,6 +281,7 @@ func (a *UIApp) routes() http.Handler {
mux.HandleFunc("/api/ui/tasks/resume", a.handleResume)
mux.HandleFunc("/api/ui/tasks/clear", a.handleClear)
mux.HandleFunc("/api/ui/merge", a.handleMerge)
mux.HandleFunc("/api/ui/merge/cancel", a.handleMergeCancel)
mux.HandleFunc("/api/ui/ffmpeg", a.handleFFmpeg)
mux.HandleFunc("/api/ui/config", a.handleConfig)
mux.HandleFunc("/api/ui/network/check", a.handleNetworkCheck)
@@ -529,6 +534,12 @@ func (a *UIApp) loadState() {
}
seen := map[string]bool{}
changed := false
for _, state := range a.merges {
if state != nil && (state.Status == "running" || state.Status == "queued") {
state.Status, state.Detail, state.Error = "failed", "", "服务已重启,合并已中断;原分集已保留,可重新合并"
changed = true
}
}
for _, task := range state.Tasks {
if task == nil || task.RemoveRequested {
changed = true
@@ -1372,6 +1383,11 @@ func (a *UIApp) handleTasks(w http.ResponseWriter, r *http.Request) {
for id := range merges {
if !dramaAllowed(r.Context(), id, "") {
delete(merges, id)
continue
}
merges[id].PlaybackTaskID = ""
if _, _, err := a.mergedPlaybackTaskLocked(mergedPlaybackPrefix + id); err == nil {
merges[id].PlaybackTaskID = mergedPlaybackPrefix + id
}
}
a.mu.Unlock()
@@ -1548,37 +1564,33 @@ func (a *UIApp) handleMerge(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusMethodNotAllowed, map[string]string{"error": "method not allowed"})
return
}
ids, deleteEpisodes, ok := readMergeRequest(w, r)
if !ok {
ids, deleteEpisodes, async, ok := readMergeRequest(w, r)
if !ok || !requireDownload(w, r) || !a.requireDramaSources(w, r, ids) {
return
}
if !a.requireDramaSources(w, r, ids) {
return
}
a.mergeMu.Lock()
defer a.mergeMu.Unlock()
a.mu.Lock()
if !a.requireTaskSourcesLocked(w, r, taskSelection{DramaIDs: ids}) {
a.mu.Unlock()
return
}
byDrama := map[string][]*UITask{}
for _, id := range a.taskOrder {
task := a.tasks[id]
if task == nil || task.Status != uiStatusSuccess {
continue
}
byDrama[task.DramaID] = append(byDrama[task.DramaID], cloneUITask(task))
}
jobs, err := a.enqueueMergesLocked(ids, deleteEpisodes)
a.mu.Unlock()
results := make([]uiMergeResult, 0, len(ids))
for _, dramaID := range ids {
items := byDrama[dramaID]
res := a.mergeDrama(r.Context(), dramaID, items, deleteEpisodes)
results = append(results, res)
if err != nil {
writeJSON(w, http.StatusConflict, map[string]string{"error": a.redactError(err)})
return
}
if async {
writeJSON(w, http.StatusAccepted, map[string]any{"accepted": len(jobs), "async": true})
return
}
results := make([]uiMergeResult, 0, len(jobs))
for _, job := range jobs {
select {
case <-r.Context().Done():
return
case <-job.done:
results = append(results, job.result)
}
}
writeJSON(w, http.StatusOK, map[string]any{"data": results})
}
@@ -1673,6 +1685,11 @@ func (a *UIApp) mergeDrama(ctx context.Context, dramaID string, tasks []*UITask,
return res
}
res.Detail = method
if err := cmdCtx.Err(); err != nil {
res.Error = "合并已取消,原分集已保留"
a.setMergeState(res, "failed", 0, false, deleteEpisodes)
return res
}
if ok, _ := existingGood(partPath, a.cfg.SkipBytes); !ok {
res.Error = "合并输出文件无效"
a.setMergeState(res, "failed", 0, false, deleteEpisodes)
@@ -1731,8 +1748,9 @@ func (a *UIApp) setMergeState(res uiMergeResult, status string, progress int, sk
a.mu.Unlock()
}
func readMergeRequest(w http.ResponseWriter, r *http.Request) ([]string, bool, bool) {
func readMergeRequest(w http.ResponseWriter, r *http.Request) ([]string, bool, bool, bool) {
var req struct {
Async bool `json:"async"`
DramaIDs []string `json:"dramaIds"`
DeleteEpisodes bool `json:"deleteEpisodes"`
}
@@ -1741,10 +1759,10 @@ func readMergeRequest(w http.ResponseWriter, r *http.Request) ([]string, bool, b
dec.DisallowUnknownFields()
if err := dec.Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid json: " + err.Error()})
return nil, false, false
return nil, false, false, false
}
ids, ok := cleanIDList(w, req.DramaIDs)
return ids, req.DeleteEpisodes, ok
return ids, req.DeleteEpisodes, req.Async, ok
}
func (a *UIApp) handleConfig(w http.ResponseWriter, r *http.Request) {