You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/views/episodes.html

1120 lines
56 KiB

{% extends '_main.html' %}
{% block title %}Series - Bazarr{% endblock %}
{% block page_head %}
<style>
#seriesFanart {
background-repeat: no-repeat;
background-size: cover;
background-position: top center;
box-sizing: initial;
margin-left: -32px;
margin-top: -5px;
padding: 2em;
}
#seriesDetails {
padding: 30px;
background: rgba(0, 0, 0, 0.7);
color: white;
margin: -32px;
}
#seriesPoster {
max-height: 250px;
}
h1 {
color: white;
}
span {
margin-right: 0.5em;
}
.badge {
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
</style>
{% endblock page_head %}
{% block bcleft %}
<div class="">
<button class="btn btn-outline" id="scan_button">
<div><i class="fas fa-sync align-top text-themecolor text-center font-20" aria-hidden="true"></i></div>
<div class="align-bottom text-themecolor small text-center">Scan Disk</div>
</button>
<button class="btn btn-outline" id="search_button">
<div><i class="fas fa-search align-top text-themecolor text-center font-20" aria-hidden="true"></i></div>
<div class="align-bottom text-themecolor small text-center">Search</div>
</button>
</div>
{% endblock bcleft %}
{% block bcright %}
<div class="d-flex m-t-5 justify-content-end">
<button class="btn btn-outline" id="edit_button">
<div><i class="fas fa-wrench align-top text-themecolor text-center font-20" aria-hidden="true"></i></div>
<div class="align-bottom text-themecolor small text-center">Edit Series</div>
</button>
</div>
{% endblock bcright %}
{% block body %}
<div class="container-fluid" id="seriesFanart">
<div class="row justify-content-md-center" id="seriesDetails">
<div class="col-sm-auto" id="seriesPosterColumn">
<img id="seriesPoster" src="">
</div>
<div class="col">
<div class="container-fluid">
<div class="row">
<h1><span id="seriesTitle"></span></h1>
<i class="far fa-clone" id="seriesAlternateTitles" data-toggle="tooltip" data-placement="right"
title="None" data-html="true"></i>
</div>
<div class="row">
<h5><span id="seriesAudioLanguage" class="badge badge-secondary"></span></h5>
<h5><span id="seriesMappedPath" class="badge badge-secondary"></span></h5>
<h5><span id="seriesFileCount" class="badge badge-secondary"></span></h5>
<h5><span id="seriesType" class="badge badge-secondary"></span></h5>
<h5><span id="seriesTags" class="badge badge-secondary" data-toggle="tooltip" data-placement="right"
title="None" data-html="true">Tags</span></h5>
</div>
<div class="row">
<h5><span id="seriesSubtitlesLanguages"></span></h5>
</div>
<div class="row">
<h5><span id="seriesHearingImpaired" class="badge badge-secondary"></span></h5>
<h5><span id="seriesForced" class="badge badge-secondary"></span></h5>
</div>
<div class="row">
<span id="seriesDescription"></span>
</div>
</div>
</div>
</div>
</div>
<!-- ============================================================== -->
<!-- Bread crumb and right sidebar toggle -->
<!-- ============================================================== -->
<table id="episodes" class="table table-striped" style="width:100%">
<thead>
<tr>
<th></th>
<th>Episode</th>
<th>Title</th>
<th>Existing Subtitles</th>
<th>Missing Subtitles</th>
<th>Manual Search</th>
<th>Manual Upload</th>
<th>Tools</th>
</tr>
</thead>
</table>
<div id="manualSearchModal" class="modal" tabindex="-1" role="dialog" data-backdrop="static">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span id="series_title_span"></span></h5><br>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<h6>Episode path is: <span id="episode_path_span" class="badge badge-secondary"></span>
<br>Scenename is: <span id="episode_scenename_span" class="badge badge-secondary"></span></h6>
<div class="container-fluid" style="padding:0px;">
<table id="search_result" class="table table-striped" style="width:100%">
<thead>
<tr>
<th style="text-align: left;">Score:</th>
<th></th>
<th style="text-align: left;">Provider:</th>
<th style="text-align: left;">Matching:</th>
<th style="text-align: left;">Releases:</th>
<th style="text-align: left;">Uploader:</th>
<th></th>
</tr>
</thead>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<div id="uploadModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span id="upload_series_title_span"></span></h5><br>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form class="form" name="upload_form" id="upload_form">
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-sm-2 text-right">
Language
</div>
<div class="form-group col-sm-8 pl-sm-0">
<select class="selectpicker" id="manual_language_select" name="language"></select>
</div>
</div>
<div class="row">
<div class="col-sm-2 text-right">
Forced
</div>
<div class="form-group col-sm-1 pl-sm-0">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="forced_checkbox"
name="forced">
<span class="custom-control-label" for="forced_checkbox"></span>
</label>
</div>
</div>
<div class="row">
<div class="col-sm-2 text-right">
File
</div>
<div class="form-group col-sm-7 pl-sm-0">
<div class="custom-file">
<input type="file" class="custom-file-input" id="upload" name="upload">
<label class="custom-file-label" for="upload">Choose file</label>
</div>
</div>
</div>
</div>
<input type="hidden" id="upload_episodePath" name="episodePath" value=""/>
<input type="hidden" id="upload_sceneName" name="sceneName" value=""/>
<input type="hidden" id="upload_sonarrSeriesId" name="sonarrSeriesId" value=""/>
<input type="hidden" id="upload_sonarrEpisodeId" name="sonarrEpisodeId" value=""/>
<input type="hidden" id="upload_title" name="title" value=""/>
<input type="hidden" id="upload_audioLanguage" name="audioLanguage" value=""/>
</div>
<div class="modal-footer">
<button type="submit" id="upload_save_button" class="btn btn-info">Save</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<div id="editModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span id="edit_series_title_span"></span></h5><br>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form class="form" name="edit_form" id="edit_form">
<div class="modal-body">
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 text-right">
Audio Language
</div>
<div class="form-group col-sm-8 pl-sm-0">
<span id="edit_audio_language_span"></span>
</div>
</div>
<div class="row">
<div class="col-sm-3 text-right">
Subtitles Language(s)
</div>
<div class="form-group col-sm-8 pl-sm-0">
<select class="selectpicker" id="edit_languages_select" name="languages" multiple
data-live-search="true"></select>
</div>
</div>
<div class="row">
<div class="col-sm-3 text-right">
Hearing-Impaired
</div>
<div class="form-group col-sm-1 pl-sm-0">
<label class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="hi_checkbox" name="hi">
<span class="custom-control-label" for="hi_checkbox"></span>
</label>
</div>
</div>
<div class="row">
<div class="col-sm-3 text-right">
Forced
</div>
<div class="form-group col-sm-8 pl-sm-0">
<select class="selectpicker" id="edit_forced_select" name="forced">
<option value="False">False</option>
<option value="True">True</option>
<option value="Both">Both</option>
</select>
</div>
</div>
</div>
<input type="hidden" id="edit_sonarrSeriesId" name="sonarrSeriesId" value=""/>
</div>
<div class="modal-footer">
<button type="submit" id="edit_save_button" class="btn btn-info">Save</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<div id="episodeHistoryModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span id="episode_history_title_span"></span></h5><br>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<table id="episode_history_result" class="table table-striped" style="width:100%">
<thead>
<tr>
<th></th>
<th style="text-align: left;">Language.:</th>
<th style="text-align: left;">Provider:</th>
<th style="text-align: left;">Score:</th>
<th style="text-align: left;">Date:</th>
<th style="text-align: left;">Actions:</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div id="episodeToolsModal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-xl" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><span id="episode_tools_title_span"></span></h5><br>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="container-fluid">
<table id="episode_tools_result" class="table table-striped" style="width:100%">
<thead>
<tr>
<th style="text-align: left;">Language:</th>
<th style="text-align: left;">Filename:</th>
<th style="text-align: left;">Sync:</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock body %}
{% block tail %}
<script>
// make the filename appear in upload file dialog once a file have been selected.
$(document).ready(function () {
document.querySelector('.custom-file-input').addEventListener('change', function (e) {
var fileName = document.getElementById("upload").files[0].name;
var nextSibling = e.target.nextElementSibling;
nextSibling.innerText = fileName;
});
$('#series_nav').addClass("active");
seriesDetailsRefresh();
getLanguages();
getEnabledLanguages();
var collapsedGroups = {};
var table = $('#episodes').DataTable({
dom: "tr",
processing: true,
serverSide: true,
language: {
zeroRecords: 'No Episodes Found For This Series',
processing: "Loading Episodes..."
},
searching: false,
ordering: false,
lengthChange: false,
responsive: true,
pageLength: {{ settings.general.page_size }},
ajax: "{{ url_for('api.episodes') }}?seriesid={{id}}",
initComplete: function() {
$('.dtrg-start').each( function(i, item) {
if (i > 0) {
var name = $(item).data('name');
collapsedGroups[name] = !collapsedGroups[name];
table.draw(false);
}
})
},
rowGroup: {
dataSrc: 'season',
startRender: function (rows, group) {
var collapsed = !!collapsedGroups[group];
rows.nodes().each(function (r) {
r.style.display = collapsed ? 'none' : '';
});
return $('<tr/>')
.append('<td colspan=' + rows.columns()[0].length + '>Season ' + group + '</td>')
.attr('data-name', group)
.toggleClass('collapsed', collapsed);
}
},
columns: [
{
data: "monitored",
render: function (data, type, row) {
if (data === 'False') {
return '<i class="far fa-bookmark" data-toggle="tooltip" data-placement="right" title="Episode unmonitored in Sonarr"></i>';
} else if (data === 'True') {
return '<i class="fas fa-bookmark" data-toggle="tooltip" data-placement="right" title="Episode monitored in Sonarr"></i>';
}
}
},
{data: "episode"},
{
data: null,
render: function (data) {
var title_path = '<a href="" data-toggle="tooltip" title="Path is: ' + data.mapped_path + '" data-season=' + data.season + ' data-episode=' + data.episode + ' data-episodeTitle="' + data.title + '" data-sonarrEpisodeId=' + data.sonarrEpisodeId + ' class="episode_history">' + data.title + '</a>';
if (data.scene_name) {
return '<i class="fas fa-info-circle" data-toggle="tooltip" data-placement="right" title="' + data.scene_name + '"></i> ' + title_path;
} else {
return title_path;
}
}
},
{
data: null,
render: function (data) {
if (data.subtitles !== 'None') {
var languages = '';
data.subtitles.forEach(appendFunc);
return languages;
} else {
return null;
}
function appendFunc(value) {
if (value[1] === null) {
languages = languages + '<span class="badge badge-secondary" data-toggle="tooltip" data-placement="right" title="' + value[0].name + ((value[0].forced) ? ' forced' : '') + '">' + value[0].code2 + ((value[0].forced) ? ':forced' : '') + '</span> ';
} else {
languages = languages + '<a href="" class="remove_subtitles badge badge-secondary" data-toggle="tooltip" data-placement="right" title="' + value[0].name + ((value[0].forced) ? ' forced' : '') + '" data-episodePath="' + data.mapped_path + '" data-language="' + value[0].code3 + '" data-forced="' + value[0].forced + '" data-subtitlesPath="' + value[1] + '" data-sonarrEpisodeId=' + data.sonarrEpisodeId + '>' + value[0].code2 + ((value[0].forced) ? ':forced' : '') + ' <i class="far fa-trash-alt"></i></a> ';
}
}
}
},
{
data: null,
render: function (data) {
if (data.missing_subtitles !== 'None') {
var languages = '';
data.missing_subtitles.forEach(appendFunc);
return languages;
} else {
return null;
}
function appendFunc(value) {
languages = languages + '<a href="" class="get_subtitle badge badge-secondary" data-toggle="tooltip" data-placement="right" title="' + value.name + ((value.forced) ? ' forced' : '') + '" data-episodepath="' + data.mapped_path + '" data-scenename="' + data.scene_name + '" data-title="' + data.title + '" data-language="' + value.code3 + '" data-hi="' + seriesDetails.hearing_impaired + '" data-forced="' + value.forced + '" data-sonarrepisodeid=' + data.sonarrEpisodeId + '>' + value.code2 + ((value.forced) ? ':forced' : '') + ' <i class="fas fa-search"></i></a> ';
}
}
},
{
data: null,
render: function (data) {
if (data.desired_languages !== '[]') {
return '<a href="" class="manual_search badge badge-secondary" data-season=' + data.season + ' data-episode=' + data.episode + ' data-episode_title="' + data.title + '" data-episodePath="' + data.mapped_path + '" data-sceneName="' + data.scene_name + '" data-language="' + data.desired_languages + '" data-sonarrEpisodeId=' + data.sonarrEpisodeId + '><i class="fas fa-user"></i></a>';
} else {
return ''
}
}
},
{
data: null,
render: function (data) {
if (data.desired_languages !== '[]') {
return '<a href="" class="upload_subtitle badge badge-secondary" data-episodePath="' + data.mapped_path + '" data-sceneName"' + data.scene_name + '" data-sonarrSeriesId="' + seriesDetails['sonarrSeriesId'] + '" data-sonarrEpisodeId="' + data.sonarrEpisodeId + '" data-season="' + data.season + '" data-episode="' + data.episode + '" data-episode_title="' + data.title + '"><i class="fas fa-cloud-upload-alt"></i></a>';
} else {
return ''
}
}
},
{
data: null,
render: function (data) {
return '<a href="" class="episode_tools badge badge-secondary" data-sonarrEpisodeId="' + data.sonarrEpisodeId + '" data-season="' + data.season + '" data-episode="' + data.episode + '" data-episode_title="' + data.title + '"><i class="fa fa-briefcase"></i></a>';
}
}
]
});
$('#episodes').on('click', 'tr.dtrg-start', function () {
var name = $(this).data('name');
collapsedGroups[name] = !collapsedGroups[name];
table.draw(false);
});
$('#episodes').on('click', '.remove_subtitles', function (e) {
$(this).tooltip('dispose');
e.preventDefault();
const values = {
episodePath: $(this).attr("data-episodePath"),
language: $(this).attr("data-language"),
forced: $(this).attr("data-forced"),
subtitlesPath: $(this).attr("data-subtitlesPath"),
sonarrSeriesId: seriesDetails['sonarrSeriesId'],
sonarrEpisodeId: $(this).attr("data-sonarrEpisodeId"),
tvdbid: seriesDetails['tvdbId']
};
var cell = $(this).parent();
$.ajax({
url: "{{ url_for('api.episodessubtitlesdelete') }}",
type: "DELETE",
dataType: "json",
data: values,
beforeSend: function () {
cell.html('<div class="spinner-border spinner-border-sm" role="status"><span class="sr-only">Loading...</span></div>');
}
});
});
$('#episodes').on('click', '.get_subtitle', function (e) {
$(this).tooltip('dispose');
e.preventDefault();
const values = {
episodePath: $(this).attr("data-episodepath"),
sceneName: $(this).attr("data-scenename"),
language: $(this).attr("data-language"),
hi: $(this).attr("data-hi"),
forced: $(this).attr("data-forced"),
sonarrSeriesId: seriesDetails['sonarrSeriesId'],
sonarrEpisodeId: $(this).attr('data-sonarrepisodeid'),
title: seriesDetails['title']
};
var cell = $(this).parent();
$.ajax({
url: "{{ url_for('api.episodessubtitlesdownload') }}",
type: "POST",
dataType: "json",
data: values,
beforeSend: function () {
cell.html('<div class="spinner-border spinner-border-sm" role="status"><span class="sr-only">Loading...</span></div>');
}
});
});
$('#episodes').on('click', '.manual_search', function (e) {
e.preventDefault();
$("#series_title_span").html(seriesDetails['title'] + ' - ' + $(this).data("season") + 'x' + $(this).data("episode") + ' - ' + $(this).data("episode_title"));
$("#episode_path_span").html($(this).attr("data-episodePath"));
$("#episode_scenename_span").html($(this).attr("data-sceneName"));
episodePath = $(this).attr("data-episodePath");
sceneName = $(this).attr("data-sceneName");
language = $(this).attr("data-language");
hi = seriesDetails['hearing_impaired'];
forced = seriesDetails['forced'];
sonarrSeriesId = seriesDetails['sonarrSeriesId'];
sonarrEpisodeId = $(this).attr("data-sonarrEpisodeId");
var languages = Array.from(seriesDetails['languages']);
var is_pb = languages.includes('pb');
var is_pt = languages.includes('pt');
const values = {
episodePath: episodePath,
sceneName: sceneName,
language: language,
hi: hi,
forced: forced,
sonarrSeriesId: sonarrSeriesId,
sonarrEpisodeId: sonarrEpisodeId,
title: seriesDetails['title']
};
$('#search_result').DataTable({
destroy: true,
language: {
zeroRecords: 'No Subtitles Found For This Episode',
processing: "Searching{% if settings.general.anti_captcha_provider != 'None' %} (possibly solving captcha){% endif %}...",
search: "Filter:"
},
paging: true,
lengthChange: true,
pageLength: {{ settings.general.page_size_manual_search }},
lengthMenu: [ 5, 10, 15, 20, 25 ],
searching: true,
scrollX: true,
ordering: false,
processing: true,
serverSide: false,
ajax: {
url: '{{ url_for('api.episodessubtitlesmanualsearch') }}',
type: 'POST',
data: values
},
columns: [
{
data: 'score',
render: function (data) {
return data + '%';
}
},
{
data: null,
render: function (data) {
let lng = data.language;
if (data.language === "pt" && is_pb === true && is_pt === false) {
lng = 'pb'
}
let text = '<div class="badge badge-secondary" style="margin:1px;">' + lng.toUpperCase() + '</div>';
if (data.hearing_impaired == "True") {
text += '<div class="badge badge-dark" style="margin:1px;">HI</div>';
}
return text;
}
},
{
data: null,
render: function (data) {
return '<a href="' + data.url + '" target="_blank">' + data.provider + '</a>';
}
},
{
data: null,
searchable: false,
render: function (data) {
const array_matches = data.matches;
const array_dont_matches = data.dont_matches;
let i;
let text = '<div class="dropdown"><div class="btn-group dropdown"><button class="btn btn-secondary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" style="margin:1px;"><i class="fas fa-check-circle" style="color: green;"></i> ' + array_matches.length + '</button><div class="dropdown-menu" aria-labelledby="dropdownMenuButton">';
for (i = 0; i < array_matches.length; i++) {
text += '<a class="dropdown-item disabled" href="#">' + array_matches[i] + '</a>';
}
text += '</div>';
text += '<div class="dropdown"><button class="btn btn-secondary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" style="margin:1px;"><i class="fas fa-times-circle" style="color: red;"></i> ' + array_dont_matches.length + '</button><div class="dropdown-menu" aria-labelledby="dropdownMenuButton">';
for (i = 0; i < array_dont_matches.length; i++) {
text += '<a class="dropdown-item disabled" href="#">' + array_dont_matches[i] + '</a>';
}
text += '</div></div></div>';
return text;
}
},
{
data: null,
render: function (data) {
const array_release_info = data.release_info;
let i;
let text;
if (array_release_info.length == 1) {
text = '<div style="font-size: 75%;font-weight: 400;">' + array_release_info[0] + '</div>';
}
else {
text = '<div class="dropdown"><button class="btn btn-secondary btn-sm dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><i class="\tfas fa-comment-dots"></i> ' + array_release_info.length + '</button><div class="dropdown-menu" aria-labelledby="dropdownMenuButton">';
for (i = 0; i < array_release_info.length; i++) {
text += '<a class="dropdown-item" href="#">' + array_release_info[i] + '</a>';
}
text += '</div></div>';
}
return text;
}
},
{
data: 'uploader',
render: function ( data ) {
return '<div class="badge badge-secondary">' + data + '</div>';
}
},
{
data: null,
searchable: false,
render: function (data) {
return '<a href="" class="manual_download badge badge-secondary" data-episodePath="' + episodePath + '" data-sceneName="' + sceneName + '" data-sonarrEpisodeId=' + sonarrEpisodeId + ' data-subtitle="' + data.subtitle + '" data-provider="' + data.provider + '" data-language="' + data.language + '" data-forced="' + forced + '"><i class="fas fa-download" style="margin-right:0px" ></i></a>';
}
}
]
});
$('#manualSearchModal')
.modal({
focus: false
});
});
$('#search_result').on('click', '.manual_download', function (e) {
e.preventDefault();
const values = {
episodePath: $(this).attr("data-episodepath"),
sceneName: $(this).attr("data-scenename"),
language: $(this).attr("data-language"),
hi: seriesDetails['hearing_impaired'],
forced: $(this).attr("data-forced"),
provider: $(this).attr("data-provider"),
subtitle: $(this).attr("data-subtitle"),
sonarrSeriesId: seriesDetails['sonarrSeriesId'],
sonarrEpisodeId: $(this).attr('data-sonarrepisodeid'),
title: seriesDetails['title']
};
var cell = $(this).parent()
;
$.ajax({
url: "{{ url_for('api.episodessubtitlesmanualdownload') }}",
type: "POST",
dataType: "json",
data: values,
beforeSend: function () {
cell.html('<div class="spinner-border spinner-border-sm" role="status"><span class="sr-only">Loading...</span></div>');
},
complete: function (data) {
$('#manualSearchModal').modal('hide');
}
});
});
$('#episodes').on('click', '.upload_subtitle', function (e) {
e.preventDefault();
$("#upload_series_title_span").html(seriesDetails['title'] + ' - ' + $(this).data("season") + 'x' + $(this).data("episode") + ' - ' + $(this).data("episode_title"));
$('#upload_episodePath').val($(this).data("episodepath"));
$('#upload_sceneName').val($(this).data("scenename"));
$('#upload_sonarrSeriesId').val($(this).data("sonarrseriesid"));
$('#upload_sonarrEpisodeId').val($(this).data("sonarrepisodeid"));
$('#upload_title').val($(this).data("episode_title"));
$('#upload_audioLanguage').val(seriesDetails['audio_language']['name']);
$('#manual_language_select').empty();
$.each(enabledLanguages, function (i, item) {
$('#manual_language_select').append('<option value="' + item.code2 + '">' + item.name + '</option>');
});
$("#manual_language_select").selectpicker("refresh");
$('#uploadModal')
.modal({
focus: false
});
});
$('#upload_form').on('submit', function (e) {
e.preventDefault();
var formdata = new FormData(document.getElementById("upload_form"));
$.ajax({
url: "{{ url_for('api.episodessubtitlesupload') }}",
data: formdata,
processData: false,
contentType: false,
type: 'POST',
beforeSend: function () {
$('#upload_save_button').html('<div class="spinner-border spinner-border-sm" role="status"><span class="sr-only">Loading...</span></div>');
},
complete: function () {
$('#uploadModal').modal('hide');
}
});
});
$('#scan_button').on('click', function (e) {
e.preventDefault();
$.ajax({
url: "{{ url_for('api.episodesscandisk', seriesid=id) }}",
type: 'GET',
beforeSend: function () {
$('#scan_button').find("i").addClass('fa-spin');
},
complete: function () {
$('#scan_button').find("i").removeClass('fa-spin');
}
});
});
$('#search_button').on('click', function (e) {
e.preventDefault();
$.ajax({
url: "{{ url_for('api.episodessearchmissing', seriesid=id) }}",
type: 'GET',
beforeSend: function () {
$('#search_button').find("i").addClass('fa-spin');
},
complete: function () {
$('#search_button').find("i").removeClass('fa-spin');
}
});
});
$('#edit_button').on('click', function (e) {
e.preventDefault();
$("#edit_series_title_span").html(seriesDetails['title']);
$("#edit_audio_language_span").text(seriesDetails['audio_language']['name']);
$('#edit_sonarrSeriesId').val(seriesDetails['sonarrSeriesId']);
$('#edit_languages_select').empty();
if ('{{settings.general.single_language}}' === 'True') {
$('#edit_languages_select').selectpicker({maxOptions: 1});
$('#edit_languages_select').append('<option value="None">None</option>');
}
$.each(enabledLanguages, function (i, item) {
$('#edit_languages_select').append('<option value="' + item.code2 + '">' + item.name + '</option>');
});
$("#edit_languages_select").selectpicker("refresh");
var selected_languages = Array();
$.each(Array.from(seriesDetails['languages']), function (i, item) {
selected_languages.push(item.code2);
});
$('#edit_languages_select').selectpicker('val', selected_languages);
$('#hi_checkbox').prop('checked', (seriesDetails['hearing_impaired'] === 'True'));
$('#edit_forced_select').val(seriesDetails['forced']).change();
$('#editModal')
.modal({
focus: false
});
});
$('#edit_form').on('submit', function (e) {
e.preventDefault();
var formdata = new FormData(document.getElementById("edit_form"));
$.ajax({
url: "{{ url_for('api.series') }}?seriesid={{id}}",
data: formdata,
processData: false,
contentType: false,
type: 'POST',
success: function () {
seriesDetailsRefresh();
$('#editModal').modal('hide');
}
});
});
$('#uploadModal').on('hidden.bs.modal', function () {
$(this).find('form')[0].reset();
});
events.on('event', function (event) {
var event_json = JSON.parse(event);
if (event_json.series === {{id}}) {
if (event_json.type === 'series' && event_json.action === 'update' && event_json.episode == null) {
seriesDetailsRefresh();
}
if (event_json.type === 'episode' && event_json.action === 'insert') {
$.ajax({
url: "{{ url_for('api.episodes') }}?seriesid=" + event_json.series + "&episodeid=" + event_json.episode,
success: function (data) {
if (data.data.length) {
$('#episodes').DataTable().rows.add(data.data);
$('#episodes').DataTable().columns.adjust().draw(false);
$('[data-toggle="tooltip"]').tooltip({html: true});
}
}
})
} else if (event_json.type === 'episode' && event_json.action === 'update') {
var rowId = $('#episodes').DataTable().row('#row_' + event_json.episode);
if (rowId.length) {
$.ajax({
url: "{{ url_for('api.episodes') }}?seriesid=" + event_json.series + "&episodeid=" + event_json.episode,
success: function (data) {
if (data.data.length) {
$('#episodes').DataTable().row(rowId).data(data.data[0]).draw('page');
$('[data-toggle="tooltip"]').tooltip({html: true});
}
}
})
}
} else if (event_json.type === 'episode' && event_json.action === 'delete') {
var rowId = $('#episodes').DataTable().row('#row_' + event_json.episode);
if (rowId.length) {
$('#episodes').DataTable().row(rowId).remove();
$('#episodes').DataTable().columns.adjust().draw(false);
$('[data-toggle="tooltip"]').tooltip({html: true});
}
}
}
if (event_json.type === 'episodeBlacklist' || event_json.type === 'episodeHistory') {
if ($('#episode_history_result').DataTable().ajax.json()) {
$('#episode_history_result').DataTable().ajax.reload(resetPaging = false);
$('[data-toggle="tooltip"]').tooltip({html: true});
}
}
});
$('#episodes').on('click', '.episode_history', function (e) {
$(this).tooltip('dispose');
e.preventDefault();
$("#episode_history_title_span").html(seriesDetails['title'] + ' - ' + $(this).data("season") + 'x' + $(this).data("episode") + ' - ' + $(this).data("episodetitle"));
sonarrEpisodeId = $(this).data("sonarrepisodeid");
$('#episode_history_result').DataTable({
destroy: true,
language: {
zeroRecords: 'No History Records Found For This Episode'
},
paging: true,
lengthChange: false,
pageLength: 5,
searching: true,
ordering: false,
scrollX: true,
processing: true,
serverSide: false,
ajax: {
url: '{{ url_for( 'api.episodeshistory' )}}?episodeid=' + sonarrEpisodeId
},
columns: [
{
data: 'action',
"render": function (data) {
if (data === 0) {
return "<i class='fas fa-trash' title='Subtitle file has been erased.' data-toggle='tooltip' data-placement='right'></i>";
} else if (data === 1) {
return "<i class='fas fa-download' title='Subtitle file has been downloaded.' data-toggle='tooltip' data-placement='right'></i>";
} else if (data === 2) {
return "<i class='fas fa-user' title='Subtitle file has been manually downloaded.' data-toggle='tooltip' data-placement='right'></i>";
} else if (data === 3) {
return "<i class='fas fa-recycle' title='Subtitle file has been upgraded.' data-toggle='tooltip' data-placement='right'></i>";
} else if (data === 4) {
return "<i class='fas fa-cloud-upload-alt' title='Subtitle file has been manually uploaded.' data-toggle='tooltip' data-placement='right'></i>";
} else if (data === 5) {
return "<i class='fas fa-clock' title='Subtitle file has been synced.' data-toggle='tooltip' data-placement='right'></i>";
}
}
},
{
data: 'language',
render: function (value) {
if (value) {
return value.name + ((value.forced) ? ' forced' : '')
} else {
return '<i>undefined</i>'
}
}
},
{data: 'provider'},
{data: 'score'},
{data: 'timestamp'},
{
data: null,
render: function (data) {
if (data.subs_id && data.subtitles_path && !data.blacklisted) {
return '<a href="" class="blacklist_subtitles badge badge-secondary" data-toggle="tooltip" data-placement="right" title="Blacklist this subtitles" data-sonarrSeriesId="' + data.sonarrSeriesId + '" data-sonarrEpisodeId="' + data.sonarrEpisodeId + '" data-language="' + data.language.code2 + '" data-forced="' + data.language.forced + '" data-provider="' + data.provider + '" data-subs_id="' + data.subs_id + '" data-video_path="' + data.video_path + '" data-subtitles_path="' + data.mapped_subtitles_path + '"><i class="far fa-file-excel"></i></a>';
} else {
return null;
}
}
}
]
});
$('#episodeHistoryModal')
.modal({
focus: false
});
});
$('#episode_history_result').on('click', '.blacklist_subtitles', function (e) {
$(this).tooltip('dispose');
e.preventDefault();
const values = {
sonarr_series_id: $(this).attr('data-sonarrseriesid'),
sonarr_episode_id: $(this).attr('data-sonarrepisodeid'),
provider: $(this).attr('data-provider'),
subs_id: $(this).attr('data-subs_id'),
language: $(this).attr('data-language'),
forced: $(this).attr('data-forced'),
video_path: $(this).attr('data-video_path'),
subtitles_path: $(this).attr('data-subtitles_path')
};
var cell = $(this).parent();
$.ajax({
url: "{{ url_for('api.blacklistepisodesubtitlesadd') }}",
type: "POST",
dataType: "json",
data: values,
beforeSend: function () {
cell.html('<div class="spinner-border spinner-border-sm" role="status"><span class="sr-only">Loading...</span></div>');
}
});
});
$('#episodes').on('click', '.episode_tools', function (e) {
$(this).tooltip('dispose');
e.preventDefault();
$("#episode_tools_title_span").html(seriesDetails['title'] + ' - ' + $(this).data("season") + 'x' + $(this).data("episode") + ' - ' + $(this).data("episode_title"));
sonarrEpisodeId = $(this).data("sonarrepisodeid");
$('#episode_tools_result').DataTable({
destroy: true,
language: {
zeroRecords: 'No External Subtitles Found For This Episode'
},
paging: true,
lengthChange: false,
pageLength: 5,
searching: true,
ordering: false,
scrollX: true,
processing: false,
serverSide: false,
ajax: {
url: '{{ url_for( 'api.episodestools' )}}?episodeid=' + sonarrEpisodeId
},
columns: [
{data: 'language.name'},
{data: 'filename'},
{
data: null,
"render": function (data) {
return '<a href="" class="subtitles_sync badge badge-secondary" data-language="' + data.language.code3 + '" data-path="' + data.path + '" data-videopath="' + data.videopath + '"><i class="far fa-play-circle"></i></a>';
}
}
]
});
$('#episodeToolsModal')
.modal({
focus: false
});
});
$('#episode_tools_result').on('click', '.subtitles_sync', function (e) {
e.preventDefault();
const values = {
language: $(this).attr("data-language"),
subtitlesPath: $(this).attr("data-path"),
videoPath: $(this).attr("data-videopath"),
mediaType: 'series'
};
var cell = $(this).parent();
$.ajax({
url: "{{ url_for('api.syncsubtitles') }}",
type: "POST",
dataType: "json",
data: values,
beforeSend: function () {
cell.html('<div class="spinner-border spinner-border-sm" role="status"><span class="sr-only">Loading...</span></div>');
},
complete: function () {
$('#episodeToolsModal').modal('hide');
}
});
});
});
function seriesDetailsRefresh() {
$.ajax({
url: "{{ url_for('api.series') }}?seriesid={{id}}"
}).done(function (data) {
seriesDetails = data.data[0];
$(document).prop('title', seriesDetails['title'] + ' - Bazarr');
$('#seriesFanart').css('background-image', "url('{{ url_for('image_proxy', url='MediaCover/'+id+'/fanart.jpg') }}')");
$('#seriesPoster').attr("src", "{{ url_for('image_proxy', url='MediaCover/'+id+'/poster-250.jpg') }}");
$('#seriesTitle').text(seriesDetails['title']);
if (seriesDetails['alternateTitles'].length > 0) {
$('#seriesAlternateTitles').attr("data-original-title", "<b>Alternative Titles:</b><br>" + seriesDetails['alternateTitles']);
} else {
$('#seriesAlternateTitles').hide();
}
if (seriesDetails['tags'].length > 0) {
$('#seriesTags').attr("data-original-title", "<b>Tags:</b><br>" + seriesDetails['tags']);
} else {
$('#seriesTags').hide();
}
$('#seriesAudioLanguage').text(seriesDetails['audio_language']['name']);
$('#seriesMappedPath').text(seriesDetails['mapped_path']);
$('#seriesMappedPath').attr("data-original-title", seriesDetails['mapped_path']);
$('#seriesFileCount').text(seriesDetails['episodeFileCount'] + ' files');
$('#seriesType').text(seriesDetails['seriesType']);
var languages = '';
if (seriesDetails['languages'] && seriesDetails['languages'] !== 'None') {
seriesDetails['languages'].forEach(appendFunc);
}
function appendFunc(value) {
languages = languages + '<span class="badge badge-secondary" data-toggle="tooltip" data-placement="right" title="' + value.name + '">' + value.code2 + '</span> ';
}
$('#seriesSubtitlesLanguages').html(languages);
$('#seriesHearingImpaired').text('Hearing-Impaired: ' + seriesDetails['hearing_impaired']);
$('#seriesForced').text('Forced: ' + seriesDetails['forced']);
$('#seriesDescription').text(seriesDetails['overview']);
if (seriesDetails['desired_languages'] == '[]') {
$('#search_button').hide();
} else {
$('#search_button').show();
}
$('[data-toggle="tooltip"]').tooltip({html: true});
});
}
function getLanguages() {
$.ajax({
url: "{{ url_for('api.languages') }}?enabled=false",
success: function (data) {
availableLanguages = data;
}
});
}
function getEnabledLanguages() {
$.ajax({
url: "{{ url_for('api.languages') }}?enabled=true",
success: function (data) {
enabledLanguages = data;
}
});
}
</script>
{% endblock tail %}