Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenSubtitles directly on the client side #6428

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/apps/stable/routes/legacyRoutes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
controller: 'user/subtitles/index',
view: 'user/subtitles/index.html'
}
}, {
path: 'mypreferencesopensubtitles.html',
pageProps: {
controller: 'user/opensubtitles/index',
view: 'user/opensubtitles/index.html'
}
}, {
path: 'tv.html',
pageProps: {
Expand Down
37 changes: 37 additions & 0 deletions src/components/opensubtitlesSettings/opensubtitlesSettings.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<form style="margin:0 auto;">
<div class="verticalSection">
<h2 class="sectionTitle">
${Settings}
</h2>
<div class="checkboxContainer checkboxContainer-withDescription fldEnableOpenSubtitles">
<label>
<input is="emby-checkbox" type="checkbox" id="chkEnableOpenSubtitles"/>
<span>${EnablePlugin}</span>
</label>
</div>
<div id="OpenSubtitlesContainerOnlyIfAvailable" class="hide">
<div class="inputContainer">
<input is="emby-input" type="text" id="txtOpenSubtitlesUser" required="required" label="${LabelUser}" autocomplete="username" autocapitalize="off" />
</div>
<div class="inputContainer">
<input is="emby-input" id="txtOpenSubtitlesPassword" type="password" label="${LabelPassword}" autocomplete="current-password" />
</div>
<div class="selectContainer">
<select is="emby-select" id="selectPrimarySubtitleLanguage" label="${LabelPreferredSubtitleLanguage}" class="selectPreferredSubtitleLanguage"></select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectSecondarySubtitleLanguage" label="${LabelSecondarySubtitleLanguage}" class="selectPreferredSubtitleLanguage"></select>
</div>
<div class="selectContainer">
<select is="emby-select" id="selectTertiarySubtitleLanguage" label="${LabelTertiarySubtitleLanguage}" class="selectPreferredSubtitleLanguage"></select>
</div>
<div class="inputContainer hide">
<input is="emby-input" type="text" id="txtOpenSubtitlesApiToken" label="API Token" placeholder="Check opensubtitles.com/users/profile"/>
</div>
</div>
<div class="loginStatus hide"></div>
</div>
<button is="emby-button" type="submit" class="raised button-submit block btnSave">
<span>${Save}</span>
</button>
</form>
27 changes: 21 additions & 6 deletions src/components/playback/playbackmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { toApi } from 'utils/jellyfin-apiclient/compat';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind.js';
import browser from 'scripts/browser.js';
import { bindSkipSegment } from './skipsegment.ts';
import OpenSubtitlesManager from '../../scripts/opensubtitles/opensubtitles';

const UNLIMITED_ITEMS = -1;

Expand Down Expand Up @@ -1252,6 +1253,7 @@ export class PlaybackManager {
mediaStreams.push(currentMediaSource.MediaStreams[i]);
}
}
OpenSubtitlesManager.appendSubtitleTracks( mediaStreams, currentMediaSource );

// No known streams, nothing to change
if (!mediaStreams.length) {
Expand Down Expand Up @@ -2591,7 +2593,7 @@ export class PlaybackManager {

const getMediaStreams = isLiveTv ? Promise.resolve([]) : apiClient.getItem(apiClient.getCurrentUserId(), mediaSourceId)
.then(fullItem => {
return fullItem.MediaStreams;
return OpenSubtitlesManager.appendSubtitleTracks( fullItem.MediaStreams, fullItem );
});

return Promise.all([promise, player.getDeviceProfile(item), apiClient.getCurrentUser(), getMediaStreams]).then(function (responses) {
Expand Down Expand Up @@ -2643,8 +2645,16 @@ export class PlaybackManager {
mediaSource.DefaultSecondarySubtitleStreamIndex = -1;
}

const subtitleTrack1 = mediaSource.MediaStreams[mediaSource.DefaultSubtitleStreamIndex];
const subtitleTrack2 = mediaSource.MediaStreams[mediaSource.DefaultSecondarySubtitleStreamIndex];
// Append web subtitles
if ( mediaSource.DefaultSubtitleStreamIndex >= mediaSource.MediaStreams.length ) {
OpenSubtitlesManager.appendSubtitleTracks( mediaSource.MediaStreams, mediaSource );
}
const subtitleTrack1 = mediaSource.MediaStreams.filter(function (t) {
return t.Index === mediaSource.DefaultSubtitleStreamIndex;
})[0];
const subtitleTrack2 = mediaSource.MediaStreams.filter(function (t) {
return t.Index === mediaSource.DefaultSecondarySubtitleStreamIndex;
})[0];

if (!self.trackHasSecondarySubtitleSupport(subtitleTrack1, player)
|| !self.trackHasSecondarySubtitleSupport(subtitleTrack2, player)) {
Expand Down Expand Up @@ -2874,6 +2884,7 @@ export class PlaybackManager {
format: textStream.Codec
});
}
OpenSubtitlesManager.appendSubtitleTracks( tracks, mediaSource );

return tracks;
}
Expand Down Expand Up @@ -3866,9 +3877,13 @@ export class PlaybackManager {
return Promise.reject();
}

getSubtitleUrl(textStream, serverId) {
async getSubtitleUrl(textStream, serverId) {
const apiClient = ServerConnections.getApiClient(serverId);

if ( textStream?.OpenSubstitlesFileId ) {
// This is an opensubtitles item
await OpenSubtitlesManager.getDownloadLink( textStream.OpenSubstitlesFileId );
return OpenSubtitlesManager.downloadData.link;
}
return !textStream.IsExternalUrl ? apiClient.getUrl(textStream.DeliveryUrl) : textStream.DeliveryUrl;
}

Expand Down Expand Up @@ -3997,8 +4012,8 @@ export class PlaybackManager {
}

const mediaSource = this.currentMediaSource(player);

const mediaStreams = mediaSource?.MediaStreams || [];
OpenSubtitlesManager.appendSubtitleTracks( mediaStreams, mediaSource );
return mediaStreams.filter(function (s) {
return s.Type === 'Subtitle';
}).sort(itemHelper.sortTracks);
Expand Down
35 changes: 30 additions & 5 deletions src/controllers/itemDetails/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { getPortraitShape, getSquareShape } from 'utils/card';
import Dashboard from 'utils/dashboard';
import Events from 'utils/events';
import { getItemBackdropImageUrl } from 'utils/jellyfin-apiclient/backdropImage';
import OpenSubtitlesManager from '../../scripts/opensubtitles/opensubtitles';

import 'elements/emby-itemscontainer/emby-itemscontainer';
import 'elements/emby-checkbox/emby-checkbox';
Expand Down Expand Up @@ -279,21 +280,28 @@ function renderAudioSelections(page, mediaSources) {
}
}

function renderSubtitleSelections(page, mediaSources) {
async function renderSubtitleSelections(page, mediaSources) {
const mediaSource = getSelectedMediaSource(page, mediaSources);

// Append web subtitles to the mediaSource
await OpenSubtitlesManager.appendSubtitleTracks(mediaSource.MediaStreams, mediaSource);
const tracks = mediaSource.MediaStreams.filter(function (m) {
return m.Type === 'Subtitle';
});
tracks.sort(itemHelper.sortTracks);

const select = page.querySelector('.selectSubtitles');
select.setLabel(globalize.translate('Subtitles'));
const selectedId = mediaSource.DefaultSubtitleStreamIndex == null ? -1 : mediaSource.DefaultSubtitleStreamIndex;

let selected = selectedId === -1 ? ' selected' : '';
select.innerHTML = '<option value="-1">' + globalize.translate('Off') + '</option>' + tracks.map(function (v) {
selected = v.Index === selectedId ? ' selected' : '';
return '<option value="' + v.Index + '" ' + selected + '>' + v.DisplayTitle + '</option>';
let out = '<option value="' + v.Index + '" ' + selected;
if ( Object.hasOwn(v, 'OpenSubstitlesFileId') ) {
out += ' data-OpenSubstitlesFileId=' + v.OpenSubstitlesFileId;
}
out += '>' + v.DisplayTitle + '</option>';
return out;
}).join('');

if (tracks.length > 0) {
Expand Down Expand Up @@ -1067,6 +1075,7 @@ function renderTagline(page, item) {
}

function renderDetails(page, item, apiClient, context) {
OpenSubtitlesManager.mediaItem = item; // This object may have `ProviderIds`
renderSimilarItems(page, item, context);
renderMoreFromSeason(page, item, apiClient);
renderMoreFromArtist(page, item, apiClient);
Expand Down Expand Up @@ -1915,6 +1924,7 @@ export default function (view, params) {

Promise.all([getPromise(apiClient, pageParams), apiClient.getCurrentUser()]).then(([item, user]) => {
currentItem = item;
OpenSubtitlesManager.mediaItem = item; // This object may have `ProviderIds`
reloadFromItem(instance, page, pageParams, item, user);
}).catch((error) => {
console.error('failed to get item or current user: ', error);
Expand Down Expand Up @@ -1970,14 +1980,29 @@ export default function (view, params) {
playItem(item, item.UserData && mode === 'resume' ? item.UserData.PlaybackPositionTicks : 0);
}

function onPlayClick() {
async function onPlayClick() {
let actionElem = this;
let action = actionElem.getAttribute('data-action');

if (!action) {
actionElem = actionElem.querySelector('[data-action]') || actionElem;
action = actionElem.getAttribute('data-action');
}
//-----------------
// Make sure the opensubtitle url is ready for the player
try {
if ( currentItem?.MediaType === 'Video' ) {
const select = view.querySelector('.selectSubtitles');
const selectedSubtitleOption = select.options[ select.selectedIndex ];
if ( Object.hasOwn(selectedSubtitleOption.dataset, 'OpenSubstitlesFileId') ) {
OpenSubtitlesManager.appendSubtitleTracks( currentItem.MediaStreams, currentItem );
await OpenSubtitlesManager.getDownloadLink( selectedSubtitleOption.dataset.OpenSubstitlesFileId );
}
}
} catch (err) {
console.error( err );
}
//-----------------

playCurrentItem(actionElem, action);
}
Expand Down Expand Up @@ -2096,8 +2121,8 @@ export default function (view, params) {
view.querySelector('.selectSource').addEventListener('change', function () {
renderVideoSelections(view, self._currentPlaybackMediaSources);
renderAudioSelections(view, self._currentPlaybackMediaSources);
renderSubtitleSelections(view, self._currentPlaybackMediaSources);
updateMiscInfo();
renderSubtitleSelections(view, self._currentPlaybackMediaSources);
});
view.addEventListener('viewshow', function (e) {
const page = this;
Expand Down
2 changes: 2 additions & 0 deletions src/controllers/playback/video/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,8 @@ export default function (view) {
const secondaryStreams = playbackManager.secondarySubtitleTracks(player);
let currentIndex = playbackManager.getSubtitleStreamIndex(player);

playbackManager.pause(player);

if (currentIndex == null) {
currentIndex = -1;
}
Expand Down
11 changes: 11 additions & 0 deletions src/controllers/user/menu/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,17 @@ <h2 class="sectionTitle" style="padding-left:.25em;">${HeaderUser}</h2>
</div>
</a>
</div>
<div class="localSection verticalSection verticalSection-extrabottompadding">
<h2 class="sectionTitle" style="padding-left:.25em;">${HeaderApp}</h2>
<a is="emby-linkbutton" data-ripple="false" href="#" style="display:block;padding:0;margin:0;" class="lnkOpenSubtitlePreferences listItem-border">
<div class="listItem">
<span class="material-icons listItemIcon listItemIcon-transparent subtitles" aria-hidden="true"></span>
<div class="listItemBody">
<div class="listItemBodyText">OpenSubtitles</div>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
1 change: 1 addition & 0 deletions src/controllers/user/menu/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function (view, params) {
page.querySelector('.lnkHomePreferences').setAttribute('href', '#/mypreferenceshome.html?userId=' + userId);
page.querySelector('.lnkPlaybackPreferences').setAttribute('href', '#/mypreferencesplayback.html?userId=' + userId);
page.querySelector('.lnkSubtitlePreferences').setAttribute('href', '#/mypreferencessubtitles.html?userId=' + userId);
page.querySelector('.lnkOpenSubtitlePreferences').setAttribute('href', '#/mypreferencesopensubtitles.html?userId=' + userId);
page.querySelector('.lnkQuickConnectPreferences').setAttribute('href', '#/quickconnect?userId=' + userId);
page.querySelector('.lnkControlsPreferences').setAttribute('href', '#/mypreferencescontrols.html?userId=' + userId);

Expand Down
3 changes: 3 additions & 0 deletions src/controllers/user/opensubtitles/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div id="homeScreenPreferencesPage" data-role="page" class="page libraryPage userPreferencesPage noSecondaryNavPage" data-title="OpenSubtitles" data-backbutton="true">
<div class="settingsContainer padded-left padded-right padded-bottom-page"></div>
</div>
Loading
Loading