Compare commits

..

No commits in common. "f2427ee6c7fc3717d2969cba0ebe0bab6565b451" and "9488795186720e835c74af08aab4abae43dbb0a9" have entirely different histories.

23 changed files with 79 additions and 448 deletions

View File

@ -84,6 +84,7 @@ func FillEpisodeFromDB(eDB *episodeDB, pEpisode *polochon.ShowEpisode) {
pEpisode.Title = eDB.Title
pEpisode.Rating = eDB.Rating
pEpisode.Plot = eDB.Plot
pEpisode.Thumb = eDB.Thumb
pEpisode.Runtime = eDB.Runtime
pEpisode.Aired = eDB.Aired
pEpisode.Thumb = imageURL(fmt.Sprintf(

View File

@ -90,7 +90,6 @@ func FillMovieFromDB(mDB *movieDB, pMovie *polochon.Movie) {
pMovie.SortTitle = mDB.SortTitle
pMovie.Tagline = mDB.Tagline
pMovie.Thumb = imageURL("movies/" + mDB.ImdbID + ".jpg")
pMovie.Fanart = imageURL("movies/" + mDB.ImdbID + "-fanart.jpg")
}
// updateFromMovie will update the movieDB from a Movie

View File

@ -9,8 +9,7 @@ import (
// TorrentVideo reprensents a torrent embeding the video inforamtions
type TorrentVideo struct {
*polochon.Torrent
Thumb string `json:"thumb"`
Fanart string `json:"fanart"`
Img string `json:"img"`
Video polochon.Video `json:"video,omitempty"`
}
@ -48,13 +47,11 @@ func (t *TorrentVideo) Update(detailer polochon.Detailer, db *sqlx.DB, log *logr
if err := GetShow(db, v.Show); err != nil {
return
}
t.Fanart = v.Show.Fanart
t.Thumb = v.Show.Poster
t.Img = v.Show.Poster
v.Show = nil
}
case *polochon.Movie:
t.Thumb = v.Thumb
t.Fanart = v.Fanart
t.Img = v.Thumb
}
}

View File

@ -49,51 +49,6 @@ func PolochonMoviesHandler(env *web.Env, w http.ResponseWriter, r *http.Request)
return env.RenderJSON(w, movies)
}
// GetMovieHandler will return a single movie
func GetMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
id := vars["id"]
user := auth.GetCurrentUser(r, env.Log)
client, err := user.NewPapiClient(env.Database)
if err != nil {
return env.RenderError(w, err)
}
movies, err := client.GetMovies()
if err != nil {
return env.RenderError(w, err)
}
moviesWishlist, err := models.GetMovieWishlist(env.Database, user.ID)
if err != nil {
return env.RenderError(w, err)
}
pMovie, _ := movies.Has(id)
movie := New(
id,
client,
pMovie,
moviesWishlist.IsMovieInWishlist(id),
)
detailers := []polochon.Detailer{env.Backend.Detailer}
err = movie.GetDetails(env, detailers)
if err != nil {
return env.RenderError(w, err)
}
torrenters := []polochon.Torrenter{env.Backend.Torrenter}
err = movie.GetTorrents(env, torrenters)
if err != nil {
env.Log.Errorf("error while getting movie torrents : %s", err)
}
return env.RenderJSON(w, movie)
}
// RefreshMovieHandler refreshes details for a movie
func RefreshMovieHandler(env *web.Env, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)

View File

@ -150,13 +150,7 @@ func (m *Movie) Refresh(env *web.Env, detailers []polochon.Detailer) error {
}
// Download poster
err = web.Download(m.Thumb, m.imgFile("thumb"), 300)
if err != nil {
log.Errorf("got error trying to download the poster %q", err)
}
// Download fanart
err = web.Download(m.Fanart, m.imgFile("fanart"), 960)
err = web.Download(m.Thumb, m.imgFile(), true)
if err != nil {
log.Errorf("got error trying to download the poster %q", err)
}
@ -243,19 +237,13 @@ func (m *Movie) RefreshTorrents(env *web.Env, torrenters []polochon.Torrenter) e
}
// imgURL returns the default image url
func (m *Movie) imgURL(imgType string) string {
var location string
if imgType == "thumb" {
location = m.ImdbID
} else {
location = m.ImdbID + "-" + imgType
}
return fmt.Sprintf("movies/%s.jpg", location)
func (m *Movie) imgURL() string {
return fmt.Sprintf("movies/%s.jpg", m.ImdbID)
}
// imgFile returns the image location on disk
func (m *Movie) imgFile(imgType string) string {
return filepath.Join(models.PublicDir, "img", m.imgURL(imgType))
func (m *Movie) imgFile() string {
return filepath.Join(models.PublicDir, "img", m.imgURL())
}
// getPolochonMovies returns an array of the user's polochon movies

View File

@ -41,7 +41,6 @@ func setupRoutes(env *web.Env) {
env.Handle("/movies/explore/options", extmedias.MovieExplorerOptions).WithRole(models.UserRole).Methods("GET")
env.Handle("/movies/search/{search}", movies.SearchMovie).WithRole(models.UserRole).Methods("GET")
env.Handle("/movies/{id:tt[0-9]+}", movies.PolochonDeleteHandler).WithRole(models.UserRole).Methods("DELETE")
env.Handle("/movies/{id:tt[0-9]+}", movies.GetMovieHandler).WithRole(models.UserRole).Methods("GET")
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/{lang}", movies.RefreshMovieSubtitlesHandler).WithRole(models.UserRole).Methods("POST")

View File

@ -172,22 +172,22 @@ func (s *Show) downloadImages(env *web.Env) {
for _, img := range []struct {
url string
urlType string
maxWidth uint
scale bool
}{
{
url: s.Show.Banner,
urlType: "banner",
maxWidth: 0,
scale: false,
},
{
url: s.Show.Fanart,
urlType: "fanart",
maxWidth: 960,
scale: false,
},
{
url: s.Show.Poster,
urlType: "poster",
maxWidth: 300,
scale: true,
},
} {
if img.url == "" {
@ -197,7 +197,7 @@ func (s *Show) downloadImages(env *web.Env) {
if _, err := os.Stat(s.imgFile(img.urlType)); err == nil {
continue
}
if err := web.Download(img.url, s.imgFile(img.urlType), img.maxWidth); err != nil {
if err := web.Download(img.url, s.imgFile(img.urlType), img.scale); err != nil {
env.Log.Errorf("failed to dowload %s: %s", img.urlType, err)
}
}
@ -214,7 +214,7 @@ func (s *Show) downloadImages(env *web.Env) {
continue
}
err := web.Download(e.Thumb, fileName, 0)
err := web.Download(e.Thumb, fileName, false)
if err != nil {
env.Log.Errorf("failed to dowload the thumb for season %d episode %d ( %s ) : %s", e.Season, e.Episode, e.Thumb, err)
}

View File

@ -13,7 +13,7 @@ import (
)
// Download used for downloading file
var Download = func(srcURL, dest string, maxWidth uint) error {
var Download = func(srcURL, dest string, scale bool) error {
if err := createDirectory(dest); err != nil {
return err
}
@ -36,8 +36,8 @@ var Download = func(srcURL, dest string, maxWidth uint) error {
return err
}
if maxWidth != 0 {
image = resize.Resize(maxWidth, 0, image, resize.Lanczos3)
if scale {
image = resize.Resize(300, 0, image, resize.Lanczos3)
}
// Create the file

View File

@ -48,17 +48,6 @@ export function getMovieDetails(imdbId) {
);
}
export function fetchMovieDetails(imdbId) {
return request(
"MOVIE_FETCH_DETAILS",
configureAxios().get(`/movies/${imdbId}`),
null,
{
imdbId,
}
);
}
export function deleteMovie(imdbId, lastFetchUrl) {
return request("MOVIE_DELETE", configureAxios().delete(`/movies/${imdbId}`), [
fetchMovies(lastFetchUrl),

View File

@ -33,7 +33,6 @@ import { AdminPanel } from "./components/admins/panel";
import { Notifications } from "./components/notifications/notifications";
import { Alert } from "./components/alerts/alert";
import MovieList from "./components/movies/list";
import { MovieDetails } from "./components/movies/details";
import { AppNavBar } from "./components/navbar";
import { WsHandler } from "./components/websocket";
import { ShowDetails } from "./components/shows/details";
@ -67,7 +66,6 @@ const App = () => (
/>
<Route path="/movies/polochon" exact component={MovieList} />
<Route path="/movies/wishlist" exact component={MovieList} />
<Route path="/movies/details/:imdbId" exact component={MovieDetails} />
<Route path="/movies/search/:search" exact component={MovieList} />
<Route
path="/movies/explore/:source/:category"
@ -83,9 +81,7 @@ const App = () => (
component={ShowList}
/>
<Route path="/shows/details/:imdbId" exact component={ShowDetails} />
<Route
render={() => <Redirect to="/movies/explore/trakttv/trending" />}
/>
<Route render={() => <Redirect to="/movies/explore/yts/seeds" />} />
</Switch>
</Container>
</div>

View File

@ -1,162 +0,0 @@
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import { useSelector, useDispatch } from "react-redux";
import {
fetchMovieDetails,
getMovieDetails,
movieWishlistToggle,
} from "../../actions/movies";
import { searchMovieSubtitle } from "../../actions/subtitles";
import Loader from "../loader/loader";
import { Fanart } from "../details/fanart";
import { Plot } from "../details/plot";
import { Rating } from "../details/rating";
import { ReleaseDate } from "../details/releaseDate";
import { Title } from "../details/title";
import { PolochonMetadata } from "../details/polochon";
import { TrackingLabel } from "../details/tracking";
import { Genres } from "../details/genres";
import { Runtime } from "../details/runtime";
import { DownloadAndStream } from "../buttons/download";
import { ImdbBadge } from "../buttons/imdb";
import { TorrentsButton } from "../buttons/torrents";
import { SubtitlesButton } from "../buttons/subtitles";
import { ShowMore } from "../buttons/showMore";
export const MovieDetails = ({ match }) => {
const dispatch = useDispatch();
const loading = useSelector((state) => state.movie.loading);
const fanartUrl = useSelector((state) => state.movie.movie.fanart);
useEffect(() => {
dispatch(fetchMovieDetails(match.params.imdbId));
}, [dispatch, match]);
if (loading) {
return <Loader />;
}
return (
<React.Fragment>
<Fanart url={fanartUrl} />
<Header />
</React.Fragment>
);
};
MovieDetails.propTypes = {
match: PropTypes.object.isRequired,
};
export const Header = () => {
const dispatch = useDispatch();
const {
audioCodec,
container,
fetchingDetails,
fetchingSubtitles,
genres,
imdb_id: imdbId,
plot,
polochon_url: polochonUrl,
poster_url: posterUrl,
quality,
rating,
runtime,
size,
subtitles,
title,
torrents,
videoCodec,
votes,
wishlisted,
year,
release_group: releaseGroup,
} = useSelector((state) => state.movie.movie);
const inLibrary = polochonUrl !== "";
if (!imdbId || imdbId === "") {
return null;
}
return (
<div className="col-12 col-lg-10 offset-lg-1 mb-3">
<div className="d-flex flex-column flex-md-row">
<div className="d-flex justify-content-center">
<img className="overflow-hidden object-fit-cover" src={posterUrl} />
</div>
<div className="ml-sm-1">
<div className="card-body">
<p className="card-title">
<Title
title={title}
wishlisted={wishlisted}
wishlist={() =>
dispatch(movieWishlistToggle(imdbId, wishlisted))
}
/>
</p>
<p className="card-text">
<ReleaseDate date={year} />
</p>
<p className="card-text">
<Runtime runtime={runtime} />
</p>
<p className="card-text">
<Genres genres={genres} />
</p>
<p className="card-text">
<Rating rating={rating} votes={votes} />
</p>
<div className="card-text">
<ImdbBadge imdbId={imdbId} />
<DownloadAndStream
url={polochonUrl}
name={title}
subtitles={subtitles}
/>
</div>
<p className="card-text">
<TrackingLabel inLibrary={inLibrary} wishlisted={wishlisted} />
</p>
<p className="card-text">
<PolochonMetadata
quality={quality}
releaseGroup={releaseGroup}
container={container}
audioCodec={audioCodec}
videoCodec={videoCodec}
size={size}
/>
</p>
<p className="card-text">
<Plot plot={plot} />
</p>
<div className="card-text">
<ShowMore id={imdbId} inLibrary={inLibrary}>
<TorrentsButton
torrents={torrents}
searching={fetchingDetails}
search={() => dispatch(getMovieDetails(imdbId))}
url={`#/torrents/search/movies/${encodeURI(title)}`}
/>
<SubtitlesButton
inLibrary={inLibrary}
fetchingSubtitles={fetchingSubtitles}
subtitles={subtitles}
search={(lang) => dispatch(searchMovieSubtitle(imdbId, lang))}
/>
</ShowMore>
</div>
</div>
</div>
</div>
</div>
);
};

View File

@ -37,7 +37,7 @@ const fetchUrl = (match) => {
}
};
const MovieList = ({ match, history }) => {
const MovieList = ({ match }) => {
const dispatch = useDispatch();
useEffect(() => {
@ -63,10 +63,6 @@ const MovieList = ({ match, history }) => {
[dispatch]
);
const movieDetails = (imdbId) => {
history.push("/movies/details/" + imdbId);
};
return (
<div className="row">
<ListPosters
@ -77,8 +73,8 @@ const MovieList = ({ match, history }) => {
exploreOptions={exploreOptions}
selectedImdbId={selectedImdbId}
onClick={selectFunc}
onDoubleClick={movieDetails}
onKeyEnter={movieDetails}
onDoubleClick={() => {}}
onKeyEnter={() => {}}
params={match.params}
loading={loading}
/>
@ -109,7 +105,6 @@ MovieList.propTypes = {
updateFilter: PropTypes.func,
movieWishlistToggle: PropTypes.func,
selectMovie: PropTypes.func,
history: PropTypes.object,
match: PropTypes.object,
};

View File

@ -99,7 +99,7 @@ Search.propTypes = {
const MoviesDropdown = () => (
<NavDropdown title="Movies" id="navbar-movies-dropdown">
<LinkContainer to="/movies/explore/trakttv/trending">
<LinkContainer to="/movies/explore/yts/seeds">
<NavDropdown.Item>Discover</NavDropdown.Item>
</LinkContainer>
<LinkContainer to="/movies/polochon">

View File

@ -4,7 +4,7 @@ import { useSelector, useDispatch } from "react-redux";
import Loader from "../loader/loader";
import { Fanart } from "../details/fanart";
import { Fanart } from "./details/fanart";
import { Header } from "./details/header";
import { SeasonsList } from "./details/seasons";
@ -13,7 +13,6 @@ import { fetchShowDetails } from "../../actions/shows";
export const ShowDetails = ({ match }) => {
const dispatch = useDispatch();
const loading = useSelector((state) => state.show.loading);
const fanartUrl = useSelector((state) => state.show.show.fanart_url);
useEffect(() => {
dispatch(fetchShowDetails(match.params.imdbId));
@ -25,7 +24,7 @@ export const ShowDetails = ({ match }) => {
return (
<React.Fragment>
<Fanart url={fanartUrl} />
<Fanart />
<div className="row no-gutters">
<Header />
<SeasonsList />

View File

@ -1,10 +1,8 @@
import React from "react";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
export const Fanart = ({ url }) => {
if (url == "") {
return null;
}
export const Fanart = () => {
const url = useSelector((state) => state.show.show.fanart_url);
return (
<div className="show-fanart mx-n3 mt-n1">
<img
@ -14,9 +12,3 @@ export const Fanart = ({ url }) => {
</div>
);
};
Fanart.propTypes = {
url: PropTypes.string.isRequired,
};
Fanart.defaultProps = {
url: "",
};

View File

@ -1,27 +1,17 @@
import React from "react";
import PropTypes from "prop-types";
export const Poster = ({ thumb, fanart }) => {
if (thumb === "" && fanart === "") {
export const Poster = ({ url }) => {
if (!url || url === "") {
return null;
}
return (
<div className="col-md-2">
{thumb !== "" && (
<img className="card-img d-none d-sm-block" src={thumb} />
)}
{fanart !== "" && (
<img className="card-img d-block d-sm-none" src={fanart} />
)}
<div className="col-md-2 d-none d-md-block">
<img className="card-img" src={url} />
</div>
);
};
Poster.propTypes = {
thumb: PropTypes.string,
fanart: PropTypes.string,
};
Poster.defaultProps = {
thumb: "",
fanart: "",
url: PropTypes.string,
};

View File

@ -1,7 +1,6 @@
import React from "react";
import PropTypes from "prop-types";
import { useSelector } from "react-redux";
import { Link } from "react-router-dom";
import { Torrent } from "./torrent";
import { Poster } from "./poster";
@ -18,32 +17,18 @@ export const TorrentGroup = ({ torrentKey }) => {
const title = (torrent) => {
switch (torrent.type) {
case "movie":
return (
<Link
className="link-unstyled"
to={`/movies/details/${torrent.video.imdb_id}`}
>
{torrent.video.title}
</Link>
);
return torrent.video.title;
case "episode":
return (
<Link
className="link-unstyled"
to={`/shows/details/${torrent.video.show_imdb_id}`}
>
{torrent.video.show_title}
</Link>
);
return torrent.video.show_title;
default:
return <span>Files</span>;
return "Files";
}
};
return (
<div className="w-100 mb-3 card">
<div className="row no-gutters">
<Poster thumb={torrents[0].thumb} fanart={torrents[0].fanart} />
<Poster url={torrents[0].img} />
<div className="col-sm">
<div className="card-body">
<h4 className="card-title">{title(torrents[0])}</h4>

View File

@ -5,7 +5,6 @@ import { enableMapSet } from "immer";
enableMapSet();
import movies from "./movies";
import movie from "./movie";
import shows from "./shows";
import show from "./show";
import user from "./users";
@ -17,7 +16,6 @@ import notifications from "./notifications";
export default combineReducers({
movies,
movie,
shows,
show,
user,

View File

@ -1,77 +0,0 @@
import { produce } from "immer";
import { formatSubtitle, formatMovie } from "./utils";
const defaultState = {
loading: false,
movie: {},
};
export default (state = defaultState, action) =>
produce(state, (draft) => {
switch (action.type) {
case "MOVIE_FETCH_DETAILS_PENDING":
draft.loading = true;
break;
case "MOVIE_FETCH_DETAILS_FULFILLED": {
draft.movie = formatMovie(action.payload.response.data);
draft.loading = false;
break;
}
case "MOVIE_GET_DETAILS_PENDING": {
let imdbId = action.payload.main.imdbId;
if (draft.movie.imdb_id !== imdbId) {
break;
}
draft.movie.fetchingDetails = true;
break;
}
case "MOVIE_GET_DETAILS_FULFILLED": {
let imdbId = action.payload.main.imdbId;
if (draft.movie.imdb_id !== imdbId) {
break;
}
draft.movie = formatMovie(action.payload.response.data);
break;
}
case "MOVIE_SUBTITLES_UPDATE_PENDING": {
let imdbId = action.payload.main.imdbId;
if (draft.movie.imdb_id !== imdbId) {
break;
}
let lang = action.payload.main.lang;
draft.movie.fetchingSubtitles.push(lang);
if (draft.movie.subtitles.get(lang)) {
draft.movie.subtitles.get(lang).searching = true;
}
break;
}
case "MOVIE_SUBTITLES_UPDATE_FULFILLED": {
let imdbId = action.payload.main.imdbId;
if (draft.movie.imdb_id !== imdbId) {
break;
}
let lang = action.payload.main.lang;
let data = action.payload.response.data;
draft.movie.fetchingSubtitles = draft.movie.fetchingSubtitles.filter(
(l) => l != lang
);
if (data) {
draft.movie.subtitles.set(lang, formatSubtitle(data));
}
break;
}
default:
return draft;
}
});

View File

@ -1,6 +1,7 @@
import { produce } from "immer";
import { formatSubtitle, formatMovie } from "./utils";
import { formatTorrents } from "../utils";
import { formatSubtitle, formatSubtitles } from "./utils";
const defaultState = {
loading: false,
@ -10,6 +11,14 @@ const defaultState = {
exploreOptions: {},
};
const formatMovie = (movie) => {
movie.fetchingDetails = false;
movie.fetchingSubtitles = [];
movie.torrents = formatTorrents(movie);
movie.subtitles = formatSubtitles(movie.subtitles);
return movie;
};
const formatMovies = (movies = []) => {
let allMoviesInPolochon = true;
movies.map((movie) => {
@ -57,17 +66,10 @@ export default (state = defaultState, action) =>
break;
case "MOVIE_GET_DETAILS_PENDING":
if (!draft.movies.get(action.payload.main.imdbId)) {
break;
}
draft.movies.get(action.payload.main.imdbId).fetchingDetails = true;
break;
case "MOVIE_GET_DETAILS_FULFILLED":
if (!draft.movies.get(action.payload.main.imdbId)) {
break;
}
draft.movies.set(
action.payload.response.data.imdb_id,
formatMovie(action.payload.response.data)
@ -90,10 +92,6 @@ export default (state = defaultState, action) =>
case "MOVIE_SUBTITLES_UPDATE_PENDING": {
let imdbId = action.payload.main.imdbId;
let lang = action.payload.main.lang;
if (!draft.movies.get(imdbId)) {
break;
}
draft.movies.get(imdbId).fetchingSubtitles.push(lang);
if (draft.movies.get(imdbId).subtitles.get(lang)) {
draft.movies.get(imdbId).subtitles.get(lang).searching = true;
@ -105,10 +103,6 @@ export default (state = defaultState, action) =>
let imdbId = action.payload.main.imdbId;
let lang = action.payload.main.lang;
let data = action.payload.response.data;
if (!draft.movies.get(imdbId)) {
break;
}
draft.movies.get(imdbId).fetchingSubtitles = draft.movies
.get(imdbId)
.fetchingSubtitles.filter((l) => l != lang);

View File

@ -1,6 +1,7 @@
import { produce } from "immer";
import { formatTorrents, formatSubtitle, formatSubtitles } from "./utils";
import { formatTorrents } from "../utils";
import { formatSubtitle, formatSubtitles } from "./utils";
const defaultState = {
loading: false,

View File

@ -20,32 +20,3 @@ export const formatSubtitle = (subtitle) => {
subtitle.searching = false;
return subtitle;
};
export const formatTorrents = (input) => {
if (!input.torrents || input.torrents.length == 0) {
return undefined;
}
let torrentMap = new Map();
input.torrents.forEach((torrent) => {
if (!torrent.result || !torrent.result.source) {
return;
}
if (!torrentMap.has(torrent.result.source)) {
torrentMap.set(torrent.result.source, new Map());
}
torrentMap.get(torrent.result.source).set(torrent.quality, torrent);
});
return torrentMap;
};
export const formatMovie = (movie) => {
movie.fetchingDetails = false;
movie.fetchingSubtitles = [];
movie.torrents = formatTorrents(movie);
movie.subtitles = formatSubtitles(movie.subtitles);
return movie;
};

View File

@ -35,5 +35,26 @@ export const prettySize = (fileSizeInBytes) => {
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
};
export const formatTorrents = (input) => {
if (!input.torrents || input.torrents.length == 0) {
return undefined;
}
let torrentMap = new Map();
input.torrents.forEach((torrent) => {
if (!torrent.result || !torrent.result.source) {
return;
}
if (!torrentMap.has(torrent.result.source)) {
torrentMap.set(torrent.result.source, new Map());
}
torrentMap.get(torrent.result.source).set(torrent.quality, torrent);
});
return torrentMap;
};
export const upperCaseFirst = (string) =>
string.charAt(0).toUpperCase() + string.slice(1);