Compare commits
6 Commits
56c5156380
...
v0.27.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 87e72ec55e | |||
| 23a88ec6bb | |||
| d33a961f2d | |||
| 566886ef0e | |||
| 65acd5d21b | |||
| a27fcf334a |
@@ -2,6 +2,10 @@ export default class DownloadOptionTr extends HTMLTableRowElement {
|
|||||||
H264_CODECS = ['h264', 'h.264', 'x264']
|
H264_CODECS = ['h264', 'h.264', 'x264']
|
||||||
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
|
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
|
||||||
|
|
||||||
|
#downloadBtnEl;
|
||||||
|
#selectEpisodeInputEl;
|
||||||
|
|
||||||
|
url;
|
||||||
size;
|
size;
|
||||||
quality;
|
quality;
|
||||||
resolution;
|
resolution;
|
||||||
@@ -12,34 +16,39 @@ export default class DownloadOptionTr extends HTMLTableRowElement {
|
|||||||
mediaType;
|
mediaType;
|
||||||
season;
|
season;
|
||||||
episode;
|
episode;
|
||||||
|
filename;
|
||||||
|
imdbId;
|
||||||
|
episodeId;
|
||||||
|
mediaTitle;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
this.url = this.getAttribute('url');
|
||||||
this.size = this.getAttribute('size');
|
this.size = this.getAttribute('size');
|
||||||
this.quality = this.getAttribute('quality');
|
this.quality = this.getAttribute('quality');
|
||||||
this.resolution = this.getAttribute('resolution');
|
this.resolution = this.getAttribute('resolution');
|
||||||
this.codec = this.getAttribute('codec');
|
this.codec = this.getAttribute('codec');
|
||||||
this.seeders = this.getAttribute('seeders');
|
this.seeders = this.getAttribute('seeders');
|
||||||
this.provider = this.getAttribute('provider');
|
this.provider = this.getAttribute('provider');
|
||||||
|
this.filename = this.getAttribute('filename');
|
||||||
|
this.imdbId = this.getAttribute('imdb-id');
|
||||||
this.languages = JSON.parse(this.getAttribute('languages'));
|
this.languages = JSON.parse(this.getAttribute('languages'));
|
||||||
this.mediaType = this.getAttribute('media-type');
|
this.mediaType = this.getAttribute('media-type');
|
||||||
|
this.mediaTitle = this.getAttribute('media-title');
|
||||||
this.season = this.getAttribute('season') ?? null;
|
this.season = this.getAttribute('season') ?? null;
|
||||||
this.episode = this.getAttribute('episode') ?? null;
|
this.episode = this.getAttribute('episode') ?? null;
|
||||||
|
this.episodeId = this.getAttribute('episode-id') ?? null;
|
||||||
|
this.#downloadBtnEl = this.querySelector('.download-btn');
|
||||||
|
this.#selectEpisodeInputEl = this.querySelector('input[type="checkbox"]');
|
||||||
|
|
||||||
// document.addEventListener('filterDownloadOptions', this.filter.bind(this));
|
this.#downloadBtnEl.addEventListener('click', () => this.download());
|
||||||
}
|
}
|
||||||
connectedCallback() {
|
get isSelected() {
|
||||||
|
return this.#selectEpisodeInputEl.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
// attribute change
|
set isSelected(value) {
|
||||||
attributeChangedCallback(property, oldValue, newValue) {
|
this.#selectEpisodeInputEl.checked = value;
|
||||||
if (oldValue === newValue) return;
|
|
||||||
this[ property ] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observedAttributes() {
|
|
||||||
return ['size', 'quality', 'resolution', 'codec', 'seeders', 'provider'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filter({ detail: { activeFilter } }) {
|
filter({ detail: { activeFilter } }) {
|
||||||
@@ -90,4 +99,26 @@ export default class DownloadOptionTr extends HTMLTableRowElement {
|
|||||||
|
|
||||||
return include;
|
return include;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download() {
|
||||||
|
fetch('/api/download', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: this.url,
|
||||||
|
title: this.mediaTitle,
|
||||||
|
filename: this.filename,
|
||||||
|
mediaType: this.mediaType,
|
||||||
|
imdbId: this.imdbId,
|
||||||
|
episodeId: this.episodeId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(json => {
|
||||||
|
console.log(json)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
export default class EpisodeContainer extends HTMLElement {
|
export default class EpisodeContainer extends HTMLElement {
|
||||||
H264_CODECS = ['h264', 'h.264', 'x264']
|
|
||||||
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
|
|
||||||
|
|
||||||
options = [];
|
options = [];
|
||||||
|
showTitle;
|
||||||
|
|
||||||
#episodeSelectorEl;
|
#episodeSelectorEl;
|
||||||
#resultsToggleBtnEl;
|
#resultsToggleBtnEl;
|
||||||
@@ -12,6 +10,7 @@ export default class EpisodeContainer extends HTMLElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
this.showTitle = this.getAttribute('show-title');
|
||||||
this.#resultsTableEl = this.querySelector('.results-container');
|
this.#resultsTableEl = this.querySelector('.results-container');
|
||||||
this.#resultsToggleBtnEl = this.querySelector('.dropdown-button');
|
this.#resultsToggleBtnEl = this.querySelector('.dropdown-button');
|
||||||
this.#resultsCountBadgeEl = this.querySelector('.results-count-badge');
|
this.#resultsCountBadgeEl = this.querySelector('.results-count-badge');
|
||||||
@@ -22,21 +21,9 @@ export default class EpisodeContainer extends HTMLElement {
|
|||||||
this.#resultsCountBadgeEl.addEventListener('click', () => this.toggleResults());
|
this.#resultsCountBadgeEl.addEventListener('click', () => this.toggleResults());
|
||||||
|
|
||||||
document.addEventListener('filterDownloadOptions', this.filter.bind(this));
|
document.addEventListener('filterDownloadOptions', this.filter.bind(this));
|
||||||
|
document.addEventListener('downloadSelectedEpisodes', this.downloadSelectedResults.bind(this));
|
||||||
document.addEventListener('selectEpisodeForDownload', (e) => this.selectEpisodeForDownload(e.detail.select));
|
document.addEventListener('selectEpisodeForDownload', (e) => this.selectEpisodeForDownload(e.detail.select));
|
||||||
}
|
}
|
||||||
connectedCallback() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// attribute change
|
|
||||||
attributeChangedCallback(property, oldValue, newValue) {
|
|
||||||
if (oldValue === newValue) return;
|
|
||||||
this[ property ] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get observedAttributes() {
|
|
||||||
return ['name'];
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleResults() {
|
toggleResults() {
|
||||||
this.#resultsToggleBtnEl.classList.toggle('rotate-90');
|
this.#resultsToggleBtnEl.classList.toggle('rotate-90');
|
||||||
@@ -50,6 +37,20 @@ export default class EpisodeContainer extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadSelectedResults() {
|
||||||
|
if (this.#episodeSelectorEl.disabled === false &&
|
||||||
|
this.#episodeSelectorEl.checked === true
|
||||||
|
) {
|
||||||
|
console.log('episode is selected')
|
||||||
|
this.options.forEach(option => {
|
||||||
|
if (option.isSelected === true) {
|
||||||
|
option.download();
|
||||||
|
}
|
||||||
|
option.isSelected = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
filter({ detail: { activeFilter } }) {
|
filter({ detail: { activeFilter } }) {
|
||||||
let firstIncluded = true;
|
let firstIncluded = true;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
export default class MovieContainer extends HTMLElement {
|
export default class MovieContainer extends HTMLElement {
|
||||||
H264_CODECS = ['h264', 'h.264', 'x264']
|
|
||||||
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
|
|
||||||
|
|
||||||
#resultsTableEl;
|
#resultsTableEl;
|
||||||
#resultsCountNumberEl;
|
#resultsCountNumberEl;
|
||||||
|
|
||||||
@@ -13,12 +10,6 @@ export default class MovieContainer extends HTMLElement {
|
|||||||
document.addEventListener('filterDownloadOptions', this.filter.bind(this));
|
document.addEventListener('filterDownloadOptions', this.filter.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
// attribute change
|
|
||||||
attributeChangedCallback(property, oldValue, newValue) {
|
|
||||||
if (oldValue === newValue) return;
|
|
||||||
this[ property ] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
filter({ detail: { activeFilter } }) {
|
filter({ detail: { activeFilter } }) {
|
||||||
const options = this.querySelectorAll('tr.download-option');
|
const options = this.querySelectorAll('tr.download-option');
|
||||||
let firstIncluded = true;
|
let firstIncluded = true;
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import { Controller } from '@hotwired/stimulus';
|
|||||||
*/
|
*/
|
||||||
/* stimulusFetch: 'lazy' */
|
/* stimulusFetch: 'lazy' */
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
H264_CODECS = ['h264', 'h.264', 'x264']
|
|
||||||
H265_CODECS = ['h265', 'h.265', 'x265', 'hevc']
|
|
||||||
|
|
||||||
static values = {
|
static values = {
|
||||||
title: String,
|
title: String,
|
||||||
tmdbId: String,
|
tmdbId: String,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default class extends Controller {
|
|||||||
"quality": "",
|
"quality": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
static outlets = ['movie-results', 'tv-results', 'tv-episode-list']
|
static outlets = ['tv-episode-list']
|
||||||
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'quality', 'selectAll', 'downloadSelected']
|
static targets = ['resolution', 'codec', 'language', 'provider', 'season', 'quality', 'selectAll', 'downloadSelected']
|
||||||
static values = {
|
static values = {
|
||||||
'imdbId': String,
|
'imdbId': String,
|
||||||
@@ -55,6 +55,10 @@ export default class extends Controller {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
downloadSelectedEpisodes() {
|
||||||
|
document.dispatchEvent(new CustomEvent('downloadSelectedEpisodes', {}));
|
||||||
|
}
|
||||||
|
|
||||||
addLanguages(option) {
|
addLanguages(option) {
|
||||||
const languages = Object.assign([], option.languages);
|
const languages = Object.assign([], option.languages);
|
||||||
languages.forEach((language) => {
|
languages.forEach((language) => {
|
||||||
@@ -164,10 +168,6 @@ export default class extends Controller {
|
|||||||
this.tvEpisodeListOutlet.setSeason(event.target.value);
|
this.tvEpisodeListOutlet.setSeason(event.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
uncheckSelectAllBtn() {
|
|
||||||
this.selectAllTarget.checked = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadSeason() {
|
downloadSeason() {
|
||||||
fetch(`/api/download/season/${this.imdbIdValue}/${this.activeFilter['season']}`, {
|
fetch(`/api/download/season/${this.imdbIdValue}/${this.activeFilter['season']}`, {
|
||||||
headers: {
|
headers: {
|
||||||
@@ -175,13 +175,4 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadSelectedEpisodes() {
|
|
||||||
this.tvResultsOutlets.forEach(episode => {
|
|
||||||
if (episode.isActive() && episode.isSelected()) {
|
|
||||||
episode.download();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.selectAllTarget.checked = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export default class extends Controller {
|
|||||||
const autocompleteController = this.application.getControllerForElementAndIdentifier(this.element, 'symfony--ux-autocomplete--autocomplete')
|
const autocompleteController = this.application.getControllerForElementAndIdentifier(this.element, 'symfony--ux-autocomplete--autocomplete')
|
||||||
window.location.href = `/search?term=${autocompleteController.tomSelect.lastValue}`
|
window.location.href = `/search?term=${autocompleteController.tomSelect.lastValue}`
|
||||||
}
|
}
|
||||||
|
document.querySelector("#search-button").addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const autocompleteController = this.application.getControllerForElementAndIdentifier(this.element, 'symfony--ux-autocomplete--autocomplete')
|
||||||
|
window.location.href = `/search?term=${autocompleteController.tomSelect.lastQuery}`
|
||||||
|
});
|
||||||
this.element.addEventListener('autocomplete:pre-connect', this._onPreConnect);
|
this.element.addEventListener('autocomplete:pre-connect', this._onPreConnect);
|
||||||
this.element.addEventListener('autocomplete:connect', this._onConnect);
|
this.element.addEventListener('autocomplete:connect', this._onConnect);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default class extends Controller {
|
|||||||
active: Boolean,
|
active: Boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
static targets = ['list', 'count', 'episodeSelector', 'toggleButton', 'listContainer']
|
static targets = ['list', 'count', 'episodeSelector',]
|
||||||
static outlets = ['loading-icon']
|
static outlets = ['loading-icon']
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
@@ -37,21 +37,4 @@ export default class extends Controller {
|
|||||||
this.episodeSelectorTarget.disabled = true;
|
this.episodeSelectorTarget.disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelected() {
|
|
||||||
return this.episodeSelectorTarget.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
download() {
|
|
||||||
this.element.options.forEach(option => {
|
|
||||||
const optionSelector = option.querySelector('input[type="checkbox"]');
|
|
||||||
if (true === optionSelector.checked) {
|
|
||||||
const downloadBtn = option.querySelector('button.download-btn');
|
|
||||||
const downloadBtnController = this.application.getControllerForElementAndIdentifier(downloadBtn, 'download-button');
|
|
||||||
downloadBtnController.download();
|
|
||||||
optionSelector.checked = false;
|
|
||||||
this.episodeSelectorTarget.checked = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/Base/Util/ImdbMatcher.php
Normal file
11
src/Base/Util/ImdbMatcher.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Base\Util;
|
||||||
|
|
||||||
|
class ImdbMatcher
|
||||||
|
{
|
||||||
|
public static function isMatch(string $imdbId): bool
|
||||||
|
{
|
||||||
|
return preg_match('/^tt\d{7}$/', $imdbId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Search\Action\Handler;
|
namespace App\Search\Action\Handler;
|
||||||
|
|
||||||
|
use App\Base\Util\ImdbMatcher;
|
||||||
|
use App\Search\Action\Result\RedirectToMediaResult;
|
||||||
use App\Search\Action\Result\SearchResult;
|
use App\Search\Action\Result\SearchResult;
|
||||||
use App\Tmdb\Tmdb;
|
use App\Tmdb\Tmdb;
|
||||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||||
@@ -17,6 +19,13 @@ class SearchHandler implements HandlerInterface
|
|||||||
|
|
||||||
public function handle(CommandInterface $command): ResultInterface
|
public function handle(CommandInterface $command): ResultInterface
|
||||||
{
|
{
|
||||||
|
if (ImdbMatcher::isMatch($command->term)) {
|
||||||
|
$result = $this->tmdb->findByImdbId($command->term);
|
||||||
|
return new RedirectToMediaResult(
|
||||||
|
imdbId: $result->imdbId,
|
||||||
|
mediaType: $result->mediaType,
|
||||||
|
);
|
||||||
|
}
|
||||||
return new SearchResult(
|
return new SearchResult(
|
||||||
term: $command->term,
|
term: $command->term,
|
||||||
results: $this->tmdb->search($command->term)
|
results: $this->tmdb->search($command->term)
|
||||||
|
|||||||
13
src/Search/Action/Result/RedirectToMediaResult.php
Normal file
13
src/Search/Action/Result/RedirectToMediaResult.php
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Search\Action\Result;
|
||||||
|
|
||||||
|
use OneToMany\RichBundle\Contract\ResultInterface;
|
||||||
|
|
||||||
|
class RedirectToMediaResult implements ResultInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public string $imdbId,
|
||||||
|
public string $mediaType,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use App\Search\Action\Handler\GetMediaInfoHandler;
|
|||||||
use App\Search\Action\Handler\SearchHandler;
|
use App\Search\Action\Handler\SearchHandler;
|
||||||
use App\Search\Action\Input\GetMediaInfoInput;
|
use App\Search\Action\Input\GetMediaInfoInput;
|
||||||
use App\Search\Action\Input\SearchInput;
|
use App\Search\Action\Input\SearchInput;
|
||||||
|
use App\Search\Action\Result\RedirectToMediaResult;
|
||||||
use App\Tmdb\TmdbResult;
|
use App\Tmdb\TmdbResult;
|
||||||
use App\Torrentio\Action\Command\GetMovieOptionsCommand;
|
use App\Torrentio\Action\Command\GetMovieOptionsCommand;
|
||||||
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
use App\Torrentio\Action\Command\GetTvShowOptionsCommand;
|
||||||
@@ -28,6 +29,13 @@ final class WebController extends AbstractController
|
|||||||
): Response {
|
): Response {
|
||||||
$results = $this->searchHandler->handle($searchInput->toCommand());
|
$results = $this->searchHandler->handle($searchInput->toCommand());
|
||||||
|
|
||||||
|
if ($results instanceof RedirectToMediaResult) {
|
||||||
|
return $this->redirectToRoute('app_search_result', [
|
||||||
|
'mediaType' => $results->mediaType,
|
||||||
|
'imdbId' => $results->imdbId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->render('search/results.html.twig', [
|
return $this->render('search/results.html.twig', [
|
||||||
'results' => $results,
|
'results' => $results,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Tmdb\Framework\Controller;
|
namespace App\Tmdb\Framework\Controller;
|
||||||
|
|
||||||
|
use App\Base\Util\ImdbMatcher;
|
||||||
use App\Tmdb\Tmdb;
|
use App\Tmdb\Tmdb;
|
||||||
use App\Tmdb\TmdbResult;
|
use App\Tmdb\TmdbResult;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
@@ -17,10 +18,20 @@ class ApiController extends AbstractController
|
|||||||
$results = [];
|
$results = [];
|
||||||
|
|
||||||
$term = $request->query->get('query') ?? null;
|
$term = $request->query->get('query') ?? null;
|
||||||
|
$term = trim($term);
|
||||||
|
|
||||||
if (null !== $term) {
|
if (null !== $term) {
|
||||||
|
if (ImdbMatcher::isMatch($term)) {
|
||||||
|
$tmdbResult = $tmdb->findByImdbId($term);
|
||||||
|
$results = [
|
||||||
|
[
|
||||||
|
'data' => $tmdbResult,
|
||||||
|
'text' => $tmdbResult->title,
|
||||||
|
'value' => "$tmdbResult->mediaType|$tmdbResult->imdbId",
|
||||||
|
]
|
||||||
|
];
|
||||||
|
} else {
|
||||||
$tmdbResults = $tmdb->search($term);
|
$tmdbResults = $tmdb->search($term);
|
||||||
|
|
||||||
foreach ($tmdbResults as $tmdbResult) {
|
foreach ($tmdbResults as $tmdbResult) {
|
||||||
/** @var TmdbResult $tmdbResult */
|
/** @var TmdbResult $tmdbResult */
|
||||||
$results[] = [
|
$results[] = [
|
||||||
@@ -30,6 +41,7 @@ class ApiController extends AbstractController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $this->json([
|
return $this->json([
|
||||||
'results' => $results,
|
'results' => $results,
|
||||||
|
|||||||
@@ -185,6 +185,28 @@ class Tmdb
|
|||||||
throw new \Exception("No results found for $id");
|
throw new \Exception("No results found for $id");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function findByImdbId(string $imdbId)
|
||||||
|
{
|
||||||
|
$finder = new Find($this->client);
|
||||||
|
$result = $finder->findBy($imdbId, ['external_source' => 'imdb_id']);
|
||||||
|
|
||||||
|
if (count($result['movie_results']) > 0) {
|
||||||
|
$result = $result['movie_results'][0];
|
||||||
|
$mediaType = MediaType::Movie->value;
|
||||||
|
} elseif (count($result['tv_results']) > 0) {
|
||||||
|
$result = $result['tv_results'][0];
|
||||||
|
$mediaType = MediaType::TvShow->value;
|
||||||
|
} elseif (count($result['tv_episode_results']) > 0) {
|
||||||
|
$result = $result['tv_episode_results'][0];
|
||||||
|
$mediaType = MediaType::TvShow->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result['media_type'] = $mediaType;
|
||||||
|
$result = $this->mediaDetails($imdbId, $result['media_type']);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
public function movieDetails(string $id)
|
public function movieDetails(string $id)
|
||||||
{
|
{
|
||||||
$client = new MovieRepository($this->client);
|
$client = new MovieRepository($this->client);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class GetTvShowOptionsHandler implements HandlerInterface
|
|||||||
$file = $this->mediaFiles->episodeExists($parentShow->title, $command->season, $command->episode);
|
$file = $this->mediaFiles->episodeExists($parentShow->title, $command->season, $command->episode);
|
||||||
|
|
||||||
return new GetTvShowOptionsResult(
|
return new GetTvShowOptionsResult(
|
||||||
|
parentShow: $parentShow,
|
||||||
media: $media,
|
media: $media,
|
||||||
file: MediaFileDto::fromSplFileInfo($file),
|
file: MediaFileDto::fromSplFileInfo($file),
|
||||||
season: $command->season,
|
season: $command->season,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use OneToMany\RichBundle\Contract\ResultInterface;
|
|||||||
class GetTvShowOptionsResult implements ResultInterface
|
class GetTvShowOptionsResult implements ResultInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
public TmdbResult $parentShow,
|
||||||
public TmdbResult $media,
|
public TmdbResult $media,
|
||||||
public MediaFileDto|false $file,
|
public MediaFileDto|false $file,
|
||||||
public string $season,
|
public string $season,
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ class CodecList
|
|||||||
|
|
||||||
public static function asSelectOptions(): array
|
public static function asSelectOptions(): array
|
||||||
{
|
{
|
||||||
$result = [];
|
return [
|
||||||
foreach (static::$codecs as $codec) {
|
'h264' => 'h264',
|
||||||
$result[$codec] = $codec;
|
'h265/HEVC' => 'h265',
|
||||||
}
|
];
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
>
|
>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
|
id="search-button"
|
||||||
class="absolute top-1 right-1 flex items-center rounded
|
class="absolute top-1 right-1 flex items-center rounded
|
||||||
bg-green-600 py-1 px-2.5 border border-transparent text-center
|
bg-green-600 py-1 px-2.5 border border-transparent text-center
|
||||||
text-sm text-white transition-all
|
text-sm text-white transition-all
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<div data-live-id="{{ uniqid() }}" class="episode-container flex flex-col gap-4">
|
<div data-live-id="{{ uniqid() }}" class="episode-container flex flex-col gap-4">
|
||||||
{% for episode in this.getEpisodes().items %}
|
{% for episode in this.getEpisodes().items %}
|
||||||
<episode-container id="{{ episode_anchor(episode['season_number'], episode['episode_number']) }}" class="results"
|
<episode-container id="{{ episode_anchor(episode['season_number'], episode['episode_number']) }}" class="results"
|
||||||
|
show-title="{{ this.title }}"
|
||||||
data-tv-results-loading-icon-outlet=".loading-icon"
|
data-tv-results-loading-icon-outlet=".loading-icon"
|
||||||
data-download-button-outlet=".download-btn"
|
data-download-button-outlet=".download-btn"
|
||||||
{{ stimulus_controller('tv_results', {
|
{{ stimulus_controller('tv_results', {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
{% for result in results.results %}
|
{% for result in results.results %}
|
||||||
<tr is="dl-tr"
|
<tr is="dl-tr"
|
||||||
class="download-option bg-white dark:bg-slate-700 flex flex-col flex-no wrap r-tablerow border-b border-gray-500"
|
class="download-option bg-white dark:bg-slate-700 flex flex-col flex-no wrap r-tablerow border-b border-gray-500"
|
||||||
|
url="{{ result.url }}"
|
||||||
size="{{ result.size }}"
|
size="{{ result.size }}"
|
||||||
quality="{{ result.quality }}"
|
quality="{{ result.quality }}"
|
||||||
resolution="{{ result.resolution }}"
|
resolution="{{ result.resolution }}"
|
||||||
@@ -51,10 +52,16 @@
|
|||||||
provider="{{ result.provider }}"
|
provider="{{ result.provider }}"
|
||||||
languages="{{ result.languages|json_encode }}"
|
languages="{{ result.languages|json_encode }}"
|
||||||
media-type="{{ results.media.mediaType }}"
|
media-type="{{ results.media.mediaType }}"
|
||||||
|
imdb-id="{{ results.media.imdbId }}"
|
||||||
|
filename="{{ result.filename }}"
|
||||||
data-local-id="{{ result.localId }}"
|
data-local-id="{{ result.localId }}"
|
||||||
{% if "tvshows" == results.media.mediaType %}
|
{% if "tvshows" == results.media.mediaType %}
|
||||||
season="{{ result.season }}"
|
season="{{ result.season }}"
|
||||||
episode="{{ result.episodeNumber }}"
|
episode="{{ result.episodeNumber }}"
|
||||||
|
episode-id="{{ episode_id(result.season, result.episodeNumber) }}"
|
||||||
|
media-title="{{ results.parentShow.title }}"
|
||||||
|
{% else %}
|
||||||
|
media-title="{{ results.media.title }}"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
>
|
>
|
||||||
<td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
<td id="size" class="px-4 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-gray-50">
|
||||||
@@ -79,17 +86,7 @@
|
|||||||
{{ result.languageFlags|raw }}
|
{{ result.languageFlags|raw }}
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-start mb:justify-end">
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-end text-gray-800 dark:text-gray-50 flex flex-row gap-2 items-center justify-start mb:justify-end">
|
||||||
<button class="download-btn p-1.5 bg-green-600 rounded-md text-gray-50"
|
<button class="download-btn p-1.5 bg-green-600 rounded-md text-gray-50">
|
||||||
{{ stimulus_controller('download_button', {
|
|
||||||
url: result.url,
|
|
||||||
title: results.media.title,
|
|
||||||
filename: result.filename,
|
|
||||||
mediaType: results.media.mediaType,
|
|
||||||
imdbId: results.media.imdbId ?? app.current_route_parameters.imdbId,
|
|
||||||
episodeId: results|episode_id_from_results
|
|
||||||
}) }}
|
|
||||||
{{ stimulus_action('download_button', 'download', 'click') }}
|
|
||||||
>
|
|
||||||
Download
|
Download
|
||||||
</button>
|
</button>
|
||||||
<label for="select">
|
<label for="select">
|
||||||
|
|||||||
Reference in New Issue
Block a user