init
This commit is contained in:
81
handler/engine.go
Normal file
81
handler/engine.go
Normal 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
260
handler/handler.go
Normal 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
145
handler/movietorrent.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user