feat: support for torrent with multi episodes

This commit is contained in:
Simon Ding
2024-11-15 15:43:07 +08:00
parent c433ccaa0e
commit fbfee65a50
14 changed files with 649 additions and 91 deletions

View File

@@ -1,6 +1,7 @@
package core
import (
"bytes"
"fmt"
"polaris/ent"
"polaris/ent/episode"
@@ -14,7 +15,7 @@ import (
"github.com/pkg/errors"
)
func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum, episodeNum int) (*string, error) {
func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum int, episodeNums ...int) (*string, error) {
trc, dlc, err := c.GetDownloadClient()
if err != nil {
return nil, errors.Wrap(err, "connect transmission")
@@ -32,19 +33,6 @@ func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum,
return nil, errors.New("no enough space")
}
var ep *ent.Episode
if episodeNum > 0 {
for _, e := range series.Episodes {
if e.SeasonNumber == seasonNum && e.EpisodeNumber == episodeNum {
ep = e
}
}
if ep == nil {
return nil, errors.Errorf("no episode of season %d episode %d", seasonNum, episodeNum)
}
} else { //season package download
ep = &ent.Episode{}
}
magnet, err := utils.Link2Magnet(r1.Link)
if err != nil {
return nil, errors.Errorf("converting link to magnet error, link: %v, error: %v", r1.Link, err)
@@ -58,9 +46,31 @@ func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum,
dir := fmt.Sprintf("%s/Season %02d/", series.TargetDir, seasonNum)
if len(episodeNums) > 0 {
for _, epNum := range episodeNums {
var ep *ent.Episode
for _, e := range series.Episodes {
if e.SeasonNumber == seasonNum && e.EpisodeNumber == epNum {
ep = e
}
}
if ep == nil {
return nil, errors.Errorf("no episode of season %d episode %d", seasonNum, epNum)
}
if ep.Status == episode.StatusMissing {
c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
}
}
} else { //season package download
c.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading)
}
history, err := c.db.SaveHistoryRecord(ent.History{
MediaID: seriesId,
EpisodeID: ep.ID,
EpisodeNums: episodeNums,
SeasonNum: seasonNum,
SourceTitle: r1.Name,
TargetDir: dir,
Status: history.StatusRunning,
@@ -73,72 +83,112 @@ func (c *Client) DownloadEpisodeTorrent(r1 torznab.Result, seriesId, seasonNum,
if err != nil {
return nil, errors.Wrap(err, "save record")
}
if episodeNum > 0 {
if ep.Status == episode.StatusMissing {
c.db.SetEpisodeStatus(ep.ID, episode.StatusDownloading)
}
} else {
c.db.SetSeasonAllEpisodeStatus(seriesId, seasonNum, episode.StatusDownloading)
}
c.tasks[history.ID] = &Task{Torrent: torrent}
c.sendMsg(fmt.Sprintf(message.BeginDownload, r1.Name))
name := r1.Name
if len(episodeNums) > 0 {
buff := &bytes.Buffer{}
for i, ep := range episodeNums {
if i != 0 {
buff.WriteString(",")
}
buff.WriteString(fmt.Sprint(ep))
}
name = fmt.Sprintf("第%s集 (%s)", buff.String(), name)
} else {
name = fmt.Sprintf("全集 (%s)", name)
}
c.sendMsg(fmt.Sprintf(message.BeginDownload, name))
log.Infof("success add %s to download task", r1.Name)
return &r1.Name, nil
}
func (c *Client) SearchAndDownload(seriesId, seasonNum, episodeNum int) (*string, error) {
var episodes []int
if episodeNum > 0 {
episodes = append(episodes, episodeNum)
/*
tmdb 校验获取的资源名如果用资源名在tmdb搜索出来的结果能匹配上想要的资源则认为资源有效否则无效
解决名称过于简单的影视会匹配过多资源的问题, 例如:梦魇绝镇 FROM
*/
func (c *Client) checkBtReourceWithTmdb(r *torznab.Result, seriesId int) bool {
m := metadata.ParseTv(r.Name)
se, err := c.MustTMDB().SearchMedia(m.NameEn, "", 1)
if err != nil {
log.Warnf("tmdb search error, consider this torrent ok: ", err)
return true
} else {
if len(se.Results) == 0 {
log.Debugf("tmdb search no result, consider this torrent ok: %s", r.Name) //because tv name parse is not accurate
return true
}
series := c.db.GetMediaDetails(seriesId)
se0 := se.Results[0]
if se0.ID != int64(series.TmdbID) {
log.Warnf("bt reosurce name not match tmdb id: %s", r.Name)
return false
} else { //resource tmdb id match
return true
}
}
}
func (c *Client) SearchAndDownload(seriesId, seasonNum int, episodeNums ...int) ([]string, error) {
res, err := SearchTvSeries(c.db, &SearchParam{
MediaId: seriesId,
SeasonNum: seasonNum,
Episodes: episodes,
Episodes: episodeNums,
CheckFileSize: true,
CheckResolution: true,
})
if err != nil {
return nil, err
}
/*
tmdb 校验获取的资源名如果用资源名在tmdb搜索出来的结果能匹配上想要的资源则认为资源有效否则无效
解决名称过于简单的影视会匹配过多资源的问题, 例如:梦魇绝镇 FROM
*/
var r1 *torznab.Result
for _, r := range res { //
wanted := make(map[int]bool, len(episodeNums))
for _, ep := range episodeNums {
wanted[ep] = true
}
var torrentNames []string
lo:
for _, r := range res {
if !c.checkBtReourceWithTmdb(&r, seriesId) {
continue
}
m := metadata.ParseTv(r.Name)
se, err := c.MustTMDB().SearchMedia(m.NameEn, "", 1)
if err != nil {
log.Warnf("tmdb search error, consider this torrent ok: ", err)
r1 = &r
break
} else {
if len(se.Results) == 0 {
log.Debugf("tmdb search no result, consider this torrent ok: %s", r.Name) //because tv name parse is not accurate
r1 = &r
break
m.ParseExtraDescription(r.Description)
if len(episodeNums) == 0 { //want season pack
if m.IsSeasonPack {
name, err := c.DownloadEpisodeTorrent(r, seriesId, seasonNum)
if err != nil {
return nil, err
}
torrentNames = append(torrentNames, *name)
}
series := c.db.GetMediaDetails(seriesId)
} else {
torrentEpisodes := make([]int, 0)
for i := m.StartEpisode; i <= m.EndEpisode; i++ {
if !wanted[i] { //torrent has episode not wanted
continue lo
}
torrentEpisodes = append(torrentEpisodes, i)
}
name, err := c.DownloadEpisodeTorrent(r, seriesId, seasonNum, torrentEpisodes...)
if err != nil {
return nil, err
}
torrentNames = append(torrentNames, *name)
se0 := se.Results[0]
if se0.ID != int64(series.TmdbID) {
log.Warnf("bt reosurce name not match tmdb id: %s", r.Name)
continue
} else { //resource tmdb id match
r1 = &r
for _, num := range torrentEpisodes {
delete(wanted, num) //delete downloaded episode from wanted
}
}
}
if r1 != nil {
log.Infof("found resource to download: %+v", r1)
return c.DownloadEpisodeTorrent(*r1, seriesId, seasonNum, episodeNum)
} else {
return nil, errors.Errorf("no resource")
if len(wanted) > 0 {
log.Warnf("still wanted but not downloaded episodes: %v", wanted)
}
return torrentNames, nil
}
func (c *Client) DownloadMovie(m *ent.Media, link, name string, size int, indexerID int) (*string, error) {

View File

@@ -115,6 +115,38 @@ func (c *Client) postTaskProcessing(id int) {
}
}
func getSeasonNum(h *ent.History) int {
if h.SeasonNum != 0 {
return h.SeasonNum
}
seasonNum, err := utils.SeasonId(h.TargetDir)
if err != nil {
log.Errorf("no season id: %v", h.TargetDir)
seasonNum = -1
}
return seasonNum
}
func (c *Client) GetEpisodeIds(r *ent.History) []int {
var episodeIds []int
seasonNum := getSeasonNum(r)
if r.EpisodeID > 0 {
episodeIds = append(episodeIds, r.EpisodeID)
}
if len(r.EpisodeNums) > 0 {
series := c.db.GetMediaDetails(r.MediaID)
for _, epNum := range r.EpisodeNums {
for _, ep := range series.Episodes {
if ep.SeasonNumber == seasonNum && ep.EpisodeNumber == epNum {
episodeIds = append(episodeIds, ep.ID)
}
}
}
}
return episodeIds
}
func (c *Client) moveCompletedTask(id int) (err1 error) {
torrent := c.tasks[id]
r := c.db.GetHistory(id)
@@ -123,11 +155,7 @@ func (c *Client) moveCompletedTask(id int) (err1 error) {
return nil
}
c.db.SetHistoryStatus(r.ID, history.StatusUploading)
seasonNum, err := utils.SeasonId(r.TargetDir)
if err != nil {
log.Errorf("no season id: %v", r.TargetDir)
seasonNum = -1
}
downloadclient, err := c.db.GetDownloadClient(r.DownloadClientID)
if err != nil {
log.Errorf("get task download client error: %v, use default one", err)
@@ -138,13 +166,19 @@ func (c *Client) moveCompletedTask(id int) (err1 error) {
return err
}
seasonNum := getSeasonNum(r)
episodeIds := c.GetEpisodeIds(r)
defer func() {
if err1 != nil {
c.db.SetHistoryStatus(r.ID, history.StatusFail)
if r.EpisodeID != 0 {
if !c.db.IsEpisodeDownloadingOrDownloaded(r.EpisodeID) {
c.db.SetEpisodeStatus(r.EpisodeID, episode.StatusMissing)
if len(episodeIds) > 0 {
for _, id := range episodeIds {
if !c.db.IsEpisodeDownloadingOrDownloaded(id) {
c.db.SetEpisodeStatus(id, episode.StatusMissing)
}
}
} else {
c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusMissing)
@@ -175,8 +209,10 @@ func (c *Client) moveCompletedTask(id int) (err1 error) {
}
c.db.SetHistoryStatus(r.ID, history.StatusSeeding)
if r.EpisodeID != 0 {
c.db.SetEpisodeStatus(r.EpisodeID, episode.StatusDownloaded)
if len(episodeIds) > 0 {
for _, id := range episodeIds {
c.db.SetEpisodeStatus(id, episode.StatusDownloaded)
}
} else {
c.db.SetSeasonAllEpisodeStatus(r.MediaID, seasonNum, episode.StatusDownloaded)
}
@@ -286,10 +322,10 @@ func (c *Client) DownloadSeriesAllEpisodes(id int) []string {
}
}
if wantedSeasonPack {
name, err := c.SearchAndDownload(id, seasonNum, -1)
names, err := c.SearchAndDownload(id, seasonNum)
if err == nil {
allNames = append(allNames, *name)
log.Infof("begin download torrent resource: %v", name)
allNames = append(allNames, names...)
log.Infof("begin download torrent resource: %v", names)
} else {
log.Warnf("finding season pack error: %v", err)
wantedSeasonPack = false
@@ -297,6 +333,7 @@ func (c *Client) DownloadSeriesAllEpisodes(id int) []string {
}
if !wantedSeasonPack {
seasonEpisodesWanted := make(map[int][]int, 0)
for _, ep := range epsides {
if !ep.Monitored {
continue
@@ -317,14 +354,16 @@ func (c *Client) DownloadSeriesAllEpisodes(id int) []string {
}
}
name, err := c.SearchAndDownload(id, ep.SeasonNumber, ep.EpisodeNumber)
seasonEpisodesWanted[ep.SeasonNumber] = append(seasonEpisodesWanted[ep.SeasonNumber], ep.EpisodeNumber)
}
for se, eps := range seasonEpisodesWanted {
names, err := c.SearchAndDownload(id, se, eps...)
if err != nil {
log.Warnf("finding resoruces of season %d episode %d error: %v", ep.SeasonNumber, ep.EpisodeNumber, err)
log.Warnf("finding resoruces of season %d episode %v error: %v", se, eps, err)
continue
} else {
allNames = append(allNames, *name)
log.Infof("begin download torrent resource: %v", name)
allNames = append(allNames, names...)
log.Infof("begin download torrent resource: %v", names)
}
}

View File

@@ -40,16 +40,8 @@ lo:
for _, r := range res {
//log.Infof("torrent resource: %+v", r)
meta := metadata.ParseTv(r.Name)
meta.ParseExtraDescription(r.Description)
if meta.IsSeasonPack { //try to parse episode number with description
mm := metadata.ParseTv(r.Description)
if mm.StartEpisode > 0 { //sometimes they put episode info in desc text
meta.IsSeasonPack = false
meta.StartEpisode = mm.StartEpisode
meta.EndEpisode = mm.EndEpisode
}
}
if isImdbidNotMatch(series.ImdbID, r.ImdbId) { //has imdb id and not match
continue
}
@@ -70,6 +62,9 @@ lo:
}
if len(param.Episodes) > 0 { //not season pack, but episode number not equal
if meta.StartEpisode <= 0 {
continue lo
}
for i := meta.StartEpisode; i <= meta.EndEpisode; i++ {
if !slices.Contains(param.Episodes, i) {
continue lo

View File

@@ -120,7 +120,7 @@ func (s *Server) SearchTvAndDownload(c *gin.Context) (interface{}, error) {
if err != nil {
return nil, errors.Wrap(err, "download")
}
name = *name1
name = name1[0]
}
return gin.H{