初始提交:短剧库源码(含转码优化 ultrafast)
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user