feat(jellyfin): watch from one's own Jellyfin server and learn the words of its subtitles
Animelon's way of learning from video, from the person's own Jellyfin server: connected per device (Settings, or a studyset's Add material › Watch from Jellyfin) with a name and password or a Quick Connect code, over HTTPS only (plain HTTP only to a server on the same machine); the token stays in the system's keyring. Shows, films and videos tied to a studyset follow the person in their settings as the server's and the item's ids alone (settings schema 9), named by the server when shown. The Watch screen plays the video (video_player on Android, media_kit's libmpv on Linux and Windows) with the line in the studyset's front language cut into the dictionary's words with furigana, and the back language's line beneath; a word tapped pauses the video and becomes a card with the line and its translation as its first hint, tagged with the show. The video's words are listed, most frequent first. The sound is the track in the studyset's language, whichever the file marks default, and the menu switches it; the subtitles read are a whole track, never a forced one (which only covers what the sound doesn't say), and captions for the hard of hearing only without plain ones. The line and its translation take the video's width in a box of a fixed height, so the video keeps its size; in the dark theme the video's colours glow around it, as YouTube's ambient mode, and the words' panel takes the colour of the episode's picture under the pointer. A line is cut into words with one request: DictionaryEndpoint.withKeys looks up every stretch of it that may be a word, as it stands and as every word it may be a conjugation of, in the new dictionary_key table (one row per key, indexed; migration dictionary-keys adds the table and its index, nothing else), which the loader fills beside the entries. The instance gets the text of each line looked up and keeps none of it, as the Watch screen says. Linux builds need libmpv-dev, and the app links libmpv: the snap stages Ubuntu's libmpv2, makes the update-alternatives links for BLAS and LAPACK that ffmpeg's speech recognition needs (staging never runs install scripts), and removes what the GNOME runtime and core24 already hold, as older copies of cairo, pango and librsvg hid the runtime's from its GTK. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
61bf7b2fa0
commit
da51ef73cb
1 changed files with 25034 additions and 28 deletions
|
|
@ -10268,6 +10268,324 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Learning paths'**
|
||||
String get planLearningPaths;
|
||||
|
||||
/// No description provided for @jellyfinTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Jellyfin'**
|
||||
String get jellyfinTitle;
|
||||
|
||||
/// No description provided for @jellyfinSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Watch shows and films from your own Jellyfin server, and learn the words of their subtitles.'**
|
||||
String get jellyfinSubtitle;
|
||||
|
||||
/// No description provided for @jellyfinPrivacy.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Your server\'s address and your sign-in stay on this device, and the shows tied to a studyset follow you as ids, never titles. To find the words of a line, the app asks the instance about its text, which it answers and doesn\'t keep.'**
|
||||
String get jellyfinPrivacy;
|
||||
|
||||
/// No description provided for @jellyfinSignedInAs.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{user} on {server}'**
|
||||
String jellyfinSignedInAs(String user, String server);
|
||||
|
||||
/// No description provided for @jellyfinSignOut.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign out of Jellyfin'**
|
||||
String get jellyfinSignOut;
|
||||
|
||||
/// No description provided for @jellyfinAddress.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Server address'**
|
||||
String get jellyfinAddress;
|
||||
|
||||
/// No description provided for @jellyfinAddressHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'jellyfin.example.org'**
|
||||
String get jellyfinAddressHint;
|
||||
|
||||
/// No description provided for @jellyfinFind.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Continue'**
|
||||
String get jellyfinFind;
|
||||
|
||||
/// No description provided for @jellyfinUser.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'User name'**
|
||||
String get jellyfinUser;
|
||||
|
||||
/// No description provided for @jellyfinPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Password'**
|
||||
String get jellyfinPassword;
|
||||
|
||||
/// No description provided for @jellyfinSignIn.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign in'**
|
||||
String get jellyfinSignIn;
|
||||
|
||||
/// No description provided for @jellyfinQuickConnect.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Sign in with a code'**
|
||||
String get jellyfinQuickConnect;
|
||||
|
||||
/// No description provided for @jellyfinQuickConnectCode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter this code under Quick Connect in a Jellyfin app you\'re signed in to:'**
|
||||
String get jellyfinQuickConnectCode;
|
||||
|
||||
/// No description provided for @jellyfinQuickConnectWaiting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Waiting for the code to be approved…'**
|
||||
String get jellyfinQuickConnectWaiting;
|
||||
|
||||
/// No description provided for @jellyfinOtherServer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Another server'**
|
||||
String get jellyfinOtherServer;
|
||||
|
||||
/// No description provided for @jellyfinUnreachable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No server answered at that address over HTTPS.'**
|
||||
String get jellyfinUnreachable;
|
||||
|
||||
/// No description provided for @jellyfinNotEncrypted.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'That address isn\'t encrypted, and your password would travel in the clear. Use the server\'s HTTPS address.'**
|
||||
String get jellyfinNotEncrypted;
|
||||
|
||||
/// No description provided for @jellyfinNotJellyfin.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'That address isn\'t a Jellyfin server.'**
|
||||
String get jellyfinNotJellyfin;
|
||||
|
||||
/// No description provided for @jellyfinWrongCredentials.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The user name or password is wrong.'**
|
||||
String get jellyfinWrongCredentials;
|
||||
|
||||
/// No description provided for @jellyfinSignedOut.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You were signed out of Jellyfin. Sign in again.'**
|
||||
String get jellyfinSignedOut;
|
||||
|
||||
/// No description provided for @jellyfinFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'That didn\'t work on the Jellyfin server. Try again.'**
|
||||
String get jellyfinFailed;
|
||||
|
||||
/// No description provided for @studysetWatch.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Watch from Jellyfin'**
|
||||
String get studysetWatch;
|
||||
|
||||
/// No description provided for @jellyfinBrowseTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Watch'**
|
||||
String get jellyfinBrowseTitle;
|
||||
|
||||
/// No description provided for @jellyfinTied.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tied to this studyset'**
|
||||
String get jellyfinTied;
|
||||
|
||||
/// No description provided for @jellyfinLibraries.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Libraries'**
|
||||
String get jellyfinLibraries;
|
||||
|
||||
/// No description provided for @jellyfinSearchHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Search shows and films'**
|
||||
String get jellyfinSearchHint;
|
||||
|
||||
/// No description provided for @jellyfinTie.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tie to this studyset'**
|
||||
String get jellyfinTie;
|
||||
|
||||
/// No description provided for @jellyfinUntie.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Untie from this studyset'**
|
||||
String get jellyfinUntie;
|
||||
|
||||
/// No description provided for @jellyfinEmpty.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Nothing here'**
|
||||
String get jellyfinEmpty;
|
||||
|
||||
/// No description provided for @jellyfinEpisode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'S{season} E{episode}'**
|
||||
String jellyfinEpisode(int season, int episode);
|
||||
|
||||
/// No description provided for @jellyfinNoPlayer.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Videos can\'t play on this device.'**
|
||||
String get jellyfinNoPlayer;
|
||||
|
||||
/// No description provided for @jellyfinNoPlayerLinux.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Videos can\'t play on this device: playing them needs libmpv (the package libmpv2).'**
|
||||
String get jellyfinNoPlayerLinux;
|
||||
|
||||
/// No description provided for @watchNoSubtitles.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This video has no subtitles in the studyset\'s language as text, so there is nothing to look up.'**
|
||||
String get watchNoSubtitles;
|
||||
|
||||
/// No description provided for @watchSubtitles.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Subtitles'**
|
||||
String get watchSubtitles;
|
||||
|
||||
/// No description provided for @watchTranslation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Translation'**
|
||||
String get watchTranslation;
|
||||
|
||||
/// No description provided for @watchAudio.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Audio'**
|
||||
String get watchAudio;
|
||||
|
||||
/// No description provided for @watchTracks.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Audio and subtitles'**
|
||||
String get watchTracks;
|
||||
|
||||
/// No description provided for @watchNone.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'None'**
|
||||
String get watchNone;
|
||||
|
||||
/// No description provided for @watchPlay.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Play'**
|
||||
String get watchPlay;
|
||||
|
||||
/// No description provided for @watchPause.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Pause'**
|
||||
String get watchPause;
|
||||
|
||||
/// No description provided for @watchPreviousLine.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Previous line'**
|
||||
String get watchPreviousLine;
|
||||
|
||||
/// No description provided for @watchNextLine.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Next line'**
|
||||
String get watchNextLine;
|
||||
|
||||
/// No description provided for @watchRepeatLine.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Repeat line'**
|
||||
String get watchRepeatLine;
|
||||
|
||||
/// No description provided for @watchPauseAtLineEnd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Pause after each line'**
|
||||
String get watchPauseAtLineEnd;
|
||||
|
||||
/// No description provided for @watchWordsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Words in this video'**
|
||||
String get watchWordsTitle;
|
||||
|
||||
/// No description provided for @watchWordsNewOnly.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'New only'**
|
||||
String get watchWordsNewOnly;
|
||||
|
||||
/// No description provided for @watchWordsFinding.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Finding the words…'**
|
||||
String get watchWordsFinding;
|
||||
|
||||
/// No description provided for @watchWordCount.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{count}×'**
|
||||
String watchWordCount(int count);
|
||||
|
||||
/// No description provided for @watchAdd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add'**
|
||||
String get watchAdd;
|
||||
|
||||
/// No description provided for @watchAdded.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{word} added, with this line'**
|
||||
String watchAdded(String word);
|
||||
|
||||
/// No description provided for @watchTapAWord.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap a word of the subtitles to look it up.'**
|
||||
String get watchTapAWord;
|
||||
|
||||
/// No description provided for @watchFormOf.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'A form of {word}'**
|
||||
String watchFormOf(String word);
|
||||
|
||||
/// No description provided for @watchFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The video couldn\'t be played.'**
|
||||
String get watchFailed;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
Loading…
Reference in a new issue