diff --git a/backend/external_medias/handlers.go b/backend/external_medias/handlers.go index 0a72c6c..cfd4a42 100644 --- a/backend/external_medias/handlers.go +++ b/backend/external_medias/handlers.go @@ -141,7 +141,7 @@ func GetShows(env *web.Env, user *models.User, source string, category string, f for _, id := range media.IDs { pShow, _ := pShows.Has(id) wShow, _ := wShows.IsShowInWishlist(id) - show := shows.NewWithClient(&polochon.Show{ImdbID: id}, client, pShow, wShow) + show := shows.NewWithClient(id, client, pShow, wShow) // First check in the DB before := []polochon.Detailer{env.Backend.Detailer} diff --git a/backend/movies/handlers.go b/backend/movies/handlers.go index 7fe76a2..f7b8526 100644 --- a/backend/movies/handlers.go +++ b/backend/movies/handlers.go @@ -283,6 +283,7 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er func RefreshMovieSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) id := vars["id"] + lang := polochon.Language(vars["lang"]) // Get the user user := auth.GetCurrentUser(r, env.Log) @@ -294,29 +295,35 @@ func RefreshMovieSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.R } movie := &papi.Movie{Movie: &polochon.Movie{ImdbID: id}} - refreshSubs, err := client.UpdateSubtitles(movie) + sub, err := client.UpdateSubtitle(movie, lang) if err != nil { return env.RenderError(w, err) } - subs := []subtitles.Subtitle{} - for _, lang := range refreshSubs { - subtitleURL, _ := client.SubtitleURL(movie, lang) - subs = append(subs, subtitles.Subtitle{ - Language: lang, - URL: subtitleURL, - VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", id, lang), - }) + // TODO: handle this with a better error + if sub == nil { + return env.RenderJSON(w, nil) } - return env.RenderJSON(w, subs) + url, err := client.DownloadURLWithToken(sub) + if err != nil { + return env.RenderError(w, err) + } + + s := &subtitles.Subtitle{ + Subtitle: sub.Subtitle, + URL: url, + VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", id, sub.Lang), + } + + return env.RenderJSON(w, s) } // DownloadVVTSubtitle returns a vvt subtitle for the movie func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) id := vars["id"] - lang := vars["lang"] + lang := polochon.Language(vars["lang"]) // Get the user user := auth.GetCurrentUser(r, env.Log) @@ -327,7 +334,14 @@ func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) e return env.RenderError(w, err) } - url, err := client.SubtitleURL(&papi.Movie{Movie: &polochon.Movie{ImdbID: id}}, lang) + s := &papi.Subtitle{ + Subtitle: &polochon.Subtitle{ + Video: &papi.Movie{Movie: &polochon.Movie{ImdbID: id}}, + Lang: lang, + }, + } + + url, err := client.DownloadURLWithToken(s) if err != nil { return env.RenderError(w, err) } diff --git a/backend/movies/movies.go b/backend/movies/movies.go index 9a53ea6..8d57759 100644 --- a/backend/movies/movies.go +++ b/backend/movies/movies.go @@ -48,7 +48,7 @@ func (m *Movie) MarshalJSON() ([]byte, error) { if m.pMovie != nil { // Get the DownloadURL - movieToMarshal.PolochonURL, _ = m.client.DownloadURL(m.pMovie) + movieToMarshal.PolochonURL, _ = m.client.DownloadURLWithToken(m.pMovie) // Get the metadata movieToMarshal.DateAdded = m.pMovie.DateAdded @@ -58,13 +58,14 @@ func (m *Movie) MarshalJSON() ([]byte, error) { movieToMarshal.Container = m.pMovie.Container // Append the Subtitles - for _, l := range m.pMovie.Subtitles { - subtitleURL, _ := m.client.SubtitleURL(m.pMovie, l) - movieToMarshal.Subtitles = append(movieToMarshal.Subtitles, subtitles.Subtitle{ - Language: l, - URL: subtitleURL, - VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, l), - }) + for _, s := range m.pMovie.Subtitles { + subtitleURL, _ := m.client.DownloadURLWithToken(s) + movieToMarshal.Subtitles = append(movieToMarshal.Subtitles, + subtitles.Subtitle{ + Subtitle: s.Subtitle, + URL: subtitleURL, + VVTFile: fmt.Sprintf("/movies/%s/subtitles/%s", m.ImdbID, s.Lang), + }) } } @@ -73,13 +74,18 @@ func (m *Movie) MarshalJSON() ([]byte, error) { // New returns a new Movie with all the needed infos func New(imdbID string, client *papi.Client, pMovie *papi.Movie, isWishlisted bool) *Movie { + var m *polochon.Movie + if pMovie != nil && pMovie.Movie != nil { + m = pMovie.Movie + } else { + m = &polochon.Movie{ImdbID: imdbID} + } + return &Movie{ client: client, pMovie: pMovie, Wishlisted: isWishlisted, - Movie: &polochon.Movie{ - ImdbID: imdbID, - }, + Movie: m, } } diff --git a/backend/routes.go b/backend/routes.go index 2f9303d..ddc819d 100644 --- a/backend/routes.go +++ b/backend/routes.go @@ -43,7 +43,7 @@ func setupRoutes(env *web.Env) { env.Handle("/movies/{id:tt[0-9]+}", movies.PolochonDeleteHandler).WithRole(models.UserRole).Methods("DELETE") env.Handle("/movies/{id:tt[0-9]+}/refresh", movies.RefreshMovieHandler).WithRole(models.UserRole).Methods("POST") env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET") - env.Handle("/movies/{id:tt[0-9]+}/subtitles/refresh", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST") + env.Handle("/movies/{id:tt[0-9]+}/subtitles/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST") env.Handle("/movies/refresh", extmedias.RefreshMoviesHandler).WithRole(models.AdminRole).Methods("POST") // Shows routes @@ -54,7 +54,7 @@ func setupRoutes(env *web.Env) { env.Handle("/shows/{id:tt[0-9]+}", shows.GetDetailsHandler).WithRole(models.UserRole).Methods("GET") env.Handle("/shows/{id:tt[0-9]+}/refresh", shows.RefreshShowHandler).WithRole(models.UserRole).Methods("POST") env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}", shows.RefreshEpisodeHandler).WithRole(models.UserRole).Methods("POST") - env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}/subtitles/refresh", shows.RefreshEpisodeSubtitlesHandler).WithRole(models.UserRole).Methods("POST") + env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}/subtitles/{lang}", shows.RefreshEpisodeSubtitlesHandler).WithRole(models.UserRole).Methods("POST") env.Handle("/shows/{id:tt[0-9]+}/seasons/{season:[0-9]+}/episodes/{episode:[0-9]+}/subtitles/{lang}", shows.DownloadVVTSubtitle).WithRole(models.UserRole).Methods("GET") env.Handle("/shows/refresh", extmedias.RefreshShowsHandler).WithRole(models.AdminRole).Methods("POST") diff --git a/backend/shows/episodes.go b/backend/shows/episodes.go index 2a7b9cd..43b2ca6 100644 --- a/backend/shows/episodes.go +++ b/backend/shows/episodes.go @@ -3,7 +3,6 @@ package shows import ( "encoding/json" "fmt" - "time" "git.quimbo.fr/odwrtw/canape/backend/models" "git.quimbo.fr/odwrtw/canape/backend/subtitles" @@ -26,18 +25,13 @@ func (e *Episode) MarshalJSON() ([]byte, error) { var downloadURL string var subs []subtitles.Subtitle - var dateAdded time.Time - var quality string - var audioCodec string - var videoCodec string - var container string // If the episode is present, fill the downloadURL if e.show.pShow != nil { pEpisode := e.show.pShow.GetEpisode(e.Season, e.Episode) if pEpisode != nil { // Get the DownloadURL - downloadURL, _ = e.show.client.DownloadURL( + downloadURL, _ = e.show.client.DownloadURLWithToken( &papi.Episode{ ShowEpisode: &polochon.ShowEpisode{ ShowImdbID: e.ShowImdbID, @@ -46,19 +40,20 @@ func (e *Episode) MarshalJSON() ([]byte, error) { }, }, ) - dateAdded = pEpisode.DateAdded - quality = string(pEpisode.Quality) - audioCodec = pEpisode.AudioCodec - videoCodec = pEpisode.VideoCodec - container = pEpisode.Container + + e.ShowEpisode.VideoMetadata = pEpisode.ShowEpisode.VideoMetadata + e.ShowEpisode.File = pEpisode.ShowEpisode.File // Append the Subtitles - for _, l := range pEpisode.Subtitles { - subtitleURL, _ := e.show.client.SubtitleURL(pEpisode, l) + for _, s := range pEpisode.Subtitles { + subtitleURL, _ := e.show.client.DownloadURLWithToken(s) subs = append(subs, subtitles.Subtitle{ - Language: l, - URL: subtitleURL, - VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, l), + Subtitle: &polochon.Subtitle{ + File: polochon.File{Size: s.Size}, + Lang: s.Lang, + }, + URL: subtitleURL, + VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, s.Lang), }) } } @@ -69,21 +64,11 @@ func (e *Episode) MarshalJSON() ([]byte, error) { *alias PolochonURL string `json:"polochon_url"` Subtitles []subtitles.Subtitle `json:"subtitles"` - DateAdded time.Time `json:"date_added"` - Quality string `json:"quality"` - AudioCodec string `json:"audio_codec"` - VideoCodec string `json:"video_codec"` - Container string `json:"container"` Thumb string `json:"thumb"` }{ alias: (*alias)(e), PolochonURL: downloadURL, Subtitles: subs, - DateAdded: dateAdded, - Quality: quality, - AudioCodec: audioCodec, - VideoCodec: videoCodec, - Container: container, Thumb: e.Thumb, } diff --git a/backend/shows/handlers.go b/backend/shows/handlers.go index 1d3eca5..3ee5405 100644 --- a/backend/shows/handlers.go +++ b/backend/shows/handlers.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "strconv" "net/http" @@ -35,15 +34,15 @@ func GetDetailsHandler(env *web.Env, w http.ResponseWriter, r *http.Request) err pShow, err := client.GetShow(id) if err != nil && err != papi.ErrResourceNotFound { - log.Println("Got error getting show ", err) + env.Log.Println("Got error getting show ", err) } wShow, err := models.IsShowWishlisted(env.Database, user.ID, id) if err != nil && err != papi.ErrResourceNotFound { - log.Println("Got error getting wishlisted show ", err) + env.Log.Println("Got error getting wishlisted show ", err) } - s := NewWithClient(&polochon.Show{ImdbID: id}, client, pShow, wShow) + s := NewWithClient(id, client, pShow, wShow) // First try from the db first := []polochon.Detailer{env.Backend.Detailer} // Then try from the polochon detailers @@ -81,15 +80,15 @@ func RefreshShowHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er pShow, err := client.GetShow(id) if err != nil && err != papi.ErrResourceNotFound { - log.Println("Got error getting show ", err) + env.Log.Println("Got error getting show ", err) } wShow, err := models.IsShowWishlisted(env.Database, user.ID, id) if err != nil && err != papi.ErrResourceNotFound { - log.Println("Got error getting wishlisted show ", err) + env.Log.Println("Got error getting wishlisted show ", err) } - s := NewWithClient(&polochon.Show{ImdbID: id}, client, pShow, wShow) + s := NewWithClient(id, client, pShow, wShow) // Refresh the polochon detailers detailers := env.Config.Show.Detailers err = s.Refresh(env, detailers) @@ -163,7 +162,7 @@ func SearchShow(env *web.Env, w http.ResponseWriter, r *http.Request) error { for _, s := range shows { pShow, _ := pShows.Has(s.ImdbID) wShow, _ := wShows.IsShowInWishlist(s.ImdbID) - show := NewWithClient(s, client, pShow, wShow) + show := NewWithClient(s.ImdbID, client, pShow, wShow) // First try from the db first := []polochon.Detailer{env.Backend.Detailer} @@ -242,8 +241,7 @@ func GetWishlistHandler(env *web.Env, w http.ResponseWriter, r *http.Request) er showList := []*Show{} for _, wishedShow := range wShows.List() { pShow, _ := pShows.Has(wishedShow.ImdbID) - poloShow := &polochon.Show{ImdbID: wishedShow.ImdbID} - show := NewWithClient(poloShow, client, pShow, wishedShow) + show := NewWithClient(wishedShow.ImdbID, client, pShow, wishedShow) // First check in the DB before := []polochon.Detailer{env.Backend.Detailer} @@ -309,7 +307,7 @@ func RefreshEpisodeHandler(env *web.Env, w http.ResponseWriter, r *http.Request) } s := &Show{ - Show: &polochon.Show{ImdbID: id}, + Show: pShow.Show, client: client, pShow: pShow, } @@ -343,6 +341,7 @@ func RefreshEpisodeHandler(env *web.Env, w http.ResponseWriter, r *http.Request) func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) id := vars["id"] + lang := polochon.Language(vars["lang"]) // No need to check errors here as the router is making sure that season // and episode are numbers @@ -366,29 +365,35 @@ func RefreshEpisodeSubtitlesHandler(env *web.Env, w http.ResponseWriter, r *http }, } - refreshedSubs, err := client.UpdateSubtitles(e) + sub, err := client.UpdateSubtitle(e, lang) if err != nil { return env.RenderError(w, err) } - subs := []subtitles.Subtitle{} - for _, lang := range refreshedSubs { - subtitleURL, _ := client.SubtitleURL(e, lang) - subs = append(subs, subtitles.Subtitle{ - Language: lang, - URL: subtitleURL, - VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, lang), - }) + // TODO: handle this with a better error + if sub == nil { + return env.RenderJSON(w, nil) } - return env.RenderJSON(w, subs) + url, err := client.DownloadURL(sub) + if err != nil { + return env.RenderError(w, err) + } + + s := &subtitles.Subtitle{ + Subtitle: sub.Subtitle, + URL: url, + VVTFile: fmt.Sprintf("/shows/%s/seasons/%d/episodes/%d/subtitles/%s", e.ShowImdbID, e.Season, e.Episode, lang), + } + + return env.RenderJSON(w, s) } // DownloadVVTSubtitle returns a vvt subtitle for the movie func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) id := vars["id"] - lang := vars["lang"] + lang := polochon.Language(vars["lang"]) season, _ := strconv.Atoi(vars["season"]) episode, _ := strconv.Atoi(vars["episode"]) @@ -401,13 +406,20 @@ func DownloadVVTSubtitle(env *web.Env, w http.ResponseWriter, r *http.Request) e return env.RenderError(w, err) } - url, err := client.SubtitleURL(&papi.Episode{ - ShowEpisode: &polochon.ShowEpisode{ - ShowImdbID: id, - Season: season, - Episode: episode, + s := &papi.Subtitle{ + Subtitle: &polochon.Subtitle{ + Video: &papi.Episode{ + ShowEpisode: &polochon.ShowEpisode{ + ShowImdbID: id, + Season: season, + Episode: episode, + }, + }, + Lang: lang, }, - }, lang) + } + + url, err := client.DownloadURLWithToken(s) if err != nil { return env.RenderError(w, err) } diff --git a/backend/shows/shows.go b/backend/shows/shows.go index aa14690..1a370c0 100644 --- a/backend/shows/shows.go +++ b/backend/shows/shows.go @@ -62,7 +62,14 @@ func New(imdbID string) *Show { } // NewWithClient returns a new Show with a polochon ShowConfig -func NewWithClient(show *polochon.Show, client *papi.Client, pShow *papi.Show, wShow *models.WishedShow) *Show { +func NewWithClient(imdbID string, client *papi.Client, pShow *papi.Show, wShow *models.WishedShow) *Show { + var show *polochon.Show + if pShow == nil || pShow.Show == nil { + show = &polochon.Show{ImdbID: imdbID} + } else { + show = pShow.Show + } + s := &Show{ Show: show, client: client, @@ -252,7 +259,7 @@ func getPolochonShows(env *web.Env, user *models.User) ([]*Show, error) { // Create Shows objects from the shows retrieved for _, pShow := range pshows.List() { wShow, _ := wShows.IsShowInWishlist(pShow.ImdbID) - show := NewWithClient(&polochon.Show{ImdbID: pShow.ImdbID}, client, pShow, wShow) + show := NewWithClient(pShow.ImdbID, client, pShow, wShow) shows = append(shows, show) } return shows, nil diff --git a/backend/subtitles/subtitles.go b/backend/subtitles/subtitles.go index e5a4127..c8a5771 100644 --- a/backend/subtitles/subtitles.go +++ b/backend/subtitles/subtitles.go @@ -1,8 +1,10 @@ package subtitles +import polochon "github.com/odwrtw/polochon/lib" + // Subtitle represents a Subtitle type Subtitle struct { - Language string `json:"language"` - URL string `json:"url"` - VVTFile string `json:"vvt_file"` + *polochon.Subtitle + URL string `json:"url"` + VVTFile string `json:"vvt_file"` } diff --git a/frontend/js/actions/subtitles.js b/frontend/js/actions/subtitles.js index 6460791..6e95a78 100644 --- a/frontend/js/actions/subtitles.js +++ b/frontend/js/actions/subtitles.js @@ -1,24 +1,25 @@ import { configureAxios, request } from "../requests"; -export const searchMovieSubtitles = (imdbId) => { +export const searchMovieSubtitle = (imdbId, lang) => { return request( "MOVIE_SUBTITLES_UPDATE", - configureAxios().post(`/movies/${imdbId}/subtitles/refresh`), + configureAxios().post(`/movies/${imdbId}/subtitles/${lang}`), null, - { imdbId: imdbId } + { imdbId, lang } ); }; -export const searchEpisodeSubtitles = (imdbId, season, episode) => { +export const searchEpisodeSubtitle = (imdbId, season, episode, lang) => { const url = `/shows/${imdbId}/seasons/${season}/episodes/${episode}`; return request( "EPISODE_SUBTITLES_UPDATE", - configureAxios().post(`${url}/subtitles/refresh`), + configureAxios().post(`${url}/subtitles/${lang}`), null, { - imdbId: imdbId, - season: season, - episode: episode, + imdbId, + season, + episode, + lang, } ); }; diff --git a/frontend/js/components/buttons/download.js b/frontend/js/components/buttons/download.js index f3025c5..664efe9 100644 --- a/frontend/js/components/buttons/download.js +++ b/frontend/js/components/buttons/download.js @@ -18,7 +18,7 @@ export const DownloadAndStream = ({ url, name, subtitles }) => { DownloadAndStream.propTypes = { url: PropTypes.string, name: PropTypes.string, - subtitles: PropTypes.array, + subtitles: PropTypes.object, }; const DownloadButton = ({ url }) => ( @@ -67,30 +67,29 @@ const StreamButton = ({ name, url, subtitles }) => { StreamButton.propTypes = { name: PropTypes.string.isRequired, url: PropTypes.string.isRequired, - subtitles: PropTypes.array, + subtitles: PropTypes.object, }; const Player = ({ url, subtitles }) => { - const subs = subtitles || []; - return (
); }; Player.propTypes = { - subtitles: PropTypes.array, + subtitles: PropTypes.object, url: PropTypes.string.isRequired, }; diff --git a/frontend/js/components/buttons/subtitles.js b/frontend/js/components/buttons/subtitles.js index 91ab89e..35df142 100644 --- a/frontend/js/components/buttons/subtitles.js +++ b/frontend/js/components/buttons/subtitles.js @@ -3,25 +3,22 @@ import PropTypes from "prop-types"; import Dropdown from "react-bootstrap/Dropdown"; +import { prettySize, upperCaseFirst } from "../../utils"; + export const SubtitlesButton = ({ subtitles, inLibrary, - searching, search, + fetchingSubtitles, }) => { if (inLibrary === false) { return null; } + /* eslint-disable */ const [show, setShow] = useState(false); /* eslint-enable */ - const onSelect = (eventKey) => { - if (eventKey === null || eventKey != 1) { - setShow(false); - } - }; - const onToggle = (isOpen, event, metadata) => { // Don't close on select if (metadata && metadata.source !== "select") { @@ -29,10 +26,27 @@ export const SubtitlesButton = ({ } }; - const count = subtitles && subtitles.length !== 0 ? subtitles.length : 0; + const searchAll = () => { + const langs = ["fr_FR", "en_US"]; + for (const lang of langs) { + search(lang); + } + }; + + const count = subtitles && subtitles.size !== 0 ? subtitles.size : 0; + + let searching = fetchingSubtitles; + if (count > 0) { + subtitles.forEach((subtitle) => { + if (subtitle.searching === true) { + searching = true; + } + }); + } + return ( - + Subtitles @@ -40,9 +54,13 @@ export const SubtitlesButton = ({ - - - Automatic search + +
+ Automatic search +
+
{count > 0 && ( @@ -53,10 +71,13 @@ export const SubtitlesButton = ({ )} {count > 0 && - subtitles.map((subtitle, index) => ( - - {subtitle.language.split("_")[1]} - + [...subtitles.entries()].map(([lang, subtitle]) => ( + ))} @@ -64,8 +85,38 @@ export const SubtitlesButton = ({ ); }; SubtitlesButton.propTypes = { - subtitles: PropTypes.array, + subtitles: PropTypes.object, inLibrary: PropTypes.bool.isRequired, - searching: PropTypes.bool.isRequired, + fetchingSubtitles: PropTypes.bool.isRequired, search: PropTypes.func.isRequired, }; + +export const SubtitleEntry = ({ subtitle, search }) => { + const lang = upperCaseFirst(subtitle.lang.split("_")[0]); + const size = subtitle.size ? subtitle.size : 0; + + const handleRefresh = () => { + search(subtitle.lang); + }; + + return ( + +
+ + {lang} + {size !== 0 && ({prettySize(size)})} + +
+
+ + ); +}; +SubtitleEntry.propTypes = { + search: PropTypes.func.isRequired, + subtitle: PropTypes.object, +}; diff --git a/frontend/js/components/details/polochon.js b/frontend/js/components/details/polochon.js index 704b693..d68a5d8 100644 --- a/frontend/js/components/details/polochon.js +++ b/frontend/js/components/details/polochon.js @@ -1,18 +1,23 @@ import React from "react"; import PropTypes from "prop-types"; +import { prettySize } from "../../utils"; + export const PolochonMetadata = ({ quality, container, videoCodec, audioCodec, releaseGroup, + size, }) => { if (!quality || quality === "") { return null; } - const metadata = [quality, container, videoCodec, audioCodec, releaseGroup] + const s = size === 0 ? "" : prettySize(size); + + const metadata = [quality, container, videoCodec, audioCodec, releaseGroup, s] .filter((m) => m && m !== "") .join(", "); @@ -29,4 +34,5 @@ PolochonMetadata.propTypes = { videoCodec: PropTypes.string, audioCodec: PropTypes.string, releaseGroup: PropTypes.string, + size: PropTypes.number, }; diff --git a/frontend/js/components/list/details.js b/frontend/js/components/list/details.js index 885b4ca..6af8e6f 100644 --- a/frontend/js/components/list/details.js +++ b/frontend/js/components/list/details.js @@ -53,6 +53,7 @@ const ListDetails = (props) => { container={props.data.container} audioCodec={props.data.audio_codec} videoCodec={props.data.video_codec} + size={props.data.size} /> {props.children} diff --git a/frontend/js/components/movies/subtitlesButton.js b/frontend/js/components/movies/subtitlesButton.js index f931078..2bbfbd5 100644 --- a/frontend/js/components/movies/subtitlesButton.js +++ b/frontend/js/components/movies/subtitlesButton.js @@ -1,7 +1,7 @@ import React from "react"; import { useDispatch, useSelector } from "react-redux"; -import { searchMovieSubtitles } from "../../actions/subtitles"; +import { searchMovieSubtitle } from "../../actions/subtitles"; import { SubtitlesButton } from "../buttons/subtitles"; @@ -15,16 +15,16 @@ export const MovieSubtitlesButton = () => { const subtitles = useSelector( (state) => state.movies.movies.get(imdbId).subtitles ); - const searching = useSelector( + const fetchingSubtitles = useSelector( (state) => state.movies.movies.get(imdbId).fetchingSubtitles ); return ( dispatch(searchMovieSubtitles(imdbId))} + search={(lang) => dispatch(searchMovieSubtitle(imdbId, lang))} /> ); }; diff --git a/frontend/js/components/shows/details/episode.js b/frontend/js/components/shows/details/episode.js index ac83b5e..bc4738e 100644 --- a/frontend/js/components/shows/details/episode.js +++ b/frontend/js/components/shows/details/episode.js @@ -74,6 +74,7 @@ export const Episode = ({ season, episode }) => { container={data.container} audioCodec={data.audio_codec} videoCodec={data.video_codec} + size={data.size} light /> { const dispatch = useDispatch(); const imdbId = useSelector((state) => state.show.show.imdb_id); - const searching = useSelector((state) => - state.show.show.seasons.get(season).get(episode).fetchingSubtitles - ? state.show.show.seasons.get(season).get(episode).fetchingSubtitles - : false + const fetchingSubtitles = useSelector( + (state) => + state.show.show.seasons.get(season).get(episode).fetchingSubtitles ); const inLibrary = useSelector( (state) => state.show.show.seasons.get(season).get(episode).polochon_url !== "" ); - const subtitles = useSelector((state) => - state.show.show.seasons.get(season).get(episode).subtitles - ? state.show.show.seasons.get(season).get(episode).subtitles - : [] + const subtitles = useSelector( + (state) => state.show.show.seasons.get(season).get(episode).subtitles ); - const search = () => { - dispatch(searchEpisodeSubtitles(imdbId, season, episode)); + const search = (lang) => { + dispatch(searchEpisodeSubtitle(imdbId, season, episode, lang)); }; return ( ); diff --git a/frontend/js/reducers/movies.js b/frontend/js/reducers/movies.js index 64ffa8a..e7f3c34 100644 --- a/frontend/js/reducers/movies.js +++ b/frontend/js/reducers/movies.js @@ -1,6 +1,7 @@ import { produce } from "immer"; import { formatTorrents } from "../utils"; +import { formatSubtitle, formatSubtitles } from "./utils"; const defaultState = { loading: false, @@ -14,6 +15,7 @@ const formatMovie = (movie) => { movie.fetchingDetails = false; movie.fetchingSubtitles = false; movie.torrents = formatTorrents(movie); + movie.subtitles = formatSubtitles(movie.subtitles); return movie; }; @@ -89,12 +91,25 @@ export default (state = defaultState, action) => case "MOVIE_SUBTITLES_UPDATE_PENDING": draft.movies.get(action.payload.main.imdbId).fetchingSubtitles = true; + if ( + draft.movies + .get(action.payload.main.imdbId) + .subtitles.get(action.payload.main.lang) + ) { + draft.movies + .get(action.payload.main.imdbId) + .subtitles.get(action.payload.main.lang).searching = true; + } break; case "MOVIE_SUBTITLES_UPDATE_FULFILLED": draft.movies.get(action.payload.main.imdbId).fetchingSubtitles = false; - draft.movies.get(action.payload.main.imdbId).subtitles = - action.payload.response.data; + draft.movies + .get(action.payload.main.imdbId) + .subtitles.set( + action.payload.response.data.lang, + formatSubtitle(action.payload.response.data) + ); break; case "SELECT_MOVIE": diff --git a/frontend/js/reducers/show.js b/frontend/js/reducers/show.js index dc52f2f..7afe96a 100644 --- a/frontend/js/reducers/show.js +++ b/frontend/js/reducers/show.js @@ -1,6 +1,7 @@ import { produce } from "immer"; import { formatTorrents } from "../utils"; +import { formatSubtitle, formatSubtitles } from "./utils"; const defaultState = { loading: false, @@ -10,6 +11,7 @@ const defaultState = { const formatEpisode = (episode) => { // Format the episode's torrents episode.torrents = formatTorrents(episode); + episode.subtitles = formatSubtitles(episode.subtitles); // Set the default fetching data episode.fetching = false; @@ -102,16 +104,30 @@ export default (state = defaultState, action) => draft.show.seasons .get(action.payload.main.season) .get(action.payload.main.episode).fetchingSubtitles = true; + if ( + draft.show.seasons + .get(action.payload.main.season) + .get(action.payload.main.episode) + .subtitles.get(action.payload.main.lang) + ) { + draft.show.seasons + .get(action.payload.main.season) + .get(action.payload.main.episode) + .subtitles.get(action.payload.main.lang).searching = true; + } break; case "EPISODE_SUBTITLES_UPDATE_FULFILLED": { draft.show.seasons .get(action.payload.main.season) - .get(action.payload.main.episode).subtitles = - action.payload.response.data; + .get(action.payload.main.episode).fetchingSubtitles = false; draft.show.seasons .get(action.payload.main.season) - .get(action.payload.main.episode).fetchingSubtitles = false; + .get(action.payload.main.episode) + .subtitles.set( + action.payload.main.lang, + formatSubtitle(action.payload.response.data) + ); break; } default: diff --git a/frontend/js/reducers/utils.js b/frontend/js/reducers/utils.js new file mode 100644 index 0000000..a395628 --- /dev/null +++ b/frontend/js/reducers/utils.js @@ -0,0 +1,22 @@ +export const formatSubtitles = (subtitles) => { + if (!subtitles || subtitles.length == 0) { + return new Map(); + } + + let map = new Map(); + subtitles.forEach((subtitle) => { + subtitle = formatSubtitle(subtitle); + map.set(subtitle.lang, subtitle); + }); + + return map; +}; + +export const formatSubtitle = (subtitle) => { + if (!subtitle) { + return undefined; + } + + subtitle.searching = false; + return subtitle; +}; diff --git a/frontend/scss/app.scss b/frontend/scss/app.scss index 25182e0..bda5b1b 100644 --- a/frontend/scss/app.scss +++ b/frontend/scss/app.scss @@ -146,3 +146,8 @@ div.sweet-alert > h2 { .toast { background-color: $card-bg; } + +.link-unstyled, .link-unstyled:link, .link-unstyled:hover { + color: inherit; + text-decoration: inherit; +}