forked from TGlide/movie-web
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
linked captions + primary navigation dropdown
Co-authored-by: Jip Frijlink <[email protected]>
- Loading branch information
Showing
19 changed files
with
359 additions
and
325 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,116 +1,33 @@ | ||
import { gql, request } from "graphql-request"; | ||
import { list } from "subsrt-ts"; | ||
import { unzip } from "unzipit"; | ||
|
||
import { proxiedFetch } from "@/backend/helpers/fetch"; | ||
import { languageMap } from "@/setup/iso6391"; | ||
import { PlayerMeta } from "@/stores/player/slices/source"; | ||
import { convertSubtitlesToSrt } from "@/components/player/utils/captions"; | ||
import { CaptionListItem } from "@/stores/player/slices/source"; | ||
import { SimpleCache } from "@/utils/cache"; | ||
|
||
const GQL_API = "https://gqlos.plus-sub.com"; | ||
|
||
const subtitleSearchQuery = gql` | ||
query SubtitleSearch($tmdb_id: String!, $ep: Int, $season: Int) { | ||
subtitleSearch( | ||
tmdb_id: $tmdb_id | ||
language: "" | ||
episode_number: $ep | ||
season_number: $season | ||
) { | ||
data { | ||
attributes { | ||
language | ||
subtitle_id | ||
ai_translated | ||
auto_translation | ||
ratings | ||
votes | ||
legacy_subtitle_id | ||
} | ||
id | ||
} | ||
} | ||
export const subtitleTypeList = list().map((type) => `.${type}`); | ||
const downloadCache = new SimpleCache<string, string>(); | ||
downloadCache.setCompare((a, b) => a === b); | ||
const expirySeconds = 24 * 60 * 60; | ||
|
||
/** | ||
* Always returns SRT | ||
*/ | ||
export async function downloadCaption( | ||
caption: CaptionListItem | ||
): Promise<string> { | ||
const cached = downloadCache.get(caption.url); | ||
if (cached) return cached; | ||
|
||
let data: string | undefined; | ||
if (caption.needsProxy) { | ||
data = await proxiedFetch<string>(caption.url, { responseType: "text" }); | ||
} else { | ||
data = await fetch(caption.url).then((v) => v.text()); | ||
} | ||
`; | ||
|
||
interface RawSubtitleSearchItem { | ||
id: string; | ||
attributes: { | ||
language: string; | ||
ai_translated: boolean | null; | ||
auto_translation: null | boolean; | ||
ratings: number; | ||
votes: number | null; | ||
legacy_subtitle_id: string | null; | ||
}; | ||
} | ||
if (!data) throw new Error("failed to get caption data"); | ||
|
||
export interface SubtitleSearchItem { | ||
id: string; | ||
attributes: { | ||
language: string; | ||
ai_translated: boolean | null; | ||
auto_translation: null | boolean; | ||
ratings: number; | ||
votes: number | null; | ||
legacy_subtitle_id: string; | ||
}; | ||
const output = convertSubtitlesToSrt(data); | ||
downloadCache.set(caption.url, output, expirySeconds); | ||
return output; | ||
} | ||
|
||
interface SubtitleSearchData { | ||
subtitleSearch: { | ||
data: RawSubtitleSearchItem[]; | ||
}; | ||
} | ||
|
||
export async function searchSubtitles( | ||
meta: PlayerMeta | ||
): Promise<SubtitleSearchItem[]> { | ||
const data = await request<SubtitleSearchData>({ | ||
document: subtitleSearchQuery, | ||
url: GQL_API, | ||
variables: { | ||
tmdb_id: meta.tmdbId, | ||
ep: meta.episode?.number, | ||
season: meta.season?.number, | ||
}, | ||
}); | ||
|
||
const sortedByLanguage: Record<string, RawSubtitleSearchItem[]> = {}; | ||
data.subtitleSearch.data.forEach((v) => { | ||
if (!sortedByLanguage[v.attributes.language]) | ||
sortedByLanguage[v.attributes.language] = []; | ||
sortedByLanguage[v.attributes.language].push(v); | ||
}); | ||
|
||
return Object.values(sortedByLanguage).map((langs) => { | ||
const onlyLegacySubs = langs.filter( | ||
(v): v is SubtitleSearchItem => !!v.attributes.legacy_subtitle_id | ||
); | ||
const sortedByRating = onlyLegacySubs.sort( | ||
(a, b) => | ||
b.attributes.ratings * (b.attributes.votes ?? 0) - | ||
a.attributes.ratings * (a.attributes.votes ?? 0) | ||
); | ||
return sortedByRating[0]; | ||
}); | ||
} | ||
|
||
export async function downloadSrt(legacySubId: string): Promise<string> { | ||
// TODO there is cloudflare protection so this may not always work. what to do about that? | ||
// TODO also there is ratelimit on the page itself | ||
// language code is hardcoded here, it does nothing | ||
const zipFile = await proxiedFetch<ArrayBuffer>( | ||
`https://dl.opensubtitles.org/en/subtitleserve/sub/${legacySubId}`, | ||
{ | ||
responseType: "arrayBuffer", | ||
} | ||
); | ||
|
||
const { entries } = await unzip(zipFile); | ||
const srtEntry = Object.values(entries).find((v) => v.name); | ||
if (!srtEntry) throw new Error("No srt file found in zip"); | ||
const srtData = srtEntry.text(); | ||
return srtData; | ||
} | ||
|
||
export const subtitleTypeList = list().map((type) => `.${type}`); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.