mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2024-11-17 19:08:18 -07:00
Merge branch 'jellyfin:master' into chapter-markers
This commit is contained in:
commit
ca84407884
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,7 +8,6 @@ config.json
|
||||
|
||||
# ide
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# log
|
||||
yarn-error.log
|
||||
|
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
7
.vscode/settings.json
vendored
Normal file
7
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"eslint.format.enable": true,
|
||||
"editor.formatOnSave": false
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
FROM fedora:33
|
||||
FROM fedora:36
|
||||
|
||||
# Docker build arguments
|
||||
ARG SOURCE_DIR=/jellyfin
|
||||
@ -11,7 +11,7 @@ ENV IS_DOCKER=YES
|
||||
|
||||
# Prepare Fedora environment
|
||||
RUN dnf update -y \
|
||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs autoconf automake glibc-devel
|
||||
&& dnf install -y @buildsys-build rpmdevtools git dnf-plugins-core nodejs autoconf automake glibc-devel make
|
||||
|
||||
# Link to build script
|
||||
RUN ln -sf ${SOURCE_DIR}/deployment/build.fedora /build.sh
|
||||
|
@ -9,8 +9,12 @@ TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
|
||||
|
||||
epel-7-x86_64_repos := https://rpm.nodesource.com/pub_16.x/el/\$$releasever/\$$basearch/
|
||||
|
||||
fed_ver := $(shell rpm -E %fedora)
|
||||
# fallback when not running on Fedora
|
||||
fed_ver ?= 36
|
||||
TARGET ?= fedora-$(fed_ver)-x86_64
|
||||
|
||||
outdir ?= $(PWD)/$(DIR)/
|
||||
TARGET ?= fedora-35-x86_64
|
||||
|
||||
srpm: $(DIR)/$(SRPM)
|
||||
tarball: $(DIR)/$(TARBALL)
|
||||
|
@ -4,7 +4,7 @@ Name: jellyfin-web
|
||||
Version: 10.8.0
|
||||
Release: 1%{?dist}
|
||||
Summary: The Free Software Media System web client
|
||||
License: GPLv3
|
||||
License: GPLv2
|
||||
URL: https://jellyfin.org
|
||||
# Jellyfin Server tarball created by `make -f .copr/Makefile srpm`, real URL ends with `v%%{version}.tar.gz`
|
||||
Source0: jellyfin-web-%{version}.tar.gz
|
||||
@ -17,9 +17,6 @@ BuildRequires: git
|
||||
BuildRequires: npm
|
||||
%endif
|
||||
|
||||
# Disable Automatic Dependency Processing
|
||||
AutoReqProv: no
|
||||
|
||||
%description
|
||||
Jellyfin is a free software media system that puts you in control of managing and streaming your media.
|
||||
|
||||
@ -27,22 +24,26 @@ Jellyfin is a free software media system that puts you in control of managing an
|
||||
%prep
|
||||
%autosetup -n jellyfin-web-%{version} -b 0
|
||||
|
||||
%build
|
||||
|
||||
%install
|
||||
%if 0%{?rhel} > 0 && 0%{?rhel} < 8
|
||||
# Required for CentOS build
|
||||
chown root:root -R .
|
||||
%endif
|
||||
|
||||
|
||||
%build
|
||||
npm ci --no-audit --unsafe-perm
|
||||
%{__mkdir} -p %{buildroot}%{_datadir}
|
||||
mv dist %{buildroot}%{_datadir}/jellyfin-web
|
||||
%{__install} -D -m 0644 LICENSE %{buildroot}%{_datadir}/licenses/jellyfin/LICENSE
|
||||
|
||||
|
||||
%install
|
||||
%{__mkdir} -p %{buildroot}%{_libdir}/jellyfin/jellyfin-web
|
||||
%{__cp} -r dist/* %{buildroot}%{_libdir}/jellyfin/jellyfin-web
|
||||
|
||||
|
||||
%files
|
||||
%defattr(644,root,root,755)
|
||||
%{_datadir}/jellyfin-web
|
||||
%{_datadir}/licenses/jellyfin/LICENSE
|
||||
%{_libdir}/jellyfin/jellyfin-web
|
||||
%license LICENSE
|
||||
|
||||
|
||||
%changelog
|
||||
* Fri Dec 04 2020 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||
|
17
package-lock.json
generated
17
package-lock.json
generated
@ -10760,6 +10760,23 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
},
|
||||
"react-router": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
|
||||
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
|
||||
"requires": {
|
||||
"history": "^5.2.0"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
|
||||
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
|
||||
"requires": {
|
||||
"history": "^5.2.0",
|
||||
"react-router": "6.3.0"
|
||||
}
|
||||
},
|
||||
"read-file-stdin": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/read-file-stdin/-/read-file-stdin-0.2.1.tgz",
|
||||
|
@ -96,6 +96,7 @@
|
||||
"pdfjs-dist": "2.12.313",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-router-dom": "6.3.0",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"screenfull": "6.0.0",
|
||||
"sortablejs": "1.14.0",
|
||||
|
169
src/components/ConnectionRequired.tsx
Normal file
169
src/components/ConnectionRequired.tsx
Normal file
@ -0,0 +1,169 @@
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import alert from './alert';
|
||||
import { appRouter } from './appRouter';
|
||||
import loading from './loading/loading';
|
||||
import ServerConnections from './ServerConnections';
|
||||
import globalize from '../scripts/globalize';
|
||||
|
||||
enum BounceRoutes {
|
||||
Home = '/home.html',
|
||||
Login = '/login.html',
|
||||
SelectServer = '/selectserver.html',
|
||||
StartWizard = '/wizardstart.html'
|
||||
}
|
||||
|
||||
// TODO: This should probably be in the SDK
|
||||
enum ConnectionState {
|
||||
SignedIn = 'SignedIn',
|
||||
ServerSignIn = 'ServerSignIn',
|
||||
ServerSelection = 'ServerSelection',
|
||||
ServerUpdateNeeded = 'ServerUpdateNeeded'
|
||||
}
|
||||
|
||||
type ConnectionRequiredProps = {
|
||||
isAdminRequired?: boolean,
|
||||
isUserRequired?: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* A component that ensures a server connection has been established.
|
||||
* Additional parameters exist to verify a user or admin have authenticated.
|
||||
* If a condition fails, this component will navigate to the appropriate page.
|
||||
*/
|
||||
const ConnectionRequired: FunctionComponent<ConnectionRequiredProps> = ({
|
||||
children,
|
||||
isAdminRequired = false,
|
||||
isUserRequired = true
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [ isLoading, setIsLoading ] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const bounce = async (connectionResponse: any) => {
|
||||
switch (connectionResponse.State) {
|
||||
case ConnectionState.SignedIn:
|
||||
// Already logged in, bounce to the home page
|
||||
console.debug('[ConnectionRequired] already logged in, redirecting to home');
|
||||
navigate(BounceRoutes.Home);
|
||||
return;
|
||||
case ConnectionState.ServerSignIn:
|
||||
// Bounce to the login page
|
||||
console.debug('[ConnectionRequired] not logged in, redirecting to login page');
|
||||
navigate(BounceRoutes.Login, {
|
||||
state: {
|
||||
serverid: connectionResponse.ApiClient.serverId()
|
||||
}
|
||||
});
|
||||
return;
|
||||
case ConnectionState.ServerSelection:
|
||||
// Bounce to select server page
|
||||
console.debug('[ConnectionRequired] redirecting to select server page');
|
||||
navigate(BounceRoutes.SelectServer);
|
||||
return;
|
||||
case ConnectionState.ServerUpdateNeeded:
|
||||
// Show update needed message and bounce to select server page
|
||||
try {
|
||||
await alert({
|
||||
text: globalize.translate('ServerUpdateNeeded', 'https://github.com/jellyfin/jellyfin'),
|
||||
html: globalize.translate('ServerUpdateNeeded', '<a href="https://github.com/jellyfin/jellyfin">https://github.com/jellyfin/jellyfin</a>')
|
||||
});
|
||||
} catch (ex) {
|
||||
console.warn('[ConnectionRequired] failed to show alert', ex);
|
||||
}
|
||||
console.debug('[ConnectionRequired] server update required, redirecting to select server page');
|
||||
navigate(BounceRoutes.SelectServer);
|
||||
return;
|
||||
}
|
||||
|
||||
console.warn('[ConnectionRequired] unhandled connection state', connectionResponse.State);
|
||||
};
|
||||
|
||||
const validateConnection = async () => {
|
||||
// Check connection status on initial page load
|
||||
const firstConnection = appRouter.firstConnectionResult;
|
||||
appRouter.firstConnectionResult = null;
|
||||
|
||||
if (firstConnection && firstConnection.State !== ConnectionState.SignedIn) {
|
||||
if (firstConnection.State === ConnectionState.ServerSignIn) {
|
||||
// Verify the wizard is complete
|
||||
try {
|
||||
const infoResponse = await fetch(`${firstConnection.ApiClient.serverAddress()}/System/Info/Public`);
|
||||
if (!infoResponse.ok) {
|
||||
throw new Error('Public system info request failed');
|
||||
}
|
||||
const systemInfo = await infoResponse.json();
|
||||
if (!systemInfo?.StartupWizardCompleted) {
|
||||
// Bounce to the wizard
|
||||
console.info('[ConnectionRequired] startup wizard is not complete, redirecting there');
|
||||
navigate(BounceRoutes.StartWizard);
|
||||
return;
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error('[ConnectionRequired] checking wizard status failed', ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Bounce to the correct page in the login flow
|
||||
bounce(firstConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: appRouter will call appHost.exit() if navigating back when you are already at the default route.
|
||||
// This case will need to be handled elsewhere before appRouter can be killed.
|
||||
|
||||
const client = ServerConnections.currentApiClient();
|
||||
|
||||
// If this is a user route, ensure a user is logged in
|
||||
if ((isAdminRequired || isUserRequired) && !client?.isLoggedIn()) {
|
||||
try {
|
||||
console.warn('[ConnectionRequired] unauthenticated user attempted to access user route');
|
||||
bounce(await ServerConnections.connect());
|
||||
} catch (ex) {
|
||||
console.warn('[ConnectionRequired] error bouncing from user route', ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is an admin route, ensure the user has access
|
||||
if (isAdminRequired) {
|
||||
try {
|
||||
const user = await client.getCurrentUser();
|
||||
if (!user.Policy.IsAdministrator) {
|
||||
console.warn('[ConnectionRequired] normal user attempted to access admin route');
|
||||
bounce(await ServerConnections.connect());
|
||||
return;
|
||||
}
|
||||
} catch (ex) {
|
||||
console.warn('[ConnectionRequired] error bouncing from admin route', ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
loading.show();
|
||||
validateConnection();
|
||||
}, [ isAdminRequired, isUserRequired, navigate ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
loading.hide();
|
||||
}
|
||||
}, [ isLoading ]);
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>{children}</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionRequired;
|
48
src/components/HistoryRouter.tsx
Normal file
48
src/components/HistoryRouter.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React, { useLayoutEffect } from 'react';
|
||||
import { HistoryRouterProps, Router } from 'react-router-dom';
|
||||
import { Update } from 'history';
|
||||
|
||||
/** Strips leading "!" from paths */
|
||||
const normalizePath = (pathname: string) => pathname.replace(/^!/, '');
|
||||
|
||||
/**
|
||||
* A slightly customized version of the HistoryRouter from react-router-dom.
|
||||
* We need to use HistoryRouter to have a shared history state between react-router and appRouter, but it does not seem
|
||||
* to be properly exported in the upstream package.
|
||||
* We also needed some customizations to handle #! routes.
|
||||
* Refs: https://github.com/remix-run/react-router/blob/v6.3.0/packages/react-router-dom/index.tsx#L222
|
||||
*/
|
||||
export function HistoryRouter({ basename, children, history }: HistoryRouterProps) {
|
||||
const [state, setState] = React.useState<Update>({
|
||||
action: history.action,
|
||||
location: history.location
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const onHistoryChange = (update: Update) => {
|
||||
if (update.location.pathname.startsWith('!')) {
|
||||
// When the location changes, we need to check for #! paths and replace the location with the "!" stripped
|
||||
history.replace(normalizePath(update.location.pathname), update.location.state);
|
||||
} else {
|
||||
setState(update);
|
||||
}
|
||||
};
|
||||
|
||||
history.listen(onHistoryChange);
|
||||
}, [ history ]);
|
||||
|
||||
return (
|
||||
<Router
|
||||
basename={basename}
|
||||
// eslint-disable-next-line react/no-children-prop
|
||||
children={children}
|
||||
location={{
|
||||
...state.location,
|
||||
// The original location does not get replaced with the normalized version, so we need to strip it here
|
||||
pathname: normalizePath(state.location.pathname)
|
||||
}}
|
||||
navigationType={state.action}
|
||||
navigator={history}
|
||||
/>
|
||||
);
|
||||
}
|
69
src/components/Page.tsx
Normal file
69
src/components/Page.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React, { FunctionComponent, HTMLAttributes, useEffect, useRef } from 'react';
|
||||
|
||||
import viewManager from './viewManager/viewManager';
|
||||
|
||||
type PageProps = {
|
||||
id: string, // id is required for libraryMenu
|
||||
title?: string,
|
||||
isBackButtonEnabled?: boolean,
|
||||
isNowPlayingBarEnabled?: boolean,
|
||||
isThemeMediaSupported?: boolean
|
||||
};
|
||||
|
||||
/**
|
||||
* Page component that handles hiding active non-react views, triggering the required events for
|
||||
* navigation and appRouter state updates, and setting the correct classes and data attributes.
|
||||
*/
|
||||
const Page: FunctionComponent<PageProps & HTMLAttributes<HTMLDivElement>> = ({
|
||||
children,
|
||||
id,
|
||||
className = '',
|
||||
title,
|
||||
isBackButtonEnabled = true,
|
||||
isNowPlayingBarEnabled = true,
|
||||
isThemeMediaSupported = false
|
||||
}) => {
|
||||
const element = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// hide active non-react views
|
||||
viewManager.hideView();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const event = {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
detail: {
|
||||
isRestored: false,
|
||||
options: {
|
||||
enableMediaControl: isNowPlayingBarEnabled,
|
||||
supportsThemeMedia: isThemeMediaSupported
|
||||
}
|
||||
}
|
||||
};
|
||||
// viewbeforeshow - switches between the admin dashboard and standard themes
|
||||
element.current?.dispatchEvent(new CustomEvent('viewbeforeshow', event));
|
||||
// pagebeforeshow - hides tabs on tables pages in libraryMenu
|
||||
element.current?.dispatchEvent(new CustomEvent('pagebeforeshow', event));
|
||||
// viewshow - updates state of appRouter
|
||||
element.current?.dispatchEvent(new CustomEvent('viewshow', event));
|
||||
// pageshow - updates header/navigation in libraryMenu
|
||||
element.current?.dispatchEvent(new CustomEvent('pageshow', event));
|
||||
}, [ element, isNowPlayingBarEnabled, isThemeMediaSupported ]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={element}
|
||||
id={id}
|
||||
data-role='page'
|
||||
className={`page ${className}`}
|
||||
data-title={title}
|
||||
data-backbutton={`${isBackButtonEnabled}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
@ -122,7 +122,11 @@ class AppRouter {
|
||||
isBack: action === Action.Pop
|
||||
});
|
||||
} else {
|
||||
console.warn('[appRouter] "%s" route not found', normalizedPath, location);
|
||||
console.info('[appRouter] "%s" route not found', normalizedPath, location);
|
||||
this.currentRouteInfo = {
|
||||
route: {},
|
||||
path: normalizedPath + location.search
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +143,7 @@ class AppRouter {
|
||||
Events.on(apiClient, 'requestfail', this.onRequestFail);
|
||||
});
|
||||
|
||||
ServerConnections.connect().then(result => {
|
||||
return ServerConnections.connect().then(result => {
|
||||
this.firstConnectionResult = result;
|
||||
|
||||
// Handle the initial route
|
||||
|
@ -39,7 +39,8 @@ function getDeviceProfile(item) {
|
||||
profile = profileBuilder(builderOpts);
|
||||
}
|
||||
|
||||
const maxTranscodingVideoWidth = appHost.screen()?.maxAllowedWidth;
|
||||
const maxVideoWidth = appSettings.maxVideoWidth();
|
||||
const maxTranscodingVideoWidth = maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth;
|
||||
|
||||
if (maxTranscodingVideoWidth) {
|
||||
profile.TranscodingProfiles.forEach((transcodingProfile) => {
|
||||
|
@ -131,7 +131,8 @@ import { Events } from 'jellyfin-apiclient';
|
||||
}
|
||||
|
||||
function setCurrentTimeIfNeeded(element, seconds) {
|
||||
if (Math.abs(element.currentTime || 0, seconds) <= 1) {
|
||||
// If it's worth skipping (1 sec or less of a difference)
|
||||
if (Math.abs((element.currentTime || 0) - seconds) >= 1) {
|
||||
element.currentTime = seconds;
|
||||
}
|
||||
}
|
||||
|
@ -33,10 +33,10 @@ import template from './imageDownloader.template.html';
|
||||
let selectedProvider;
|
||||
let browsableParentId;
|
||||
|
||||
function getBaseRemoteOptions(page) {
|
||||
function getBaseRemoteOptions(page, forceCurrentItemId = false) {
|
||||
const options = {};
|
||||
|
||||
if (page.querySelector('#chkShowParentImages').checked && browsableParentId) {
|
||||
if (!forceCurrentItemId && page.querySelector('#chkShowParentImages').checked && browsableParentId) {
|
||||
options.itemId = browsableParentId;
|
||||
} else {
|
||||
options.itemId = currentItemId;
|
||||
@ -140,7 +140,7 @@ import template from './imageDownloader.template.html';
|
||||
}
|
||||
|
||||
function downloadRemoteImage(page, apiClient, url, type, provider) {
|
||||
const options = getBaseRemoteOptions(page);
|
||||
const options = getBaseRemoteOptions(page, true);
|
||||
|
||||
options.Type = type;
|
||||
options.ImageUrl = url;
|
||||
|
@ -113,7 +113,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
|
||||
if (stream.Profile) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoProfile'), stream.Profile));
|
||||
}
|
||||
if (stream.Level) {
|
||||
if (stream.Level > 0) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoLevel'), stream.Level));
|
||||
}
|
||||
if (stream.Width || stream.Height) {
|
||||
@ -128,7 +128,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
|
||||
}
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoInterlaced'), (stream.IsInterlaced ? 'Yes' : 'No')));
|
||||
}
|
||||
if (stream.AverageFrameRate || stream.RealFrameRate) {
|
||||
if ((stream.AverageFrameRate || stream.RealFrameRate) && stream.Type === 'Video') {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoFramerate'), (stream.AverageFrameRate || stream.RealFrameRate)));
|
||||
}
|
||||
if (stream.ChannelLayout) {
|
||||
@ -137,7 +137,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
|
||||
if (stream.Channels) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoChannels'), `${stream.Channels} ch`));
|
||||
}
|
||||
if (stream.BitRate && stream.Codec !== 'mjpeg') {
|
||||
if (stream.BitRate) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoBitrate'), `${parseInt(stream.BitRate / 1000)} kbps`));
|
||||
}
|
||||
if (stream.SampleRate) {
|
||||
@ -149,6 +149,36 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </s
|
||||
if (stream.VideoRange) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange));
|
||||
}
|
||||
if (stream.VideoRangeType) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType));
|
||||
}
|
||||
if (stream.VideoDoViTitle) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoDoViTitle'), stream.VideoDoViTitle));
|
||||
if (stream.DvVersionMajor != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMajor'), stream.DvVersionMajor));
|
||||
}
|
||||
if (stream.DvVersionMinor != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoDvVersionMinor'), stream.DvVersionMinor));
|
||||
}
|
||||
if (stream.DvProfile != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoDvProfile'), stream.DvProfile));
|
||||
}
|
||||
if (stream.DvLevel != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoDvLevel'), stream.DvLevel));
|
||||
}
|
||||
if (stream.RpuPresentFlag != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoRpuPresentFlag'), stream.RpuPresentFlag));
|
||||
}
|
||||
if (stream.ElPresentFlag != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoElPresentFlag'), stream.ElPresentFlag));
|
||||
}
|
||||
if (stream.BlPresentFlag != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoBlPresentFlag'), stream.BlPresentFlag));
|
||||
}
|
||||
if (stream.DvBlSignalCompatibilityId != null) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoDvBlSignalCompatibilityId'), stream.DvBlSignalCompatibilityId));
|
||||
}
|
||||
}
|
||||
if (stream.ColorSpace) {
|
||||
attributes.push(createAttribute(globalize.translate('MediaInfoColorSpace'), stream.ColorSpace));
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
|
||||
import SearchFields from '../search/SearchFields';
|
||||
import SearchResults from '../search/SearchResults';
|
||||
import SearchSuggestions from '../search/SearchSuggestions';
|
||||
import LiveTVSearchResults from '../search/LiveTVSearchResults';
|
||||
|
||||
type SearchProps = {
|
||||
serverId?: string,
|
||||
parentId?: string,
|
||||
collectionType?: string
|
||||
};
|
||||
|
||||
const SearchPage: FunctionComponent<SearchProps> = ({ serverId, parentId, collectionType }: SearchProps) => {
|
||||
const [ query, setQuery ] = useState<string>();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchFields onSearch={setQuery} />
|
||||
{!query &&
|
||||
<SearchSuggestions
|
||||
serverId={serverId || window.ApiClient.serverId()}
|
||||
parentId={parentId}
|
||||
/>
|
||||
}
|
||||
<SearchResults
|
||||
serverId={serverId || window.ApiClient.serverId()}
|
||||
parentId={parentId}
|
||||
collectionType={collectionType}
|
||||
query={query}
|
||||
/>
|
||||
<LiveTVSearchResults
|
||||
serverId={serverId || window.ApiClient.serverId()}
|
||||
parentId={parentId}
|
||||
collectionType={collectionType}
|
||||
query={query}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchPage;
|
@ -2281,7 +2281,7 @@ class PlaybackManager {
|
||||
score += 1;
|
||||
if (prevRelIndex == newRelIndex)
|
||||
score += 1;
|
||||
if (prevStream.Title && prevStream.Title == stream.Title)
|
||||
if (prevStream.DisplayTitle && prevStream.DisplayTitle == stream.DisplayTitle)
|
||||
score += 2;
|
||||
if (prevStream.Language && prevStream.Language != 'und' && prevStream.Language == stream.Language)
|
||||
score += 2;
|
||||
@ -2306,7 +2306,7 @@ class PlaybackManager {
|
||||
}
|
||||
}
|
||||
|
||||
function autoSetNextTracks(prevSource, mediaSource) {
|
||||
function autoSetNextTracks(prevSource, mediaSource, audio, subtitle) {
|
||||
try {
|
||||
if (!prevSource) return;
|
||||
|
||||
@ -2315,18 +2315,13 @@ class PlaybackManager {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof prevSource.DefaultAudioStreamIndex != 'number'
|
||||
|| typeof prevSource.DefaultSubtitleStreamIndex != 'number')
|
||||
return;
|
||||
|
||||
if (typeof mediaSource.DefaultAudioStreamIndex != 'number'
|
||||
|| typeof mediaSource.DefaultSubtitleStreamIndex != 'number') {
|
||||
console.warn('AutoSet - No stream indexes (but prevSource has them)');
|
||||
return;
|
||||
if (audio && typeof prevSource.DefaultAudioStreamIndex == 'number') {
|
||||
rankStreamType(prevSource.DefaultAudioStreamIndex, prevSource, mediaSource, 'Audio');
|
||||
}
|
||||
|
||||
rankStreamType(prevSource.DefaultAudioStreamIndex, prevSource, mediaSource, 'Audio');
|
||||
rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle');
|
||||
if (subtitle && typeof prevSource.DefaultSubtitleStreamIndex == 'number') {
|
||||
rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`AutoSet - Caught unexpected error: ${e}`);
|
||||
}
|
||||
@ -2390,9 +2385,9 @@ class PlaybackManager {
|
||||
// this reference was only needed by sendPlaybackListToPlayer
|
||||
playOptions.items = null;
|
||||
|
||||
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) {
|
||||
if (userSettings.enableSetUsingLastTracks())
|
||||
autoSetNextTracks(prevSource, mediaSource);
|
||||
return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(async (mediaSource) => {
|
||||
const user = await apiClient.getCurrentUser();
|
||||
autoSetNextTracks(prevSource, mediaSource, user.Configuration.RememberAudioSelections, user.Configuration.RememberSubtitleSelections);
|
||||
|
||||
const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition, player);
|
||||
|
||||
|
@ -41,7 +41,7 @@ import template from './playbackSettings.template.html';
|
||||
select.innerHTML = html;
|
||||
}
|
||||
|
||||
function setMaxBitrateIntoField(select, isInNetwork, mediatype) {
|
||||
function fillQuality(select, isInNetwork, mediatype, maxVideoWidth) {
|
||||
const options = mediatype === 'Audio' ? qualityoptions.getAudioQualityOptions({
|
||||
|
||||
currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype),
|
||||
@ -52,7 +52,8 @@ import template from './playbackSettings.template.html';
|
||||
|
||||
currentMaxBitrate: appSettings.maxStreamingBitrate(isInNetwork, mediatype),
|
||||
isAutomaticBitrateEnabled: appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype),
|
||||
enableAuto: true
|
||||
enableAuto: true,
|
||||
maxVideoWidth
|
||||
|
||||
});
|
||||
|
||||
@ -60,6 +61,10 @@ import template from './playbackSettings.template.html';
|
||||
// render empty string instead of 0 for the auto option
|
||||
return `<option value="${i.bitrate || ''}">${i.name}</option>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function setMaxBitrateIntoField(select, isInNetwork, mediatype) {
|
||||
fillQuality(select, isInNetwork, mediatype);
|
||||
|
||||
if (appSettings.enableAutomaticBitrateDetection(isInNetwork, mediatype)) {
|
||||
select.value = '';
|
||||
@ -68,12 +73,13 @@ import template from './playbackSettings.template.html';
|
||||
}
|
||||
}
|
||||
|
||||
function fillChromecastQuality(select) {
|
||||
function fillChromecastQuality(select, maxVideoWidth) {
|
||||
const options = qualityoptions.getVideoQualityOptions({
|
||||
|
||||
currentMaxBitrate: appSettings.maxChromecastBitrate(),
|
||||
isAutomaticBitrateEnabled: !appSettings.maxChromecastBitrate(),
|
||||
enableAuto: true
|
||||
enableAuto: true,
|
||||
maxVideoWidth
|
||||
});
|
||||
|
||||
select.innerHTML = options.map(i => {
|
||||
@ -180,7 +186,8 @@ import template from './playbackSettings.template.html';
|
||||
context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer();
|
||||
context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode();
|
||||
context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay();
|
||||
context.querySelector('.chkSetUsingLastTracks').checked = userSettings.enableSetUsingLastTracks();
|
||||
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
|
||||
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
|
||||
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
|
||||
|
||||
setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
|
||||
@ -192,6 +199,9 @@ import template from './playbackSettings.template.html';
|
||||
const selectChromecastVersion = context.querySelector('.selectChromecastVersion');
|
||||
selectChromecastVersion.value = userSettings.chromecastVersion();
|
||||
|
||||
const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth');
|
||||
selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth();
|
||||
|
||||
const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength');
|
||||
fillSkipLengths(selectSkipForwardLength);
|
||||
selectSkipForwardLength.value = userSettings.skipForwardLength();
|
||||
@ -209,6 +219,7 @@ import template from './playbackSettings.template.html';
|
||||
appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked);
|
||||
|
||||
appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value);
|
||||
appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value);
|
||||
|
||||
setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
|
||||
setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
|
||||
@ -222,7 +233,8 @@ import template from './playbackSettings.template.html';
|
||||
userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked);
|
||||
|
||||
userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked);
|
||||
userSettingsInstance.enableSetUsingLastTracks(context.querySelector('.chkSetUsingLastTracks').checked);
|
||||
user.Configuration.RememberAudioSelections = context.querySelector('.chkRememberAudioSelections').checked;
|
||||
user.Configuration.RememberSubtitleSelections = context.querySelector('.chkRememberSubtitleSelections').checked;
|
||||
userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value);
|
||||
userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value);
|
||||
userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value);
|
||||
@ -247,6 +259,36 @@ import template from './playbackSettings.template.html';
|
||||
});
|
||||
}
|
||||
|
||||
function setSelectValue(select, value, defaultValue) {
|
||||
select.value = value;
|
||||
|
||||
if (select.selectedIndex < 0) {
|
||||
select.value = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
function onMaxVideoWidthChange(e) {
|
||||
const context = this.options.element;
|
||||
|
||||
const selectVideoInNetworkQuality = context.querySelector('.selectVideoInNetworkQuality');
|
||||
const selectVideoInternetQuality = context.querySelector('.selectVideoInternetQuality');
|
||||
const selectChromecastVideoQuality = context.querySelector('.selectChromecastVideoQuality');
|
||||
|
||||
const selectVideoInNetworkQualityValue = selectVideoInNetworkQuality.value;
|
||||
const selectVideoInternetQualityValue = selectVideoInternetQuality.value;
|
||||
const selectChromecastVideoQualityValue = selectChromecastVideoQuality.value;
|
||||
|
||||
const maxVideoWidth = parseInt(e.target.value || '0', 10) || 0;
|
||||
|
||||
fillQuality(selectVideoInNetworkQuality, true, 'Video', maxVideoWidth);
|
||||
fillQuality(selectVideoInternetQuality, false, 'Video', maxVideoWidth);
|
||||
fillChromecastQuality(selectChromecastVideoQuality, maxVideoWidth);
|
||||
|
||||
setSelectValue(selectVideoInNetworkQuality, selectVideoInNetworkQualityValue, '');
|
||||
setSelectValue(selectVideoInternetQuality, selectVideoInternetQualityValue, '');
|
||||
setSelectValue(selectChromecastVideoQuality, selectChromecastVideoQualityValue, '');
|
||||
}
|
||||
|
||||
function onSubmit(e) {
|
||||
const self = this;
|
||||
const apiClient = ServerConnections.getApiClient(self.options.serverId);
|
||||
@ -274,6 +316,8 @@ import template from './playbackSettings.template.html';
|
||||
options.element.querySelector('.btnSave').classList.remove('hide');
|
||||
}
|
||||
|
||||
options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self));
|
||||
|
||||
self.loadData();
|
||||
|
||||
if (options.autoFocus) {
|
||||
|
@ -41,6 +41,19 @@
|
||||
<div class="selectContainer fldChromecastQuality hide">
|
||||
<select is="emby-select" class="selectChromecastVideoQuality" label="${LabelMaxChromecastBitrate}"></select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
<select is="emby-select" class="selectLabelMaxVideoWidth" label="${LabelMaxVideoResolution}">
|
||||
<option value="0">${Auto}</option>
|
||||
<option value="-1">${ScreenResolution}</option>
|
||||
<option value="640">360p</option>
|
||||
<option value="852">480p</option>
|
||||
<option value="1280">720p</option>
|
||||
<option value="1920">1080p</option>
|
||||
<option value="3840">4K</option>
|
||||
<option value="7680">8K</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="verticalSection verticalSection-extrabottompadding musicQualitySection hide">
|
||||
@ -84,10 +97,18 @@
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkSetUsingLastTracks" />
|
||||
<span>${SetUsingLastTracks}</span>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkRememberAudioSelections" />
|
||||
<span>${RememberAudioSelections}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${SetUsingLastTracksHelp}</div>
|
||||
<div class="fieldDescription checkboxFieldDescription">${RememberAudioSelectionsHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" class="chkRememberSubtitleSelections" />
|
||||
<span>${RememberSubtitleSelections}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${RememberSubtitleSelectionsHelp}</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxContainer checkboxContainer-withDescription fldEnableNextVideoOverlay">
|
||||
|
@ -269,31 +269,10 @@ import ServerConnections from '../ServerConnections';
|
||||
});
|
||||
}
|
||||
|
||||
if (videoStream.VideoRange) {
|
||||
if (videoStream.VideoRangeType) {
|
||||
sessionStats.push({
|
||||
label: globalize.translate('LabelVideoRange'),
|
||||
value: videoStream.VideoRange
|
||||
});
|
||||
}
|
||||
|
||||
if (videoStream.ColorSpace) {
|
||||
sessionStats.push({
|
||||
label: globalize.translate('LabelColorSpace'),
|
||||
value: videoStream.ColorSpace
|
||||
});
|
||||
}
|
||||
|
||||
if (videoStream.ColorTransfer) {
|
||||
sessionStats.push({
|
||||
label: globalize.translate('LabelColorTransfer'),
|
||||
value: videoStream.ColorTransfer
|
||||
});
|
||||
}
|
||||
|
||||
if (videoStream.ColorPrimaries) {
|
||||
sessionStats.push({
|
||||
label: globalize.translate('LabelColorPrimaries'),
|
||||
value: videoStream.ColorPrimaries
|
||||
label: globalize.translate('LabelVideoRangeType'),
|
||||
value: videoStream.VideoRangeType
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { appHost } from '../components/apphost';
|
||||
import globalize from '../scripts/globalize';
|
||||
import appSettings from '../scripts/settings/appSettings';
|
||||
|
||||
export function getVideoQualityOptions(options) {
|
||||
const maxStreamingBitrate = options.currentMaxBitrate;
|
||||
@ -12,7 +13,9 @@ export function getVideoQualityOptions(options) {
|
||||
videoWidth = videoHeight * (16 / 9);
|
||||
}
|
||||
|
||||
const hostScreenWidth = appHost.screen()?.maxAllowedWidth || 4096;
|
||||
const maxVideoWidth = options.maxVideoWidth == null ? appSettings.maxVideoWidth() : options.maxVideoWidth;
|
||||
|
||||
const hostScreenWidth = (maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth) || 4096;
|
||||
const maxAllowedWidth = videoWidth || 4096;
|
||||
|
||||
const qualityOptions = [];
|
||||
|
@ -21,8 +21,8 @@ const CARD_OPTIONS = {
|
||||
|
||||
type LiveTVSearchResultsProps = {
|
||||
serverId?: string;
|
||||
parentId?: string;
|
||||
collectionType?: string;
|
||||
parentId?: string | null;
|
||||
collectionType?: string | null;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ import SearchResultsRow from './SearchResultsRow';
|
||||
|
||||
type SearchResultsProps = {
|
||||
serverId?: string;
|
||||
parentId?: string;
|
||||
collectionType?: string;
|
||||
parentId?: string | null;
|
||||
collectionType?: string | null;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ const createSuggestionLink = ({ name, href }: { name: string, href: string }) =>
|
||||
|
||||
type SearchSuggestionsProps = {
|
||||
serverId?: string;
|
||||
parentId?: string;
|
||||
parentId?: string | null;
|
||||
}
|
||||
|
||||
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ serverId = window.ApiClient.serverId(), parentId }: SearchSuggestionsProps) => {
|
||||
|
@ -32,7 +32,7 @@ function getSubtitleAppearanceObject(context) {
|
||||
appearanceSettings.dropShadow = context.querySelector('#selectDropShadow').value;
|
||||
appearanceSettings.font = context.querySelector('#selectFont').value;
|
||||
appearanceSettings.textBackground = context.querySelector('#inputTextBackground').value;
|
||||
appearanceSettings.textColor = context.querySelector('#inputTextColor').value;
|
||||
appearanceSettings.textColor = layoutManager.tv ? context.querySelector('#selectTextColor').value : context.querySelector('#inputTextColor').value;
|
||||
appearanceSettings.verticalPosition = context.querySelector('#sliderVerticalPosition').value;
|
||||
|
||||
return appearanceSettings;
|
||||
@ -57,6 +57,7 @@ function loadForm(context, user, userSettings, appearanceSettings, apiClient) {
|
||||
context.querySelector('#selectTextWeight').value = appearanceSettings.textWeight || 'normal';
|
||||
context.querySelector('#selectDropShadow').value = appearanceSettings.dropShadow || '';
|
||||
context.querySelector('#inputTextBackground').value = appearanceSettings.textBackground || 'transparent';
|
||||
context.querySelector('#selectTextColor').value = appearanceSettings.textColor || '#ffffff';
|
||||
context.querySelector('#inputTextColor').value = appearanceSettings.textColor || '#ffffff';
|
||||
context.querySelector('#selectFont').value = appearanceSettings.font || '';
|
||||
context.querySelector('#sliderVerticalPosition').value = appearanceSettings.verticalPosition;
|
||||
@ -171,6 +172,7 @@ function embed(options, self) {
|
||||
options.element.querySelector('#selectTextWeight').addEventListener('change', onAppearanceFieldChange);
|
||||
options.element.querySelector('#selectDropShadow').addEventListener('change', onAppearanceFieldChange);
|
||||
options.element.querySelector('#selectFont').addEventListener('change', onAppearanceFieldChange);
|
||||
options.element.querySelector('#selectTextColor').addEventListener('change', onAppearanceFieldChange);
|
||||
options.element.querySelector('#inputTextColor').addEventListener('change', onAppearanceFieldChange);
|
||||
options.element.querySelector('#inputTextBackground').addEventListener('change', onAppearanceFieldChange);
|
||||
|
||||
@ -201,6 +203,10 @@ function embed(options, self) {
|
||||
sliderVerticalPosition.classList.add('focusable');
|
||||
sliderVerticalPosition.enableKeyboardDragging();
|
||||
}, 0);
|
||||
|
||||
// Replace color picker
|
||||
dom.parentWithTag(options.element.querySelector('#inputTextColor'), 'DIV').classList.add('hide');
|
||||
dom.parentWithTag(options.element.querySelector('#selectTextColor'), 'DIV').classList.remove('hide');
|
||||
}
|
||||
|
||||
options.element.querySelector('.chkPreview').addEventListener('change', (e) => {
|
||||
|
@ -95,8 +95,21 @@
|
||||
<input is="emby-input" id="inputTextBackground" label="${LabelTextBackgroundColor}" type="text" />
|
||||
</div>
|
||||
|
||||
<div class="inputContainer hide">
|
||||
<input is="emby-input" id="inputTextColor" label="${LabelTextColor}" type="text" />
|
||||
<div class="selectContainer">
|
||||
<input is="emby-input" id="inputTextColor" label="${LabelTextColor}" type="color" />
|
||||
</div>
|
||||
|
||||
<div class="selectContainer hide">
|
||||
<select is="emby-select" id="selectTextColor" label="${LabelTextColor}">
|
||||
<option value="#ffffff">${White}</option>
|
||||
<option value="#ffff00">${Yellow}</option>
|
||||
<option value="#008000">${Green}</option>
|
||||
<option value="#00ffff">${Cyan}</option>
|
||||
<option value="#0000ff">${Blue}</option>
|
||||
<option value="#ff00ff">${Magenta}</option>
|
||||
<option value="#ff0000">${Red}</option>
|
||||
<option value="#000000">${Black}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="selectContainer">
|
||||
|
@ -147,6 +147,15 @@ class ViewManager {
|
||||
});
|
||||
}
|
||||
|
||||
hideView() {
|
||||
if (currentView) {
|
||||
dispatchViewEvent(currentView, null, 'viewbeforehide');
|
||||
dispatchViewEvent(currentView, null, 'viewhide');
|
||||
currentView.classList.add('hide');
|
||||
currentView = null;
|
||||
}
|
||||
}
|
||||
|
||||
tryRestoreView(options, onViewChanging) {
|
||||
if (options.cancel) {
|
||||
return Promise.reject({ cancelled: true });
|
||||
|
@ -126,13 +126,24 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkboxListContainer checkboxContainer-withDescription fldVppTonemapping hide">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkVppTonemapping" />
|
||||
<span>${EnableVppTonemapping}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${AllowVppTonemappingHelp}</div>
|
||||
<div class="vppTonemappingOptions hide">
|
||||
<div class="checkboxListContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkVppTonemapping" />
|
||||
<span>${EnableVppTonemapping}</span>
|
||||
</label>
|
||||
<div class="fieldDescription checkboxFieldDescription">${AllowVppTonemappingHelp}</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtVppTonemappingBrightness" pattern="[0-9]*" min="0" max="100" step=".00001" label="${LabelVppTonemappingBrightness}" />
|
||||
<div class="fieldDescription">${LabelVppTonemappingBrightnessHelp}</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<input is="emby-input" type="number" id="txtVppTonemappingContrast" pattern="[0-9]*" min="1" max="2" step=".00001" label="${LabelVppTonemappingContrast}" />
|
||||
<div class="fieldDescription">${LabelVppTonemappingContrastHelp}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tonemappingOptions hide">
|
||||
<div class="checkboxListContainer checkboxContainer-withDescription">
|
||||
<label>
|
||||
|
@ -37,6 +37,8 @@ import alert from '../../components/alert';
|
||||
page.querySelector('#txtTonemappingThreshold').value = config.TonemappingThreshold;
|
||||
page.querySelector('#txtTonemappingPeak').value = config.TonemappingPeak;
|
||||
page.querySelector('#txtTonemappingParam').value = config.TonemappingParam || '';
|
||||
page.querySelector('#txtVppTonemappingBrightness').value = config.VppTonemappingBrightness;
|
||||
page.querySelector('#txtVppTonemappingContrast').value = config.VppTonemappingContrast;
|
||||
page.querySelector('#selectEncoderPreset').value = config.EncoderPreset || '';
|
||||
page.querySelector('#txtH264Crf').value = config.H264Crf || '';
|
||||
page.querySelector('#txtH265Crf').value = config.H265Crf || '';
|
||||
@ -91,6 +93,8 @@ import alert from '../../components/alert';
|
||||
config.TonemappingThreshold = form.querySelector('#txtTonemappingThreshold').value;
|
||||
config.TonemappingPeak = form.querySelector('#txtTonemappingPeak').value;
|
||||
config.TonemappingParam = form.querySelector('#txtTonemappingParam').value || '0';
|
||||
config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value;
|
||||
config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value;
|
||||
config.EncoderPreset = form.querySelector('#selectEncoderPreset').value;
|
||||
config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0');
|
||||
config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0');
|
||||
@ -205,9 +209,9 @@ import alert from '../../components/alert';
|
||||
}
|
||||
|
||||
if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) {
|
||||
page.querySelector('.fldVppTonemapping').classList.remove('hide');
|
||||
page.querySelector('.vppTonemappingOptions').classList.remove('hide');
|
||||
} else {
|
||||
page.querySelector('.fldVppTonemapping').classList.add('hide');
|
||||
page.querySelector('.vppTonemappingOptions').classList.add('hide');
|
||||
}
|
||||
|
||||
if (this.value == 'qsv') {
|
||||
|
@ -1,2 +0,0 @@
|
||||
<div id="searchPage" data-role="page" class="page libraryPage allLibraryPage noSecondaryNavPage" data-title="${Search}" data-backbutton="true">
|
||||
</div>
|
@ -17,6 +17,11 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.emby-input[type=color] {
|
||||
height: 2.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.emby-input::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
@ -158,6 +158,7 @@
|
||||
<div class="mainAnimatedPages skinBody">
|
||||
<div class="splashLogo"></div>
|
||||
</div>
|
||||
<div id="reactRoot"></div>
|
||||
<div class="mainDrawerHandle"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -74,7 +74,7 @@ function enableHlsPlayer(url, item, mediaSource, mediaType) {
|
||||
type: 'HEAD'
|
||||
}).then(function (response) {
|
||||
const contentType = (response.headers.get('Content-Type') || '').toLowerCase();
|
||||
if (contentType === 'application/x-mpegurl') {
|
||||
if (contentType === 'application/vnd.apple.mpegurl' || contentType === 'application/x-mpegurl') {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
|
@ -1376,6 +1376,9 @@ function tryRemoveElement(elem) {
|
||||
// Can't autoplay in these browsers so we need to use the full controls, at least until playback starts
|
||||
if (!appHost.supports('htmlvideoautoplay')) {
|
||||
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" controls="controls" webkit-playsinline playsinline>';
|
||||
} else if (browser.web0s) {
|
||||
// in webOS, setting preload auto allows resuming videos
|
||||
html += '<video class="' + cssClass + '" preload="auto" autoplay="autoplay" webkit-playsinline playsinline>';
|
||||
} else {
|
||||
// Chrome 35 won't play with preload none
|
||||
html += '<video class="' + cssClass + '" preload="metadata" autoplay="autoplay" webkit-playsinline playsinline>';
|
||||
|
24
src/routes/index.tsx
Normal file
24
src/routes/index.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
|
||||
import ConnectionRequired from '../components/ConnectionRequired';
|
||||
import SearchPage from './search';
|
||||
|
||||
const AppRoutes = () => (
|
||||
<Routes>
|
||||
<Route path='/'>
|
||||
<Route
|
||||
path='search.html'
|
||||
element={
|
||||
<ConnectionRequired>
|
||||
<SearchPage />
|
||||
</ConnectionRequired>
|
||||
}
|
||||
/>
|
||||
{/* Suppress warnings for unhandled routes */}
|
||||
<Route path='*' element={null} />
|
||||
</Route>
|
||||
</Routes>
|
||||
);
|
||||
|
||||
export default AppRoutes;
|
44
src/routes/search.tsx
Normal file
44
src/routes/search.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
|
||||
import Page from '../components/Page';
|
||||
import SearchFields from '../components/search/SearchFields';
|
||||
import SearchResults from '../components/search/SearchResults';
|
||||
import SearchSuggestions from '../components/search/SearchSuggestions';
|
||||
import LiveTVSearchResults from '../components/search/LiveTVSearchResults';
|
||||
import globalize from '../scripts/globalize';
|
||||
|
||||
const SearchPage: FunctionComponent = () => {
|
||||
const [ query, setQuery ] = useState<string>();
|
||||
const [ searchParams ] = useSearchParams();
|
||||
|
||||
return (
|
||||
<Page
|
||||
id='searchPage'
|
||||
title={globalize.translate('Search')}
|
||||
className='mainAnimatedPage libraryPage allLibraryPage noSecondaryNavPage'
|
||||
>
|
||||
<SearchFields onSearch={setQuery} />
|
||||
{!query &&
|
||||
<SearchSuggestions
|
||||
serverId={searchParams.get('serverId') || window.ApiClient.serverId()}
|
||||
parentId={searchParams.get('parentId')}
|
||||
/>
|
||||
}
|
||||
<SearchResults
|
||||
serverId={searchParams.get('serverId') || window.ApiClient.serverId()}
|
||||
parentId={searchParams.get('parentId')}
|
||||
collectionType={searchParams.get('collectionType')}
|
||||
query={query}
|
||||
/>
|
||||
<LiveTVSearchResults
|
||||
serverId={searchParams.get('serverId') || window.ApiClient.serverId()}
|
||||
parentId={searchParams.get('parentId')}
|
||||
collectionType={searchParams.get('collectionType')}
|
||||
query={query}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchPage;
|
@ -312,7 +312,7 @@ import browser from './browser';
|
||||
return -1;
|
||||
}
|
||||
|
||||
function getPhysicalAudioChannels(options) {
|
||||
function getPhysicalAudioChannels(options, videoTestElement) {
|
||||
const allowedAudioChannels = parseInt(userSettings.allowedAudioChannels(), 10);
|
||||
|
||||
if (allowedAudioChannels > 0) {
|
||||
@ -324,8 +324,14 @@ import browser from './browser';
|
||||
}
|
||||
|
||||
const isSurroundSoundSupportedBrowser = browser.safari || browser.chrome || browser.edgeChromium || browser.firefox || browser.tv || browser.ps4 || browser.xboxOne;
|
||||
const isAc3Eac3Supported = supportsAc3(videoTestElement) || supportsEac3(videoTestElement);
|
||||
const speakerCount = getSpeakerCount();
|
||||
|
||||
// AC3/EAC3 hinted that device is able to play dolby surround sound.
|
||||
if (isAc3Eac3Supported && isSurroundSoundSupportedBrowser) {
|
||||
return speakerCount > 6 ? speakerCount : 6;
|
||||
}
|
||||
|
||||
if (speakerCount > 2) {
|
||||
if (isSurroundSoundSupportedBrowser) {
|
||||
return speakerCount;
|
||||
@ -348,12 +354,12 @@ import browser from './browser';
|
||||
export default function (options) {
|
||||
options = options || {};
|
||||
|
||||
const physicalAudioChannels = getPhysicalAudioChannels(options);
|
||||
|
||||
const bitrateSetting = getMaxBitrate();
|
||||
|
||||
const videoTestElement = document.createElement('video');
|
||||
|
||||
const physicalAudioChannels = getPhysicalAudioChannels(options, videoTestElement);
|
||||
|
||||
const canPlayVp8 = videoTestElement.canPlayType('video/webm; codecs="vp8"').replace(/no/, '');
|
||||
const canPlayVp9 = videoTestElement.canPlayType('video/webm; codecs="vp9"').replace(/no/, '');
|
||||
const webmAudioCodecs = ['vorbis'];
|
||||
@ -851,6 +857,29 @@ import browser from './browser';
|
||||
hevcProfiles = 'main|main 10';
|
||||
}
|
||||
|
||||
const h264VideoRangeTypes = 'SDR';
|
||||
let hevcVideoRangeTypes = 'SDR';
|
||||
let vp9VideoRangeTypes = 'SDR';
|
||||
let av1VideoRangeTypes = 'SDR';
|
||||
|
||||
if (browser.safari && ((browser.iOS && browser.iOSVersion >= 11) || browser.osx)) {
|
||||
hevcVideoRangeTypes += '|HDR10|HLG';
|
||||
if ((browser.iOS && browser.iOSVersion >= 13) || browser.osx) {
|
||||
hevcVideoRangeTypes += '|DOVI';
|
||||
}
|
||||
}
|
||||
|
||||
if (browser.tizen || browser.web0s) {
|
||||
hevcVideoRangeTypes += '|HDR10|HLG|DOVI';
|
||||
vp9VideoRangeTypes += '|HDR10|HLG';
|
||||
av1VideoRangeTypes += '|HDR10|HLG';
|
||||
}
|
||||
|
||||
if (browser.edgeChromium || browser.chrome || browser.firefox) {
|
||||
vp9VideoRangeTypes += '|HDR10|HLG';
|
||||
av1VideoRangeTypes += '|HDR10|HLG';
|
||||
}
|
||||
|
||||
const h264CodecProfileConditions = [
|
||||
{
|
||||
Condition: 'NotEquals',
|
||||
@ -864,6 +893,12 @@ import browser from './browser';
|
||||
Value: h264Profiles,
|
||||
IsRequired: false
|
||||
},
|
||||
{
|
||||
Condition: 'EqualsAny',
|
||||
Property: 'VideoRangeType',
|
||||
Value: h264VideoRangeTypes,
|
||||
IsRequired: false
|
||||
},
|
||||
{
|
||||
Condition: 'LessThanEqual',
|
||||
Property: 'VideoLevel',
|
||||
@ -885,6 +920,12 @@ import browser from './browser';
|
||||
Value: hevcProfiles,
|
||||
IsRequired: false
|
||||
},
|
||||
{
|
||||
Condition: 'EqualsAny',
|
||||
Property: 'VideoRangeType',
|
||||
Value: hevcVideoRangeTypes,
|
||||
IsRequired: false
|
||||
},
|
||||
{
|
||||
Condition: 'LessThanEqual',
|
||||
Property: 'VideoLevel',
|
||||
@ -893,6 +934,24 @@ import browser from './browser';
|
||||
}
|
||||
];
|
||||
|
||||
const vp9CodecProfileConditions = [
|
||||
{
|
||||
Condition: 'EqualsAny',
|
||||
Property: 'VideoRangeType',
|
||||
Value: vp9VideoRangeTypes,
|
||||
IsRequired: false
|
||||
}
|
||||
];
|
||||
|
||||
const av1CodecProfileConditions = [
|
||||
{
|
||||
Condition: 'EqualsAny',
|
||||
Property: 'VideoRangeType',
|
||||
Value: av1VideoRangeTypes,
|
||||
IsRequired: false
|
||||
}
|
||||
];
|
||||
|
||||
if (!browser.edgeUwp && !browser.tizen && !browser.web0s) {
|
||||
h264CodecProfileConditions.push({
|
||||
Condition: 'NotEquals',
|
||||
@ -982,6 +1041,18 @@ import browser from './browser';
|
||||
Conditions: hevcCodecProfileConditions
|
||||
});
|
||||
|
||||
profile.CodecProfiles.push({
|
||||
Type: 'Video',
|
||||
Codec: 'vp9',
|
||||
Conditions: vp9CodecProfileConditions
|
||||
});
|
||||
|
||||
profile.CodecProfiles.push({
|
||||
Type: 'Video',
|
||||
Codec: 'av1',
|
||||
Conditions: av1CodecProfileConditions
|
||||
});
|
||||
|
||||
const globalVideoConditions = [];
|
||||
|
||||
if (globalMaxVideoBitrate) {
|
||||
|
@ -11,6 +11,7 @@
|
||||
case 'Sony PS4':
|
||||
return baseUrl + 'playstation.svg';
|
||||
case 'Kodi':
|
||||
case 'Kodi JellyCon':
|
||||
return baseUrl + 'kodi.svg';
|
||||
case 'Jellyfin Android':
|
||||
case 'AndroidTV':
|
||||
@ -18,6 +19,8 @@
|
||||
return baseUrl + 'android.svg';
|
||||
case 'Jellyfin Mobile (iOS)':
|
||||
case 'Jellyfin Mobile (iPadOS)':
|
||||
case 'Jellyfin iOS':
|
||||
case 'Infuse':
|
||||
return baseUrl + 'apple.svg';
|
||||
case 'Jellyfin Web':
|
||||
switch (device.Name || device.DeviceName) {
|
||||
|
@ -308,12 +308,6 @@ import { appRouter } from '../components/appRouter';
|
||||
type: 'home'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/search.html',
|
||||
path: 'search.html',
|
||||
pageComponent: 'SearchPage'
|
||||
});
|
||||
|
||||
defineRoute({
|
||||
alias: '/list.html',
|
||||
path: 'list.html',
|
||||
|
@ -92,6 +92,19 @@ class AppSettings {
|
||||
return val ? parseInt(val) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set 'Maximum video width'
|
||||
* @param {number|undefined} val - Maximum video width or undefined.
|
||||
* @return {number} Maximum video width.
|
||||
*/
|
||||
maxVideoWidth(val) {
|
||||
if (val !== undefined) {
|
||||
return this.set('maxVideoWidth', val.toString());
|
||||
}
|
||||
|
||||
return parseInt(this.get('maxVideoWidth') || '0', 10) || 0;
|
||||
}
|
||||
|
||||
set(name, value, userId) {
|
||||
const currentValue = this.get(name, userId);
|
||||
AppStorage.setItem(this.#getKey(name, userId), value);
|
||||
|
@ -164,19 +164,6 @@ export class UserSettings {
|
||||
return toBoolean(this.get('enableNextVideoInfoOverlay', false), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set 'SetUsingLastTracks' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'SetUsingLastTracks' or undefined.
|
||||
* @return {boolean} 'SetUsingLastTracks' state.
|
||||
*/
|
||||
enableSetUsingLastTracks(val) {
|
||||
if (val !== undefined) {
|
||||
return this.set('enableSetUsingLastTracks', val.toString());
|
||||
}
|
||||
|
||||
return toBoolean(this.get('enableSetUsingLastTracks', false), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set 'Theme Songs' state.
|
||||
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined.
|
||||
@ -561,7 +548,6 @@ export const allowedAudioChannels = currentSettings.allowedAudioChannels.bind(cu
|
||||
export const preferFmp4HlsContainer = currentSettings.preferFmp4HlsContainer.bind(currentSettings);
|
||||
export const enableCinemaMode = currentSettings.enableCinemaMode.bind(currentSettings);
|
||||
export const enableNextVideoInfoOverlay = currentSettings.enableNextVideoInfoOverlay.bind(currentSettings);
|
||||
export const enableSetUsingLastTracks = currentSettings.enableSetUsingLastTracks.bind(currentSettings);
|
||||
export const enableThemeSongs = currentSettings.enableThemeSongs.bind(currentSettings);
|
||||
export const enableThemeVideos = currentSettings.enableThemeVideos.bind(currentSettings);
|
||||
export const enableFastFadein = currentSettings.enableFastFadein.bind(currentSettings);
|
||||
|
@ -7,6 +7,8 @@ import 'classlist.js';
|
||||
import 'whatwg-fetch';
|
||||
import 'resize-observer-polyfill';
|
||||
import '../assets/css/site.scss';
|
||||
import React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Events } from 'jellyfin-apiclient';
|
||||
import ServerConnections from '../components/ServerConnections';
|
||||
import globalize from './globalize';
|
||||
@ -18,7 +20,7 @@ import { appHost } from '../components/apphost';
|
||||
import { getPlugins } from './settings/webSettings';
|
||||
import { pluginManager } from '../components/pluginManager';
|
||||
import packageManager from '../components/packageManager';
|
||||
import { appRouter } from '../components/appRouter';
|
||||
import { appRouter, history } from '../components/appRouter';
|
||||
import '../elements/emby-button/emby-button';
|
||||
import './autoThemes';
|
||||
import './libraryMenu';
|
||||
@ -40,6 +42,8 @@ import SyncPlayHtmlVideoPlayer from '../components/syncPlay/ui/players/HtmlVideo
|
||||
import SyncPlayHtmlAudioPlayer from '../components/syncPlay/ui/players/HtmlAudioPlayer';
|
||||
import { currentSettings } from './settings/userSettings';
|
||||
import taskButton from './taskbutton';
|
||||
import { HistoryRouter } from '../components/HistoryRouter.tsx';
|
||||
import AppRoutes from '../routes/index.tsx';
|
||||
|
||||
function loadCoreDictionary() {
|
||||
const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'et', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw'];
|
||||
@ -167,7 +171,14 @@ async function onAppReady() {
|
||||
ServerConnections.currentApiClient()?.ensureWebSocket();
|
||||
});
|
||||
|
||||
appRouter.start();
|
||||
await appRouter.start();
|
||||
|
||||
ReactDOM.render(
|
||||
<HistoryRouter history={history}>
|
||||
<AppRoutes />
|
||||
</HistoryRouter>,
|
||||
document.getElementById('reactRoot')
|
||||
);
|
||||
|
||||
if (!browser.tv && !browser.xboxOne && !browser.ps4) {
|
||||
import('../components/nowPlayingBar/nowPlayingBar');
|
||||
|
@ -1653,5 +1653,27 @@
|
||||
"HomeVideosPhotos": "Domácí videa a fotky",
|
||||
"Bold": "Tučné",
|
||||
"LabelTextWeight": "Tloušťka textu:",
|
||||
"EnableSplashScreen": "Povolit úvodní obrazovku"
|
||||
"EnableSplashScreen": "Povolit úvodní obrazovku",
|
||||
"MediaInfoDvBlSignalCompatibilityId": "ID kompatibility signálu DV Bl",
|
||||
"MediaInfoBlPresentFlag": "Natavení předvolby DV Bl",
|
||||
"MediaInfoElPresentFlag": "Nastavení předvolby DV El",
|
||||
"MediaInfoRpuPresentFlag": "Nastavení předvolby DV RPU",
|
||||
"MediaInfoDvLevel": "Úroveň DV",
|
||||
"MediaInfoDvProfile": "Profil DV",
|
||||
"MediaInfoDvVersionMinor": "Vedlejší verze DV",
|
||||
"MediaInfoDvVersionMajor": "Hlavní verze DV",
|
||||
"MediaInfoDoViTitle": "Název DV",
|
||||
"MediaInfoVideoRangeType": "Typ rozsahu videa",
|
||||
"LabelVideoRangeType": "Typ rozsahu videa:",
|
||||
"VideoRangeTypeNotSupported": "Typ rozsahu videa není podporován",
|
||||
"LabelVppTonemappingContrastHelp": "Zvýší kontrast při mapování tonů VPP. Doporučená hodnota je 1.2, výchozí hodnota je 1.",
|
||||
"LabelVppTonemappingContrast": "Zvýšení kontrastu mapování tónů VPP:",
|
||||
"LabelVppTonemappingBrightnessHelp": "Zvýší jas při mapování tonů VPP. Doporučená i výchozí hodnota je 0.",
|
||||
"LabelVppTonemappingBrightness": "Zvýšení jasu mapování tónů VPP:",
|
||||
"ScreenResolution": "Rozlišení obrazovky",
|
||||
"RememberSubtitleSelectionsHelp": "Pokusí se nastavit titulkovou stopu co nejpodobněji předchozímu videu.",
|
||||
"RememberSubtitleSelections": "Nastavit titulkovou stopu podle předchozí položky",
|
||||
"RememberAudioSelectionsHelp": "Pokusí se nastavit zvukovou stopu co nejpodobněji předchozímu videu.",
|
||||
"RememberAudioSelections": "Nastavit zvukovou stopu podle předchozí položky",
|
||||
"LabelMaxVideoResolution": "Maximální rozlišení videa pro překódování"
|
||||
}
|
||||
|
@ -1653,5 +1653,21 @@
|
||||
"HomeVideosPhotos": "Heimvideos und -bilder",
|
||||
"Bold": "Fett",
|
||||
"LabelTextWeight": "Schriftstärke:",
|
||||
"EnableSplashScreen": "Aktiviere den Splash Screen"
|
||||
"EnableSplashScreen": "Aktiviere den Splash Screen",
|
||||
"MediaInfoRpuPresentFlag": "DV rpu Flag anwesend",
|
||||
"MediaInfoDvLevel": "DV Level",
|
||||
"MediaInfoDvProfile": "DV Profil",
|
||||
"MediaInfoDvVersionMinor": "DV Nebenversion",
|
||||
"MediaInfoDvVersionMajor": "DV Hauptversion",
|
||||
"MediaInfoDoViTitle": "DV Titel",
|
||||
"LabelVppTonemappingContrastHelp": "VPP Tonemapping Kontrast anwenden. Empfohlene und Standartwerte sind 1,2 und 1.",
|
||||
"LabelVppTonemappingContrast": "VPP Tonemapping Kontrast:",
|
||||
"LabelVppTonemappingBrightnessHelp": "VPP Tonemapping Helligkeit anwenden. Empfohlener und Standartwert sind 0.",
|
||||
"LabelVppTonemappingBrightness": "VPP Tonemapping Helligkeit:",
|
||||
"ScreenResolution": "Bildschirmauflösung",
|
||||
"RememberSubtitleSelectionsHelp": "Versuche den zum letzten Video ähnlichsten Untertitel zu setzen.",
|
||||
"RememberSubtitleSelections": "Setze den Untertitel auf Basis des letzten Objekts",
|
||||
"RememberAudioSelectionsHelp": "Versuchen die ähnlichste Tonspur zum letzten Video zu setzen.",
|
||||
"RememberAudioSelections": "Tonspur auf Basis des letzten Objekt auswählen",
|
||||
"LabelMaxVideoResolution": "Maximal erlaubte Video Transcodierungs-Auflösung"
|
||||
}
|
||||
|
@ -1653,5 +1653,27 @@
|
||||
"EnableEnhancedNvdecDecoderHelp": "Experimental NVDEC implementation, do not enable this option unless you encounter decoding errors.",
|
||||
"EnableSplashScreen": "Enable the splash screen",
|
||||
"Bold": "Bold",
|
||||
"HomeVideosPhotos": "Home Videos and Photos"
|
||||
"HomeVideosPhotos": "Home Videos and Photos",
|
||||
"MediaInfoDvBlSignalCompatibilityId": "DV bl signal compatibility id",
|
||||
"MediaInfoBlPresentFlag": "DV bl preset flag",
|
||||
"MediaInfoElPresentFlag": "DV el preset flag",
|
||||
"MediaInfoRpuPresentFlag": "DV rpu preset flag",
|
||||
"MediaInfoDvLevel": "DV level",
|
||||
"MediaInfoDvProfile": "DV profile",
|
||||
"MediaInfoDvVersionMinor": "DV version minor",
|
||||
"MediaInfoDvVersionMajor": "DV version major",
|
||||
"MediaInfoDoViTitle": "DV title",
|
||||
"MediaInfoVideoRangeType": "Video range type",
|
||||
"LabelVideoRangeType": "Video range type:",
|
||||
"VideoRangeTypeNotSupported": "The video's range type is not supported",
|
||||
"LabelVppTonemappingContrastHelp": "Apply contrast gain in VPP tone mapping. The recommended and default values are 1.2 and 1.",
|
||||
"LabelVppTonemappingContrast": "VPP Tone mapping contrast gain:",
|
||||
"LabelVppTonemappingBrightnessHelp": "Apply brightness gain in VPP tone mapping. Both recommended and default values are 0.",
|
||||
"LabelVppTonemappingBrightness": "VPP Tone mapping brightness gain:",
|
||||
"ScreenResolution": "Screen Resolution",
|
||||
"RememberSubtitleSelectionsHelp": "Try to set the subtitle track to the closest match to the last video.",
|
||||
"RememberSubtitleSelections": "Set subtitle track based on previous item",
|
||||
"RememberAudioSelectionsHelp": "Try to set the audio track to the closest match to the last video.",
|
||||
"RememberAudioSelections": "Set audio track based on previous item",
|
||||
"LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution"
|
||||
}
|
||||
|
@ -709,6 +709,7 @@
|
||||
"LabelLibraryPageSizeHelp": "Set the amount of items to show on a library page. Set to 0 in order to disable paging.",
|
||||
"LabelMaxDaysForNextUp": "Max days in 'Next Up':",
|
||||
"LabelMaxDaysForNextUpHelp": "Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.",
|
||||
"LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution",
|
||||
"LabelLineup": "Lineup:",
|
||||
"LabelLocalCustomCss": "Custom CSS code for styling which applies to this client only. You may want to disable server custom CSS code.",
|
||||
"LabelLocalHttpServerPortNumber": "Local HTTP port number:",
|
||||
@ -1354,7 +1355,11 @@
|
||||
"RefreshQueued": "Refresh queued.",
|
||||
"ReleaseDate": "Release date",
|
||||
"ReleaseGroup": "Release Group",
|
||||
"RememberAudioSelections": "Set audio track based on previous item",
|
||||
"RememberAudioSelectionsHelp": "Try to set the audio track to the closest match to the last video.",
|
||||
"RememberMe": "Remember Me",
|
||||
"RememberSubtitleSelections": "Set subtitle track based on previous item",
|
||||
"RememberSubtitleSelectionsHelp": "Try to set the subtitle track to the closest match to the last video.",
|
||||
"Remixer": "Remixer",
|
||||
"RemoveFromCollection": "Remove from collection",
|
||||
"RemoveFromPlaylist": "Remove from playlist",
|
||||
@ -1378,6 +1383,7 @@
|
||||
"ScanForNewAndUpdatedFiles": "Scan for new and updated files",
|
||||
"ScanLibrary": "Scan library",
|
||||
"Schedule": "Schedule",
|
||||
"ScreenResolution": "Screen Resolution",
|
||||
"Search": "Search",
|
||||
"SearchForCollectionInternetMetadata": "Search the internet for artwork and metadata",
|
||||
"SearchForMissingMetadata": "Search for missing metadata",
|
||||
@ -1400,8 +1406,6 @@
|
||||
"Settings": "Settings",
|
||||
"SettingsSaved": "Settings saved.",
|
||||
"SettingsWarning": "Changing these values may cause instability or connectivity failures. If you experience any problems, we recommend changing them back to default.",
|
||||
"SetUsingLastTracks": "Set Subtitle/Audio Tracks with Previous Item",
|
||||
"SetUsingLastTracksHelp": "Try to set the Subtitle/Audio track to the closest match to the last video.",
|
||||
"Share": "Share",
|
||||
"ShowAdvancedSettings": "Show advanced settings",
|
||||
"ShowIndicatorsFor": "Show indicators for:",
|
||||
@ -1643,5 +1647,21 @@
|
||||
"ThemeSong": "Theme Song",
|
||||
"ThemeVideo": "Theme Video",
|
||||
"EnableEnhancedNvdecDecoderHelp": "Experimental NVDEC implementation, do not enable this option unless you encounter decoding errors.",
|
||||
"EnableSplashScreen": "Enable the splash screen"
|
||||
"EnableSplashScreen": "Enable the splash screen",
|
||||
"LabelVppTonemappingBrightness": "VPP Tone mapping brightness gain:",
|
||||
"LabelVppTonemappingBrightnessHelp": "Apply brightness gain in VPP tone mapping. Both recommended and default values are 0.",
|
||||
"LabelVppTonemappingContrast": "VPP Tone mapping contrast gain:",
|
||||
"LabelVppTonemappingContrastHelp": "Apply contrast gain in VPP tone mapping. The recommended and default values are 1.2 and 1.",
|
||||
"VideoRangeTypeNotSupported": "The video's range type is not supported",
|
||||
"LabelVideoRangeType": "Video range type:",
|
||||
"MediaInfoVideoRangeType": "Video range type",
|
||||
"MediaInfoDoViTitle": "DV title",
|
||||
"MediaInfoDvVersionMajor": "DV version major",
|
||||
"MediaInfoDvVersionMinor": "DV version minor",
|
||||
"MediaInfoDvProfile": "DV profile",
|
||||
"MediaInfoDvLevel": "DV level",
|
||||
"MediaInfoRpuPresentFlag": "DV rpu preset flag",
|
||||
"MediaInfoElPresentFlag": "DV el preset flag",
|
||||
"MediaInfoBlPresentFlag": "DV bl preset flag",
|
||||
"MediaInfoDvBlSignalCompatibilityId": "DV bl signal compatibility id"
|
||||
}
|
||||
|
@ -1649,5 +1649,6 @@
|
||||
"HomeVideosPhotos": "Hejmaj Videoj kaj Fotoj",
|
||||
"Bold": "Grasa",
|
||||
"LabelTextWeight": "Teksta pezo:",
|
||||
"EnableSplashScreen": "Ebligi la salutŝildon"
|
||||
"EnableSplashScreen": "Ebligi la salutŝildon",
|
||||
"MediaInfoDoViTitle": "DV titolo"
|
||||
}
|
||||
|
@ -1645,5 +1645,6 @@
|
||||
"EnableRewatchingNextUpHelp": "Habilite 'Mostrar episodios ya vistos' en las secciones 'Siguiente'.",
|
||||
"EnableRewatchingNextUp": "Habilitar \"Volver a mirar\" en \"Siguiente\"",
|
||||
"Bold": "Audaz",
|
||||
"LabelTextWeight": "Peso del texto:"
|
||||
"LabelTextWeight": "Peso del texto:",
|
||||
"EnableSplashScreen": "Habilitar pantalla de inicio"
|
||||
}
|
||||
|
@ -1651,5 +1651,21 @@
|
||||
"HomeVideosPhotos": "Kotivideot ja valokuvat",
|
||||
"Bold": "Lihavoitu",
|
||||
"LabelTextWeight": "Tekstin vahvuus:",
|
||||
"EnableSplashScreen": "Käytä aloitusruutua"
|
||||
"EnableSplashScreen": "Käytä aloitusruutua",
|
||||
"MediaInfoDvLevel": "DV-taso",
|
||||
"MediaInfoDvProfile": "DV-profiili",
|
||||
"MediaInfoDoViTitle": "DV-nimike",
|
||||
"ScreenResolution": "Näyttötarkkuus",
|
||||
"RememberAudioSelectionsHelp": "Pyri valitsemaan parhaiten edellisen videon ääniraitaa vastaava ääniraita.",
|
||||
"RememberSubtitleSelectionsHelp": "Pyri valitsemaan parhaiten edellisen videon tekstitysraitaa vastaava tekstitysraita.",
|
||||
"RememberSubtitleSelections": "Valitse tekstitysraita edellisen kohteen perusteella",
|
||||
"RememberAudioSelections": "Valitse ääniraita edellisen kohteen perusteella",
|
||||
"LabelMaxVideoResolution": "Suurin sallittu videon transkoodaustarkkuus",
|
||||
"MediaInfoVideoRangeType": "Videon aluetyyppi",
|
||||
"LabelVideoRangeType": "Videon aluetyyppi:",
|
||||
"VideoRangeTypeNotSupported": "Videon aluetyyppiä ei tueta",
|
||||
"LabelVppTonemappingContrastHelp": "Käytä kontrastin vahvistusta VPP-sävykartoituksen kanssa. Molemmat ovat suositeltavia ja oletusarvot ovat 0.",
|
||||
"LabelVppTonemappingBrightnessHelp": "Käytä kirkkauden vahvistusta VPP-sävykartoituksen kanssa. Molemmat ovat suositeltavia ja oletusarvot ovat 0.",
|
||||
"LabelVppTonemappingContrast": "VPP-sävykartoituksen kontrastin vahvistus:",
|
||||
"LabelVppTonemappingBrightness": "VPP-sävykartoituksen kirkkauden vahvistus:"
|
||||
}
|
||||
|
@ -77,7 +77,7 @@
|
||||
"ButtonRemove": "Supprimer",
|
||||
"ButtonRename": "Renommer",
|
||||
"ButtonResetEasyPassword": "Réinitialiser le code Easy PIN",
|
||||
"ButtonResume": "Reprendre",
|
||||
"ButtonResume": "Reprise",
|
||||
"ButtonRevoke": "Révoquer",
|
||||
"ButtonScanAllLibraries": "Actualiser toutes les médiathèques",
|
||||
"ButtonSelectDirectory": "Sélectionner le répertoire",
|
||||
@ -1653,5 +1653,27 @@
|
||||
"HomeVideosPhotos": "Vidéos et photos personnelles",
|
||||
"LabelTextWeight": "Poids de la police :",
|
||||
"Bold": "Gras",
|
||||
"EnableSplashScreen": "Activer l'écran de démarrage"
|
||||
"EnableSplashScreen": "Activer l'écran de démarrage",
|
||||
"LabelMaxVideoResolution": "Résolution maximale du transcodage vidéo :",
|
||||
"MediaInfoDvBlSignalCompatibilityId": "ID de compatibilité du signal DV bl",
|
||||
"MediaInfoBlPresentFlag": "Indicateur de préréglage DV bl",
|
||||
"MediaInfoElPresentFlag": "Indicateur de préréglage DV el",
|
||||
"MediaInfoRpuPresentFlag": "Indicateur de préréglage DV rpu",
|
||||
"MediaInfoDvLevel": "Niveau de DV",
|
||||
"MediaInfoDvProfile": "Profile DV",
|
||||
"MediaInfoDvVersionMinor": "Version DV mineure",
|
||||
"MediaInfoDvVersionMajor": "Version DV majeure",
|
||||
"MediaInfoDoViTitle": "Titre DV",
|
||||
"MediaInfoVideoRangeType": "Type de plage vidéo",
|
||||
"LabelVideoRangeType": "Type de plage vidéo :",
|
||||
"VideoRangeTypeNotSupported": "Le type de plage de la vidéo n'est pas pris en charge",
|
||||
"LabelVppTonemappingContrastHelp": "Appliquer le gain de contraste dans le mappage de tonalité VPP. Les valeurs recommandées et par défaut sont 1.2 et 1.",
|
||||
"LabelVppTonemappingContrast": "Gain de contraste de mappage de tonalité VPP :",
|
||||
"LabelVppTonemappingBrightnessHelp": "Appliquer le gain de luminosité dans le mappage de tonalité VPP. Les valeurs recommandées et par défaut sont 0.",
|
||||
"LabelVppTonemappingBrightness": "Gain de luminosité du mappage VPP :",
|
||||
"ScreenResolution": "Résolution d'écran",
|
||||
"RememberSubtitleSelectionsHelp": "Choisir la piste de sous-titres la plus proche de la dernière vidéo.",
|
||||
"RememberSubtitleSelections": "Définir la piste de sous-titre en fonction de l'élément précédent",
|
||||
"RememberAudioSelectionsHelp": "Choisir la piste audio la plus proche de la dernière vidéo.",
|
||||
"RememberAudioSelections": "Définir la piste audio en fonction de l'élément précédent"
|
||||
}
|
||||
|
@ -1653,5 +1653,23 @@
|
||||
"HomeVideosPhotos": "Otthoni videók és fotók",
|
||||
"Bold": "Félkövér",
|
||||
"LabelTextWeight": "Betűvastagság:",
|
||||
"EnableSplashScreen": "Indítóképernyő engedélyezése"
|
||||
"EnableSplashScreen": "Indítóképernyő engedélyezése",
|
||||
"MediaInfoDvLevel": "DV Szint",
|
||||
"MediaInfoDvProfile": "DV Profil",
|
||||
"MediaInfoDvVersionMinor": "DV Mellék Verzió",
|
||||
"MediaInfoDvVersionMajor": "DV Fő Verzió",
|
||||
"MediaInfoDoViTitle": "DV cím",
|
||||
"MediaInfoVideoRangeType": "Videó tartomány típusa",
|
||||
"LabelVideoRangeType": "Videó tartomány típusa:",
|
||||
"VideoRangeTypeNotSupported": "A videó tartománytípusa nem támogatott",
|
||||
"LabelVppTonemappingContrastHelp": "Alkalmazzon kontraszterősítést a VPP tónusleképezésben. Az ajánlott és alapértelmezett értékek 1.2 és 1.",
|
||||
"LabelVppTonemappingContrast": "VPP Tone leképezés kontraszt erőssége:",
|
||||
"LabelVppTonemappingBrightnessHelp": "Fényerőbeállítása a VPP hangszínleképezésben. Az ajánlott és az alapértelmezett érték is 0.",
|
||||
"LabelVppTonemappingBrightness": "VPP Tone leképezési fényerőnövekedés:",
|
||||
"ScreenResolution": "Képernyőfelbontás",
|
||||
"RememberSubtitleSelectionsHelp": "Próbálja meg úgy beállítani a feliratsávot, hogy a legközelebb legyen az utolsó videóhoz.",
|
||||
"RememberSubtitleSelections": "Feliratsáv beállítása az előző elem alapján",
|
||||
"RememberAudioSelectionsHelp": "Próbálja meg úgy beállítani a hangsávot, hogy a legközelebb legyen az utolsó videóhoz.",
|
||||
"RememberAudioSelections": "Hangsáv beállítása az előző elem alapján",
|
||||
"LabelMaxVideoResolution": "Maximálisan engedélyezett felbontás, Video Transzkódolás alatt"
|
||||
}
|
||||
|
@ -1651,5 +1651,10 @@
|
||||
"AllowEmbeddedSubtitlesHelp": "Desabilitar legendas que estiverem empacotadas nos contêineres de mídia. Precisa de uma atualização total das bibliotecas.",
|
||||
"AllowEmbeddedSubtitles": "Desabilitar diferentes tipos de legendas embutidas",
|
||||
"Bold": "Negrito",
|
||||
"LabelTextWeight": "Intensidade do texto:"
|
||||
"LabelTextWeight": "Intensidade do texto:",
|
||||
"EnableSplashScreen": "Ativar tela de abertura",
|
||||
"ScreenResolution": "Resolução de tela",
|
||||
"RememberAudioSelectionsHelp": "Tentar definir a faixa de áudio com a correspondência mais próxima do último vídeo.",
|
||||
"RememberAudioSelections": "Definir faixa de áudio baseado no item anterior",
|
||||
"LabelMaxVideoResolution": "Resolução máxima de transcodificação de vídeo permitida"
|
||||
}
|
||||
|
@ -1653,5 +1653,27 @@
|
||||
"HomeVideosPhotos": "Домашние видео и фото",
|
||||
"Bold": "Жирный",
|
||||
"LabelTextWeight": "Насыщенность шрифта:",
|
||||
"EnableSplashScreen": "Включить экран-заставку"
|
||||
"EnableSplashScreen": "Включить экран-заставку",
|
||||
"MediaInfoDvLevel": "Уровень DV",
|
||||
"MediaInfoDvProfile": "DV-профиль",
|
||||
"MediaInfoDvVersionMinor": "Дополнительная версия DV",
|
||||
"MediaInfoDvVersionMajor": "Основная версия DV",
|
||||
"MediaInfoDoViTitle": "Название DV",
|
||||
"MediaInfoVideoRangeType": "Тип диапазона видео",
|
||||
"LabelVideoRangeType": "Тип диапазона видео:",
|
||||
"ScreenResolution": "Разрешение экрана",
|
||||
"RememberAudioSelectionsHelp": "Попытка задать аудиодорожку по ближайшему совпадению с предыдущим видео.",
|
||||
"RememberSubtitleSelectionsHelp": "Попытка задать дорожку субтитров по ближайшему совпадению с предыдущим видео.",
|
||||
"RememberSubtitleSelections": "Задать дорожку субтитров на основе предыдущего элемента",
|
||||
"RememberAudioSelections": "Задать аудиодорожку на основе предыдущего элемента",
|
||||
"LabelMaxVideoResolution": "Максимально допустимое разрешение перекодирующегося видео",
|
||||
"LabelVppTonemappingContrastHelp": "Применяется усиление контрастности при VPP-тонмаппинге. Значения рекомендованные и по умолчанию равны 1.2 и 1.",
|
||||
"MediaInfoDvBlSignalCompatibilityId": "ID совместимости сигнала DV bl",
|
||||
"MediaInfoBlPresentFlag": "Флаг предустановки DV bl",
|
||||
"MediaInfoElPresentFlag": "Флаг предустановки DV el",
|
||||
"MediaInfoRpuPresentFlag": "Флаг предустановки DV rpu",
|
||||
"VideoRangeTypeNotSupported": "Тип диапазона видео не поддерживается",
|
||||
"LabelVppTonemappingBrightnessHelp": "Применяется усиление яркости при VPP-тонмаппинге. Значения рекомендованные и по умолчанию равны 0.",
|
||||
"LabelVppTonemappingContrast": "Усиление контрастности VPP-тонмаппинга:",
|
||||
"LabelVppTonemappingBrightness": "Усиление яркости VPP-тонмаппинга:"
|
||||
}
|
||||
|
@ -207,7 +207,7 @@
|
||||
"HeaderAddToPlaylist": "Lägg till i spellista",
|
||||
"HeaderAddUpdateImage": "Lägg till/uppdatera bild",
|
||||
"HeaderAdditionalParts": "Ytterligare delar",
|
||||
"HeaderAlbumArtists": "Albumsartister",
|
||||
"HeaderAlbumArtists": "Albumartister",
|
||||
"HeaderAlert": "Varning",
|
||||
"HeaderAllowMediaDeletionFrom": "Tillåt mediaborttagning från:",
|
||||
"HeaderApiKey": "API-nyckel",
|
||||
|
@ -1644,5 +1644,24 @@
|
||||
"HomeVideosPhotos": "Videos và Ảnh Gia Đình",
|
||||
"Bold": "In đậm",
|
||||
"LabelTextWeight": "Trọng lượng văn bản:",
|
||||
"EnableSplashScreen": "Bật màn hình giật gân"
|
||||
"EnableSplashScreen": "Bật màn hình giật gân",
|
||||
"MediaInfoDvLevel": "Mức DV",
|
||||
"MediaInfoDvProfile": "Hồ sơ DV",
|
||||
"MediaInfoDvVersionMinor": "Phiên bản DV nhỏ",
|
||||
"MediaInfoDvVersionMajor": "Phiên bản DV chính",
|
||||
"MediaInfoDoViTitle": "Tiêu đề DV",
|
||||
"MediaInfoVideoRangeType": "Loại phạm vi video",
|
||||
"LabelVideoRangeType": "Loại phạm vi video:",
|
||||
"LabelVppTonemappingContrastHelp": "Áp dụng độ tăng tương phản trong ánh xạ tông màu VPP. Giá trị đề xuất và mặc định là 1,2 và 1.",
|
||||
"LabelVppTonemappingContrast": "Độ tương phản ánh xạ tông màu VPP:",
|
||||
"LabelVppTonemappingBrightnessHelp": "Áp dụng tăng độ sáng trong ánh xạ tông màu VPP. Giá trị đề xuất và giá trị mặc định đều là 0.",
|
||||
"LabelVppTonemappingBrightness": "Tăng độ sáng ánh xạ tông màu VPP:",
|
||||
"ScreenResolution": "Độ Phân Giải Màn Hình",
|
||||
"RememberSubtitleSelectionsHelp": "Cố gắng đặt phụ đề phù hợp nhất với video cuối cùng.",
|
||||
"RememberSubtitleSelections": "Đặt phụ đề dựa trên mục trước",
|
||||
"RememberAudioSelectionsHelp": "Cố gắng đặt bản âm thanh phù hợp nhất với video cuối cùng.",
|
||||
"RememberAudioSelections": "Đặt bản nhạc dựa trên mục trước đó",
|
||||
"LabelMaxVideoResolution": "Độ Phân Giải Chuyển Mã Video Tối Đa Được Phép",
|
||||
"VideoRangeTypeNotSupported": "Loại phạm vi của video không được hỗ trợ",
|
||||
"Interview": "Phỏng vấn"
|
||||
}
|
||||
|
@ -1653,5 +1653,23 @@
|
||||
"HomeVideosPhotos": "主页视频及照片",
|
||||
"Bold": "粗体",
|
||||
"LabelTextWeight": "文字大小:",
|
||||
"EnableSplashScreen": "显示启动画面"
|
||||
"EnableSplashScreen": "显示启动画面",
|
||||
"MediaInfoDvBlSignalCompatibilityId": "杜比视界 BL 兼容性序号",
|
||||
"MediaInfoBlPresentFlag": "杜比视界 BL 存在标记",
|
||||
"MediaInfoElPresentFlag": "杜比视界 EL 存在标记",
|
||||
"MediaInfoRpuPresentFlag": "杜比视界 RPU 存在标记",
|
||||
"MediaInfoDvLevel": "杜比视界等级",
|
||||
"MediaInfoDvProfile": "杜比视界配置",
|
||||
"MediaInfoDvVersionMinor": "杜比视界次要版本",
|
||||
"MediaInfoDvVersionMajor": "杜比视界主要版本",
|
||||
"MediaInfoDoViTitle": "杜比视界标题",
|
||||
"MediaInfoVideoRangeType": "动态范围类型",
|
||||
"LabelVideoRangeType": "动态范围类型:",
|
||||
"VideoRangeTypeNotSupported": "视频动态范围不支持",
|
||||
"LabelVppTonemappingContrastHelp": "在 VPP 色调映射中应用对比度增益。推荐值和默认值分别为 1.2 和 1。",
|
||||
"LabelVppTonemappingBrightness": "VPP 色调映射亮度增益:",
|
||||
"LabelVppTonemappingContrast": "VPP 色调映射对比度增益:",
|
||||
"LabelVppTonemappingBrightnessHelp": "在 VPP 色调映射中应用亮度增益。推荐值和默认值均为 0。",
|
||||
"ScreenResolution": "屏幕分辨率",
|
||||
"LabelMaxVideoResolution": "允许的最大视频转码分辨率"
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules[\\/](?!@uupaa[\\/]dynamic-import-polyfill|blurhash|date-fns|epubjs|flv.js|libarchive.js|marked|screenfull)/,
|
||||
exclude: /node_modules[\\/](?!@uupaa[\\/]dynamic-import-polyfill|blurhash|date-fns|epubjs|flv.js|libarchive.js|marked|react-router|screenfull)/,
|
||||
use: [{
|
||||
loader: 'babel-loader'
|
||||
}]
|
||||
|
Loading…
Reference in New Issue
Block a user