mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-10-30 02:02:36 -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-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=="],
|
||||
|
||||
|
||||
@ -163,7 +163,7 @@ const ControlButtons = ({
|
||||
<ProgressText player={player} {...spacing} />
|
||||
</View>
|
||||
<View {...css({ flexDirection: "row" })}>
|
||||
<SubtitleMenu {...menuProps} />
|
||||
<SubtitleMenu player={player} {...menuProps} />
|
||||
<AudioMenu player={player} {...menuProps} />
|
||||
<VideoMenu 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 VideoSettings from "@material-symbols/svg-400/rounded/video_settings-fill.svg";
|
||||
import { type ComponentProps, createContext, useContext } from "react";
|
||||
import { useEvent, type VideoPlayer } from "react-native-video";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton, Menu, tooltip } from "~/primitives";
|
||||
import { useDisplayName } from "~/track-utils";
|
||||
import { useEvent, type VideoPlayer } from "react-native-video";
|
||||
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>>>;
|
||||
|
||||
export const SubtitleMenu = (props: Partial<MenuProps>) => {
|
||||
export const SubtitleMenu = ({
|
||||
player,
|
||||
...props
|
||||
}: {
|
||||
player: VideoPlayer;
|
||||
} & Partial<MenuProps>) => {
|
||||
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 (
|
||||
<Menu
|
||||
Trigger={IconButton}
|
||||
@ -23,24 +54,19 @@ export const SubtitleMenu = (props: Partial<MenuProps>) => {
|
||||
{...tooltip(t("player.subtitles"), true)}
|
||||
{...props}
|
||||
>
|
||||
{/* <Menu.Item */}
|
||||
{/* label={t("player.subtitle-none")} */}
|
||||
{/* selected={!selectedSubtitle} */}
|
||||
{/* onSelect={() => setSubtitle(null)} */}
|
||||
{/* /> */}
|
||||
{/* {subtitles */}
|
||||
{/* .filter((x) => !!x.link) */}
|
||||
{/* .map((x, i) => ( */}
|
||||
{/* <Menu.Item */}
|
||||
{/* key={x.index ?? i} */}
|
||||
{/* label={ */}
|
||||
{/* x.link ? getSubtitleName(x) : `${getSubtitleName(x)} (${x.codec})` */}
|
||||
{/* } */}
|
||||
{/* selected={selectedSubtitle === x} */}
|
||||
{/* disabled={!x.link} */}
|
||||
{/* onSelect={() => setSubtitle(x)} */}
|
||||
{/* /> */}
|
||||
{/* ))} */}
|
||||
<Menu.Item
|
||||
label={t("player.subtitle-none")}
|
||||
selected={selectedIdx === -1}
|
||||
onSelect={() => select(null, -1)}
|
||||
/>
|
||||
{data?.subtitles.map((x, i) => (
|
||||
<Menu.Item
|
||||
key={x.index ?? x.link}
|
||||
label={getDisplayName(x)}
|
||||
selected={i === selectedIdx}
|
||||
onSelect={() => select(x, i)}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
@ -62,10 +62,13 @@ export const Player = () => {
|
||||
imageUri: data?.show?.thumbnail?.high ?? undefined,
|
||||
},
|
||||
externalSubtitles: info?.subtitles
|
||||
.filter((x) => x.link)
|
||||
.filter(
|
||||
(x) => Platform.OS === "web" || playMode === "hls" || x.isExternal,
|
||||
)
|
||||
.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!,
|
||||
// TODO: translate this `Unknown`
|
||||
label: x.title ?? "Unknown",
|
||||
language: x.language ?? "und",
|
||||
type: x.codec,
|
||||
|
||||
@ -287,7 +287,7 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
|
||||
extension := OrNull(SubtitleExtensions[stream.CodecName])
|
||||
var link string
|
||||
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)
|
||||
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 {
|
||||
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
|
||||
|
||||
@ -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)
|
||||
err = tx.Commit()
|
||||
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 {
|
||||
link := fmt.Sprintf(
|
||||
"video/%s/subtitle/%d.%s",
|
||||
"/video/%s/subtitle/%d.%s",
|
||||
base64.RawURLEncoding.EncodeToString([]byte(ret.Path)),
|
||||
*s.Index,
|
||||
*s.Extension,
|
||||
@ -391,5 +391,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
||||
return set(ret, err)
|
||||
}
|
||||
|
||||
err = ret.SearchExternalSubtitles()
|
||||
if err != nil {
|
||||
return set(ret, err)
|
||||
}
|
||||
|
||||
return set(ret, nil)
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ outer:
|
||||
for codec, ext := range SubtitleExtensions {
|
||||
if strings.HasSuffix(match, ext) {
|
||||
link := fmt.Sprintf(
|
||||
"video/%s/direct/%s",
|
||||
"/video/%s/direct/%s",
|
||||
base64.RawURLEncoding.EncodeToString([]byte(match)),
|
||||
filepath.Base(match),
|
||||
)
|
||||
|
||||
@ -145,7 +145,7 @@ func (s *MetadataService) extractThumbnail(ctx context.Context, path string, sha
|
||||
timestamps := ts
|
||||
ts += interval
|
||||
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(ts),
|
||||
base64.RawURLEncoding.EncodeToString([]byte(path)),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user