Compare commits
3 Commits
dc80a4a4fe
...
95f77cf189
Author | SHA1 | Date | |
---|---|---|---|
|
95f77cf189 | ||
|
820d45d80d | ||
|
0b53777f39 |
@ -12,4 +12,6 @@ var (
|
||||
ErrAnimeListNotObtainable = adverr.NewErrTmpl("ErrAnimeListNotObtainable", "anime list from anilist.co not obtainable (reason: %s)")
|
||||
ErrTorrentNotObtainable = adverr.NewErrTmpl("ErrTorrentNotObtainable", "torrents from nyaa.si not obtainable (reason: %s)")
|
||||
ErrInvalidAnimeStatus = adverr.NewErrTmpl("ErrInvalidAnimeStatus", "invalid status '%s' in ANIME_STATUS (allowed: %s)")
|
||||
ErrInvalidGlobSyntax = adverr.NewErrTmpl("ErrInvalidGlobSyntax", "invalid filepath.Glob syntax: '%s'")
|
||||
ErrNoSuitableFileFound = adverr.NewErrTmpl("ErrNoSuitableFileFound", "no file found with essential properties for anime '%s' and episode %d")
|
||||
)
|
||||
|
25
file_filter.go
Normal file
25
file_filter.go
Normal file
@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.milar.in/milarin/slices"
|
||||
)
|
||||
|
||||
func HasFileEssentialProperties(props *FileProperties) bool {
|
||||
if props.Resolution < MinResolution || props.Resolution > MaxResolution {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, essentialLanguage := range EssentialLanguages {
|
||||
if !slices.Contains(props.Languages, essentialLanguage) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for _, essentialSubtitle := range EssentialSubtitles {
|
||||
if !slices.Contains(props.Subtitles, essentialSubtitle) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
17
file_priority.go
Normal file
17
file_priority.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
type FilePriority struct {
|
||||
Properties *FileProperties
|
||||
Priority int
|
||||
PreferredProperties map[string]int
|
||||
}
|
||||
|
||||
func NewFilePriority(props *FileProperties) *FilePriority {
|
||||
priority, preferredProperties := DeterminePriority(props)
|
||||
|
||||
return &FilePriority{
|
||||
Properties: props,
|
||||
Priority: priority,
|
||||
PreferredProperties: preferredProperties,
|
||||
}
|
||||
}
|
79
file_properties.go
Normal file
79
file_properties.go
Normal file
@ -0,0 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.milar.in/nyaanime/model"
|
||||
"git.milar.in/nyaanime/parsers"
|
||||
ffprobe "gopkg.in/vansante/go-ffprobe.v2"
|
||||
)
|
||||
|
||||
type FileProperties struct {
|
||||
Filepath string
|
||||
Languages []string
|
||||
Subtitles []string
|
||||
Resolution model.Resolution
|
||||
}
|
||||
|
||||
var _ model.PropertyHolder = &FileProperties{}
|
||||
|
||||
// TODO cache
|
||||
func AnalyzeFile(path string) (*FileProperties, error) {
|
||||
props := &FileProperties{Filepath: path}
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := ffprobe.ProbeReader(context.Background(), file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultVideoLang := ""
|
||||
for _, s := range data.StreamType(ffprobe.StreamVideo) {
|
||||
if s.Disposition.Default > 0 {
|
||||
props.Resolution = model.Resolution(s.Height)
|
||||
defaultVideoLang = parsers.ParseLanguage(s.Tags.Language)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range data.StreamType(ffprobe.StreamAudio) {
|
||||
if s.Tags.Language != "" {
|
||||
props.Languages = append(props.Languages, parsers.ParseLanguage(s.Tags.Language))
|
||||
} else if s.Disposition.Default > 0 {
|
||||
props.Languages = append(props.Languages, defaultVideoLang)
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range data.StreamType(ffprobe.StreamSubtitle) {
|
||||
if s.Tags.Language != "" {
|
||||
props.Subtitles = append(props.Subtitles, parsers.ParseLanguage(s.Tags.Language))
|
||||
} else if s.Disposition.Default > 0 {
|
||||
props.Subtitles = append(props.Subtitles, defaultVideoLang)
|
||||
}
|
||||
}
|
||||
|
||||
return props, nil
|
||||
}
|
||||
|
||||
func (fp *FileProperties) GetLanguages() []string {
|
||||
return fp.Languages
|
||||
}
|
||||
|
||||
func (fp *FileProperties) GetSubtitles() []string {
|
||||
return fp.Subtitles
|
||||
}
|
||||
|
||||
func (fp *FileProperties) GetResolution() model.Resolution {
|
||||
return fp.Resolution
|
||||
}
|
||||
|
||||
func (fp *FileProperties) String() string {
|
||||
return fmt.Sprintf("")
|
||||
}
|
5
go.mod
5
go.mod
@ -9,9 +9,10 @@ require (
|
||||
git.milar.in/milarin/envvars v1.0.3
|
||||
git.milar.in/milarin/gmath v0.0.2
|
||||
git.milar.in/milarin/slices v0.0.2
|
||||
git.milar.in/nyaanime/model v0.0.0-20220821124037-0a28c6b41556
|
||||
git.milar.in/nyaanime/parsers v0.0.0-20220815144327-52de61265e27
|
||||
git.milar.in/nyaanime/model v0.0.0-20220822093541-87208e95e7ac
|
||||
git.milar.in/nyaanime/parsers v0.0.0-20220822100125-2813a7868f6a
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
10
go.sum
10
go.sum
@ -10,14 +10,18 @@ git.milar.in/milarin/gmath v0.0.2 h1:avz+75f8XqAYA1wEB6kis0R5xvRuepBKTqBuJBjh6Yw
|
||||
git.milar.in/milarin/gmath v0.0.2/go.mod h1:HDLftG5RLpiNGKiIWh+O2G1PYkNzyLDADO8Cd/1abiE=
|
||||
git.milar.in/milarin/slices v0.0.2 h1:j92MuP0HWKSaHJMq/FRxDtSDIGiOTvw+KogUTwuulr0=
|
||||
git.milar.in/milarin/slices v0.0.2/go.mod h1:XRNfE99aNKeaPOY1phjOlpIQqeGCW1LOqqh8UHS+vAk=
|
||||
git.milar.in/nyaanime/model v0.0.0-20220821124037-0a28c6b41556 h1:RplYz4+CMK9ByI3ELusBltWFRlDs6VMOGk5EyENnLi0=
|
||||
git.milar.in/nyaanime/model v0.0.0-20220821124037-0a28c6b41556/go.mod h1:OzhQgj0b/Hf9fg8VXYxYt8ONQOvHm8xC44TmS9kQ150=
|
||||
git.milar.in/nyaanime/model v0.0.0-20220822093541-87208e95e7ac h1:rM5Mpo4/OJuZaBNZdylag+gi8giWVwDbqsoPjhDP9+g=
|
||||
git.milar.in/nyaanime/model v0.0.0-20220822093541-87208e95e7ac/go.mod h1:OzhQgj0b/Hf9fg8VXYxYt8ONQOvHm8xC44TmS9kQ150=
|
||||
git.milar.in/nyaanime/parsers v0.0.0-20220815144327-52de61265e27 h1:0+5j9MMJQS8+Luss19hD6hvNFxcBDRal2XwSUTyq7WU=
|
||||
git.milar.in/nyaanime/parsers v0.0.0-20220815144327-52de61265e27/go.mod h1:qm6fIFBFs90uz7IJ8RKgDir0K8Fa8isixGLgrtC6kgU=
|
||||
git.milar.in/nyaanime/parsers v0.0.0-20220822100125-2813a7868f6a h1:7vrKOL/vpqJ8YFZ9tmq9iPLoBuLnZgptHWaScyFOFFo=
|
||||
git.milar.in/nyaanime/parsers v0.0.0-20220822100125-2813a7868f6a/go.mod h1:qm6fIFBFs90uz7IJ8RKgDir0K8Fa8isixGLgrtC6kgU=
|
||||
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/vansante/go-ffprobe v1.1.0 h1:Tz5X+38tF8YYEFVz+PUTrtvlED35IorB7XI0USOqZWU=
|
||||
github.com/vansante/go-ffprobe v1.1.0/go.mod h1:AEIxsTWYTTeXpel90yu5J/QxuDWNaKCO50xRBN4rdac=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E=
|
||||
golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
@ -26,3 +30,5 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.1.0 h1:Gh8oVkvOSZG/DgEMmBw8h4oLvJhSQHROEGp3TaVGq08=
|
||||
gopkg.in/vansante/go-ffprobe.v2 v2.1.0/go.mod h1:qF0AlAjk7Nqzqf3y333Ly+KxN3cKF2JqA3JT5ZheUGE=
|
||||
|
87
local_file_check.go
Normal file
87
local_file_check.go
Normal file
@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.milar.in/milarin/anilist"
|
||||
"git.milar.in/nyaanime/model"
|
||||
)
|
||||
|
||||
type AnimePathPatternData struct {
|
||||
Title anilist.MediaTitle
|
||||
Episode int
|
||||
Ext string
|
||||
}
|
||||
|
||||
func GetAnimeEpFilepath(animeEp model.AnimeEpisode, ext string) string {
|
||||
tmplData := AnimePathPatternData{
|
||||
Title: animeEp.Anime.Title,
|
||||
Episode: animeEp.Episode,
|
||||
Ext: ext,
|
||||
}
|
||||
|
||||
b := new(strings.Builder)
|
||||
if err := AnimeEpFilepathPattern.Execute(b, tmplData); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func AnimeEpExistsLocally(animeEp model.AnimeEpisode) bool {
|
||||
animeEpPath := filepath.Join(AnimePath, GetAnimeEpFilepath(animeEp, "*"))
|
||||
|
||||
files, err := filepath.Glob(animeEpPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return len(files) > 0
|
||||
}
|
||||
|
||||
func GetLocalAnimeEpProperties(animeEp model.AnimeEpisode) (*FilePriority, bool, error) {
|
||||
animeEpPath := filepath.Join(AnimePath, GetAnimeEpFilepath(animeEp, "*"))
|
||||
files, err := filepath.Glob(animeEpPath)
|
||||
if err != nil {
|
||||
return nil, false, ErrInvalidGlobSyntax.Wrap(err, animeEpPath)
|
||||
}
|
||||
|
||||
var mostPrio *FilePriority
|
||||
|
||||
for _, file := range files {
|
||||
props, err := AnalyzeFile(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !HasFileEssentialProperties(props) {
|
||||
continue
|
||||
}
|
||||
|
||||
fp := NewFilePriority(props)
|
||||
|
||||
if mostPrio == nil || fp.Priority > mostPrio.Priority {
|
||||
mostPrio = fp
|
||||
}
|
||||
}
|
||||
|
||||
return mostPrio, mostPrio != nil, nil
|
||||
}
|
||||
|
||||
// TODO cache?
|
||||
func TorrentFileDownloading(torrent *model.ParsedTorrent) bool {
|
||||
torrentFile := filepath.Join(TorrentPath, fmt.Sprintf("%s.torrent", torrent.Torrent.ID))
|
||||
|
||||
if _, err := os.Stat(torrentFile); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
addedTorrentFile := filepath.Join(TorrentPath, fmt.Sprintf("%s.torrent.added", torrent.Torrent.ID))
|
||||
if _, err := os.Stat(addedTorrentFile); err == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
50
main.go
50
main.go
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
@ -21,6 +22,15 @@ var (
|
||||
StorageUser = envvars.String("STORAGE_USER", "")
|
||||
StoragePass = envvars.String("STORAGE_PASS", "")
|
||||
|
||||
TorrentPath = envvars.String("TORRENT_PATH", "")
|
||||
AnimePath = envvars.String("ANIME_PATH", "")
|
||||
|
||||
AnimeEpFilepathPattern = envvars.Object(
|
||||
"EPISODE_FILEPATH_PATTERN",
|
||||
template.Must(template.New("anime-episode-filepath-pattern").Parse(`{{.Title.UserPreferred}}/{{.Title.UserPreferred}} Episode {{.Episode}}.{{.Ext}}`)),
|
||||
template.New("anime-episode-filepath-pattern").Parse,
|
||||
)
|
||||
|
||||
AnimeStatus = envvars.ObjectSlice("ANIME_STATUS", ",", ParseMediaListStatus)
|
||||
|
||||
// essential torrent properties
|
||||
@ -57,9 +67,9 @@ var (
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("language priorites:", PreferredLanguages)
|
||||
fmt.Println("subtitle priorites:", PreferredSubtitles)
|
||||
fmt.Println("resolution priorites:", PreferredResolutions)
|
||||
fmt.Println("language priorites:", Map2Str(PreferredLanguages))
|
||||
fmt.Println("subtitle priorites:", Map2Str(PreferredSubtitles))
|
||||
fmt.Println("resolution priorites:", Map2Str(PreferredResolutions))
|
||||
|
||||
if len(AnimeStatus) == 0 {
|
||||
AnimeStatus = []anilist.MediaListStatus{
|
||||
@ -92,18 +102,44 @@ func checkTorrents() {
|
||||
return
|
||||
}
|
||||
|
||||
/*animes, err = GetAnimesToDownloadByAnimeID()
|
||||
animes, err := GetAnimesToDownloadByAnimeID()
|
||||
if err != nil {
|
||||
fmt.Println(adverr.Wrap("retrieving anime list failed", err))
|
||||
return
|
||||
}*/
|
||||
}
|
||||
|
||||
parsedTorrents := ParseTorrentsByAnimeEpSortedByProperties(torrents)
|
||||
|
||||
for animeEp, torrentPriorities := range parsedTorrents {
|
||||
fmt.Printf("\nanime: %s | episode: %d | torrents found: %d\n", animeEp.Anime, animeEp.Episode, len(torrentPriorities))
|
||||
_, animeOnList := animes[animeEp.Anime.ID]
|
||||
props, found, err := GetLocalAnimeEpProperties(animeEp)
|
||||
episodeInCollection := err == nil && found
|
||||
|
||||
fmt.Print(FormatFilePriority(animeEp, props, animeOnList, episodeInCollection, len(torrentPriorities)))
|
||||
|
||||
if !animeOnList {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, torrentPriority := range torrentPriorities {
|
||||
PrintTorrentPriority(torrentPriority)
|
||||
if TorrentFileDownloading(torrentPriority.ParsedTorrent) {
|
||||
fmt.Printf("%s | CURRENTLY DOWNLOADING\n", FormatTorrentPriority(torrentPriority))
|
||||
continue
|
||||
}
|
||||
|
||||
if episodeInCollection {
|
||||
if props.Priority > torrentPriority.Priority {
|
||||
fmt.Printf("%s | LOWER PRIORITY\n", FormatTorrentPriority(torrentPriority))
|
||||
} else {
|
||||
fmt.Printf("%s | HIGHER PRIORITY | STARTING DOWNLOAD\n", FormatTorrentPriority(torrentPriority))
|
||||
// TODO start download
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%s | NOT IN COLLECTION | STARTING DOWNLOAD\n", FormatTorrentPriority(torrentPriority))
|
||||
// TODO start download
|
||||
}
|
||||
|
||||
// TODO download anime episode with highest priority (first one in slice)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ func ParseTorrentsByAnimeEp(torrents []model.Torrent) map[model.AnimeEpisode][]*
|
||||
}
|
||||
|
||||
animeEpisode := model.AnimeEpisode{
|
||||
Anime: parsedTorrent.Anime.Title.Native,
|
||||
Anime: parsedTorrent.Anime,
|
||||
Episode: parsedTorrent.Episode,
|
||||
}
|
||||
|
||||
|
@ -19,26 +19,26 @@ func SortTorrentsByPreferredProperties(torrents map[model.AnimeEpisode][]*model.
|
||||
return torrentsWithPrio
|
||||
}
|
||||
|
||||
func DeterminePriority(torrent *model.ParsedTorrent) (priority int, preferredProperties map[string]int) {
|
||||
func DeterminePriority(props model.PropertyHolder) (priority int, preferredProperties map[string]int) {
|
||||
preferredProperties = map[string]int{}
|
||||
|
||||
for _, lang := range torrent.Languages {
|
||||
for _, lang := range props.GetLanguages() {
|
||||
if langPriority, ok := PreferredLanguages[lang]; ok {
|
||||
priority += langPriority
|
||||
preferredProperties["lang/"+lang] = langPriority
|
||||
}
|
||||
}
|
||||
|
||||
for _, sub := range torrent.Subtitles {
|
||||
for _, sub := range props.GetSubtitles() {
|
||||
if subPriority, ok := PreferredSubtitles[sub]; ok {
|
||||
priority += subPriority
|
||||
preferredProperties["sub/"+sub] = subPriority
|
||||
}
|
||||
}
|
||||
|
||||
if prefRes, ok := PreferredResolutions[torrent.Resolution]; ok {
|
||||
if prefRes, ok := PreferredResolutions[props.GetResolution()]; ok {
|
||||
priority += prefRes
|
||||
preferredProperties["res/"+torrent.Resolution.String()] = prefRes
|
||||
preferredProperties["res/"+props.GetResolution().String()] = prefRes
|
||||
}
|
||||
|
||||
return
|
||||
|
31
utils.go
31
utils.go
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"git.milar.in/milarin/anilist"
|
||||
"git.milar.in/milarin/slices"
|
||||
"git.milar.in/nyaanime/model"
|
||||
)
|
||||
|
||||
var AllMediaListStatuses = []anilist.MediaListStatus{
|
||||
@ -43,8 +44,8 @@ func Map2Str[K comparable, T any](m map[K]T) string {
|
||||
return str[:len(str)-1]
|
||||
}
|
||||
|
||||
func PrintTorrentPriority(torrentPriority *TorrentPriority) {
|
||||
fmt.Printf("id: %s | resolution: %d | languages: %s | subtitles: %s | seeders: %d | leechers: %d, | downloads: %d | trusted: %t | preferred properties: %s | priority: %d\n",
|
||||
func FormatTorrentPriority(torrentPriority *TorrentPriority) string {
|
||||
return fmt.Sprintf("id: %s | resolution: %d | languages: %s | subtitles: %s | seeders: %d | leechers: %d, | downloads: %d | trusted: %t | preferred properties: %s | priority: %d",
|
||||
torrentPriority.ParsedTorrent.Torrent.ID,
|
||||
torrentPriority.ParsedTorrent.Resolution,
|
||||
strings.Join(torrentPriority.ParsedTorrent.Languages, ","),
|
||||
@ -57,3 +58,29 @@ func PrintTorrentPriority(torrentPriority *TorrentPriority) {
|
||||
torrentPriority.Priority,
|
||||
)
|
||||
}
|
||||
|
||||
func FormatFilePriority(animeEp model.AnimeEpisode, props *FilePriority, onList, inCollection bool, torrentCount int) string {
|
||||
if !onList {
|
||||
return fmt.Sprintf("\nanime: %s | episode: %d | torrents found: %d | NOT ON LIST\n",
|
||||
animeEp.Anime.Title.Romaji,
|
||||
animeEp.Episode,
|
||||
torrentCount,
|
||||
)
|
||||
}
|
||||
|
||||
if inCollection {
|
||||
return fmt.Sprintf("\nanime: %s | episode: %d | torrents found: %d | IN COLLECTION | file properties: %s | file priority: %d\n",
|
||||
animeEp.Anime.Title.Romaji,
|
||||
animeEp.Episode,
|
||||
torrentCount,
|
||||
Map2Str(props.PreferredProperties),
|
||||
props.Priority,
|
||||
)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\nanime: %s | episode: %d | torrents found: %d\n",
|
||||
animeEp.Anime.Title.Romaji,
|
||||
animeEp.Episode,
|
||||
torrentCount,
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user