192 lines
5.5 KiB
Go
192 lines
5.5 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func (d *Downloader) fetchAllDramas(ctx context.Context, sourceFilter string) ([]Drama, error) {
|
|
if more, _ := ctx.Value(libraryMoreKey{}).(bool); more {
|
|
return d.fetchMoreLibrary(ctx, sourceFilter)
|
|
}
|
|
fmt.Println("正在获取剧库列表...")
|
|
type result struct {
|
|
name string
|
|
items []Drama
|
|
err error
|
|
}
|
|
jobs := []struct {
|
|
name string
|
|
timeout time.Duration
|
|
fn func(context.Context) ([]Drama, error)
|
|
}{
|
|
{name: "cloudfront", timeout: 10 * time.Minute, fn: d.fetchCloudFrontDramas},
|
|
{name: sourceHuangguoAI, timeout: 10 * time.Minute, fn: d.fetchHuangguoAIDramas},
|
|
{name: sourceHuangguoVideo, timeout: 10 * time.Minute, fn: d.fetchHuangguoVideoDramas},
|
|
{name: sourceHuangdou, timeout: 10 * time.Minute, fn: d.fetchHuangdouDramas},
|
|
{name: sourceHongguo, timeout: 10 * time.Minute, fn: d.fetchHongguoDramas},
|
|
}
|
|
selectedJobs := jobs[:0]
|
|
for _, job := range jobs {
|
|
if matchesSourceFilter(job.name, sourceFilter) && sourceAllowed(ctx, job.name) {
|
|
selectedJobs = append(selectedJobs, job)
|
|
}
|
|
}
|
|
jobs = selectedJobs
|
|
ch := make(chan result, len(jobs))
|
|
for _, job := range jobs {
|
|
job := job
|
|
go func() {
|
|
jobCtx, cancel := context.WithTimeout(ctx, job.timeout)
|
|
defer cancel()
|
|
items, err := job.fn(jobCtx)
|
|
ch <- result{name: job.name, items: items, err: err}
|
|
}()
|
|
}
|
|
seen := map[string]bool{}
|
|
var unique []Drama
|
|
failures := map[string]error{}
|
|
for range jobs {
|
|
res := <-ch
|
|
if len(res.items) == 0 && res.err == nil && !(res.name == sourceHongguo && hongguoCatalogInitialized(d.hongguoCatalogSnapshot())) {
|
|
res.err = errors.New("未返回可识别的视频数据")
|
|
}
|
|
if res.err != nil {
|
|
msg := fmt.Sprintf("%s 获取失败: %v", res.name, publicError(res.err))
|
|
fmt.Printf("%s\n", msg)
|
|
failures[res.name] = res.err
|
|
}
|
|
reportLibraryProgress(ctx, res.name, res.items, res.err, true)
|
|
for _, item := range res.items {
|
|
if item.ID == "" || seen[item.ID] {
|
|
continue
|
|
}
|
|
seen[item.ID] = true
|
|
unique = append(unique, item)
|
|
}
|
|
}
|
|
sort.SliceStable(unique, func(i, j int) bool { return unique[i].DisplayTitle() < unique[j].DisplayTitle() })
|
|
fmt.Printf("本次获取剧库:%d 部,结果将合并到本地缓存\n", len(unique))
|
|
if len(failures) > 0 {
|
|
return unique, &libraryLoadError{failures: failures}
|
|
}
|
|
return unique, nil
|
|
}
|
|
|
|
func (d *Downloader) GetDramaChapters(ctx context.Context, seriesID string) (string, []Chapter, error) {
|
|
if source, sourceID, ok := splitProviderDramaID(seriesID); ok {
|
|
return d.GetHuangguoChapters(ctx, source, sourceID)
|
|
}
|
|
var detail detailResponse
|
|
if err := d.fetchAPI(ctx, "/api/app/playlet/detail/"+url.PathEscape(seriesID), nil, &detail); err != nil {
|
|
return "", nil, err
|
|
}
|
|
chapters := detail.Chapters
|
|
if len(chapters) == 0 {
|
|
var list []Chapter
|
|
if err := d.fetchAPI(ctx, "/api/app/playlet-chapter/list/"+url.PathEscape(seriesID), nil, &list); err != nil {
|
|
return "", nil, err
|
|
}
|
|
chapters = list
|
|
}
|
|
title := detail.Title
|
|
if title == "" {
|
|
title = detail.Name
|
|
}
|
|
if title == "" {
|
|
title = "短剧"
|
|
}
|
|
return title, uniqueChapters(chapters), nil
|
|
}
|
|
|
|
func uniqueChapters(chapters []Chapter) []Chapter {
|
|
seen := map[string]bool{}
|
|
var out []Chapter
|
|
for i, ch := range chapters {
|
|
key := ch.ID
|
|
if key == "" {
|
|
key = ch.VideoURL
|
|
}
|
|
if key == "" {
|
|
key = fmt.Sprintf("idx_%d", i)
|
|
}
|
|
if seen[key] {
|
|
continue
|
|
}
|
|
seen[key] = true
|
|
out = append(out, ch)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (d *Downloader) BuildDramaTasks(ctx context.Context, drama Drama) ([]Task, error) {
|
|
return d.buildDramaTasksInDirectory(ctx, drama, "")
|
|
}
|
|
|
|
func (d *Downloader) buildDramaTasksInDirectory(ctx context.Context, drama Drama, existingDirectory string) ([]Task, error) {
|
|
var title string
|
|
var chapters []Chapter
|
|
var err error
|
|
if isHuangguoProviderSource(drama.Source) {
|
|
sourceID := strings.TrimSpace(drama.SourceID)
|
|
if sourceID == "" {
|
|
if _, parsedID, ok := splitProviderDramaID(drama.ID); ok {
|
|
sourceID = parsedID
|
|
} else {
|
|
sourceID = strings.TrimSpace(drama.ID)
|
|
}
|
|
}
|
|
title, chapters, err = d.GetHuangguoChapters(ctx, drama.Source, sourceID)
|
|
} else {
|
|
title, chapters, err = d.GetDramaChapters(ctx, drama.ID)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(chapters) == 0 {
|
|
return nil, nil
|
|
}
|
|
drama = d.hongguoCachedDrama(drama)
|
|
safeDrama := safeFilename(title)
|
|
dramaDir, err := safeJoin(d.cfg.OutputDir, safeDrama)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existingDirectory != "" {
|
|
dramaDir = existingDirectory
|
|
}
|
|
markerPath := filepath.Join(dramaDir, ".drama-id")
|
|
if b, readErr := os.ReadFile(markerPath); readErr == nil && strings.TrimSpace(string(b)) != "" && strings.TrimSpace(string(b)) != drama.ID {
|
|
dramaDir, err = safeJoin(d.cfg.OutputDir, safeFilename(fmt.Sprintf("%s_%s", title, hashShort(drama.ID))))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
markerPath = filepath.Join(dramaDir, ".drama-id")
|
|
}
|
|
if err := os.MkdirAll(dramaDir, 0o755); err != nil {
|
|
return nil, err
|
|
}
|
|
if drama.ID != "" {
|
|
_ = os.WriteFile(markerPath, []byte(drama.ID), 0o644)
|
|
}
|
|
var tasks []Task
|
|
for i, ch := range chapters {
|
|
ep := ch.EpisodeString(i + 1)
|
|
epNum := padEpisode(ep)
|
|
fileName := safeFilename(epNum + ".mp4")
|
|
outPath, err := safeJoin(dramaDir, fileName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tasks = append(tasks, Task{DramaID: drama.ID, DramaTitle: title, Chapter: ch, Index: i + 1, Total: len(chapters), OutPath: outPath, ReleaseStatus: dramaReleaseStatus(drama)})
|
|
}
|
|
return tasks, nil
|
|
}
|