Merge pull request #1212 from redSpoutnik/feature-manually-add-subtitle

Feature: manually add subtitle
This commit is contained in:
Joshua M. Boniface 2020-11-21 21:55:20 -05:00 committed by GitHub
commit b3cdb019c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 263 additions and 0 deletions

View File

@ -352,6 +352,28 @@ function centerFocus(elem, horiz, on) {
}); });
} }
function onOpenUploadMenu(e) {
const dialog = dom.parentWithClass(e.target, 'subtitleEditorDialog');
const selectLanguage = dialog.querySelector('#selectLanguage');
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
import('../subtitleuploader/subtitleuploader').then(({default: subtitleUploader}) => {
subtitleUploader.show({
languages: {
list: selectLanguage.innerHTML,
value: selectLanguage.value
},
itemId: currentItem.Id,
serverId: currentItem.ServerId
}).then(function (hasChanged) {
if (hasChanged) {
hasChanges = true;
reload(dialog, apiClient, currentItem.Id);
}
});
});
}
function showEditorInternal(itemId, serverId, template) { function showEditorInternal(itemId, serverId, template) {
hasChanges = false; hasChanges = false;
@ -379,6 +401,8 @@ function showEditorInternal(itemId, serverId, template) {
dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit); dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit);
dlg.querySelector('.btnOpenUploadMenu').addEventListener('click', onOpenUploadMenu);
const btnSubmit = dlg.querySelector('.btnSubmit'); const btnSubmit = dlg.querySelector('.btnSubmit');
if (layoutManager.tv) { if (layoutManager.tv) {

View File

@ -18,6 +18,7 @@
<select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}" autofocus></select> <select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}" autofocus></select>
</div> </div>
<button type="submit" is="paper-icon-button-light" title="${Search}" class="btnSearchSubtitles flex-shrink-zero emby-select-iconbutton"><span class="material-icons search"></span></button> <button type="submit" is="paper-icon-button-light" title="${Search}" class="btnSearchSubtitles flex-shrink-zero emby-select-iconbutton"><span class="material-icons search"></span></button>
<button type="button" is="paper-icon-button-light" title="${Upload}" class="btnOpenUploadMenu flex-shrink-zero emby-select-iconbutton"><span class="material-icons add"></span></button>
</div> </div>
<button is="emby-button" type="submit" class="raised btnSubmit block button-submit">${Search}</button> <button is="emby-button" type="submit" class="raised btnSubmit block button-submit">${Search}</button>
</form> </form>

View File

@ -0,0 +1,11 @@
.subtitleEditor-dropZone {
border: 0.2em dashed currentcolor;
border-radius: 0.25em;
text-align: center;
position: relative;
height: 12em;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -0,0 +1,172 @@
import dialogHelper from '../../components/dialogHelper/dialogHelper';
import ServerConnections from '../ServerConnections';
import dom from '../../scripts/dom';
import loading from '../../components/loading/loading';
import scrollHelper from '../../libraries/scroller';
import layoutManager from '../layoutManager';
import globalize from '../../scripts/globalize';
import template from './subtitleuploader.template.html';
import toast from '../toast/toast';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-select/emby-select';
import '../formdialog.css';
import './style.css';
let currentItemId;
let currentServerId;
let currentFile;
let hasChanges = false;
function onFileReaderError(evt) {
loading.hide();
const error = evt.target.error;
if (error.code !== error.ABORT_ERR) {
toast(globalize.translate('MessageFileReadError'));
}
}
function isValidSubtitleFile(file) {
return file && ['.sub', '.srt', '.vtt', '.ass', '.ssa']
.some(function(ext) {
return file.name.endsWith(ext);
});
}
function setFiles(page, files) {
const file = files[0];
if (!isValidSubtitleFile(file)) {
page.querySelector('#subtitleOutput').innerHTML = '';
page.querySelector('#fldUpload').classList.add('hide');
page.querySelector('#labelDropSubtitle').classList.remove('hide');
currentFile = null;
return;
}
currentFile = file;
const reader = new FileReader();
reader.onerror = onFileReaderError;
reader.onloadstart = function () {
page.querySelector('#fldUpload').classList.add('hide');
};
reader.onabort = function () {
loading.hide();
console.debug('File read cancelled');
};
// Closure to capture the file information.
reader.onload = (function (theFile) {
return function () {
// Render file.
const html = '<a><i class="material-icons" style="transform: translateY(25%);">subtitles</i><span>' + escape(theFile.name) + '</span><a/>';
page.querySelector('#subtitleOutput').innerHTML = html;
page.querySelector('#fldUpload').classList.remove('hide');
page.querySelector('#labelDropSubtitle').classList.add('hide');
};
})(file);
// Read in the subtitle file as a data URL.
reader.readAsDataURL(file);
}
function onSubmit(e) {
const file = currentFile;
if (!isValidSubtitleFile(file)) {
toast(globalize.translate('MessageSubtitleFileTypeAllowed'));
e.preventDefault();
return;
}
loading.show();
const dlg = dom.parentWithClass(this, 'dialog');
const language = dlg.querySelector('#selectLanguage').value;
const isForced = dlg.querySelector('#chkIsForced').checked;
ServerConnections.getApiClient(currentServerId).uploadItemSubtitle(currentItemId, language, isForced, file).then(function () {
dlg.querySelector('#uploadSubtitle').value = '';
loading.hide();
hasChanges = true;
dialogHelper.close(dlg);
});
e.preventDefault();
}
function initEditor(page) {
page.querySelector('.uploadSubtitleForm').addEventListener('submit', onSubmit);
page.querySelector('#uploadSubtitle').addEventListener('change', function () {
setFiles(page, this.files);
});
page.querySelector('.btnBrowse').addEventListener('click', function () {
page.querySelector('#uploadSubtitle').click();
});
}
function showEditor(options, resolve, reject) {
options = options || {};
currentItemId = options.itemId;
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
dlg.classList.add('subtitleUploaderDialog');
dlg.innerHTML = globalize.translateHtml(template, 'core');
if (layoutManager.tv) {
scrollHelper.centerFocus.on(dlg, false);
}
// Has to be assigned a z-index after the call to .open()
dlg.addEventListener('close', function () {
if (layoutManager.tv) {
scrollHelper.centerFocus.off(dlg, false);
}
loading.hide();
resolve(hasChanges);
});
dialogHelper.open(dlg);
initEditor(dlg);
const selectLanguage = dlg.querySelector('#selectLanguage');
if (options.languages) {
selectLanguage.innerHTML = options.languages.list || null;
selectLanguage.value = options.languages.value || null;
}
dlg.querySelector('.btnCancel').addEventListener('click', function () {
dialogHelper.close(dlg);
});
}
export function show(options) {
return new Promise(function (resolve, reject) {
hasChanges = false;
showEditor(options, resolve, reject);
});
}
export default {
show: show
};

View File

@ -0,0 +1,45 @@
<div class="formDialogHeader">
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><em class="material-icons arrow_back"></em></button>
<h3 class="formDialogHeaderTitle">
${HeaderUploadSubtitle}
</h3>
</div>
<div class="formDialogContent">
<div class="dialogContentInner">
<form class="uploadSubtitleForm">
<div class="flex align-items-center" style="margin:1.5em 0;">
<h2 style="margin:0;">${HeaderAddUpdateSubtitle}</h2>
<button is="emby-button" type="button" class="raised raised-mini btnBrowse" style="margin-left:1.5em;">
<em class="material-icons">folder</em>
<span>${Browse}</span>
</button>
</div>
<div>
<div class="subtitleEditor-dropZone fieldDescription">
<div id="labelDropSubtitle">${LabelDropSubtitleHere}</div>
<output id="subtitleOutput" class="flex align-items-center justify-content-center" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;"></output>
<input type="file" accept=".sub,.srt,.vtt,.ass,.ssa" id="uploadSubtitle" name="uploadSubtitle" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;opacity:0;"/>
</div>
<div id="fldUpload" class="hide">
<br />
<div class="checkboxContainer">
<label>
<input type="checkbox" is="emby-checkbox" id="chkIsForced" />
<span>${LabelIsForced}</span>
</label>
</div>
<div class="selectContainer flex-grow">
<select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}" autofocus></select>
</div>
<button is="emby-button" type="submit" class="raised button-submit block">
<span>${Upload}</span>
</button>
</div>
</div>
</form>
</div>
</div>

View File

@ -264,6 +264,8 @@
"HeaderAddToCollection": "Add to Collection", "HeaderAddToCollection": "Add to Collection",
"HeaderAddToPlaylist": "Add to Playlist", "HeaderAddToPlaylist": "Add to Playlist",
"HeaderAddUpdateImage": "Add/Update Image", "HeaderAddUpdateImage": "Add/Update Image",
"HeaderAddUpdateSubtitle": "Add/Update Subtitle",
"HeaderAddUser": "Add User",
"HeaderAdditionalParts": "Additional Parts", "HeaderAdditionalParts": "Additional Parts",
"HeaderAdmin": "Admin", "HeaderAdmin": "Admin",
"HeaderAlbumArtists": "Album Artists", "HeaderAlbumArtists": "Album Artists",
@ -433,6 +435,7 @@
"HeaderTypeText": "Enter Text", "HeaderTypeText": "Enter Text",
"HeaderUpcomingOnTV": "Upcoming On TV", "HeaderUpcomingOnTV": "Upcoming On TV",
"HeaderUploadImage": "Upload Image", "HeaderUploadImage": "Upload Image",
"HeaderUploadSubtitle": "Upload Subtitle",
"HeaderUser": "User", "HeaderUser": "User",
"HeaderUsers": "Users", "HeaderUsers": "Users",
"HeaderVideoQuality": "Video Quality", "HeaderVideoQuality": "Video Quality",
@ -546,6 +549,7 @@
"LabelDropImageHere": "Drop image here, or click to browse.", "LabelDropImageHere": "Drop image here, or click to browse.",
"LabelDroppedFrames": "Dropped frames:", "LabelDroppedFrames": "Dropped frames:",
"LabelDropShadow": "Drop shadow:", "LabelDropShadow": "Drop shadow:",
"LabelDropSubtitleHere": "Drop subtitle here, or click to browse.",
"LabelDynamicExternalId": "{0} Id:", "LabelDynamicExternalId": "{0} Id:",
"LabelEasyPinCode": "Easy pin code:", "LabelEasyPinCode": "Easy pin code:",
"LabelEmbedAlbumArtDidl": "Embed album art in Didl", "LabelEmbedAlbumArtDidl": "Embed album art in Didl",
@ -607,6 +611,7 @@
"LabelInNetworkSignInWithEasyPassword": "Enable in-network sign in with my easy pin code", "LabelInNetworkSignInWithEasyPassword": "Enable in-network sign in with my easy pin code",
"LabelInNetworkSignInWithEasyPasswordHelp": "Use the easy pin code to sign in to clients within your local network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.", "LabelInNetworkSignInWithEasyPasswordHelp": "Use the easy pin code to sign in to clients within your local network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.",
"LabelInternetQuality": "Internet quality:", "LabelInternetQuality": "Internet quality:",
"LabelIsForced": "Forced",
"LabelKeepUpTo": "Keep up to:", "LabelKeepUpTo": "Keep up to:",
"LabelKnownProxies": "Known proxies:", "LabelKnownProxies": "Known proxies:",
"LabelKidsCategories": "Children's categories:", "LabelKidsCategories": "Children's categories:",

View File

@ -223,6 +223,8 @@
"HeaderAddToCollection": "Ajouter à la collection", "HeaderAddToCollection": "Ajouter à la collection",
"HeaderAddToPlaylist": "Ajouter à la liste de lecture", "HeaderAddToPlaylist": "Ajouter à la liste de lecture",
"HeaderAddUpdateImage": "Ajouter/Mettre à jour une image", "HeaderAddUpdateImage": "Ajouter/Mettre à jour une image",
"HeaderAddUpdateSubtitle": "Ajouter/Mettre à jour des sous-titres",
"HeaderAddUser": "Ajouter un utilisateur",
"HeaderAdditionalParts": "Parties additionelles", "HeaderAdditionalParts": "Parties additionelles",
"HeaderAdmin": "Administrateur", "HeaderAdmin": "Administrateur",
"HeaderAlbumArtists": "Artistes", "HeaderAlbumArtists": "Artistes",
@ -381,6 +383,7 @@
"HeaderTypeText": "Entrer texte", "HeaderTypeText": "Entrer texte",
"HeaderUpcomingOnTV": "Prochainement à la TV", "HeaderUpcomingOnTV": "Prochainement à la TV",
"HeaderUploadImage": "Envoyer une image", "HeaderUploadImage": "Envoyer une image",
"HeaderUploadSubtitle": "Envoyer des sous-titres",
"HeaderUser": "Utilisateur", "HeaderUser": "Utilisateur",
"HeaderUsers": "Utilisateurs", "HeaderUsers": "Utilisateurs",
"HeaderVideoQuality": "Qualité vidéo", "HeaderVideoQuality": "Qualité vidéo",
@ -478,6 +481,7 @@
"LabelDownloadLanguages": "Téléchargement des langues :", "LabelDownloadLanguages": "Téléchargement des langues :",
"LabelDropImageHere": "Faites glisser l'image ici, ou cliquez pour parcourir vos fichiers.", "LabelDropImageHere": "Faites glisser l'image ici, ou cliquez pour parcourir vos fichiers.",
"LabelDropShadow": "Ombre portée :", "LabelDropShadow": "Ombre portée :",
"LabelDropSubtitleHere": "Faites glisser les sous-tires ici, ou cliquez pour parcourir vos fichiers.",
"LabelDynamicExternalId": "ID {0} :", "LabelDynamicExternalId": "ID {0} :",
"LabelEasyPinCode": "Code Easy PIN :", "LabelEasyPinCode": "Code Easy PIN :",
"LabelEmbedAlbumArtDidl": "Intégrer les images d'album dans le DIDL", "LabelEmbedAlbumArtDidl": "Intégrer les images d'album dans le DIDL",
@ -532,6 +536,7 @@
"LabelInNetworkSignInWithEasyPassword": "Activer l'authentification simplifiée dans les réseaux domestiques avec mon code Easy PIN", "LabelInNetworkSignInWithEasyPassword": "Activer l'authentification simplifiée dans les réseaux domestiques avec mon code Easy PIN",
"LabelInNetworkSignInWithEasyPasswordHelp": "Utilisez votre code Easy PIN pour vous connecter aux applications depuis l'intérieur de votre réseau local. Votre mot de passe habituel ne sera requis que depuis l'extérieur. Si le code PIN n'est pas défini, vous n'aurez pas besoin de mot de passe depuis l'intérieur de votre réseau local.", "LabelInNetworkSignInWithEasyPasswordHelp": "Utilisez votre code Easy PIN pour vous connecter aux applications depuis l'intérieur de votre réseau local. Votre mot de passe habituel ne sera requis que depuis l'extérieur. Si le code PIN n'est pas défini, vous n'aurez pas besoin de mot de passe depuis l'intérieur de votre réseau local.",
"LabelInternetQuality": "Qualité d'internet :", "LabelInternetQuality": "Qualité d'internet :",
"LabelIsForced": "Forcés",
"LabelKeepUpTo": "Garder jusqu'à :", "LabelKeepUpTo": "Garder jusqu'à :",
"LabelKidsCategories": "Catégories jeunesse :", "LabelKidsCategories": "Catégories jeunesse :",
"LabelKodiMetadataDateFormat": "Format de la date de sortie :", "LabelKodiMetadataDateFormat": "Format de la date de sortie :",