Skip to content

Commit

Permalink
Add earing Impaired suptitle flag support (#754)
Browse files Browse the repository at this point in the history
  • Loading branch information
felipemarinho97 authored Jan 4, 2025
1 parent cd89e75 commit 2ee313d
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 26 deletions.
4 changes: 4 additions & 0 deletions front/packages/models/src/resources/watch-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ export const SubtitleP = TrackP.extend({
* Is this an external subtitle (as in stored in a different file)
*/
isExternal: z.boolean(),
/**
* Is this a hearing impaired subtitle?
*/
isHearingImpaired: z.boolean(),
});
export type Subtitle = z.infer<typeof SubtitleP>;

Expand Down
3 changes: 3 additions & 0 deletions front/packages/ui/src/components/media-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ const MediaInfoTable = ({
// Only show it if there is more than one track
track.isDefault && !singleTrack ? t("mediainfo.default") : 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,
track.codec,
]
Expand Down
6 changes: 3 additions & 3 deletions front/packages/ui/src/player/components/right-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { useAtom } from "jotai";
import { useTranslation } from "react-i18next";
import { Platform, View } from "react-native";
import { type Stylable, useYoshiki } from "yoshiki/native";
import { useDisplayName } from "../../utils";
import { useSubtitleName } from "../../utils";
import { fullscreenAtom, subtitleAtom } from "../state";
import { AudiosMenu, QualitiesMenu } from "../video";

Expand All @@ -49,7 +49,7 @@ export const RightButtons = ({
} & Stylable) => {
const { css } = useYoshiki();
const { t } = useTranslation();
const getDisplayName = useDisplayName();
const getSubtitleName = useSubtitleName();
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
const [selectedSubtitle, setSubtitle] = useAtom(subtitleAtom);

Expand All @@ -74,7 +74,7 @@ export const RightButtons = ({
{subtitles.map((x, i) => (
<Menu.Item
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}
disabled={!x.link}
onSelect={() => setSubtitle(x)}
Expand Down
26 changes: 23 additions & 3 deletions front/packages/ui/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
import type { Track } from "@kyoo/models";
import type { Subtitle, Track } from "@kyoo/models";

import intl from "langmap";
import { useTranslation } from "react-i18next";

export const useLanguageName = () => {
return (lang: string) => intl[lang]?.nativeName;
};

export const useDisplayName = () => {
const getLanguageName = useLanguageName();
const { t } = useTranslation();

return (sub: Track) => {
const lng = sub.language ? getLanguageName(sub.language) : null;

if (lng && sub.title && sub.title !== lng) return `${lng} - ${sub.title}`;
if (lng) return lng;
if (sub.title) return sub.title;
if (sub.index !== null) return `Unknown (${sub.index})`;
return "Unknown";
if (sub.index !== null) return `${t("mediainfo.unknown")} (${sub.index})`;
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(" - ");
};
};

Expand Down
4 changes: 3 additions & 1 deletion front/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,14 @@
"audio": "Audio",
"subtitles": "Subtitles",
"forced": "Forced",
"hearing-impaired": "CC",
"default": "Default",
"external": "External",
"duration": "Duration",
"size": "Size",
"novideo": "No video",
"nocontainer": "Invalid container"
"nocontainer": "Invalid container",
"unknown": "Unknown"
},
"admin": {
"users": {
Expand Down
4 changes: 3 additions & 1 deletion front/translations/pt_br.json
Original file line number Diff line number Diff line change
Expand Up @@ -259,12 +259,14 @@
"audio": "Áudio",
"subtitles": "Legendas",
"forced": "Forçado",
"hearing-impaired": "CC",
"default": "Padrão",
"duration": "Duração",
"size": "Tamanho",
"novideo": "Sem vídeo",
"nocontainer": "Contêiner inválido",
"external": "Externo"
"external": "Externo",
"unknown": "Desconhecido"
},
"admin": {
"users": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
begin;

alter table subtitles drop column is_hearing_impaired;

commit;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
begin;

alter table subtitles add column is_hearing_impaired boolean not null default false;

commit;
19 changes: 11 additions & 8 deletions transcoder/src/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ type Subtitle struct {
IsDefault bool `json:"isDefault"`
/// Is this stream tagged as forced?
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)
IsExternal bool `json:"isExternal"`
/// Where the subtitle is stored (null if stored inside the video)
Expand Down Expand Up @@ -287,14 +289,15 @@ func RetriveMediaInfo(path string, sha string) (*MediaInfo, error) {
lang, _ := language.Parse(stream.Tags.Language)
idx := uint32(i)
return Subtitle{
Index: &idx,
Title: OrNull(stream.Tags.Title),
Language: NullIfUnd(lang.String()),
Codec: stream.CodecName,
Extension: extension,
IsDefault: stream.Disposition.Default != 0,
IsForced: stream.Disposition.Forced != 0,
Link: &link,
Index: &idx,
Title: OrNull(stream.Tags.Title),
Language: NullIfUnd(lang.String()),
Codec: stream.CodecName,
Extension: extension,
IsDefault: stream.Disposition.Default != 0,
IsForced: stream.Disposition.Forced != 0,
IsHearingImpaired: stream.Disposition.HearingImpaired != 0,
Link: &link,
}
}),
Chapters: Map(mi.Chapters, func(c *ffprobe.Chapter, _ int) Chapter {
Expand Down
13 changes: 7 additions & 6 deletions transcoder/src/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
}

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`,
sha,
)
Expand All @@ -171,7 +171,7 @@ func (s *MetadataService) getMetadata(path string, sha string) (*MediaInfo, erro
}
for rows.Next() {
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 {
return nil, err
}
Expand Down Expand Up @@ -273,8 +273,8 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
}
for _, s := range ret.Subtitles {
tx.Exec(`
insert into subtitles(sha, idx, title, language, codec, extension, is_default, is_forced)
values ($1, $2, $3, $4, $5, $6, $7, $8)
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, $9)
on conflict (sha, idx) do update set
sha = excluded.sha,
idx = excluded.idx,
Expand All @@ -283,9 +283,10 @@ func (s *MetadataService) storeFreshMetadata(path string, sha string) (*MediaInf
codec = excluded.codec,
extension = excluded.extension,
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 {
Expand Down
27 changes: 23 additions & 4 deletions transcoder/src/subtitles.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,45 @@ outer:
Path: &match,
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
flags = flags[:len(flags)-1]

for _, flag := range flags {
switch strings.ToLower(flag) {
switch flag {
case "default":
sub.IsDefault = true
case "forced":
sub.IsForced = true
case "hi", "sdh", "cc":
sub.IsHearingImpaired = true
default:
lang, err := language.Parse(flag)
if err == nil && lang != language.Und {
lang := lang.String()
sub.Language = &lang
langStr := lang.String()
sub.Language = &langStr
} else {
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)
continue outer
}
Expand Down
11 changes: 11 additions & 0 deletions transcoder/src/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,14 @@ func Filter[E any](s []E, f func(E) bool) []E {
}
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
}

0 comments on commit 2ee313d

Please sign in to comment.