summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--assets/css/default.css2
-rw-r--r--locales/hi.json474
-rw-r--r--locales/pt-BR.json17
-rw-r--r--locales/ru.json8
-rw-r--r--src/invidious/channels/about.cr7
-rw-r--r--src/invidious/comments.cr8
-rw-r--r--src/invidious/helpers/errors.cr2
-rw-r--r--src/invidious/helpers/i18n.cr1
-rw-r--r--src/invidious/helpers/serialized_yt_data.cr7
-rw-r--r--src/invidious/routes/feeds.cr1
-rw-r--r--src/invidious/videos.cr19
-rw-r--r--src/invidious/views/channel.ecr2
-rw-r--r--src/invidious/views/community.ecr2
-rw-r--r--src/invidious/views/components/item.ecr6
-rw-r--r--src/invidious/views/playlists.ecr2
-rw-r--r--src/invidious/views/watch.ecr6
-rw-r--r--src/invidious/yt_backend/extractors.cr48
17 files changed, 574 insertions, 38 deletions
diff --git a/assets/css/default.css b/assets/css/default.css
index 49069c92..61b7819f 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -291,7 +291,7 @@ input[type="search"]::-webkit-search-cancel-button {
.flexible { display: flex; }
.flex-left { flex: 1 1 100%; flex-wrap: wrap; }
-.flex-right { flex: 1 0 max-content; flex-wrap: nowrap; }
+.flex-right { flex: 1 0 auto; flex-wrap: nowrap; }
p.channel-name { margin: 0; }
p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
diff --git a/locales/hi.json b/locales/hi.json
new file mode 100644
index 00000000..0fc35b25
--- /dev/null
+++ b/locales/hi.json
@@ -0,0 +1,474 @@
+{
+ "last": "आखिरी",
+ "Yes": "हाँ",
+ "No": "नहीं",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "OPML के रूप में सदस्यताएँ निर्यात करें (NewPipe और FreeTube के लिए)",
+ "Log in/register": "लॉग-इन/पंजीकृत करें",
+ "Log in with Google": "Google के साथ लॉग-इन करें",
+ "preferences_autoplay_label": "अपने आप चलाने की सुविधा: ",
+ "preferences_dark_mode_label": "थीम: ",
+ "preferences_default_home_label": "डिफ़ॉल्ट मुखपृष्ठ: ",
+ "Could not fetch comments": "टिप्पणियाँ प्राप्त न की जा सकीं",
+ "comments_points_count": "{{count}} पॉइंट",
+ "comments_points_count_plural": "{{count}} पॉइंट्स",
+ "Subscription manager": "सदस्यता प्रबंधन",
+ "License: ": "लाइसेंस: ",
+ "Wilson score: ": "Wilson स्कोर: ",
+ "Wrong answer": "गलत जवाब",
+ "Erroneous CAPTCHA": "गलत CAPTCHA",
+ "Please log in": "कृपया लॉग-इन करें",
+ "Bosnian": "बोस्नियाई",
+ "Bulgarian": "बुल्गारियाई",
+ "Burmese": "बर्मी",
+ "Chinese (Traditional)": "चीनी (पारंपरिक)",
+ "Kurdish": "कुर्द",
+ "Punjabi": "पंजाबी",
+ "Sinhala": "सिंहली",
+ "Slovak": "स्लोवाक",
+ "generic_count_days": "{{count}} दिन",
+ "generic_count_days_plural": "{{count}} दिन",
+ "generic_count_hours": "{{count}} घंटे",
+ "generic_count_hours_plural": "{{count}} घंटे",
+ "generic_count_minutes": "{{count}} मिनट",
+ "generic_count_minutes_plural": "{{count}} मिनट",
+ "generic_count_seconds": "{{count}} सेकंड",
+ "generic_count_seconds_plural": "{{count}} सेकंड",
+ "generic_playlists_count": "{{count}} प्लेलिस्ट",
+ "generic_playlists_count_plural": "{{count}} प्लेलिस्ट्स",
+ "crash_page_report_issue": "अगर इनमें से कुछ भी काम नहीं करता, कृपया <a href=\"`x`\">GitHub पर एक नया मुद्दा खोल दें</a> (अंग्रेज़ी में) और अपने संदेश में यह टेक्स्ट दर्ज करें (इसे अनुवादित न करें!):",
+ "generic_views_count": "{{count}} बार देखा गया",
+ "generic_views_count_plural": "{{count}} बार देखा गया",
+ "generic_videos_count": "{{count}} वीडियो",
+ "generic_videos_count_plural": "{{count}} वीडियो",
+ "generic_subscribers_count": "{{count}} सदस्य",
+ "generic_subscribers_count_plural": "{{count}} सदस्य",
+ "generic_subscriptions_count": "{{count}} सदस्यता",
+ "generic_subscriptions_count_plural": "{{count}} सदस्यताएँ",
+ "LIVE": "लाइव",
+ "Shared `x` ago": "`x` पहले बाँटा गया",
+ "Unsubscribe": "सदस्यता छोड़ें",
+ "Subscribe": "सदस्यता लें",
+ "View channel on YouTube": "चैनल YouTube पर देखें",
+ "View playlist on YouTube": "प्लेलिस्ट YouTube पर देखें",
+ "newest": "सबसे नया",
+ "oldest": "सबसे पुराना",
+ "popular": "सर्वाधिक लोकप्रिय",
+ "Next page": "अगला पृष्ठ",
+ "Previous page": "पिछला पृष्ठ",
+ "Clear watch history?": "देखने का इतिहास मिटाएँ?",
+ "New password": "नया पासवर्ड",
+ "New passwords must match": "पासवर्ड्स को मेल खाना होगा",
+ "Cannot change password for Google accounts": "Google खातों के लिए पासवर्ड नहीं बदल सकते",
+ "Authorize token?": "टोकन को प्रमाणित करें?",
+ "Authorize token for `x`?": "`x` के लिए टोकन को प्रमाणित करें?",
+ "Import and Export Data": "डेटा को आयात और निर्यात करें",
+ "Import": "आयात करें",
+ "Import Invidious data": "Invidious JSON डेटा आयात करें",
+ "Import YouTube subscriptions": "YouTube/OPML सदस्यताएँ आयात करें",
+ "Import FreeTube subscriptions (.db)": "FreeTube सदस्यताएँ आयात करें (.db)",
+ "Import NewPipe subscriptions (.json)": "NewPipe सदस्यताएँ आयात करें (.json)",
+ "Import NewPipe data (.zip)": "NewPipe डेटा आयात करें (.zip)",
+ "Export": "निर्यात करें",
+ "Export subscriptions as OPML": "OPML के रूप में सदस्यताएँ निर्यात करें",
+ "Export data as JSON": "Invidious डेटा को JSON के रूप में निर्यात करें",
+ "Delete account?": "खाता हटाएँ?",
+ "History": "देखे गए वीडियो",
+ "An alternative front-end to YouTube": "YouTube का एक वैकल्पिक फ्रंट-एंड",
+ "JavaScript license information": "जावास्क्रिप्ट लाइसेंस की जानकारी",
+ "source": "स्रोत",
+ "Log in": "लॉग-इन करें",
+ "User ID": "सदस्य ID",
+ "Password": "पासवर्ड",
+ "Register": "पंजीकृत करें",
+ "E-mail": "ईमेल",
+ "Google verification code": "Google प्रमाणीकरण कोड",
+ "Time (h:mm:ss):": "समय (घं:मिमि:सेसे):",
+ "Text CAPTCHA": "टेक्स्ट CAPTCHA",
+ "Image CAPTCHA": "चित्र CAPTCHA",
+ "Sign In": "साइन इन करें",
+ "Preferences": "प्राथमिकताएँ",
+ "preferences_category_player": "प्लेयर की प्राथमिकताएँ",
+ "preferences_video_loop_label": "हमेशा लूप करें: ",
+ "preferences_continue_label": "डिफ़ॉल्ट से अगला चलाएँ: ",
+ "preferences_continue_autoplay_label": "अगला वीडियो अपने आप चलाएँ: ",
+ "preferences_listen_label": "डिफ़ॉल्ट से सुनें: ",
+ "preferences_local_label": "प्रॉक्सी वीडियो: ",
+ "preferences_watch_history_label": "देखने का इतिहास सक्षम करें: ",
+ "preferences_speed_label": "वीडियो चलाने की डिफ़ॉल्ट रफ़्तार: ",
+ "preferences_quality_label": "वीडियो की प्राथमिक क्वालिटी: ",
+ "preferences_quality_option_dash": "DASH (अनुकूली गुणवत्ता)",
+ "preferences_quality_option_hd720": "HD720",
+ "preferences_quality_option_medium": "मध्यम",
+ "preferences_quality_option_small": "छोटा",
+ "preferences_quality_dash_label": "प्राथमिक DASH वीडियो क्वालिटी: ",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_dash_option_auto": "अपने-आप",
+ "preferences_quality_dash_option_best": "सबसे अच्छा",
+ "preferences_quality_dash_option_worst": "सबसे खराब",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "preferences_quality_dash_option_480p": "480p",
+ "preferences_quality_dash_option_360p": "360p",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_144p": "144p",
+ "preferences_comments_label": "डिफ़ॉल्ट टिप्पणियाँ: ",
+ "preferences_volume_label": "प्लेयर का वॉल्यूम: ",
+ "youtube": "YouTube",
+ "reddit": "Reddit",
+ "invidious": "Invidious",
+ "preferences_captions_label": "डिफ़ॉल्ट कैप्शन: ",
+ "Fallback captions: ": "वैकल्पिक कैप्शन: ",
+ "preferences_related_videos_label": "संबंधित वीडियो दिखाएँ: ",
+ "preferences_annotations_label": "डिफ़ॉल्ट से टिप्पणियाँ दिखाएँ: ",
+ "preferences_extend_desc_label": "अपने आप वीडियो के विवरण का विस्तार करें: ",
+ "preferences_vr_mode_label": "उत्तरदायी 360 डिग्री वीडियो (WebGL की ज़रूरत है): ",
+ "preferences_category_visual": "यथादृश्य प्राथमिकताएँ",
+ "preferences_region_label": "सामग्री का राष्ट्र: ",
+ "preferences_player_style_label": "प्लेयर का स्टाइल: ",
+ "Dark mode: ": "डार्क मोड: ",
+ "dark": "डार्क",
+ "light": "लाइट",
+ "preferences_thin_mode_label": "हल्का मोड: ",
+ "preferences_category_misc": "विविध प्राथमिकताएँ",
+ "preferences_automatic_instance_redirect_label": "अपने आप अनुप्रेषित करें (redirect.invidious.io पर फ़ॉलबैक करें): ",
+ "preferences_category_subscription": "सदस्यताओं की प्राथमिकताएँ",
+ "preferences_annotations_subscribed_label": "सदस्यता लिए गए चैनलों पर डिफ़ॉल्ट से टिप्पणियाँ दिखाएँ? ",
+ "Redirect homepage to feed: ": "फ़ीड पर मुखपृष्ठ को अनुप्रेषित करें: ",
+ "preferences_max_results_label": "फ़ीड में दिखाए जाने वाले वीडियों की संख्या: ",
+ "preferences_sort_label": "वीडियों को इस मानदंड पर छाँटें: ",
+ "published": "प्रकाशित",
+ "published - reverse": "प्रकाशित - उल्टा",
+ "Only show latest video from channel: ": "चैनल से सिर्फ नवीनतम वीडियो ही दिखाएँ: ",
+ "alphabetically": "वर्णक्रमानुसार",
+ "Only show latest unwatched video from channel: ": "चैनल से सिर्फ न देखा गया नवीनतम वीडियो ही दिखाएँ: ",
+ "alphabetically - reverse": "वर्णक्रमानुसार - उल्टा",
+ "channel name": "चैनल का नाम",
+ "channel name - reverse": "चैनल का नाम - उल्टा",
+ "preferences_unseen_only_label": "सिर्फ न देखे गए वीडियो ही दिखाएँ: ",
+ "preferences_notifications_only_label": "सिर्फ सूचनाएँ दिखाएँ (अगर हो तो): ",
+ "Enable web notifications": "वेब सूचनाएँ सक्षम करें",
+ "`x` uploaded a video": "`x` ने वीडियो अपलोड किया",
+ "`x` is live": "`x` लाइव हैं",
+ "preferences_category_data": "डेटा की प्राथमिकताएँ",
+ "Clear watch history": "देखने का इतिहास साफ़ करें",
+ "Import/export data": "डेटा को आयात/निर्यात करें",
+ "Change password": "पासवर्ड बदलें",
+ "Manage subscriptions": "सदस्यताएँ प्रबंधित करें",
+ "Manage tokens": "टोकन प्रबंधित करें",
+ "Watch history": "देखने का इतिहास",
+ "Delete account": "खाता हटाएँ",
+ "preferences_category_admin": "प्रबंधक प्राथमिकताएँ",
+ "preferences_feed_menu_label": "फ़ीड मेन्यू: ",
+ "preferences_show_nick_label": "ऊपर उपनाम दिखाएँ: ",
+ "Top enabled: ": "ऊपर का हिस्सा सक्षम है: ",
+ "CAPTCHA enabled: ": "CAPTCHA सक्षम है: ",
+ "Login enabled: ": "लॉग-इन सक्षम है: ",
+ "Registration enabled: ": "पंजीकरण सक्षम है: ",
+ "Report statistics: ": "सांख्यिकी रिपोर्ट करें: ",
+ "Released under the AGPLv3 on Github.": "GitHub पर AGPLv3 के अंतर्गत प्रकाशित।",
+ "Save preferences": "प्राथमिकताएँ सहेजें",
+ "Token manager": "टोकन प्रबंधन",
+ "Token": "टोकन",
+ "tokens_count": "{{count}} टोकन",
+ "tokens_count_plural": "{{count}} टोकन",
+ "Import/export": "आयात/निर्यात करें",
+ "unsubscribe": "सदस्यता छोड़ें",
+ "revoke": "हटाएँ",
+ "Subscriptions": "सदस्यताएँ",
+ "subscriptions_unseen_notifs_count": "{{count}} अपठित सूचना",
+ "subscriptions_unseen_notifs_count_plural": "{{count}} अपठित सूचना",
+ "search": "खोजें",
+ "Log out": "लॉग-आउट करें",
+ "Source available here.": "स्रोत यहाँ उपलब्ध है।",
+ "View JavaScript license information.": "जावास्क्रिप्ट लाइसेंस की जानकारी देखें।",
+ "View privacy policy.": "निजता नीति देखें।",
+ "Trending": "रुझान में",
+ "Public": "सार्वजनिक",
+ "Unlisted": "सबके लिए उपलब्ध नहीं",
+ "Private": "निजी",
+ "View all playlists": "सभी प्लेलिस्ट देखें",
+ "Create playlist": "प्लेलिस्ट बनाएँ",
+ "Updated `x` ago": "`x` पहले अपडेट किया गया",
+ "Delete playlist `x`?": "प्लेलिस्ट `x` हटाएँ?",
+ "Delete playlist": "प्लेलिस्ट हटाएँ",
+ "Title": "शीर्षक",
+ "Playlist privacy": "प्लेलिस्ट की निजता",
+ "Editing playlist `x`": "प्लेलिस्ट `x` को संपादित किया जा रहा है",
+ "Show more": "अधिक देखें",
+ "Show less": "कम देखें",
+ "Watch on YouTube": "YouTube पर देखें",
+ "Switch Invidious Instance": "Invidious उदाहरण बदलें",
+ "search_message_no_results": "कोई परिणाम नहीं मिला।",
+ "search_message_change_filters_or_query": "अपने खोज क्वेरी को और चौड़ा करें और/या फ़िल्टर बदलें।",
+ "search_message_use_another_instance": " आप <a href=\"`x`\">दूसरे उदाहरण पर भी खोज सकते हैं</a>।",
+ "Hide annotations": "टिप्पणियाँ छिपाएँ",
+ "Show annotations": "टिप्पणियाँ दिखाएँ",
+ "Genre: ": "श्रेणी: ",
+ "Family friendly? ": "परिवार के लिए ठीक है? ",
+ "Engagement: ": "सगाई: ",
+ "Whitelisted regions: ": "स्वीकृत क्षेत्र: ",
+ "Blacklisted regions: ": "अस्वीकृत क्षेत्र: ",
+ "Shared `x`": "`x` बाँटा गया",
+ "Premieres in `x`": "`x` बाद प्रीमियर होगा",
+ "Premieres `x`": "`x` को प्रीमिर होगा",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "नमस्कार! ऐसा लगता है कि आपका जावास्क्रिप्ट अक्षम है। टिप्पणियाँ देखने के लिए यहाँ क्लिक करें, लेकिन याद रखें कि इन्हें लोड होने में थोड़ा ज़्यादा समय लग सकता है।",
+ "View YouTube comments": "YouTube टिप्पणियाँ देखें",
+ "View more comments on Reddit": "Reddit पर अधिक टिप्पणियाँ देखें",
+ "View `x` comments": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "`x` टिप्पणी देखें",
+ "": "`x` टिप्पणियाँ देखें"
+ },
+ "View Reddit comments": "Reddit पर टिप्पणियाँ",
+ "Hide replies": "जवाब छिपाएँ",
+ "Show replies": "जवाब दिखाएँ",
+ "Incorrect password": "गलत पासवर्ड",
+ "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "लॉग-इन नहीं किया जा सका, सुनिश्चित करें कि दो-कारक प्रमाणीकरण (Authenticator या SMS) सक्षम है।",
+ "Invalid TFA code": "अमान्य TFA कोड",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "लॉग-इन नाकाम रहा। ऐसा इसलिए हो सकता है कि दो-कारक प्रमाणीकरण आपके खाते पर सक्षम नहीं है।",
+ "Quota exceeded, try again in a few hours": "कोटा पार हो चुका है, कृपया कुछ घंटों में फिर कोशिश करें",
+ "CAPTCHA is a required field": "CAPTCHA एक ज़रूरी फ़ील्ड है",
+ "User ID is a required field": "सदस्य ID एक ज़रूरी फ़ील्ड है",
+ "Password is a required field": "पासवर्ड एक ज़रूरी फ़ील्ड है",
+ "Wrong username or password": "गलत सदस्यनाम या पासवर्ड",
+ "Please sign in using 'Log in with Google'": "कृपया 'Google के साथ लॉग-इन करें' के साथ साइन-इन करें",
+ "Password cannot be empty": "पासवर्ड खाली नहीं हो सकता",
+ "Password cannot be longer than 55 characters": "पासवर्ड में अधिकतम 55 अक्षर हो सकते हैं",
+ "Invidious Private Feed for `x`": "`x` के लिए Invidious निजी फ़ीड",
+ "channel:`x`": "चैनल:`x`",
+ "Deleted or invalid channel": "हटाया गया या अमान्य चैनल",
+ "This channel does not exist.": "यह चैनल मौजूद नहीं है।",
+ "Could not get channel info.": "चैनल की जानकारी प्राप्त न की जा सकी।",
+ "comments_view_x_replies": "{{count}} टिप्पणी देखें",
+ "comments_view_x_replies_plural": "{{count}} टिप्पणियाँ देखें",
+ "`x` ago": "`x` पहले",
+ "Load more": "अधिक लोड करें",
+ "Could not create mix.": "मिक्स न बनाया जा सका।",
+ "Empty playlist": "खाली प्लेलिस्ट",
+ "Not a playlist.": "यह प्लेलिस्ट नहीं है।",
+ "Playlist does not exist.": "प्लेलिस्ट मौजूद नहीं है।",
+ "Could not pull trending pages.": "रुझान के पृष्ठ प्राप्त न किए जा सके।",
+ "Hidden field \"challenge\" is a required field": "छिपाया गया फ़ील्ड \"चुनौती\" एक आवश्यक फ़ील्ड है",
+ "Hidden field \"token\" is a required field": "छिपाया गया फ़ील्ड \"टोकन\" एक आवश्यक फ़ील्ड है",
+ "Erroneous challenge": "त्रुटिपूर्ण चुनौती",
+ "Erroneous token": "त्रुटिपूर्ण टोकन",
+ "No such user": "यह सदस्य मौजूद नहीं हैं",
+ "Token is expired, please try again": "टोकन की समय-सीमा समाप्त हो चुकी है, कृपया दोबारा कोशिश करें",
+ "English": "अंग्रेज़ी",
+ "English (United Kingdom)": "अंग्रेज़ी (यूनाइटेड किंग्डम)",
+ "English (United States)": "अंग्रेज़ी (संयुक्त राष्ट्र)",
+ "English (auto-generated)": "अंग्रेज़ी (अपने-आप जनरेट हुआ)",
+ "Afrikaans": "अफ़्रीकी",
+ "Albanian": "अल्बानियाई",
+ "Amharic": "अम्हेरी",
+ "Arabic": "अरबी",
+ "Armenian": "आर्मेनियाई",
+ "Belarusian": "बेलारूसी",
+ "Azerbaijani": "अज़रबैजानी",
+ "Bangla": "बंगाली",
+ "Basque": "बास्क",
+ "Cantonese (Hong Kong)": "कैंटोनीज़ (हाँग काँग)",
+ "Catalan": "कातालान",
+ "Cebuano": "सेबुआनो",
+ "Chinese": "चीनी",
+ "Chinese (China)": "चीनी (चीन)",
+ "Chinese (Hong Kong)": "चीनी (हाँग काँग)",
+ "Chinese (Simplified)": "चीनी (सरलीकृत)",
+ "Chinese (Taiwan)": "चीनी (ताइवान)",
+ "Corsican": "कोर्सिकन",
+ "Croatian": "क्रोएशियाई",
+ "Czech": "चेक",
+ "Danish": "डेनिश",
+ "Dutch": "डच",
+ "Dutch (auto-generated)": "डच (अपने-आप जनरेट हुआ)",
+ "Esperanto": "एस्पेरांतो",
+ "Estonian": "एस्टोनियाई",
+ "Filipino": "फ़िलिपीनो",
+ "Finnish": "फ़िनिश",
+ "French": "फ़्रेंच",
+ "French (auto-generated)": "फ़्रेंच (अपने-आप जनरेट हुआ)",
+ "Galician": "गैलिशियन",
+ "Georgian": "जॉर्जियाई",
+ "German": "जर्मन",
+ "German (auto-generated)": "जर्मन (अपने-आप जनरेट हुआ)",
+ "Greek": "यूनानी",
+ "Gujarati": "गुजराती",
+ "Haitian Creole": "हैती क्रियोल",
+ "Hausa": "हौसा",
+ "Hawaiian": "हवाई",
+ "Hebrew": "हीब्रू",
+ "Hindi": "हिन्दी",
+ "Hmong": "हमोंग",
+ "Hungarian": "हंगेरी",
+ "Icelandic": "आइसलैंडिक",
+ "Igbo": "इग्बो",
+ "Indonesian": "इंडोनेशियाई",
+ "Indonesian (auto-generated)": "इंडोनेशियाई (अपने-आप जनरेट हुआ)",
+ "Interlingue": "इंटरलिंगुआ",
+ "Irish": "आयरिश",
+ "Italian": "इतालवी",
+ "Italian (auto-generated)": "इतालवी (अपने-आप जनरेट हुआ)",
+ "Japanese": "जापानी",
+ "Japanese (auto-generated)": "जापानी (अपने-आप जनरेट हुआ)",
+ "Javanese": "जावानीज़",
+ "Kannada": "कन्नड़",
+ "Kazakh": "कज़ाख़",
+ "Khmer": "खमेर",
+ "Korean": "कोरियाई",
+ "Korean (auto-generated)": "कोरियाई (अपने-आप जनरेट हुआ)",
+ "Kyrgyz": "किर्गीज़",
+ "Lao": "लाओ",
+ "Latin": "लैटिन",
+ "Latvian": "लातवियाई",
+ "Lithuanian": "लिथुएनियाई",
+ "Luxembourgish": "लग्ज़मबर्गी",
+ "Macedonian": "मकादूनियाई",
+ "Malagasy": "मालागासी",
+ "Malay": "मलय",
+ "Malayalam": "मलयालम",
+ "Maltese": "माल्टीज़",
+ "Maori": "माओरी",
+ "Marathi": "मराठी",
+ "Mongolian": "मंगोलियाई",
+ "Nepali": "नेपाली",
+ "Norwegian Bokmål": "नॉर्वेजियाई",
+ "Nyanja": "न्यानजा",
+ "Pashto": "पश्तो",
+ "Persian": "फ़ारसी",
+ "Polish": "पोलिश",
+ "Portuguese": "पुर्तगाली",
+ "Portuguese (auto-generated)": "पुर्तगाली (अपने-आप जनरेट हुआ)",
+ "Portuguese (Brazil)": "पुर्तगाली (ब्राज़ील)",
+ "Romanian": "रोमेनियाई",
+ "Russian": "रूसी",
+ "Russian (auto-generated)": "रूसी (अपने-आप जनरेट हुआ)",
+ "Samoan": "सामोन",
+ "Scottish Gaelic": "स्कॉटिश गाएलिक",
+ "Serbian": "सर्बियाई",
+ "Shona": "शोणा",
+ "Sindhi": "सिंधी",
+ "Slovenian": "स्लोवेनियाई",
+ "Somali": "सोमाली",
+ "Southern Sotho": "दक्षिणी सोथो",
+ "Spanish": "स्पेनी",
+ "Spanish (auto-generated)": "स्पेनी (अपने-आप जनरेट हुआ)",
+ "Spanish (Latin America)": "स्पेनी (लातिन अमेरिकी)",
+ "Spanish (Mexico)": "स्पेनी (मेक्सिको)",
+ "Spanish (Spain)": "स्पेनी (स्पेन)",
+ "Sundanese": "सुंडानी",
+ "Swahili": "स्वाहिली",
+ "Swedish": "स्वीडिश",
+ "Tajik": "ताजीक",
+ "Tamil": "तमिल",
+ "Telugu": "तेलुगु",
+ "Thai": "थाई",
+ "Turkish": "तुर्की",
+ "Turkish (auto-generated)": "तुर्की (अपने-आप जनरेट हुआ)",
+ "Ukrainian": "यूक्रेनी",
+ "Urdu": "उर्दू",
+ "Uzbek": "उज़्बेक",
+ "Vietnamese": "वियतनामी",
+ "Vietnamese (auto-generated)": "वियतनामी (अपने-आप जनरेट हुआ)",
+ "Welsh": "Welsh",
+ "Western Frisian": "पश्चिमी फ़्रिसियाई",
+ "Xhosa": "खोसा",
+ "Yiddish": "यहूदी",
+ "generic_count_years": "{{count}} वर्ष",
+ "generic_count_years_plural": "{{count}} वर्ष",
+ "Yoruba": "योरुबा",
+ "generic_count_months": "{{count}} महीने",
+ "generic_count_months_plural": "{{count}} महीने",
+ "Zulu": "ज़ूलू",
+ "generic_count_weeks": "{{count}} हफ़्ते",
+ "generic_count_weeks_plural": "{{count}} हफ़्ते",
+ "Fallback comments: ": "फ़ॉलबैक टिप्पणियाँ: ",
+ "Popular": "प्रसिद्ध",
+ "Search": "खोजें",
+ "Top": "ऊपर",
+ "About": "जानकारी",
+ "Rating: ": "रेटिंग: ",
+ "preferences_locale_label": "भाषा: ",
+ "View as playlist": "प्लेलिस्ट के रूप में देखें",
+ "Default": "डिफ़ॉल्ट",
+ "Download": "डाउनलोड करें",
+ "Download as: ": "इस रूप में डाउनलोड करें: ",
+ "%A %B %-d, %Y": "%A %B %-d, %Y",
+ "Music": "संगीत",
+ "Gaming": "गेमिंग",
+ "News": "समाचार",
+ "Movies": "फ़िल्में",
+ "(edited)": "(संपादित)",
+ "YouTube comment permalink": "YouTube पर टिप्पणी की स्थायी कड़ी",
+ "permalink": "स्थायी कड़ी",
+ "Videos": "वीडियो",
+ "`x` marked it with a ❤": "`x` ने इसे एक ❤ से चिह्नित किया",
+ "Audio mode": "ऑडियो मोड",
+ "Playlists": "प्लेलिस्ट्स",
+ "Video mode": "वीडियो मोड",
+ "Community": "समुदाय",
+ "search_filters_title": "फ़िल्टर",
+ "search_filters_date_label": "अपलोड करने का समय",
+ "search_filters_date_option_none": "कोई भी समय",
+ "search_filters_date_option_week": "इस हफ़्ते",
+ "search_filters_date_option_month": "इस महीने",
+ "search_filters_date_option_hour": "पिछला घंटा",
+ "search_filters_date_option_today": "आज",
+ "search_filters_date_option_year": "इस साल",
+ "search_filters_type_label": "प्रकार",
+ "search_filters_type_option_all": "कोई भी प्रकार",
+ "search_filters_type_option_video": "वीडियो",
+ "search_filters_type_option_channel": "चैनल",
+ "search_filters_sort_option_relevance": "प्रासंगिकता",
+ "search_filters_type_option_playlist": "प्लेलिस्ट",
+ "search_filters_type_option_movie": "फ़िल्म",
+ "search_filters_type_option_show": "शो",
+ "search_filters_duration_label": "अवधि",
+ "search_filters_duration_option_none": "कोई भी अवधि",
+ "search_filters_duration_option_short": "4 मिनट से कम",
+ "search_filters_duration_option_medium": "4 से 20 मिनट तक",
+ "search_filters_duration_option_long": "20 मिनट से ज़्यादा",
+ "search_filters_features_label": "सुविधाएँ",
+ "search_filters_features_option_live": "लाइव",
+ "search_filters_sort_option_rating": "रेटिंग",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "उपशीर्षक/कैप्शन",
+ "search_filters_features_option_c_commons": "क्रिएटिव कॉमन्स",
+ "search_filters_features_option_three_sixty": "360°",
+ "search_filters_features_option_vr180": "VR180",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_hdr": "HDR",
+ "search_filters_features_option_location": "जगह",
+ "search_filters_features_option_purchased": "खरीदा गया",
+ "search_filters_sort_label": "इस क्रम से लगाएँ",
+ "search_filters_sort_option_date": "अपलोड की ताऱीख",
+ "search_filters_sort_option_views": "देखे जाने की संख्या",
+ "search_filters_apply_button": "चयनित फ़िल्टर लागू करें",
+ "footer_documentation": "प्रलेख",
+ "footer_source_code": "स्रोत कोड",
+ "footer_original_source_code": "मूल स्रोत कोड",
+ "footer_modfied_source_code": "बदला गया स्रोत कोड",
+ "Current version: ": "वर्तमान संस्करण: ",
+ "next_steps_error_message": "इसके बाद आपके ये आज़माने चाहिए: ",
+ "next_steps_error_message_refresh": "साफ़ करें",
+ "next_steps_error_message_go_to_youtube": "YouTube पर जाएँ",
+ "footer_donate_page": "दान करें",
+ "adminprefs_modified_source_code_url_label": "बदले गए स्रोत कोड के रिपॉज़िटरी का URL",
+ "none": "कुछ नहीं",
+ "videoinfo_started_streaming_x_ago": "`x` पहले स्ट्रीम करना शुरू किया",
+ "videoinfo_watch_on_youTube": "YouTube पर देखें",
+ "Video unavailable": "वीडियो उपलब्ध नहीं है",
+ "preferences_save_player_pos_label": "यहाँ से चलाना शुरू करें: ",
+ "crash_page_you_found_a_bug": "शायद आपको Invidious में कोई बग नज़र आ गया है!",
+ "videoinfo_youTube_embed_link": "एम्बेड करें",
+ "videoinfo_invidious_embed_link": "एम्बोड करने की कड़ी",
+ "download_subtitles": "उपशीर्षक - `x` (.vtt)",
+ "user_created_playlists": "बनाए गए `x` प्लेलिस्ट्स",
+ "user_saved_playlists": "सहेजे गए `x` प्लेलिस्ट्स",
+ "crash_page_before_reporting": "बग रिपोर्ट करने से पहले:",
+ "crash_page_switch_instance": "<a href=\"`x`\">किसी दूसरे उदाहरण का इस्तेमाल करें</a>",
+ "crash_page_read_the_faq": "<a href=\"`x`\">अक्सर पूछे जाने वाले प्रश्न (FAQ)</a> पढ़ें",
+ "crash_page_refresh": "<a href=\"`x`\">पृष्ठ को एक बार साफ़ करें</a>",
+ "crash_page_search_issue": "<a href=\"`x`\">GitHub पर मौजूदा मुद्दे</a> ढूँढ़ें"
+}
diff --git a/locales/pt-BR.json b/locales/pt-BR.json
index f3a05a1a..2ee65272 100644
--- a/locales/pt-BR.json
+++ b/locales/pt-BR.json
@@ -21,15 +21,15 @@
"No": "Não",
"Import and Export Data": "Importar e Exportar Dados",
"Import": "Importar",
- "Import Invidious data": "Importar dados do Invidious",
- "Import YouTube subscriptions": "Importar inscrições do YouTube",
+ "Import Invidious data": "Importar dados em JSON do Invidious",
+ "Import YouTube subscriptions": "Importar inscrições do YouTube/OPML",
"Import FreeTube subscriptions (.db)": "Importar inscrições do FreeTube (.db)",
"Import NewPipe subscriptions (.json)": "Importar inscrições do NewPipe (.json)",
"Import NewPipe data (.zip)": "Importar dados do NewPipe (.zip)",
"Export": "Exportar",
"Export subscriptions as OPML": "Exportar inscrições como OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "Exportar inscrições como OPML (para NewPipe e FreeTube)",
- "Export data as JSON": "Exportar dados como JSON",
+ "Export data as JSON": "Exportar dados Invidious como JSON",
"Delete account?": "Excluir conta?",
"History": "Histórico",
"An alternative front-end to YouTube": "Uma interface alternativa para o YouTube",
@@ -66,7 +66,7 @@
"preferences_related_videos_label": "Mostrar vídeos relacionados: ",
"preferences_annotations_label": "Sempre mostrar anotações: ",
"preferences_extend_desc_label": "Estenda automaticamente a descrição do vídeo: ",
- "preferences_vr_mode_label": "Vídeos interativos de 360 graus: ",
+ "preferences_vr_mode_label": "Vídeos interativos de 360 graus (requer WebGL): ",
"preferences_category_visual": "Preferências visuais",
"preferences_player_style_label": "Estilo do tocador: ",
"Dark mode: ": "Modo escuro: ",
@@ -410,7 +410,7 @@
"crash_page_read_the_faq": "leu as <a href=\"`x`\">Perguntas Frequentes (FAQ)</a>",
"generic_views_count": "{{count}} visualização",
"generic_views_count_plural": "{{count}} visualizações",
- "preferences_quality_option_dash": "DASH (qualidade adaptiva)",
+ "preferences_quality_option_dash": "DASH (qualidade adaptável)",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_small": "Pequeno",
"preferences_quality_dash_option_auto": "Auto",
@@ -436,5 +436,10 @@
"user_saved_playlists": "`x` listas de reprodução salvas",
"Video unavailable": "Vídeo indisponível",
"videoinfo_started_streaming_x_ago": "Iniciou a transmissão a `x`",
- "search_filters_title": "Filtro"
+ "search_filters_title": "Filtro",
+ "preferences_watch_history_label": "Ative o histórico de exibição: ",
+ "search_message_no_results": "Nenhum resultado encontrado.",
+ "search_message_change_filters_or_query": "Tente ampliar sua consulta de pesquisa e/ou alterar os filtros.",
+ "English (United Kingdom)": "Inglês (Reino Unido)",
+ "English (United States)": "Inglês (Estados Unidos)"
}
diff --git a/locales/ru.json b/locales/ru.json
index a10bb050..0199f61f 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -5,8 +5,8 @@
"Subscribe": "Подписаться",
"View channel on YouTube": "Смотреть канал на YouTube",
"View playlist on YouTube": "Посмотреть плейлист на YouTube",
- "newest": "самые свежие",
- "oldest": "самые старые",
+ "newest": "сначала новые",
+ "oldest": "сначала старые",
"popular": "популярные",
"last": "недавние",
"Next page": "Следующая страница",
@@ -74,8 +74,8 @@
"dark": "темная",
"light": "светлая",
"preferences_thin_mode_label": "Облегчённое оформление: ",
- "preferences_category_misc": "Прочие предпочтения",
- "preferences_automatic_instance_redirect_label": "Автоматическое перенаправление на зеркало сайта (резервный вариант redirect.invidious.io): ",
+ "preferences_category_misc": "Прочие настройки",
+ "preferences_automatic_instance_redirect_label": "Автоматическое перенаправление на зеркало сайта (переход на redirect.invidious.io): ",
"preferences_category_subscription": "Настройки подписок",
"preferences_annotations_subscribed_label": "Всегда показывать аннотации в видео каналов, на которые вы подписаны? ",
"Redirect homepage to feed: ": "Отображать видео с каналов, на которые вы подписаны, как главную страницу: ",
diff --git a/src/invidious/channels/about.cr b/src/invidious/channels/about.cr
index 4f82a0f1..d48fd1fb 100644
--- a/src/invidious/channels/about.cr
+++ b/src/invidious/channels/about.cr
@@ -12,7 +12,8 @@ record AboutChannel,
joined : Time,
is_family_friendly : Bool,
allowed_regions : Array(String),
- tabs : Array(String)
+ tabs : Array(String),
+ verified : Bool
record AboutRelatedChannel,
ucid : String,
@@ -70,6 +71,9 @@ def get_about_info(ucid, locale) : AboutChannel
# if banner.includes? "channels/c4/default_banner"
# banner = nil
# end
+ # author_verified_badges = initdata["header"]?.try &.["c4TabbedHeaderRenderer"]?.try &.["badges"]?
+ author_verified_badge = initdata["header"].dig?("c4TabbedHeaderRenderer", "badges", 0, "metadataBadgeRenderer", "tooltip")
+ author_verified = (author_verified_badge && author_verified_badge == "Verified")
description = initdata["metadata"]["channelMetadataRenderer"]?.try &.["description"]?.try &.as_s? || ""
description_html = HTML.escape(description)
@@ -128,6 +132,7 @@ def get_about_info(ucid, locale) : AboutChannel
is_family_friendly: is_family_friendly,
allowed_regions: allowed_regions,
tabs: tabs,
+ verified: author_verified || false,
)
end
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index 91ea8607..1f8de657 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -146,6 +146,8 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
content_html = node_comment["contentText"]?.try { |t| parse_content(t, id) } || ""
author = node_comment["authorText"]?.try &.["simpleText"]? || ""
+ json.field "verified", (node_comment["authorCommentBadge"]? != nil)
+
json.field "author", author
json.field "authorThumbnails" do
json.array do
@@ -329,7 +331,11 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
end
author_name = HTML.escape(child["author"].as_s)
-
+ if child["verified"]?.try &.as_bool && child["authorIsChannelOwner"]?.try &.as_bool
+ author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark-circle\"></i>"
+ elsif child["verified"]?.try &.as_bool
+ author_name += "&nbsp;<i class=\"icon ion ion-md-checkmark\"></i>"
+ end
html << <<-END_HTML
<div class="pure-g" style="width:100%">
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
diff --git a/src/invidious/helpers/errors.cr b/src/invidious/helpers/errors.cr
index 2eab6263..b80dcdaf 100644
--- a/src/invidious/helpers/errors.cr
+++ b/src/invidious/helpers/errors.cr
@@ -46,7 +46,7 @@ def error_template_helper(env : HTTP::Server::Context, status_code : Int32, exce
TEXT
- issue_template += github_details("Backtrace", HTML.escape(exception.inspect_with_backtrace))
+ issue_template += github_details("Backtrace", exception.inspect_with_backtrace)
# URLs for the error message below
url_faq = "https://github.com/iv-org/documentation/blob/master/docs/faq.md"
diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr
index 982b97d8..3f987b4d 100644
--- a/src/invidious/helpers/i18n.cr
+++ b/src/invidious/helpers/i18n.cr
@@ -14,6 +14,7 @@ LOCALES_LIST = {
"fi" => "Suomi", # Finnish
"fr" => "Français", # French
"he" => "עברית", # Hebrew
+ "hi" => "हिन्दी", # Hindi
"hr" => "Hrvatski", # Croatian
"hu-HU" => "Magyar Nyelv", # Hungarian
"id" => "Bahasa Indonesia", # Indonesian
diff --git a/src/invidious/helpers/serialized_yt_data.cr b/src/invidious/helpers/serialized_yt_data.cr
index bfbc237c..3918bd13 100644
--- a/src/invidious/helpers/serialized_yt_data.cr
+++ b/src/invidious/helpers/serialized_yt_data.cr
@@ -12,6 +12,7 @@ struct SearchVideo
property live_now : Bool
property premium : Bool
property premiere_timestamp : Time?
+ property author_verified : Bool
def to_xml(auto_generated, query_params, xml : XML::Builder)
query_params["v"] = self.id
@@ -129,6 +130,7 @@ struct SearchPlaylist
property video_count : Int32
property videos : Array(SearchPlaylistVideo)
property thumbnail : String?
+ property author_verified : Bool
def to_json(locale : String?, json : JSON::Builder)
json.object do
@@ -141,6 +143,8 @@ struct SearchPlaylist
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
+ json.field "authorVerified", self.author_verified
+
json.field "videoCount", self.video_count
json.field "videos" do
json.array do
@@ -182,6 +186,7 @@ struct SearchChannel
property video_count : Int32
property description_html : String
property auto_generated : Bool
+ property author_verified : Bool
def to_json(locale : String?, json : JSON::Builder)
json.object do
@@ -189,7 +194,7 @@ struct SearchChannel
json.field "author", self.author
json.field "authorId", self.ucid
json.field "authorUrl", "/channel/#{self.ucid}"
-
+ json.field "authorVerified", self.author_verified
json.field "authorThumbnails" do
json.array do
qualities = {32, 48, 76, 100, 176, 512}
diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr
index f7f7b426..b5b58399 100644
--- a/src/invidious/routes/feeds.cr
+++ b/src/invidious/routes/feeds.cr
@@ -182,6 +182,7 @@ module Invidious::Routes::Feeds
paid: false,
premium: false,
premiere_timestamp: nil,
+ author_verified: false, # ¯\_(ツ)_/¯
})
end
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index c007a07b..f65b05bb 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -413,6 +413,10 @@ struct Video
end
end
+ # Livestream chunk infos
+ json.field "targetDurationSec", fmt["targetDurationSec"].as_i if fmt.has_key?("targetDurationSec")
+ json.field "maxDvrDurationSec", fmt["maxDvrDurationSec"].as_i if fmt.has_key?("maxDvrDurationSec")
+
# Audio-related data
json.field "audioQuality", fmt["audioQuality"] if fmt.has_key?("audioQuality")
json.field "audioSampleRate", fmt["audioSampleRate"].as_s.to_i if fmt.has_key?("audioSampleRate")
@@ -609,6 +613,10 @@ struct Video
info["authorThumbnail"]?.try &.as_s || ""
end
+ def author_verified : Bool
+ info["authorVerified"]?.try &.as_bool || false
+ end
+
def sub_count_text : String
info["subCountText"]?.try &.as_s || "-"
end
@@ -860,6 +868,12 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
.try &.dig?("runs", 0)
author = channel_info.try &.dig?("text")
+ author_verified_badge = related["ownerBadges"]?.try do |badges_array|
+ badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
+ end
+
+ author_verified = (author_verified_badge && author_verified_badge.size > 0).to_s
+
ucid = channel_info.try { |ci| HelperExtractors.get_browse_id(ci) }
# "4,088,033 views", only available on compact renderer
@@ -883,6 +897,7 @@ def parse_related_video(related : JSON::Any) : Hash(String, JSON::Any)?
"length_seconds" => JSON::Any.new(length || "0"),
"view_count" => JSON::Any.new(view_count || "0"),
"short_view_count" => JSON::Any.new(short_view_count || "0"),
+ "author_verified" => JSON::Any.new(author_verified),
}
end
@@ -1077,6 +1092,10 @@ def extract_video_info(video_id : String, proxy_region : String? = nil, context_
author_info = video_secondary_renderer.try &.dig?("owner", "videoOwnerRenderer")
author_thumbnail = author_info.try &.dig?("thumbnail", "thumbnails", 0, "url")
+ author_verified_badge = author_info.try &.dig?("badges", 0, "metadataBadgeRenderer", "tooltip")
+ author_verified = (!author_verified_badge.nil? && author_verified_badge == "Verified")
+ params["authorVerified"] = JSON::Any.new(author_verified)
+
params["authorThumbnail"] = JSON::Any.new(author_thumbnail.try &.as_s || "")
params["subCountText"] = JSON::Any.new(author_info.try &.["subscriberCountText"]?
diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr
index 40b553a9..92f81ee4 100644
--- a/src/invidious/views/channel.ecr
+++ b/src/invidious/views/channel.ecr
@@ -20,7 +20,7 @@
<div class="pure-u-2-3">
<div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
- <span><%= author %></span>
+ <span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div>
</div>
<div class="pure-u-1-3">
diff --git a/src/invidious/views/community.ecr b/src/invidious/views/community.ecr
index f0add06b..3bc29e55 100644
--- a/src/invidious/views/community.ecr
+++ b/src/invidious/views/community.ecr
@@ -19,7 +19,7 @@
<div class="pure-u-2-3">
<div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
- <span><%= author %></span>
+ <span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div>
</div>
<div class="pure-u-1-3" style="text-align:right">
diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr
index ce7af783..fb7ad1dc 100644
--- a/src/invidious/views/components/item.ecr
+++ b/src/invidious/views/components/item.ecr
@@ -8,7 +8,7 @@
<img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>"/>
</center>
<% end %>
- <p dir="auto"><%= HTML.escape(item.author) %></p>
+ <p dir="auto"><%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
</a>
<p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p>
<% if !item.auto_generated %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %>
@@ -30,7 +30,7 @@
<p dir="auto"><%= HTML.escape(item.title) %></p>
</a>
<a href="/channel/<%= item.ucid %>">
- <p dir="auto"><b><%= HTML.escape(item.author) %></b></p>
+ <p dir="auto"><b><%= HTML.escape(item.author) %><% if !item.is_a?(InvidiousPlaylist) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b></p>
</a>
<% when MixVideo %>
<a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>">
@@ -142,7 +142,7 @@
<div class="video-card-row flexible">
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
- <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %></p>
+ <p class="channel-name" dir="auto"><%= HTML.escape(item.author) %><% if !item.is_a?(ChannelVideo) && !item.author_verified.nil? && item.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
</a></div>
<% endpoint_params = "?v=#{item.id}" %>
diff --git a/src/invidious/views/playlists.ecr b/src/invidious/views/playlists.ecr
index 12dba088..c8718e7b 100644
--- a/src/invidious/views/playlists.ecr
+++ b/src/invidious/views/playlists.ecr
@@ -19,7 +19,7 @@
<div class="pure-u-2-3">
<div class="channel-profile">
<img src="/ggpht<%= URI.parse(channel.author_thumbnail).request_target %>">
- <span><%= author %></span>
+ <span><%= author %></span><% if !channel.verified.nil? && channel.verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %>
</div>
</div>
<div class="pure-u-1-3" style="text-align:right">
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index 2e493f4c..8b6eb903 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -207,7 +207,7 @@ we're going to need to do it here in order to allow for translations.
<% if !video.author_thumbnail.empty? %>
<img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>">
<% end %>
- <span id="channel-name"><%= author %></span>
+ <span id="channel-name"><%= author %><% if !video.author_verified.nil? && video.author_verified %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></span>
</div>
</a>
@@ -281,9 +281,9 @@ we're going to need to do it here in order to allow for translations.
<h5 class="pure-g">
<div class="pure-u-14-24">
<% if rv["ucid"]? %>
- <b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %></a></b>
+ <b style="width:100%"><a href="/channel/<%= rv["ucid"] %>"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></a></b>
<% else %>
- <b style="width:100%"><%= rv["author"]? %></b>
+ <b style="width:100%"><%= rv["author"]? %><% if rv["author_verified"]? == "true" %>&nbsp;<i class="icon ion ion-md-checkmark-circle"></i><% end %></b>
<% end %>
</div>
diff --git a/src/invidious/yt_backend/extractors.cr b/src/invidious/yt_backend/extractors.cr
index f6229a9b..a2ec7d59 100644
--- a/src/invidious/yt_backend/extractors.cr
+++ b/src/invidious/yt_backend/extractors.cr
@@ -102,7 +102,11 @@ private module Parsers
premium = false
premiere_timestamp = item_contents.dig?("upcomingEventData", "startTime").try { |t| Time.unix(t.as_s.to_i64) }
+ author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
+ badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
+ end
+ author_verified = (author_verified_badge && author_verified_badge.size > 0)
item_contents["badges"]?.try &.as_a.each do |badge|
b = badge["metadataBadgeRenderer"]
case b["label"].as_s
@@ -129,6 +133,7 @@ private module Parsers
live_now: live_now,
premium: premium,
premiere_timestamp: premiere_timestamp,
+ author_verified: author_verified || false,
})
end
@@ -156,7 +161,11 @@ private module Parsers
private def self.parse(item_contents, author_fallback)
author = extract_text(item_contents["title"]) || author_fallback.name
author_id = item_contents["channelId"]?.try &.as_s || author_fallback.id
+ author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
+ badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
+ end
+ author_verified = (author_verified_badge && author_verified_badge.size > 0)
author_thumbnail = HelperExtractors.get_thumbnails(item_contents)
# When public subscriber count is disabled, the subscriberCountText isn't sent by InnerTube.
# Always simpleText
@@ -179,6 +188,7 @@ private module Parsers
video_count: video_count,
description_html: description_html,
auto_generated: auto_generated,
+ author_verified: author_verified || false,
})
end
@@ -206,18 +216,23 @@ private module Parsers
private def self.parse(item_contents, author_fallback)
title = extract_text(item_contents["title"]) || ""
plid = item_contents["playlistId"]?.try &.as_s || ""
+ author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
+ badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
+ end
+ author_verified = (author_verified_badge && author_verified_badge.size > 0)
video_count = HelperExtractors.get_video_count(item_contents)
playlist_thumbnail = HelperExtractors.get_thumbnails(item_contents)
SearchPlaylist.new({
- title: title,
- id: plid,
- author: author_fallback.name,
- ucid: author_fallback.id,
- video_count: video_count,
- videos: [] of SearchPlaylistVideo,
- thumbnail: playlist_thumbnail,
+ title: title,
+ id: plid,
+ author: author_fallback.name,
+ ucid: author_fallback.id,
+ video_count: video_count,
+ videos: [] of SearchPlaylistVideo,
+ thumbnail: playlist_thumbnail,
+ author_verified: author_verified || false,
})
end
@@ -251,7 +266,11 @@ private module Parsers
author_info = item_contents.dig?("shortBylineText", "runs", 0)
author = author_info.try &.["text"].as_s || author_fallback.name
author_id = author_info.try { |x| HelperExtractors.get_browse_id(x) } || author_fallback.id
+ author_verified_badge = item_contents["ownerBadges"]?.try do |badges_array|
+ badges_array.as_a.find(&.dig("metadataBadgeRenderer", "tooltip").as_s.== "Verified")
+ end
+ author_verified = (author_verified_badge && author_verified_badge.size > 0)
videos = item_contents["videos"]?.try &.as_a.map do |v|
v = v["childVideoRenderer"]
v_title = v.dig?("title", "simpleText").try &.as_s || ""
@@ -267,13 +286,14 @@ private module Parsers
# TODO: item_contents["publishedTimeText"]?
SearchPlaylist.new({
- title: title,
- id: plid,
- author: author,
- ucid: author_id,
- video_count: video_count,
- videos: videos,
- thumbnail: playlist_thumbnail,
+ title: title,
+ id: plid,
+ author: author,
+ ucid: author_id,
+ video_count: video_count,
+ videos: videos,
+ thumbnail: playlist_thumbnail,
+ author_verified: author_verified || false,
})
end