This commit is contained in:
2018-11-04 15:58:15 +01:00
commit f956bcee28
1178 changed files with 584552 additions and 0 deletions

81
handler/engine.go Normal file
View File

@ -0,0 +1,81 @@
package handler
import (
"fmt"
"github.com/anacrolix/torrent"
"github.com/sirupsen/logrus"
"os"
)
type Engine struct {
client *torrent.Client
files map[string]*MovieTorrent
storagePath string
}
func NewEngine(storagePath string) (*Engine, error) {
logrus.Info("Creating Streaming engine")
if storagePath == "" {
storagePath = os.TempDir()
logrus.Debug("Storage path undefined. Using default temp directory")
}
logrus.Debugf("Storage Path: %s", storagePath)
conf := torrent.NewDefaultClientConfig()
conf.DataDir = storagePath
c, err := torrent.NewClient(conf)
if err != nil {
return nil, err
}
return &Engine{c, map[string]*MovieTorrent{}, storagePath}, nil
}
func (e *Engine) Close() {
e.client.Close()
}
func (e *Engine) Get(fileName string) (*MovieTorrent, error) {
logrus.Infof("Requesting torrent: %s", fileName)
if mt, ok := e.files[fileName]; ok {
return mt, nil
}
return nil, fmt.Errorf("not found: %s", fileName)
}
func (e *Engine) Prepare(magnet string) error {
logrus.Debugf("preparing magnet: %s", magnet)
t, err := e.client.AddMagnet(magnet)
if err != nil {
return err
}
<-t.GotInfo()
return nil
}
func (e *Engine) Download(magnet string) (*MovieTorrent, error) {
logrus.Debugf("downloading magnet: %s", magnet)
t, err := e.client.AddMagnet(magnet)
if err != nil {
return nil, err
}
<-t.GotInfo()
file := getLargestFile(t)
file.Download()
mt := newMovieTorrent(file, t)
mt.engine = e
e.files[mt.FileName] = mt
logrus.Infof("Downloading: %s", mt.FileName)
return mt, nil
}
func getLargestFile(t *torrent.Torrent) *torrent.File {
var target *torrent.File
var maxSize int64
for _, file := range t.Files() {
if maxSize < file.Length() {
maxSize = file.Length()
target = file
}
}
return target
}

260
handler/handler.go Normal file
View File

@ -0,0 +1,260 @@
package handler
import (
"fmt"
"git.adphi.net/Adphi/ytsflix/templates"
"git.adphi.net/Adphi/ytsflix/ytsclient"
"github.com/dustin/go-humanize"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"gopkg.in/h2non/filetype.v1"
"io"
"net/http"
"os"
"strings"
"sync"
"time"
)
var (
_ io.Reader = (*os.File)(nil)
)
type Handler struct {
engine *Engine
clients map[string]int
yts *ytsclient.Client
mutex sync.Mutex
storagePath string
}
type HomeData struct {
Categories map[string]map[string]Movie
}
type Movie struct {
Title string
Year int
Cover string
Link string
Synopsis string
Genre []string
Youtube string
Casting []Cast
}
type Cast struct {
Name string
Image string
}
type MovieData struct {
Title string
Link string
Subtitles map[string]string
}
func NewHandler(storagePath string) (*Handler, error) {
h := Handler{storagePath: storagePath}
var err error
h.engine, err = NewEngine(storagePath)
if err != nil {
return nil, err
}
h.yts = ytsclient.NewClient("https://yts.am/api/v2")
h.clients = map[string]int{}
return &h, nil
}
func (h *Handler) Close() {
h.engine.Close()
}
func (h *Handler) Home(writer http.ResponseWriter, request *http.Request) {
hd := HomeData{Categories: map[string]map[string]Movie{}}
var mu sync.Mutex
var wg sync.WaitGroup
wg.Add(len(ytsclient.Genres))
for _, g := range ytsclient.Genres {
go func(genre ytsclient.Genre) {
defer wg.Done()
res, err := h.yts.List(&ytsclient.ListParams{
Genre: genre,
Limit: 50,
SortBy: ytsclient.Year,
OrderBy: ytsclient.Desc,
Quality: ytsclient.Quality1080p,
})
if err != nil {
sendError(writer, err)
return
}
ms := map[string]Movie{}
for _, m := range res {
ms[m.Title] = Movie{
Link: fmt.Sprintf("/movie/%d", m.ID),
Cover: m.MediumCoverImage,
}
}
if len(ms) >= 10 {
mu.Lock()
hd.Categories[string(genre)] = ms
mu.Unlock()
}
}(g)
}
wg.Wait()
t := templates.HomeTemplate()
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(200)
t.Execute(writer, hd)
//fmt.Fprint(writer, html)
}
func (h *Handler) Movie(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
m, err := h.yts.Get(id, nil)
if err != nil {
sendError(w, err)
}
go func() { h.engine.Prepare(getMagnet(m)) }()
t := templates.MovieTemplate()
var cast []Cast
for _, c := range m.Cast {
cast = append(cast, Cast{Name: c.Name, Image: c.URLSmallImage})
}
md := Movie{
Title: m.Title,
Year: m.Year,
Genre: m.Genres,
Cover: m.MediumCoverImage,
Link: fmt.Sprintf("/torrent/%d", m.ID),
Synopsis: m.DescriptionFull,
Youtube: fmt.Sprintf("https://www.youtube.com/embed/%s", m.YtTrailerCode),
Casting: cast,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
t.Execute(w, md)
}
func (h *Handler) Search(writer http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
s := vars["search"]
ms, err := h.yts.Search(s, &ytsclient.ListParams{Quality: ytsclient.Quality1080p})
if err != nil {
sendError(writer, err)
return
}
html := "<ul>"
for _, m := range ms {
html += fmt.Sprintf("<li><a href=\"/movie/%d\">%s</a></li>", m.ID, m.Title)
}
html += "</ul>"
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(200)
fmt.Fprint(writer, html)
}
func (h *Handler) Torrents(writer http.ResponseWriter, request *http.Request) {
id := mux.Vars(request)["id"]
m, err := h.yts.Get(id, nil)
if err != nil {
sendError(writer, err)
return
}
magnet := getMagnet(m)
mt, err := h.engine.Download(magnet)
if err != nil {
sendError(writer, err)
return
}
if err := mt.DownloadSubtitles(m.ImdbCode); err != nil {
logrus.Error(err)
}
cp := mt.Progress()
go func() {
for p := range cp {
hSize := humanize.Bytes(uint64(p.Size))
downloadSpeed := humanize.Bytes(p.DownloadSpeed) + "/s"
complete := humanize.Bytes(p.Downloaded)
logrus.Debugf("%s: Progress: %s / %s %s", mt.FileName, complete, hSize, downloadSpeed)
}
}()
link := fmt.Sprintf("/watch/%s", mt.FileName)
ss := map[string]string{}
for s, v := range mt.Subtitles {
ss[strings.Replace(s, ".vtt", "", -1)] = fmt.Sprintf("/subs/%s", v)
}
md := MovieData{Title: mt.FileName, Link: link, Subtitles: ss}
writer.Header().Set("Content-Type", "text/html; charset=utf-8")
writer.WriteHeader(200)
t := templates.WatchTemplate()
t.Execute(writer, md)
}
func (h *Handler) Serve(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
var mt *MovieTorrent
var err error
movie := vars["movie"]
h.mutex.Lock()
h.clients[movie]++
h.mutex.Unlock()
if mt, err = h.engine.Get(movie); err != nil {
http.Error(w, "file not found", http.StatusBadRequest)
return
}
reader := mt.NewReader()
defer reader.Close()
defer func() {
h.mutex.Lock()
h.clients[movie]--
h.mutex.Unlock()
time.Sleep(5 * time.Second)
if h.clients[movie] == 0 {
logrus.Infof("The client closed the connection. Cleaning up: %s.", movie)
mt.Cancel()
}
}()
w.Header().Set("Connection", "Keep-Alive")
w.Header().Set("Content-Type", mt.MIME)
http.ServeContent(w, r, mt.FileName, time.Now(), reader)
}
func (h *Handler) Sub(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
m := vars["movie"]
s := vars["sub"]
logrus.Debugf("requesting subs: movie: %s, sub: %s", vars["movie"], vars["sub"])
f, err := os.Open(fmt.Sprintf("%s/%s/%s", h.storagePath, m, s))
if err != nil {
sendError(w, err)
return
}
mime := filetype.GetType(".vtt").MIME.Value
w.Header().Add("Context-Type", mime)
http.ServeContent(w, r, vars["sub"], time.Now(), f)
}
func sendError(writer http.ResponseWriter, err error) {
logrus.Error(err)
http.Error(writer, err.Error(), http.StatusInternalServerError)
}
func getMagnet(m ytsclient.Movie) string {
var to *ytsclient.Torrent
for _, t := range m.Torrents {
if t.Quality == string(ytsclient.Quality1080p) {
to = &t
break
}
}
if to == nil {
to = &m.Torrents[0]
}
magnet, _ := m.Magnet(*to)
return magnet
}

145
handler/movietorrent.go Normal file
View File

@ -0,0 +1,145 @@
package handler
import (
"fmt"
"github.com/anacrolix/torrent"
"github.com/asticode/go-astisub"
"github.com/odwrtw/yifysubs"
"github.com/sirupsen/logrus"
"gopkg.in/h2non/filetype.v1"
"os"
"path"
"path/filepath"
"strings"
"sync"
"time"
)
type MovieTorrent struct {
FileName string
MIME string
Subtitles map[string]string
torrent *torrent.Torrent
file *torrent.File
engine *Engine
}
func newMovieTorrent(file *torrent.File, t *torrent.Torrent) *MovieTorrent {
fileName := filepath.Base(file.Path())
ext := strings.SplitAfter(path.Ext(fileName), ".")[1]
MIME := filetype.GetType(ext).MIME.Value
return &MovieTorrent{
torrent: t,
file: file,
FileName: fileName,
MIME: MIME,
}
}
type Progress struct {
Downloaded uint64
Size uint64
DownloadSpeed uint64
Percentage int
}
func (m *MovieTorrent) NewReader() torrent.Reader {
reader := m.file.NewReader()
reader.SetReadahead(m.file.Length() / 100)
reader.SetResponsive()
return reader
}
func (m *MovieTorrent) Progress() chan Progress {
pc := make(chan Progress)
var p int64 = 0
size := m.torrent.Info().TotalLength()
c := m.torrent.Closed()
go func() {
defer close(pc)
for {
select {
case <-c:
logrus.Debugf("%s stopped", m.FileName)
return
default:
if p >= size {
logrus.Debugf("%s completed", m.FileName)
return
}
currentProgress := m.torrent.BytesCompleted()
percent := int(float64(currentProgress) / float64(size) * 100)
pc <- Progress{
Downloaded: uint64(currentProgress),
Size: uint64(size),
Percentage: percent,
DownloadSpeed: uint64(currentProgress - p),
}
p = currentProgress
time.Sleep(time.Second)
}
}
}()
return pc
}
func (m *MovieTorrent) Cancel() {
logrus.Infof("Stopping torrent: %s", m.FileName)
m.torrent.Drop()
}
func (m *MovieTorrent) DownloadSubtitles(imdbID string) error {
logrus.Infof("Getting subtitles for: %s", m.FileName)
//Create a client
client := yifysubs.New("http://yifysubtitles.com")
//Search subtitles
subtitles, err := client.Search(imdbID)
if err != nil {
return err
}
m.Subtitles = map[string]string{}
basePath := filepath.Join(m.engine.storagePath, filepath.Dir(m.file.Path()))
logrus.Debugf("Subtitles base path: %s", basePath)
if _, err := os.Stat(basePath); os.IsNotExist(err) {
os.Mkdir(basePath, os.ModePerm)
}
var wg sync.WaitGroup
wg.Add(len(subtitles))
mx := sync.RWMutex{}
for _, subtitle := range subtitles {
go func(sub *yifysubs.Subtitle) {
defer wg.Done()
name := fmt.Sprintf("%s.vtt", sub.Lang)
ref := filepath.Join(filepath.Dir(m.file.Path()), name)
p := filepath.Join(basePath, name)
if _, err := os.Stat(p); err == nil {
mx.Lock()
m.Subtitles[name] = ref
mx.Unlock()
return
}
file, err := os.Create(p)
if err != nil {
logrus.Error(err)
return
}
defer file.Close()
defer sub.Close()
s, _ := astisub.ReadFromSRT(sub)
if err := s.WriteToWebVTT(file); err != nil {
logrus.Debugf("error subtitle: %v", err)
os.Remove(p)
return
}
mx.Lock()
m.Subtitles[name] = ref
mx.Unlock()
}(subtitle)
}
wg.Wait()
return nil
}