Compare commits

...

10 Commits

Author SHA1 Message Date
lordwelch
d8f8588dc4 Change references to actually separate the command from the lib
Move the properties for sorting to the lib
Add some basic checks on files that are loaded to preven segfault
2018-02-04 22:22:24 -08:00
lordwelch
0c72259da4 Move to seperate package for the downloader and add a simple test command 2018-02-04 22:19:22 -08:00
lordwelch
ea1b39f4b7 Fix not adding new torrents 2018-01-21 10:10:33 -07:00
lordwelch
7be6edcc45 Bettor sort
Use absolute paths
Cleaned up stopping the torrents
Start the torrents that are selected that are below 1.0
Fix problem with duplicate links
2018-01-17 14:27:52 -07:00
lordwelch
8bf4f5bf5c Fix hash detection on download
Start adding a score function for sorting
2018-01-13 15:03:46 -07:00
lordwelch
3acdb54b62 Better error handling for transmission
Remove unnessesary removal of links
Reduce stdout spam
2018-01-11 15:54:03 -08:00
lordwelch
56fdaef90e Basic error checking for transmission
Added host option for transmission
2018-01-09 23:52:45 -07:00
lordwelch
7cd9ab2904 Forgot period on extension
Sorts by resolution before release
2018-01-08 23:25:45 -07:00
lordwelch
4e30dd5404 Add credentials
Sort mkv files to top
Set seed ratio
2018-01-06 23:33:08 -07:00
lordwelch
a5bf6ae19f Switch to stopping torrents instead of removing 2018-01-06 22:25:39 -07:00
4 changed files with 463 additions and 348 deletions

77
cmd/main.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"text/tabwriter"
"github.com/alexflint/go-arg"
tf "timmy.narnian.us/git/timmy/TorrentFilter"
"timmy.narnian.us/git/timmy/scene"
)
var (
args struct {
RES string `arg:"help:Resolution preference [480/720/1080]"`
RELEASE []string `arg:"-r,help:Release group preference order."`
NRELEASE []string `arg:"-R,help:Release groups to use only as a lost resort."`
TAGS []string `arg:"-t,help:Tags to prefer -t internal would choose an internal over another. Whichever file with the most tags is chosen. Release Group takes priority"`
}
)
func main() {
args.RES = "-1"
arg.MustParse(&args)
RES, _ := strconv.Atoi(args.RES)
tf.Res = scene.Res(RES)
for i, v := range args.RELEASE {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
tf.Release[v] = i + 1
}
for i, v := range args.NRELEASE {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
tf.Release[v] = (i + 10) * -1
}
for i, v := range args.TAGS {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
tf.Tags[v] = i + 1
}
tf.Tags["nuked"] = -99999
scanner := bufio.NewScanner(os.Stdin)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
for scanner.Scan() {
url := strings.TrimSpace(scanner.Text())
torrent := process(url)
fmt.Fprintf(w, "title: %s\t Score: %d\t\n", torrent.Name, torrent.Score())
}
w.Flush()
}
func process(torrentFile string) tf.SceneVideoTorrent {
var (
mt = new(tf.MetaTorrent)
vt = new(tf.SceneVideoTorrent)
)
f, _ := os.OpenFile(torrentFile, os.O_RDONLY, 755)
defer f.Close()
mt.ReadFile(f)
vt.Torrent = tf.NewTorrent(*mt)
vt.Parse(strings.TrimSuffix(vt.Name, filepath.Ext(vt.Name)))
//fmt.Println(vt.Original)
//fmt.Println(vt)
return *vt
}

341
download/main.go Normal file
View File

@ -0,0 +1,341 @@
package main
import (
"bufio"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
tf "timmy.narnian.us/git/timmy/TorrentFilter"
"timmy.narnian.us/git/timmy/scene"
"github.com/alexflint/go-arg"
"github.com/lordwelch/transmission"
)
var (
CurrentTorrents map[string]*tf.SeriesTorrent
unselectedDir string
Transmission *transmission.Client
CurrentHashes []string
args struct {
RES string `arg:"help:Resolution preference [480/720/1080]"`
RELEASE []string `arg:"-r,help:Release group preference order."`
NRELEASE []string `arg:"-R,help:Release groups to use only as a lost resort."`
TAGS []string `arg:"-t,help:Tags to prefer -t internal would choose an internal over another. Whichever file with the most tags is chosen. Release Group takes priority"`
Series []string `arg:"required,positional,help:TV series to download torrent file for"`
NEW bool `arg:"-n,help:Only modify new torrents"`
PATH string `arg:"-P,required,help:Path to torrent files"`
HOST string `arg:"-H,help:Host for transmission"`
}
)
func main() {
var (
stdC = make(chan tf.SceneVideoTorrent)
)
initialize()
go stdinLoop(stdC)
for i := 0; true; i++ {
fmt.Println(i)
select {
case TIME := <-time.After(time.Second):
fmt.Println(TIME)
download()
case current := <-stdC:
CurrentTorrents[current.Title].Addtorrent(current)
}
}
}
func stdinLoop(C chan tf.SceneVideoTorrent) {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
url := strings.TrimSpace(scanner.Text())
fmt.Println("url:", url)
torrentName := filepath.Base(url)
torrentPath := filepath.Join(unselectedDir, torrentName)
_, err := os.Stat(torrentPath)
if !os.IsNotExist(err) {
fmt.Println(err)
continue
}
cmd := exec.Command("wget", url, "-t", "5", "--waitretry=5", "-q", "-O", torrentPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Println("url failed: ", url)
fmt.Println(err)
continue
}
if filepath.Ext(torrentPath) == ".torrent" {
current := process(torrentPath)
for _, title := range args.Series {
if current.Title == title {
C <- current
break
}
}
}
fmt.Println("Torrent:", torrentName)
}
}
// Get hashes of torrents that were previously selected then remove the link to them
func getLinks() (hash []string) {
// get files in selected dir
selectedFolder, _ := os.Open(args.PATH)
defer selectedFolder.Close()
selectedNames, _ := selectedFolder.Readdirnames(0)
sort.Strings(selectedNames)
// Add hashes of currently selected torrents
for _, lnk := range selectedNames {
target, err := os.Readlink(filepath.Join(args.PATH, lnk))
if err == nil {
if filepath.Dir(target) == unselectedDir {
used := false
fakeTorrent := process(target)
for _, v := range args.Series {
if fakeTorrent.Title == v && len((*CurrentTorrents[fakeTorrent.Title])[fakeTorrent.Season][fakeTorrent.Episode]) > 0 {
used = true
break
}
}
if used {
realTorrent := (*CurrentTorrents[fakeTorrent.Title])[fakeTorrent.Season][fakeTorrent.Episode][0]
if realTorrent.Meta.Hash != fakeTorrent.Meta.Hash {
fmt.Printf("Better file found for: %s S%sE%s\n", realTorrent.Title, realTorrent.Season, realTorrent.Episode)
err = os.Remove(filepath.Join(args.PATH, filepath.Base(fakeTorrent.Meta.FilePath)))
os.Symlink(realTorrent.Meta.FilePath, filepath.Join(args.PATH, filepath.Base(realTorrent.Meta.FilePath)))
hash = append(hash, fakeTorrent.Meta.Hash)
}
}
}
}
}
return
}
func download() {
var (
run bool = true
err error
)
hash := getLinks()
CurrentHashes = make([]string, 0, len(CurrentHashes))
for _, s := range CurrentTorrents {
for _, se := range *s {
for _, ep := range se {
_, err = os.Open(filepath.Join(args.PATH, filepath.Base(ep[0].Meta.FilePath)))
if os.IsNotExist(err) {
os.Remove(filepath.Join(args.PATH, filepath.Base(ep[0].Meta.FilePath)))
err = os.Symlink(ep[0].Meta.FilePath, filepath.Join(args.PATH, filepath.Base(ep[0].Meta.FilePath)))
if err != nil {
fmt.Println(err)
}
fmt.Printf("File found for: %s S%sE%s\n", ep[0].Title, ep[0].Season, ep[0].Episode)
}
CurrentHashes = append(CurrentHashes, ep[0].Meta.Hash)
}
}
}
if Transmission != nil {
stopDownloads(hash)
time.Sleep(time.Second * 30)
tmap, _ := Transmission.GetTorrentMap()
if err != nil {
run = false
if timeoutErr, ok := err.(net.Error); ok && timeoutErr.Timeout() {
tmap, err = Transmission.GetTorrentMap()
if err != nil {
run = true
}
} else {
Transmission = nil
}
}
if run {
for _, s := range CurrentTorrents {
for _, se := range *s {
for _, ep := range se {
v, ok := tmap[ep[0].Meta.Hash]
if ok {
v.Set(transmission.SetTorrentArg{
SeedRatioMode: 1,
SeedRatioLimit: 1.0,
})
}
}
}
}
}
}
}
func stopDownloads(hash []string) {
var (
run bool = true
tmap transmission.TorrentMap
err error
)
if Transmission != nil {
tmap, err = Transmission.GetTorrentMap()
if err != nil {
run = false
if timeoutErr, ok := err.(net.Error); ok && timeoutErr.Timeout() {
tmap, err = Transmission.GetTorrentMap()
if err != nil {
run = true
}
} else {
Transmission = nil
}
}
if run {
// Stops torrents from transmission that are not selected this time
for _, CHash := range hash {
v, ok := tmap[CHash]
if ok {
v.Stop()
}
}
for _, CHash := range CurrentHashes {
v, ok := tmap[CHash]
if ok {
if v.UploadRatio < 1 {
v.Start()
}
}
}
}
}
}
func initialize() {
var (
err error
)
args.PATH, _ = os.Getwd()
args.RES = "-1"
args.HOST = "localhost"
arg.MustParse(&args)
RES, _ := strconv.Atoi(args.RES)
tf.Res = scene.Res(RES)
for i, v := range args.RELEASE {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
tf.Release[v] = i + 1
}
for i, v := range args.NRELEASE {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
tf.Release[v] = (i + 10) * -1
}
for i, v := range args.TAGS {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
tf.Tags[v] = i + 1
}
tf.Tags["nuked"] = -99999
CurrentTorrents = make(map[string]*tf.SeriesTorrent, len(args.Series))
for _, title := range args.Series {
fmt.Println(title)
CurrentTorrents[title] = new(tf.SeriesTorrent)
*CurrentTorrents[title] = make(tf.SeriesTorrent, 10)
}
args.PATH, _ = filepath.Abs(args.PATH)
unselectedDir = filepath.Join(args.PATH, "unselected/")
// Load all downloaded torrents
if !args.NEW {
unselectedFolder, _ := os.Open(unselectedDir)
defer unselectedFolder.Close()
unselectedNames, _ := unselectedFolder.Readdirnames(0)
sort.Strings(unselectedNames)
for _, name := range unselectedNames {
if filepath.Ext(name) == ".torrent" {
current := process(filepath.Join(unselectedDir, name))
for _, title := range args.Series {
if current.Title == title {
CurrentTorrents[title].Addtorrent(current)
break
}
}
}
}
}
username := "lordwelch"
passwd := "hello"
cl := &http.Client{
Transport: &http.Transport{
Dial: (&net.Dialer{
Timeout: 10 * time.Second,
}).Dial,
},
Timeout: time.Second * 30,
}
req, err := http.NewRequest("GET", "http://"+args.HOST+":9091/transmission/rpc", nil)
req.SetBasicAuth(username, passwd)
resp, err := cl.Do(req)
if err != nil {
fmt.Println(err)
} else {
resp.Body.Close()
Transmission, _ = transmission.New(transmission.Config{
User: username,
Password: passwd,
Address: "http://" + args.HOST + ":9091/transmission/rpc",
HTTPClient: cl,
})
}
for _, s := range CurrentTorrents {
for _, se := range *s {
for _, ep := range se {
ep.Sort()
}
}
}
download()
}
func process(torrentFile string) tf.SceneVideoTorrent {
var (
mt = new(tf.MetaTorrent)
vt = new(tf.SceneVideoTorrent)
)
f, _ := os.OpenFile(torrentFile, os.O_RDONLY, 755)
defer f.Close()
mt.ReadFile(f)
vt.Torrent = tf.NewTorrent(*mt)
vt.Parse(strings.TrimSuffix(vt.Name, filepath.Ext(vt.Name)))
//fmt.Println(vt.Original)
//fmt.Println(vt)
return *vt
}

247
main.go
View File

@ -1,247 +0,0 @@
package main
import (
"bufio"
"crypto/tls"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"timmy.narnian.us/git/timmy/scene"
"github.com/alexflint/go-arg"
"github.com/lordwelch/transmission"
)
var (
CurrentTorrents map[string]SeriesTorrent
unselectedDir string
Transmission *transmission.Client
args struct {
RES string `arg:"help:Resolution preference [480/720/1080]"`
RELEASE []string `arg:"-r,help:Release group preference order."`
NRELEASE []string `arg:"-R,help:Release groups to use only as a lost resort."`
TAGS []string `arg:"-t,help:Tags to prefer -t internal would choose an internal over another. Whichever file with the most tags is chosen. Release Group takes priority"`
Series []string `arg:"required,positional,help:TV series to download torrent file for"`
NEW bool `arg:"-n,help:Only modify new torrents"`
PATH string `arg:"-P,required,help:Path to torrent files"`
}
)
func main() {
var (
stdC = make(chan *SceneVideoTorrent)
)
initialize()
go stdinLoop(stdC)
for {
select {
case <-time.After(time.Minute * 15):
download()
case current := <-stdC:
addtorrent(CurrentTorrents[current.Title], current)
}
}
}
func stdinLoop(C chan *SceneVideoTorrent) {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
url := strings.TrimSpace(scanner.Text())
torrentName := filepath.Base(url)
torrentPath := filepath.Join(unselectedDir, torrentName)
_, err := os.Stat(torrentPath)
if !os.IsNotExist(err) {
continue
}
cmd := exec.Command("wget", url, "-q", "-O", torrentPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
fmt.Println("url failed: ", url)
fmt.Println(err)
continue
}
current := process(torrentPath)
for _, title := range args.Series {
if current.Title == title {
C <- current
break
}
}
}
}
func download() {
hash := removeLinks()
removeDownloads(hash)
for _, s := range CurrentTorrents {
for _, se := range s {
for _, ep := range se {
fmt.Println("symlink", ep.Ep[0].Title, ep.Ep[0].Season, ep.Ep[0].Episode)
os.Symlink(ep.Ep[0].Meta.FilePath, filepath.Join(filepath.Join(ep.Ep[0].Meta.FilePath, "../../"), filepath.Base(ep.Ep[0].Meta.FilePath)))
}
}
}
}
func addtorrent(St SeriesTorrent, torrent *SceneVideoTorrent) {
_, ok := St[torrent.Season]
if !ok {
St[torrent.Season] = make(SeasonTorrent, 20)
}
Ep := St[torrent.Season][torrent.Episode]
if Ep == nil {
RES, _ := strconv.Atoi(args.RES)
St[torrent.Season][torrent.Episode] = &EpisodeTorrent{
Tags: make(map[string]int),
Release: make(map[string]int),
Res: scene.Res(RES),
}
for i, v := range args.RELEASE {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
St[torrent.Season][torrent.Episode].Release[v] = i + 1
}
for i, v := range args.NRELEASE {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
St[torrent.Season][torrent.Episode].Release[v] = i + 1000000
}
for i, v := range args.TAGS {
if i+1 == 0 {
panic("You do not exist in a world that I know of")
}
St[torrent.Season][torrent.Episode].Tags[v] = i + 1
}
}
St[torrent.Season][torrent.Episode].Add(torrent)
}
// Get hashes of torrents that were previously selected then remove the link to them
func removeLinks() (hash []string) {
selectedDir := filepath.Join(unselectedDir, "../")
selectedFolder, _ := os.Open(selectedDir)
//fmt.Println("selected dir", selectedDir)
defer selectedFolder.Close()
selectedNames, _ := selectedFolder.Readdirnames(0)
for _, lnk := range selectedNames {
target, err := os.Readlink(filepath.Join(selectedDir, lnk))
//fmt.Println(target)
//fmt.Println(err)
if err == nil {
if filepath.Base(filepath.Dir(target)) == "unselected" {
hash = append(hash, process(target).Meta.Hash)
os.Remove(filepath.Join(selectedDir, lnk))
}
}
}
selectedNames, _ = selectedFolder.Readdirnames(0)
fmt.Println(selectedNames)
return
}
func removeDownloads(hash []string) {
tmap, err := Transmission.GetTorrentMap()
if err != nil {
panic(err)
}
thash := make([]*transmission.Torrent, 0, len(hash))
// Removes torrents from transmission that are not selected this time
for _, CHash := range hash {
v, ok := tmap[CHash]
if ok {
current := scene.Parse(v.Name)
if CurrentTorrents[current.Title][current.Season][current.Episode].Ep[0].Meta.Hash != CHash {
thash = append(thash, v)
}
}
}
Transmission.RemoveTorrents(false, thash...)
}
func initialize() {
var (
err error
)
args.PATH, _ = os.Getwd()
args.RES = "-1"
arg.MustParse(&args)
CurrentTorrents = make(map[string]SeriesTorrent, len(args.Series))
for _, title := range args.Series {
fmt.Println(title)
CurrentTorrents[title] = make(SeriesTorrent, 10)
}
unselectedDir, _ = filepath.Abs(filepath.Join(args.PATH, "unselected/"))
//fmt.Println("unselected dir:", unselectedDir)
// Load all downloaded torrents
unselectedFolder, _ := os.Open(unselectedDir)
defer unselectedFolder.Close()
unselectedNames, _ := unselectedFolder.Readdirnames(0)
sort.Strings(unselectedNames)
for _, name := range unselectedNames {
current := process(filepath.Join(unselectedDir, name))
for _, title := range args.Series {
if current.Title == title {
addtorrent(CurrentTorrents[title], current)
break
}
}
}
Transmission, err = transmission.New(transmission.Config{
Address: "http://timmy:9091/transmission/rpc",
HTTPClient: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
},
})
if err != nil {
panic(err)
}
download()
}
func process(torrentFile string) *SceneVideoTorrent {
var (
mt = new(MetaTorrent)
vt = new(SceneVideoTorrent)
)
f, _ := os.OpenFile(torrentFile, os.O_RDONLY, 755)
defer f.Close()
mt.ReadFile(f)
vt.Torrent = NewTorrent(*mt)
vt.Parse(strings.TrimSuffix(vt.Name, filepath.Ext(vt.Name)))
//fmt.Println(vt.Original)
fmt.Println(vt.Title)
return vt
}

146
type.go
View File

@ -1,4 +1,4 @@
package main
package TorrentFilter
import (
"crypto/sha1"
@ -45,34 +45,15 @@ type SceneVideoTorrent struct {
scene.Scene
}
type EpisodeTorrent struct {
Ep []*SceneVideoTorrent
Tags map[string]int
Res scene.Res
Release map[string]int
}
type SeasonTorrent map[string]*EpisodeTorrent
type EpisodeTorrent []SceneVideoTorrent
type SeasonTorrent map[string]EpisodeTorrent
type SeriesTorrent map[string]SeasonTorrent
func OrderedBy(fns ...func(int, int) bool) func(int, int) bool {
return func(i, j int) bool {
// Try all but the last comparison.
for _, less := range fns {
switch {
case less(i, j):
// i < j, so we have a decision.
return true
case less(j, i):
// i > j, so we have a decision.
return false
}
// i == j; try the next comparison.
}
// All comparisons to here said "equal", so just return whatever
// the final comparison reports.
return fns[len(fns)-1](i, j)
}
}
var (
Tags = make(map[string]int, 5)
Res scene.Res
Release = make(map[string]int, 5)
)
func NewTorrent(mt MetaTorrent) (T Torrent) {
if mt.Info.Length == 0 {
@ -109,98 +90,61 @@ func (Mt *MetaTorrent) ReadFile(r *os.File) error {
return nil
}
func (Vt SceneVideoTorrent) Score() (score int) {
if filepath.Ext(Vt.Name) == ".mkv" {
score += 1000
}
if Vt.Resolution == Res {
score += 900
} else {
score += int(Vt.Resolution) * 100
}
score += Release[Vt.Release] + 1
for k := range Vt.Tags {
score += Tags[k]
}
return
}
func (Vt SceneVideoTorrent) String() string {
return Vt.Torrent.Meta.FilePath
return Vt.Scene.String()
}
func (Et *EpisodeTorrent) ByRelease(i, j int) bool {
var (
ii int
ij int
ret bool
)
ii = Et.Release[Et.Ep[i].Release]
ij = Et.Release[Et.Ep[j].Release]
if ii == 0 {
ii = 999999
}
if ij == 0 {
ij = 999999
}
if ii == ij {
ret = Et.Ep[i].Release > Et.Ep[j].Release
//fmt.Println(Et.Ep[i].Release, ">", Et.Ep[j].Release, "=", ret, Et)
} else {
ret = ii < ij
}
return ret
func (Et EpisodeTorrent) Len() int {
return len(Et)
}
func (Et *EpisodeTorrent) ByTag(i, j int) bool {
var (
ii int
ij int
ret bool
)
for k := range Et.Ep[i].Tags {
if Et.Tags[k] > 0 {
ii++
}
}
for k := range Et.Ep[j].Tags {
if Et.Tags[k] > 0 {
ij++
}
}
if ii == ij {
ret = len(Et.Ep[i].Tags) < len(Et.Ep[j].Tags)
//fmt.Println(len(Et.Ep[i].Tags), "<", len(Et.Ep[j].Tags), "=", ret)
} else {
ret = ii > ij
}
return ret
func (Et EpisodeTorrent) Swap(i, j int) {
Et[i], Et[j] = Et[j], Et[i]
}
func (Et *EpisodeTorrent) ByRes(i, j int) bool {
var ret bool
ret = Et.Ep[i].Resolution > Et.Ep[j].Resolution
//fmt.Println(Et.Ep[i].Resolution, ">", Et.Ep[j].Resolution, "=", ret)
if Et.Res == Et.Ep[i].Resolution && Et.Ep[i].Resolution != Et.Ep[j].Resolution {
ret = true
}
return ret
func (Et EpisodeTorrent) Less(i, j int) bool {
return Et[i].Score() > Et[j].Score()
}
func (Et *EpisodeTorrent) Len() int {
return len(Et.Ep)
}
func (Et *EpisodeTorrent) Swap(i, j int) {
Et.Ep[i], Et.Ep[j] = Et.Ep[j], Et.Ep[i]
//fmt.Println(Et.Ep)
}
func (Et *EpisodeTorrent) Less(i, j int) bool {
return OrderedBy(Et.ByRelease, Et.ByRes, Et.ByTag)(i, j)
}
func (Et *EpisodeTorrent) Add(Vt *SceneVideoTorrent) {
Et.Ep = append(Et.Ep, Vt)
func (Et EpisodeTorrent) Sort() {
sort.Stable(Et)
}
func (St SeriesTorrent) SearchHash(hash string) *SceneVideoTorrent {
for _, v := range St {
for _, v2 := range v {
for _, v3 := range v2.Ep {
for _, v3 := range v2 {
if v3.Meta.Hash == hash {
return v3
return &v3
}
}
}
}
return &SceneVideoTorrent{}
return nil
}
func (St SeriesTorrent) Addtorrent(torrent SceneVideoTorrent) {
_, ok := St[torrent.Season]
if !ok {
St[torrent.Season] = make(SeasonTorrent, 20)
}
St[torrent.Season][torrent.Episode] = append(St[torrent.Season][torrent.Episode], torrent)
}