mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-11-01 02:57:10 -04:00
wip: Add subtitles handling
This commit is contained in:
parent
dfdeca35f3
commit
5ca1ae938f
@ -1265,7 +1265,7 @@
|
|||||||
|
|
||||||
"react-native-svg-transformer": ["react-native-svg-transformer@1.5.1", "", { "dependencies": { "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@svgr/plugin-svgo": "^8.1.0", "path-dirname": "^1.0.2" }, "peerDependencies": { "react-native": ">=0.59.0", "react-native-svg": ">=12.0.0" } }, "sha512-dFvBNR8A9VPum9KCfh+LE49YiJEF8zUSnEFciKQroR/bEOhlPoZA0SuQ0qNk7m2iZl2w59FYjdRe0pMHWMDl0Q=="],
|
"react-native-svg-transformer": ["react-native-svg-transformer@1.5.1", "", { "dependencies": { "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@svgr/plugin-svgo": "^8.1.0", "path-dirname": "^1.0.2" }, "peerDependencies": { "react-native": ">=0.59.0", "react-native-svg": ">=12.0.0" } }, "sha512-dFvBNR8A9VPum9KCfh+LE49YiJEF8zUSnEFciKQroR/bEOhlPoZA0SuQ0qNk7m2iZl2w59FYjdRe0pMHWMDl0Q=="],
|
||||||
|
|
||||||
"react-native-video": ["react-native-video@github:zoriya/react-native-video#8287a84", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.27.2" } }, "zoriya-react-native-video-8287a84"],
|
"react-native-video": ["react-native-video@github:zoriya/react-native-video#ad69842", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-nitro-modules": ">=0.27.2" } }, "zoriya-react-native-video-ad69842"],
|
||||||
|
|
||||||
"react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
|
"react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
|
||||||
|
|
||||||
|
|||||||
@ -163,7 +163,7 @@ const ControlButtons = ({
|
|||||||
<ProgressText player={player} {...spacing} />
|
<ProgressText player={player} {...spacing} />
|
||||||
</View>
|
</View>
|
||||||
<View {...css({ flexDirection: "row" })}>
|
<View {...css({ flexDirection: "row" })}>
|
||||||
<SubtitleMenu {...menuProps} />
|
<SubtitleMenu player={player} {...menuProps} />
|
||||||
<AudioMenu player={player} {...menuProps} />
|
<AudioMenu player={player} {...menuProps} />
|
||||||
<VideoMenu player={player} {...menuProps} />
|
<VideoMenu player={player} {...menuProps} />
|
||||||
<QualityMenu player={player} {...menuProps} />
|
<QualityMenu player={player} {...menuProps} />
|
||||||
|
|||||||
@ -3,19 +3,50 @@ import MusicNote from "@material-symbols/svg-400/rounded/music_note-fill.svg";
|
|||||||
import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg";
|
import SettingsIcon from "@material-symbols/svg-400/rounded/settings-fill.svg";
|
||||||
import VideoSettings from "@material-symbols/svg-400/rounded/video_settings-fill.svg";
|
import VideoSettings from "@material-symbols/svg-400/rounded/video_settings-fill.svg";
|
||||||
import { type ComponentProps, createContext, useContext } from "react";
|
import { type ComponentProps, createContext, useContext } from "react";
|
||||||
import { useEvent, type VideoPlayer } from "react-native-video";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { IconButton, Menu, tooltip } from "~/primitives";
|
import { useEvent, type VideoPlayer } from "react-native-video";
|
||||||
import { useDisplayName } from "~/track-utils";
|
|
||||||
import { useForceRerender } from "yoshiki";
|
import { useForceRerender } from "yoshiki";
|
||||||
|
import type { Subtitle } from "~/models";
|
||||||
|
import { IconButton, Menu, tooltip } from "~/primitives";
|
||||||
|
import { useFetch } from "~/query";
|
||||||
|
import { useDisplayName, useSubtitleName } from "~/track-utils";
|
||||||
|
import { useQueryState } from "~/utils";
|
||||||
|
import { Player } from "..";
|
||||||
|
|
||||||
type MenuProps = ComponentProps<typeof Menu<ComponentProps<typeof IconButton>>>;
|
type MenuProps = ComponentProps<typeof Menu<ComponentProps<typeof IconButton>>>;
|
||||||
|
|
||||||
export const SubtitleMenu = (props: Partial<MenuProps>) => {
|
export const SubtitleMenu = ({
|
||||||
|
player,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
player: VideoPlayer;
|
||||||
|
} & Partial<MenuProps>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const getDisplayName = useSubtitleName();
|
||||||
|
|
||||||
|
const rerender = useForceRerender();
|
||||||
|
useEvent(player, "onTrackChange", rerender);
|
||||||
|
|
||||||
|
const [slug] = useQueryState<string>("slug", undefined!);
|
||||||
|
const { data } = useFetch(Player.infoQuery(slug));
|
||||||
|
|
||||||
|
if (data?.subtitles.length === 0) return null;
|
||||||
|
|
||||||
|
const selectedIdx = player
|
||||||
|
.getAvailableTextTracks()
|
||||||
|
.findIndex((x) => x.selected);
|
||||||
|
|
||||||
|
const select = (track: Subtitle | null, idx: number) => {
|
||||||
|
if (!track) {
|
||||||
|
player.selectTextTrack(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: filter by codec here
|
||||||
|
const sub = player.getAvailableTextTracks()[idx];
|
||||||
|
player.selectTextTrack(sub);
|
||||||
|
};
|
||||||
|
|
||||||
// {subtitles && subtitles.length > 0 && (
|
|
||||||
// )}
|
|
||||||
return (
|
return (
|
||||||
<Menu
|
<Menu
|
||||||
Trigger={IconButton}
|
Trigger={IconButton}
|
||||||
@ -23,24 +54,19 @@ export const SubtitleMenu = (props: Partial<MenuProps>) => {
|
|||||||
{...tooltip(t("player.subtitles"), true)}
|
{...tooltip(t("player.subtitles"), true)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{/* <Menu.Item */}
|
<Menu.Item
|
||||||
{/* label={t("player.subtitle-none")} */}
|
label={t("player.subtitle-none")}
|
||||||
{/* selected={!selectedSubtitle} */}
|
selected={selectedIdx === -1}
|
||||||
{/* onSelect={() => setSubtitle(null)} */}
|
onSelect={() => select(null, -1)}
|
||||||
{/* /> */}
|
/>
|
||||||
{/* {subtitles */}
|
{data?.subtitles.map((x, i) => (
|
||||||
{/* .filter((x) => !!x.link) */}
|
<Menu.Item
|
||||||
{/* .map((x, i) => ( */}
|
key={x.index ?? x.link}
|
||||||
{/* <Menu.Item */}
|
label={getDisplayName(x)}
|
||||||
{/* key={x.index ?? i} */}
|
selected={i === selectedIdx}
|
||||||
{/* label={ */}
|
onSelect={() => select(x, i)}
|
||||||
{/* x.link ? getSubtitleName(x) : `${getSubtitleName(x)} (${x.codec})` */}
|
/>
|
||||||
{/* } */}
|
))}
|
||||||
{/* selected={selectedSubtitle === x} */}
|
|
||||||
{/* disabled={!x.link} */}
|
|
||||||
{/* onSelect={() => setSubtitle(x)} */}
|
|
||||||
{/* /> */}
|
|
||||||
{/* ))} */}
|
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -62,10 +62,13 @@ export const Player = () => {
|
|||||||
imageUri: data?.show?.thumbnail?.high ?? undefined,
|
imageUri: data?.show?.thumbnail?.high ?? undefined,
|
||||||
},
|
},
|
||||||
externalSubtitles: info?.subtitles
|
externalSubtitles: info?.subtitles
|
||||||
.filter((x) => x.link)
|
.filter(
|
||||||
|
(x) => Platform.OS === "web" || playMode === "hls" || x.isExternal,
|
||||||
|
)
|
||||||
.map((x) => ({
|
.map((x) => ({
|
||||||
|
// we also add those without link to prevent the order from getting out of sync with `info.subtitles`.
|
||||||
|
// since we never actually play those this is fine
|
||||||
uri: x.link!,
|
uri: x.link!,
|
||||||
// TODO: translate this `Unknown`
|
|
||||||
label: x.title ?? "Unknown",
|
label: x.title ?? "Unknown",
|
||||||
language: x.language ?? "und",
|
language: x.language ?? "und",
|
||||||
type: x.codec,
|
type: x.codec,
|
||||||
|
|||||||
@ -287,7 +287,7 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
|
|||||||
extension := OrNull(SubtitleExtensions[stream.CodecName])
|
extension := OrNull(SubtitleExtensions[stream.CodecName])
|
||||||
var link string
|
var link string
|
||||||
if extension != nil {
|
if extension != nil {
|
||||||
link = fmt.Sprintf("video/%s/subtitle/%d.%s", base64.RawURLEncoding.EncodeToString([]byte(path)), i, *extension)
|
link = fmt.Sprintf("/video/%s/subtitle/%d.%s", base64.RawURLEncoding.EncodeToString([]byte(path)), i, *extension)
|
||||||
}
|
}
|
||||||
lang, _ := language.Parse(stream.Tags.Language)
|
lang, _ := language.Parse(stream.Tags.Language)
|
||||||
idx := uint32(i)
|
idx := uint32(i)
|
||||||
@ -315,7 +315,7 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
|
|||||||
}),
|
}),
|
||||||
Fonts: MapStream(mi.Streams, ffprobe.StreamAttachment, func(stream *ffprobe.Stream, i uint32) string {
|
Fonts: MapStream(mi.Streams, ffprobe.StreamAttachment, func(stream *ffprobe.Stream, i uint32) string {
|
||||||
font, _ := stream.TagList.GetString("filename")
|
font, _ := stream.TagList.GetString("filename")
|
||||||
return fmt.Sprintf("video/%s/attachment/%s", base64.RawURLEncoding.EncodeToString([]byte(path)), font)
|
return fmt.Sprintf("/video/%s/attachment/%s", base64.RawURLEncoding.EncodeToString([]byte(path)), font)
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
var codecs []string
|
var codecs []string
|
||||||
|
|||||||
@ -174,7 +174,7 @@ func (s *MetadataService) GetMetadata(ctx context.Context, path string, sha stri
|
|||||||
tx.Exec(`update info set ver_keyframes = 0 where sha = $1`, sha)
|
tx.Exec(`update info set ver_keyframes = 0 where sha = $1`, sha)
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("error deleteing old keyframes from database: %v", err)
|
fmt.Printf("error deleting old keyframes from database: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +256,7 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
|
|||||||
}
|
}
|
||||||
if s.Extension != nil {
|
if s.Extension != nil {
|
||||||
link := fmt.Sprintf(
|
link := fmt.Sprintf(
|
||||||
"video/%s/subtitle/%d.%s",
|
"/video/%s/subtitle/%d.%s",
|
||||||
base64.RawURLEncoding.EncodeToString([]byte(ret.Path)),
|
base64.RawURLEncoding.EncodeToString([]byte(ret.Path)),
|
||||||
*s.Index,
|
*s.Index,
|
||||||
*s.Extension,
|
*s.Extension,
|
||||||
@ -391,5 +391,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
return set(ret, err)
|
return set(ret, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = ret.SearchExternalSubtitles()
|
||||||
|
if err != nil {
|
||||||
|
return set(ret, err)
|
||||||
|
}
|
||||||
|
|
||||||
return set(ret, nil)
|
return set(ret, nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ outer:
|
|||||||
for codec, ext := range SubtitleExtensions {
|
for codec, ext := range SubtitleExtensions {
|
||||||
if strings.HasSuffix(match, ext) {
|
if strings.HasSuffix(match, ext) {
|
||||||
link := fmt.Sprintf(
|
link := fmt.Sprintf(
|
||||||
"video/%s/direct/%s",
|
"/video/%s/direct/%s",
|
||||||
base64.RawURLEncoding.EncodeToString([]byte(match)),
|
base64.RawURLEncoding.EncodeToString([]byte(match)),
|
||||||
filepath.Base(match),
|
filepath.Base(match),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -145,7 +145,7 @@ func (s *MetadataService) extractThumbnail(ctx context.Context, path string, sha
|
|||||||
timestamps := ts
|
timestamps := ts
|
||||||
ts += interval
|
ts += interval
|
||||||
vtt += fmt.Sprintf(
|
vtt += fmt.Sprintf(
|
||||||
"%s --> %s\nvideo/%s/thumbnails.png#xywh=%d,%d,%d,%d\n\n",
|
"%s --> %s\n/video/%s/thumbnails.png#xywh=%d,%d,%d,%d\n\n",
|
||||||
tsToVttTime(timestamps),
|
tsToVttTime(timestamps),
|
||||||
tsToVttTime(ts),
|
tsToVttTime(ts),
|
||||||
base64.RawURLEncoding.EncodeToString([]byte(path)),
|
base64.RawURLEncoding.EncodeToString([]byte(path)),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user