WIP: workig download button on movies and episodes
This commit is contained in:
2
assets/bootstrap.js
vendored
2
assets/bootstrap.js
vendored
@@ -6,6 +6,7 @@ import DownloadListRow from './components/download-list-row.js';
|
||||
import MonitorListRow from './components/monitor-list-row.js';
|
||||
import MovieContainer from "./components/movie-container.js";
|
||||
import StatusCheckerSpan from "./components/status-checker-span.js";
|
||||
import DownloadMediaButton from "./components/download-media-buton.js";
|
||||
|
||||
import { startStimulusApp } from '@symfony/stimulus-bundle';
|
||||
import Popover from '@stimulus-components/popover';
|
||||
@@ -26,3 +27,4 @@ customElements.define('dl-tr', DownloadOptionTr, {extends: 'tr'});
|
||||
customElements.define('download-list-row', DownloadListRow, {extends: 'tr'});
|
||||
customElements.define('monitor-list-row', MonitorListRow, {extends: 'tr'});
|
||||
customElements.define('status-checker-span', StatusCheckerSpan, {extends: 'span'});
|
||||
customElements.define('download-media-button', DownloadMediaButton);
|
||||
|
||||
58
assets/components/download-media-buton.js
Normal file
58
assets/components/download-media-buton.js
Normal file
@@ -0,0 +1,58 @@
|
||||
export default class DownloadMediaButon extends HTMLElement
|
||||
{
|
||||
#filterEl;
|
||||
#mediaType;
|
||||
#imdbId;
|
||||
#season = null;
|
||||
#episode = null;
|
||||
|
||||
connectedCallback() {
|
||||
this.#filterEl = this.querySelector('#filter');
|
||||
this.#mediaType = this.getAttribute('media-type');
|
||||
this.#imdbId = this.getAttribute('imdb-id');
|
||||
|
||||
if (this.getAttribute('season') !== null && this.getAttribute('episode') !== null) {
|
||||
this.#season = this.getAttribute('season');
|
||||
this.#episode = this.getAttribute('episode');
|
||||
} else if (this.#mediaType === 'tvshows') {
|
||||
console.warn('Season and Episode are not set for \'tvshows\' media type');
|
||||
}
|
||||
this.addEventListener('click', this.download.bind(this));
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.removeEventListener('click', this.download.bind(this));
|
||||
}
|
||||
|
||||
connectedMoveCallback() {}
|
||||
|
||||
download() {
|
||||
const preferencesForm = document.querySelector('[name="user_media_preferences_form"]');
|
||||
const preferences = {
|
||||
resolution: preferencesForm.querySelector('[id="user_media_preferences_form_resolution"]').value,
|
||||
codec: preferencesForm.querySelector('[id="user_media_preferences_form_codec"]').value,
|
||||
language: preferencesForm.querySelector('[id="user_media_preferences_form_language"]').value,
|
||||
quality: preferencesForm.querySelector('[id="user_media_preferences_form_quality"]').value,
|
||||
provider: preferencesForm.querySelector('[id="user_media_preferences_form_provider"]').value,
|
||||
}
|
||||
console.log(preferences);
|
||||
fetch('/api/download', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
mediaType: this.#mediaType,
|
||||
imdbId: this.#imdbId,
|
||||
season: this.#season,
|
||||
episode: this.#episode,
|
||||
filter: preferences,
|
||||
})
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
console.log(json)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,19 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
||||
|
||||
$matchingOption = $this->downloadOptionEvaluator->evaluateOptions($downloadOptions->results, $filter);
|
||||
|
||||
if ($matchingOption === null) {
|
||||
$download->setProgress(100);
|
||||
$download->setStatus('Failed');
|
||||
$this->downloadRepository->getEntityManager()->flush();
|
||||
$this->broadcaster->alert(
|
||||
title:'Uh oh',
|
||||
message: 'No matching download options found.',
|
||||
type: 'warning',
|
||||
mercureAlertTopic: $command->mercureAlertTopic
|
||||
);
|
||||
throw new UnrecoverableMessageHandlingException('No matching download options found.', 404);
|
||||
}
|
||||
|
||||
$download->setUrl($matchingOption->url);
|
||||
$download->setTitle($media->media->title);
|
||||
$download->setFileName(
|
||||
@@ -120,7 +133,6 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
||||
if ($download->getStatus() !== 'Paused') {
|
||||
$this->downloadRepository->updateStatus($download->getId(), 'Complete');
|
||||
}
|
||||
|
||||
} catch (\Throwable $exception) {
|
||||
throw new UnrecoverableMessageHandlingException($exception->getMessage(), 500);
|
||||
}
|
||||
@@ -156,38 +168,42 @@ readonly class DownloadMediaHandler implements HandlerInterface
|
||||
{
|
||||
$fileType = $option->ptn->container;
|
||||
return (match ($mediaType) {
|
||||
MediaType::Movie => function () use ($media, $fileType) {
|
||||
$template = "%s (%s) [imdbid-%s].%s";
|
||||
return sprintf(
|
||||
MediaType::Movie => function () use ($media, $fileType, $option) {
|
||||
$template = "%s (%s) [imdbid-%s]";
|
||||
$filename = sprintf(
|
||||
$template,
|
||||
$media->media->title,
|
||||
$media->media->year,
|
||||
$media->media->imdbId,
|
||||
$fileType
|
||||
);
|
||||
if ($option->resolution !== null && $option->resolution !== '-') {
|
||||
$filename .= ' - [' . $option->resolution . ']';
|
||||
}
|
||||
return $filename . '.' . $fileType;
|
||||
},
|
||||
MediaType::TvShow => function () use ($media, $fileType) {
|
||||
MediaType::TvShow => function () use ($media, $fileType, $option) {
|
||||
$template = "%s %s.%s";
|
||||
$episodeId = EpisodeId::fromSeasonEpisodeNumbers(
|
||||
$media->season,
|
||||
$media->episode,
|
||||
);
|
||||
return sprintf(
|
||||
$filename = sprintf(
|
||||
$template,
|
||||
$media->media->title,
|
||||
$episodeId,
|
||||
$fileType
|
||||
);
|
||||
if ($option->resolution !== null && $option->resolution !== '-') {
|
||||
$filename .= ' - [' . $option->resolution . ']';
|
||||
}
|
||||
return $filename . '.' . $fileType;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
public function validateDownloadUrl(string $downloadUrl)
|
||||
{
|
||||
$badFileSizes = [
|
||||
2119075, // copyright infringement
|
||||
];
|
||||
|
||||
$badFileLocations = [
|
||||
'https://torrentio.strem.fun/videos/failed_infringement_v2.mp4' => 'Removed for Copyright Infringement.',
|
||||
'https://torrentio.strem.fun/videos/downloading_v2.mp4' => 'Your torrent is downloading to your debrid provider.'
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Download\Action\Input;
|
||||
use App\Download\Action\Command\DownloadMediaCommand;
|
||||
use OneToMany\RichBundle\Attribute\PropertyIgnored;
|
||||
use OneToMany\RichBundle\Attribute\SourceRequest;
|
||||
use OneToMany\RichBundle\Attribute\SourceRoute;
|
||||
use OneToMany\RichBundle\Attribute\SourceSecurity;
|
||||
use OneToMany\RichBundle\Contract\CommandInterface;
|
||||
use OneToMany\RichBundle\Contract\InputInterface;
|
||||
@@ -17,15 +18,19 @@ class DownloadMediaInput implements InputInterface
|
||||
|
||||
public function __construct(
|
||||
#[SourceRequest('imdbId')]
|
||||
#[SourceRoute('imdbId')]
|
||||
public string $imdbId,
|
||||
|
||||
#[SourceRequest('mediaType')]
|
||||
#[SourceRoute('mediaType')]
|
||||
public string $mediaType,
|
||||
|
||||
#[SourceRequest('season', nullify: true)]
|
||||
#[SourceRequest('season')]
|
||||
#[SourceRoute('season')]
|
||||
public int|string|null $season = null,
|
||||
|
||||
#[SourceRequest('episode', nullify: true)]
|
||||
#[SourceRequest('episode')]
|
||||
#[SourceRoute('episode')]
|
||||
public int|string|null $episode = null,
|
||||
|
||||
#[SourceRequest('url', nullify: true)]
|
||||
@@ -64,8 +69,8 @@ class DownloadMediaInput implements InputInterface
|
||||
return new DownloadMediaCommand(
|
||||
$this->imdbId,
|
||||
$this->mediaType,
|
||||
$this->season,
|
||||
$this->episode,
|
||||
(int) $this->season,
|
||||
(int) $this->episode,
|
||||
$this->url,
|
||||
$this->filter,
|
||||
$this->downloadId,
|
||||
|
||||
@@ -16,6 +16,8 @@ use App\Download\DownloadEvents;
|
||||
use App\Download\Framework\Entity\Download;
|
||||
use App\Download\Framework\Repository\DownloadRepository;
|
||||
use App\EventLog\Action\Command\AddEventLogCommand;
|
||||
use App\Search\Action\Command\GetMediaInfoCommand;
|
||||
use App\Search\Action\Handler\GetMediaInfoHandler;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@@ -30,15 +32,18 @@ class ApiController extends AbstractController
|
||||
private readonly Broadcaster $broadcaster, private readonly RequestStack $requestStack,
|
||||
) {}
|
||||
|
||||
#[Route('/api/download', name: 'api_download', methods: ['POST'])]
|
||||
#[Route('/api/download/{mediaType?}/{imdbId?}/{season?}/{episode?}', name: 'api_download', methods: ['GET', 'POST'])]
|
||||
public function download(
|
||||
DownloadMediaInput $input,
|
||||
DownloadMediaHandler $handler,
|
||||
GetMediaInfoHandler $getMediaInfoHandler,
|
||||
): Response {
|
||||
$media = $getMediaInfoHandler->handle(new GetMediaInfoCommand($input->imdbId, $input->mediaType));
|
||||
$download = $this->downloadRepository->insertNew(
|
||||
$this->getUser(),
|
||||
$input->imdbId,
|
||||
$input->mediaType,
|
||||
$media->media->title,
|
||||
$input->season,
|
||||
$input->episode,
|
||||
);
|
||||
|
||||
@@ -61,6 +61,7 @@ class DownloadRepository extends ServiceEntityRepository
|
||||
UserInterface $user,
|
||||
string $imdbId,
|
||||
string $mediaType,
|
||||
string $title,
|
||||
int|null $season = null,
|
||||
int|null $episode = null,
|
||||
string $status = 'New'
|
||||
@@ -69,6 +70,7 @@ class DownloadRepository extends ServiceEntityRepository
|
||||
$download = (new Download())
|
||||
->setUser($user)
|
||||
->setImdbId($imdbId)
|
||||
->setTitle($title)
|
||||
->setMediaType($mediaType)
|
||||
->setProgress(0)
|
||||
->setStatus($status);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
{% set preferences_form = form %}
|
||||
{{ form_start(preferences_form) }}
|
||||
<h3 class="font-bold text-lg mb-2 md:mb-4">Apply a filter to your results</h3>
|
||||
<h3 class="font-bold text-lg mb-2 md:mb-4">What type of file do you want?</h3>
|
||||
<div class="flex flex-col md:flex-row gap-2 justify-between">
|
||||
{{ form_row(preferences_form.resolution) }}
|
||||
{{ form_row(preferences_form.codec) }}
|
||||
@@ -37,6 +37,17 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if results.media.mediaType == "movies" %}
|
||||
<div class="w-full flex-col md:flex-row justify-between gap-2">
|
||||
<download-media-button
|
||||
class="px-2 py-1 bg-green-500 bg-opacity-60 font-medium rounded-lg text-sm cursor-pointer self-end"
|
||||
title="Download {{ results.media.title }}"
|
||||
media-type="{{ results.media.mediaType }}"
|
||||
imdb-id="{{ results.media.imdbId }}">
|
||||
download
|
||||
</download-media-button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ form_end(preferences_form) }}
|
||||
|
||||
<div class="flex flex-col-reverse md:flex-row justify-between">
|
||||
|
||||
@@ -53,11 +53,21 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 justify-between">
|
||||
<div class="flex flex-col items-center">
|
||||
<input class="episode-selector" type="checkbox"
|
||||
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
||||
/>
|
||||
</div>
|
||||
<download-media-button
|
||||
class="px-2 py-2 bg-green-500 bg-opacity-80 font-medium text-center rounded-lg text-sm cursor-pointer items-center self-end"
|
||||
title="Download season {{ episode.seasonNumber }} episode {{ episode.episodeNumber }} of {{ this.title }}"
|
||||
media-type="tvshows"
|
||||
imdb-id="{{ this.imdbId }}"
|
||||
season="{{ episode.seasonNumber }}"
|
||||
episode="{{ episode.episodeNumber }}"
|
||||
>
|
||||
<twig:ux:icon name="bi:download" width="20" />
|
||||
</download-media-button>
|
||||
|
||||
<input class="episode-selector" type="checkbox"
|
||||
{{ stimulus_target('tv-results', 'episodeSelector') }}
|
||||
/>
|
||||
|
||||
<button class="dropdown-button flex flex-col items-end transition-transform duration-300 ease-in-out rotate-90" title="Click to expand the results table for season {{ episode.seasonNumber }} episode {{ episode.episodeNumber }}.">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32">
|
||||
<path
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
|
||||
<div class="w-full flex flex-col">
|
||||
<div class="mb-4 flex flex-row gap-2 justify-between">
|
||||
<h3 class="text-xl font-medium leading-tight font-bold text-gray-50">
|
||||
{{ results.media.title }} ({{ results.media.year }})
|
||||
</h3>
|
||||
<div class="flex flex-row justify-between items-center gap-2 w-full">
|
||||
<h3 class="text-xl font-medium leading-tight font-bold text-gray-50">
|
||||
{{ results.media.title }} ({{ results.media.year }})
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{% if results.media.mediaType == "tvshows" %}
|
||||
<div {{ stimulus_controller('monitor_button', {
|
||||
@@ -96,9 +98,9 @@
|
||||
|
||||
{% if "movies" == results.media.mediaType %}
|
||||
<div class="flex flex-row justify-start items-end grow text-xs">
|
||||
<span class="results-count-badge py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white">
|
||||
<span class="results-count-number" id="movie_results_count">-</span> results
|
||||
</span>
|
||||
{# <span class="results-count-badge py-1 px-1.5 mr-1 grow-0 font-bold text-xs bg-green-600 rounded-lg hover:cursor-pointer hover:bg-green-700 text-white">#}
|
||||
{# <span class="results-count-number" id="movie_results_count">-</span> results#}
|
||||
{# </span>#}
|
||||
|
||||
<twig:Turbo:Frame id="meb_{{ results.media.imdbId }}" src="{{ path('api.library.search', {
|
||||
title: results.media.title,
|
||||
@@ -133,18 +135,18 @@
|
||||
<twig:Filter results="{{ results }}" filter="{{ filter }}" />
|
||||
|
||||
{% if "movies" == results.media.mediaType %}
|
||||
<movie-container class="results"
|
||||
{{ stimulus_controller('movie_results', {title: results.media.title, tmdbId: results.media.tmdbId, imdbId: results.media.imdbId}) }}
|
||||
>
|
||||
<twig:Turbo:Frame id="movie_results_frame" src="{{ path('app_torrentio_movies', {
|
||||
tmdbId: results.media.tmdbId,
|
||||
imdbId: results.media.imdbId,
|
||||
target: 'movie_results_frame',
|
||||
block: 'movie_results'
|
||||
}) }}">
|
||||
<twig:ux:icon name="codex:loader" height="20" width="20" data-loading-icon-target="icon" class="text-end" title="Loading download options for {{ results.media.title }}" />
|
||||
</twig:Turbo:Frame>
|
||||
</movie-container>
|
||||
{# <movie-container class="results"#}
|
||||
{# {{ stimulus_controller('movie_results', {title: results.media.title, tmdbId: results.media.tmdbId, imdbId: results.media.imdbId}) }}#}
|
||||
{# >#}
|
||||
{# <twig:Turbo:Frame id="movie_results_frame" src="{{ path('app_torrentio_movies', {#}
|
||||
{# tmdbId: results.media.tmdbId,#}
|
||||
{# imdbId: results.media.imdbId,#}
|
||||
{# target: 'movie_results_frame',#}
|
||||
{# block: 'movie_results'#}
|
||||
{# }) }}">#}
|
||||
{# <twig:ux:icon name="codex:loader" height="20" width="20" data-loading-icon-target="icon" class="text-end" title="Loading download options for {{ results.media.title }}" />#}
|
||||
{# </twig:Turbo:Frame>#}
|
||||
{# </movie-container>#}
|
||||
{% elseif "tvshows" == results.media.mediaType %}
|
||||
<twig:TvEpisodeList
|
||||
results="results"
|
||||
|
||||
Reference in New Issue
Block a user