mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add earing Impaired suptitle flag support (#754)
This commit is contained in:
parent
cd89e757c1
commit
2ee313d5f6
@ -95,6 +95,10 @@ export const SubtitleP = TrackP.extend({
|
|||||||
* Is this an external subtitle (as in stored in a different file)
|
* Is this an external subtitle (as in stored in a different file)
|
||||||
*/
|
*/
|
||||||
isExternal: z.boolean(),
|
isExternal: z.boolean(),
|
||||||
|
/**
|
||||||
|
* Is this a hearing impaired subtitle?
|
||||||
|
*/
|
||||||
|
isHearingImpaired: z.boolean(),
|
||||||
});
|
});
|
||||||
export type Subtitle = z.infer<typeof SubtitleP>;
|
export type Subtitle = z.infer<typeof SubtitleP>;
|
||||||
|
|
||||||
|
@ -59,6 +59,9 @@ const MediaInfoTable = ({
|
|||||||
// Only show it if there is more than one track
|
// Only show it if there is more than one track
|
||||||
track.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
|
track.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
|
||||||
track.isForced ? t("mediainfo.forced") : undefined,
|
track.isForced ? t("mediainfo.forced") : undefined,
|
||||||
|
"isHearingImpaired" in track && track.isHearingImpaired
|
||||||
|
? t("mediainfo.hearing-impaired")
|
||||||
|
: undefined,
|
||||||
"isExternal" in track && track.isExternal ? t("mediainfo.external") : undefined,
|
"isExternal" in track && track.isExternal ? t("mediainfo.external") : undefined,
|
||||||
track.codec,
|
track.codec,
|
||||||
]
|
]
|
||||||
|
@ -29,7 +29,7 @@ import { useAtom } from "jotai";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Platform, View } from "react-native";
|
import { Platform, View } from "react-native";
|
||||||
import { type Stylable, useYoshiki } from "yoshiki/native";
|
import { type Stylable, useYoshiki } from "yoshiki/native";
|
||||||
import { useDisplayName } from "../../utils";
|
import { useSubtitleName } from "../../utils";
|
||||||
import { fullscreenAtom, subtitleAtom } from "../state";
|
import { fullscreenAtom, subtitleAtom } from "../state";
|
||||||
import { AudiosMenu, QualitiesMenu } from "../video";
|
import { AudiosMenu, QualitiesMenu } from "../video";
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ export const RightButtons = ({
|
|||||||
} & Stylable) => {
|
} & Stylable) => {
|
||||||
const { css } = useYoshiki();
|
const { css } = useYoshiki();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const getDisplayName = useDisplayName();
|
const getSubtitleName = useSubtitleName();
|
||||||
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
||||||
const [selectedSubtitle, setSubtitle] = useAtom(subtitleAtom);
|
const [selectedSubtitle, setSubtitle] = useAtom(subtitleAtom);
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ export const RightButtons = ({
|
|||||||
{subtitles.map((x, i) => (
|
{subtitles.map((x, i) => (
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
key={x.index ?? i}
|
key={x.index ?? i}
|
||||||
label={x.link ? getDisplayName(x) : `${getDisplayName(x)} (${x.codec})`}
|
label={x.link ? getSubtitleName(x) : `${getSubtitleName(x)} (${x.codec})`}
|
||||||
selected={selectedSubtitle === x}
|
selected={selectedSubtitle === x}
|
||||||
disabled={!x.link}
|
disabled={!x.link}
|
||||||
onSelect={() => setSubtitle(x)}
|
onSelect={() => setSubtitle(x)}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import type { Track } from "@kyoo/models";
|
import type { Subtitle, Track } from "@kyoo/models";
|
||||||
|
|
||||||
import intl from "langmap";
|
import intl from "langmap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export const useLanguageName = () => {
|
export const useLanguageName = () => {
|
||||||
return (lang: string) => intl[lang]?.nativeName;
|
return (lang: string) => intl[lang]?.nativeName;
|
||||||
@ -7,6 +9,7 @@ export const useLanguageName = () => {
|
|||||||
|
|
||||||
export const useDisplayName = () => {
|
export const useDisplayName = () => {
|
||||||
const getLanguageName = useLanguageName();
|
const getLanguageName = useLanguageName();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (sub: Track) => {
|
return (sub: Track) => {
|
||||||
const lng = sub.language ? getLanguageName(sub.language) : null;
|
const lng = sub.language ? getLanguageName(sub.language) : null;
|
||||||
@ -14,8 +17,25 @@ export const useDisplayName = () => {
|
|||||||
if (lng && sub.title && sub.title !== lng) return `${lng} - ${sub.title}`;
|
if (lng && sub.title && sub.title !== lng) return `${lng} - ${sub.title}`;
|
||||||
if (lng) return lng;
|
if (lng) return lng;
|
||||||
if (sub.title) return sub.title;
|
if (sub.title) return sub.title;
|
||||||
if (sub.index !== null) return `Unknown (${sub.index})`;
|
if (sub.index !== null) return `${t("mediainfo.unknown")} (${sub.index})`;
|
||||||
return "Unknown";
|
return t("mediainfo.unknown");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSubtitleName = () => {
|
||||||
|
const getDisplayName = useDisplayName();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (sub: Subtitle) => {
|
||||||
|
const name = getDisplayName(sub);
|
||||||
|
const attributes = [name];
|
||||||
|
|
||||||
|
if (sub.isDefault) attributes.push(t("mediainfo.default"));
|
||||||
|
if (sub.isForced) attributes.push(t("mediainfo.forced"));
|
||||||
|
if (sub.isHearingImpaired) attributes.push(t("mediainfo.hearing-impaired"));
|
||||||
|
if (sub.isExternal) attributes.push(t("mediainfo.external"));
|
||||||
|
|
||||||
|
return attributes.join(" - ");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -259,12 +259,14 @@
|
|||||||
"audio": "Audio",
|
"audio": "Audio",
|
||||||
"subtitles": "Subtitles",
|
"subtitles": "Subtitles",
|
||||||
"forced": "Forced",
|
"forced": "Forced",
|
||||||
|
"hearing-impaired": "CC",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"external": "External",
|
"external": "External",
|
||||||
"duration": "Duration",
|
"duration": "Duration",
|
||||||
"size": "Size",
|
"size": "Size",
|
||||||
"novideo": "No video",
|
"novideo": "No video",
|
||||||
"nocontainer": "Invalid container"
|
"nocontainer": "Invalid container",
|
||||||
|
"unknown": "Unknown"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"users": {
|
"users": {
|
||||||
|
@ -259,12 +259,14 @@
|
|||||||
"audio": "Áudio",
|
"audio": "Áudio",
|
||||||
"subtitles": "Legendas",
|
"subtitles": "Legendas",
|
||||||
"forced": "Forçado",
|
"forced": "Forçado",
|
||||||
|
"hearing-impaired": "CC",
|
||||||
"default": "Padrão",
|
"default": "Padrão",
|
||||||
"duration": "Duração",
|
"duration": "Duração",
|
||||||
"size": "Tamanho",
|
"size": "Tamanho",
|
||||||
"novideo": "Sem vídeo",
|
"novideo": "Sem vídeo",
|
||||||
"nocontainer": "Contêiner inválido",
|
"nocontainer": "Contêiner inválido",
|
||||||
"external": "Externo"
|
"external": "Externo",
|
||||||
|
"unknown": "Desconhecido"
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
"users": {
|
"users": {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
begin;
|
||||||
|
|
||||||
|
alter table subtitles drop column is_hearing_impaired;
|
||||||
|
|
||||||
|
commit;
|
@ -0,0 +1,5 @@
|
|||||||
|
begin;
|
||||||
|
|
||||||
|
alter table subtitles add column is_hearing_impaired boolean not null default false;
|
||||||
|
|
||||||
|
commit;
|
@ -123,6 +123,8 @@ type Subtitle struct {
|
|||||||
IsDefault bool `json:"isDefault"`
|
IsDefault bool `json:"isDefault"`
|
||||||
/// Is this stream tagged as forced?
|
/// Is this stream tagged as forced?
|
||||||
IsForced bool `json:"isForced"`
|
IsForced bool `json:"isForced"`
|
||||||
|
/// Is this stream tagged as hearing impaired?
|
||||||
|
IsHearingImpaired bool `json:"isHearingImpaired"`
|
||||||
/// Is this an external subtitle (as in stored in a different file)
|
/// Is this an external subtitle (as in stored in a different file)
|
||||||
IsExternal bool `json:"isExternal"`
|
IsExternal bool `json:"isExternal"`
|
||||||
/// Where the subtitle is stored (null if stored inside the video)
|
/// Where the subtitle is stored (null if stored inside the video)
|
||||||
@ -294,6 +296,7 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
|
|||||||
Extension: extension,
|
Extension: extension,
|
||||||
IsDefault: stream.Disposition.Default != 0,
|
IsDefault: stream.Disposition.Default != 0,
|
||||||
IsForced: stream.Disposition.Forced != 0,
|
IsForced: stream.Disposition.Forced != 0,
|
||||||
|
IsHearingImpaired: stream.Disposition.HearingImpaired != 0,
|
||||||
Link: &link,
|
Link: &link,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -162,7 +162,7 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows, err = s.database.Query(
|
rows, err = s.database.Query(
|
||||||
`select s.idx, s.title, s.language, s.codec, s.extension, s.is_default, s.is_forced
|
`select s.idx, s.title, s.language, s.codec, s.extension, s.is_default, s.is_forced, s.is_hearing_impaired
|
||||||
from subtitles as s where s.sha=$1`,
|
from subtitles as s where s.sha=$1`,
|
||||||
sha,
|
sha,
|
||||||
)
|
)
|
||||||
@ -171,7 +171,7 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
|
|||||||
}
|
}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var s Subtitle
|
var s Subtitle
|
||||||
err := rows.Scan(&s.Index, &s.Title, &s.Language, &s.Codec, &s.Extension, &s.IsDefault, &s.IsForced)
|
err := rows.Scan(&s.Index, &s.Title, &s.Language, &s.Codec, &s.Extension, &s.IsDefault, &s.IsForced, &s.IsHearingImpaired)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -273,8 +273,8 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
}
|
}
|
||||||
for _, s := range ret.Subtitles {
|
for _, s := range ret.Subtitles {
|
||||||
tx.Exec(`
|
tx.Exec(`
|
||||||
insert into subtitles(sha, idx, title, language, codec, extension, is_default, is_forced)
|
insert into subtitles(sha, idx, title, language, codec, extension, is_default, is_forced, is_hearing_impaired)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
values ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||||
on conflict (sha, idx) do update set
|
on conflict (sha, idx) do update set
|
||||||
sha = excluded.sha,
|
sha = excluded.sha,
|
||||||
idx = excluded.idx,
|
idx = excluded.idx,
|
||||||
@ -283,9 +283,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
|
|||||||
codec = excluded.codec,
|
codec = excluded.codec,
|
||||||
extension = excluded.extension,
|
extension = excluded.extension,
|
||||||
is_default = excluded.is_default,
|
is_default = excluded.is_default,
|
||||||
is_forced = excluded.is_forced
|
is_forced = excluded.is_forced,
|
||||||
|
is_hearing_impaired = excluded.is_hearing_impaired
|
||||||
`,
|
`,
|
||||||
ret.Sha, s.Index, s.Title, s.Language, s.Codec, s.Extension, s.IsDefault, s.IsForced,
|
ret.Sha, s.Index, s.Title, s.Language, s.Codec, s.Extension, s.IsDefault, s.IsForced, s.IsHearingImpaired,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for _, c := range ret.Chapters {
|
for _, c := range ret.Chapters {
|
||||||
|
@ -43,26 +43,45 @@ outer:
|
|||||||
Path: &match,
|
Path: &match,
|
||||||
Link: &link,
|
Link: &link,
|
||||||
}
|
}
|
||||||
flags := separator.Split(match[len(base_path):], -1)
|
flags_str := strings.ToLower(match[len(base_path):])
|
||||||
|
flags := separator.Split(flags_str, -1)
|
||||||
|
|
||||||
// remove extension from flags
|
// remove extension from flags
|
||||||
flags = flags[:len(flags)-1]
|
flags = flags[:len(flags)-1]
|
||||||
|
|
||||||
for _, flag := range flags {
|
for _, flag := range flags {
|
||||||
switch strings.ToLower(flag) {
|
switch flag {
|
||||||
case "default":
|
case "default":
|
||||||
sub.IsDefault = true
|
sub.IsDefault = true
|
||||||
case "forced":
|
case "forced":
|
||||||
sub.IsForced = true
|
sub.IsForced = true
|
||||||
|
case "hi", "sdh", "cc":
|
||||||
|
sub.IsHearingImpaired = true
|
||||||
default:
|
default:
|
||||||
lang, err := language.Parse(flag)
|
lang, err := language.Parse(flag)
|
||||||
if err == nil && lang != language.Und {
|
if err == nil && lang != language.Und {
|
||||||
lang := lang.String()
|
langStr := lang.String()
|
||||||
sub.Language = &lang
|
sub.Language = &langStr
|
||||||
} else {
|
} else {
|
||||||
sub.Title = &flag
|
sub.Title = &flag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle Hindi (hi) collision with Hearing Impaired (hi):
|
||||||
|
// "hi" by itself means a language code, but when combined with other lang flags it means Hearing Impaired.
|
||||||
|
// In case Hindi was not detected before, but "hi" is present, assume it is Hindi.
|
||||||
|
if sub.Language == nil {
|
||||||
|
hiCount := Count(flags, "hi")
|
||||||
|
if hiCount > 0 {
|
||||||
|
languageStr := language.Hindi.String()
|
||||||
|
sub.Language = &languageStr
|
||||||
|
}
|
||||||
|
if hiCount == 1 {
|
||||||
|
sub.IsHearingImpaired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mi.Subtitles = append(mi.Subtitles, sub)
|
mi.Subtitles = append(mi.Subtitles, sub)
|
||||||
continue outer
|
continue outer
|
||||||
}
|
}
|
||||||
|
@ -25,3 +25,14 @@ func Filter[E any](s []E, f func(E) bool) []E {
|
|||||||
}
|
}
|
||||||
return s2
|
return s2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count returns the number of elements in s that are equal to e.
|
||||||
|
func Count[S []E, E comparable](s S, e E) int {
|
||||||
|
var n int
|
||||||
|
for _, v := range s {
|
||||||
|
if v == e {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user