mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-01 04:34:50 -04:00
Switch to jassub
This commit is contained in:
parent
0d4b9fe488
commit
e95f001dd2
@ -22,7 +22,7 @@ const path = require("path");
|
|||||||
const CopyPlugin = require("copy-webpack-plugin");
|
const CopyPlugin = require("copy-webpack-plugin");
|
||||||
const DefinePlugin = require("webpack").DefinePlugin;
|
const DefinePlugin = require("webpack").DefinePlugin;
|
||||||
|
|
||||||
const suboctopus = path.dirname(require.resolve("libass-wasm"));
|
const suboctopus = path.resolve(path.dirname(require.resolve("jassub")), "../dist");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("next").NextConfig}
|
* @type {import("next").NextConfig}
|
||||||
@ -124,7 +124,9 @@ if (process.env.NODE_ENV !== "production") {
|
|||||||
nextConfig.rewrites = async () => [
|
nextConfig.rewrites = async () => [
|
||||||
{
|
{
|
||||||
source: "/api/:path*",
|
source: "/api/:path*",
|
||||||
destination: `${process.env.KYOO_URL}/:path*` ?? "http://localhost:5000/:path*",
|
destination: process.env.KYOO_URL
|
||||||
|
? `${process.env.KYOO_URL}/:path*`
|
||||||
|
: "http://localhost:5000/:path*",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@
|
|||||||
"expo-modules-core": "^1.5.9",
|
"expo-modules-core": "^1.5.9",
|
||||||
"hls.js": "^1.4.10",
|
"hls.js": "^1.4.10",
|
||||||
"i18next": "^23.4.2",
|
"i18next": "^23.4.2",
|
||||||
|
"jassub": "^1.7.8",
|
||||||
"jotai": "^2.3.1",
|
"jotai": "^2.3.1",
|
||||||
"libass-wasm": "^4.1.0",
|
|
||||||
"moti": "^0.26.0",
|
"moti": "^0.26.0",
|
||||||
"next": "13.4.19",
|
"next": "13.4.19",
|
||||||
"next-translate": "^2.5.3",
|
"next-translate": "^2.5.3",
|
||||||
|
155
front/packages/ui/src/player/subtitle-octopus.d.ts
vendored
155
front/packages/ui/src/player/subtitle-octopus.d.ts
vendored
@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
* Kyoo - A portable and vast media library solution.
|
|
||||||
* Copyright (c) Kyoo.
|
|
||||||
*
|
|
||||||
* See AUTHORS.md and LICENSE file in the project root for full license information.
|
|
||||||
*
|
|
||||||
* Kyoo is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* Kyoo is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
declare module "libass-wasm" {
|
|
||||||
interface OptionsBase {
|
|
||||||
/**
|
|
||||||
* The video element to attach listeners to
|
|
||||||
*/
|
|
||||||
video?: HTMLVideoElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The canvas to render the subtitles to. If none is given it will create a new canvas and
|
|
||||||
* insert it as a sibling of the video element (only if the video element exists)
|
|
||||||
*/
|
|
||||||
canvas?: HTMLCanvasElement;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL of the worker
|
|
||||||
*
|
|
||||||
* @default `subtitles-octopus-worker.js`
|
|
||||||
*/
|
|
||||||
workerUrl?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL of the legacy worker
|
|
||||||
*
|
|
||||||
* @default `subtitles-octopus-worker-legacy.js`
|
|
||||||
*/
|
|
||||||
legacyWorkerUrl?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An array of links to the fonts used in the subtitle
|
|
||||||
*/
|
|
||||||
fonts?: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Object with all available fonts - Key is font name in lower case, value is link
|
|
||||||
*
|
|
||||||
* @example `{"arial": "/font1.ttf"}`
|
|
||||||
*/
|
|
||||||
availableFonts?: Record<string, string>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The amount of time the subtitles should be offset from the video
|
|
||||||
*
|
|
||||||
* @default 0
|
|
||||||
*/
|
|
||||||
timeOffset?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether performance info is printed in the console
|
|
||||||
*
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
debug?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The default font.
|
|
||||||
*/
|
|
||||||
fallbackFont?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A boolean, whether to load files in a lazy way via FS.createLazyFile(). Requires
|
|
||||||
* Access-Control-Expose-Headers for Accept-Ranges, Content-Length, and Content-Encoding. If
|
|
||||||
* encoding is compressed or length is not set, file will be fully fetched instead of just a
|
|
||||||
* HEAD request.
|
|
||||||
*/
|
|
||||||
lazyFileLoading?: boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function that's called when SubtitlesOctopus is ready
|
|
||||||
*/
|
|
||||||
onReady?: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function called in case of critical error meaning the subtitles wouldn't be shown and you
|
|
||||||
* should use an alternative method (for instance it occurs if browser doesn't support web
|
|
||||||
* workers)
|
|
||||||
*/
|
|
||||||
onError?: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the render mode
|
|
||||||
*
|
|
||||||
* @default wasm
|
|
||||||
*/
|
|
||||||
renderMode?: "js-blend" | "wasm-blend" | "lossy";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OptionsWithSubUrl extends OptionsBase {
|
|
||||||
subUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OptionsWithSubContent extends OptionsBase {
|
|
||||||
subContent: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Options = OptionsWithSubUrl | OptionsWithSubContent;
|
|
||||||
|
|
||||||
declare class SubtitlesOctopus {
|
|
||||||
constructor(options: Options);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render subtitles at specified time
|
|
||||||
*
|
|
||||||
* @param time
|
|
||||||
*/
|
|
||||||
setCurrentTime(time: number): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Works the same as the {@link subUrl} option. It will set the subtitle to display by its URL.
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
*/
|
|
||||||
setTrackByUrl(url: string): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Works the same as the {@link subContent} option. It will set the subtitle to display by its
|
|
||||||
* content.
|
|
||||||
*
|
|
||||||
* @param content
|
|
||||||
*/
|
|
||||||
setTrack(content: string): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This simply removes the subtitles. You can use {@link setTrackByUrl} or {@link setTrack}
|
|
||||||
* methods to set a new subtitle file to be displayed.
|
|
||||||
*/
|
|
||||||
freeTrack(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy instance
|
|
||||||
*/
|
|
||||||
dispose(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SubtitlesOctopus;
|
|
||||||
}
|
|
@ -32,7 +32,7 @@ import {
|
|||||||
import { VideoProps } from "react-native-video";
|
import { VideoProps } from "react-native-video";
|
||||||
import { useAtomValue, useSetAtom, useAtom } from "jotai";
|
import { useAtomValue, useSetAtom, useAtom } from "jotai";
|
||||||
import { useYoshiki } from "yoshiki";
|
import { useYoshiki } from "yoshiki";
|
||||||
import SubtitleOctopus from "libass-wasm";
|
import Jassub from "jassub";
|
||||||
import { playAtom, PlayMode, playModeAtom, subtitleAtom } from "./state";
|
import { playAtom, PlayMode, playModeAtom, subtitleAtom } from "./state";
|
||||||
import Hls, { Level, LoadPolicy } from "hls.js";
|
import Hls, { Level, LoadPolicy } from "hls.js";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -223,27 +223,25 @@ const Video = forwardRef<{ seek: (value: number) => void }, VideoProps>(function
|
|||||||
|
|
||||||
export default Video;
|
export default Video;
|
||||||
|
|
||||||
let htmlTrack: HTMLTrackElement | null;
|
|
||||||
let subOcto: SubtitleOctopus | null;
|
|
||||||
const useSubtitle = (
|
const useSubtitle = (
|
||||||
player: RefObject<HTMLVideoElement>,
|
player: RefObject<HTMLVideoElement>,
|
||||||
value: Subtitle | null,
|
value: Subtitle | null,
|
||||||
fonts?: string[],
|
fonts?: string[],
|
||||||
) => {
|
) => {
|
||||||
|
const htmlTrack = useRef<HTMLTrackElement | null>();
|
||||||
|
const subOcto = useRef<Jassub | null>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!player.current) return;
|
if (!player.current) return;
|
||||||
|
|
||||||
const removeHtmlSubtitle = () => {
|
const removeHtmlSubtitle = () => {
|
||||||
if (htmlTrack) htmlTrack.remove();
|
if (htmlTrack.current) htmlTrack.current.remove();
|
||||||
htmlTrack = null;
|
htmlTrack.current = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeOctoSub = () => {
|
const removeOctoSub = () => {
|
||||||
if (subOcto) {
|
if (subOcto.current) subOcto.current.destroy();
|
||||||
subOcto.freeTrack();
|
subOcto.current = null;
|
||||||
subOcto.dispose();
|
|
||||||
}
|
|
||||||
subOcto = null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!value || !value.link) {
|
if (!value || !value.link) {
|
||||||
@ -253,7 +251,7 @@ const useSubtitle = (
|
|||||||
removeOctoSub();
|
removeOctoSub();
|
||||||
if (player.current.textTracks.length > 0) player.current.textTracks[0].mode = "hidden";
|
if (player.current.textTracks.length > 0) player.current.textTracks[0].mode = "hidden";
|
||||||
const addSubtitle = async () => {
|
const addSubtitle = async () => {
|
||||||
const track: HTMLTrackElement = htmlTrack ?? document.createElement("track");
|
const track: HTMLTrackElement = htmlTrack.current ?? document.createElement("track");
|
||||||
track.kind = "subtitles";
|
track.kind = "subtitles";
|
||||||
track.label = value.displayName;
|
track.label = value.displayName;
|
||||||
if (value.language) track.srclang = value.language;
|
if (value.language) track.srclang = value.language;
|
||||||
@ -263,27 +261,43 @@ const useSubtitle = (
|
|||||||
track.onload = () => {
|
track.onload = () => {
|
||||||
if (player.current) player.current.textTracks[0].mode = "showing";
|
if (player.current) player.current.textTracks[0].mode = "showing";
|
||||||
};
|
};
|
||||||
if (!htmlTrack) {
|
if (!htmlTrack.current) {
|
||||||
htmlTrack = track;
|
htmlTrack.current = track;
|
||||||
if (player.current) player.current.appendChild(track);
|
if (player.current) player.current.appendChild(track);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
addSubtitle();
|
addSubtitle();
|
||||||
} else if (value.codec === "ass") {
|
} else if (value.codec === "ass") {
|
||||||
removeHtmlSubtitle();
|
removeHtmlSubtitle();
|
||||||
removeOctoSub();
|
// Also recreate jassub when the player changes (this is not the most effective but
|
||||||
subOcto = new SubtitleOctopus({
|
// since it creates a div/canvas, it needs to be recreated when the UI rerender)
|
||||||
video: player.current,
|
// @ts-expect-error We are accessing the private _video field here.
|
||||||
subUrl: value.link,
|
if (!subOcto.current || subOcto.current._video !== player.current) {
|
||||||
workerUrl: "/_next/static/chunks/subtitles-octopus-worker.js",
|
removeOctoSub();
|
||||||
legacyWorkerUrl: "/_next/static/chunks/subtitles-octopus-worker-legacy.js",
|
subOcto.current = new Jassub({
|
||||||
fallbackFont: "/default.woff2",
|
video: player.current,
|
||||||
fonts: fonts,
|
workerUrl: "/_next/static/chunks/jassub-worker.js",
|
||||||
// lazyFileLoading: true,
|
wasmUrl: "/_next/static/chunks/jassub-worker.wasm",
|
||||||
renderMode: "wasm-blend",
|
legacyWasmUrl: "/_next/static/chunks/jassub-worker.wasm.js",
|
||||||
});
|
// Disable offscreen renderer for firefox (see https://github.com/ThaUnknown/jassub/issues/31)
|
||||||
|
offscreenRender: !/firefox/i.test(navigator.userAgent),
|
||||||
|
subUrl: value.link,
|
||||||
|
fonts: fonts,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
subOcto.current.freeTrack();
|
||||||
|
subOcto.current.setTrackByUrl(value.link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [player, value, fonts]);
|
}, [player, value, fonts]);
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (subOcto.current) subOcto.current.destroy();
|
||||||
|
subOcto.current = null;
|
||||||
|
if (htmlTrack.current) htmlTrack.current.remove();
|
||||||
|
htmlTrack.current = null;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
};
|
};
|
||||||
|
|
||||||
const toWebVtt = async (srtUrl: string) => {
|
const toWebVtt = async (srtUrl: string) => {
|
||||||
@ -304,7 +318,6 @@ export const AudiosMenu = ({
|
|||||||
...props
|
...props
|
||||||
}: ComponentProps<typeof Menu> & { audios?: Audio[] }) => {
|
}: ComponentProps<typeof Menu> & { audios?: Audio[] }) => {
|
||||||
if (!hls || hls.audioTracks.length < 2) return null;
|
if (!hls || hls.audioTracks.length < 2) return null;
|
||||||
console.log(audios);
|
|
||||||
return (
|
return (
|
||||||
<Menu {...props}>
|
<Menu {...props}>
|
||||||
{hls.audioTracks.map((x, i) => (
|
{hls.audioTracks.map((x, i) => (
|
||||||
|
@ -8910,6 +8910,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"jassub@npm:^1.7.8":
|
||||||
|
version: 1.7.9
|
||||||
|
resolution: "jassub@npm:1.7.9"
|
||||||
|
dependencies:
|
||||||
|
rvfc-polyfill: ^1.0.7
|
||||||
|
checksum: 5f2abcb65ce2431a77fb4222446707ecd99c8d5e2182c77b385399b0fb1ce4ed9b6c429a343dcb086d3d3b0fa7b091681721f058d5ffc44751bb774370a6abc6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"jest-environment-node@npm:^29.2.1":
|
"jest-environment-node@npm:^29.2.1":
|
||||||
version: 29.6.2
|
version: 29.6.2
|
||||||
resolution: "jest-environment-node@npm:29.6.2"
|
resolution: "jest-environment-node@npm:29.6.2"
|
||||||
@ -9337,13 +9346,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"libass-wasm@npm:^4.1.0":
|
|
||||||
version: 4.1.0
|
|
||||||
resolution: "libass-wasm@npm:4.1.0"
|
|
||||||
checksum: a6ecc842594b36455b309aa3bb6ce3b59ada407ffbdc5e62bf657144dfce028c0a7d20dac34a9b3ed0b21d051b0daf14ea656f692e2aad3b1d66347a4ed95ec4
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"lightningcss-darwin-arm64@npm:1.19.0":
|
"lightningcss-darwin-arm64@npm:1.19.0":
|
||||||
version: 1.19.0
|
version: 1.19.0
|
||||||
resolution: "lightningcss-darwin-arm64@npm:1.19.0"
|
resolution: "lightningcss-darwin-arm64@npm:1.19.0"
|
||||||
@ -12537,6 +12539,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"rvfc-polyfill@npm:^1.0.7":
|
||||||
|
version: 1.0.7
|
||||||
|
resolution: "rvfc-polyfill@npm:1.0.7"
|
||||||
|
checksum: 1b0babe38866e252ce1594e82af3e6cc2e140f7d8c433fd7835c1ae28febc1aa5ad1354e7a84fa3b2611dc591bc9ca78dafc72ad41878d8cd2f2bfcb764c8abe
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"sade@npm:^1.7.3":
|
"sade@npm:^1.7.3":
|
||||||
version: 1.8.1
|
version: 1.8.1
|
||||||
resolution: "sade@npm:1.8.1"
|
resolution: "sade@npm:1.8.1"
|
||||||
@ -14256,8 +14265,8 @@ __metadata:
|
|||||||
expo-modules-core: ^1.5.9
|
expo-modules-core: ^1.5.9
|
||||||
hls.js: ^1.4.10
|
hls.js: ^1.4.10
|
||||||
i18next: ^23.4.2
|
i18next: ^23.4.2
|
||||||
|
jassub: ^1.7.8
|
||||||
jotai: ^2.3.1
|
jotai: ^2.3.1
|
||||||
libass-wasm: ^4.1.0
|
|
||||||
moti: ^0.26.0
|
moti: ^0.26.0
|
||||||
next: 13.4.19
|
next: 13.4.19
|
||||||
next-translate: ^2.5.3
|
next-translate: ^2.5.3
|
||||||
|
Loading…
x
Reference in New Issue
Block a user