summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--README.md3
-rw-r--r--assets/css/default.css6
-rw-r--r--locales/ar.json4
-rw-r--r--locales/ca.json386
-rw-r--r--locales/cs.json4
-rw-r--r--locales/de.json9
-rw-r--r--locales/el.json4
-rw-r--r--locales/en-US.json4
-rw-r--r--locales/es.json111
-rw-r--r--locales/fa.json21
-rw-r--r--locales/hi.json9
-rw-r--r--locales/hr.json10
-rw-r--r--locales/ja.json30
-rw-r--r--locales/pl.json4
-rw-r--r--locales/pt-BR.json7
-rw-r--r--locales/pt-PT.json9
-rw-r--r--locales/pt.json4
-rw-r--r--locales/sq.json6
-rw-r--r--locales/tr.json6
-rw-r--r--locales/uk.json4
-rw-r--r--locales/zh-CN.json4
-rw-r--r--locales/zh-TW.json4
-rw-r--r--src/invidious/channels/community.cr50
-rw-r--r--src/invidious/comments.cr38
-rw-r--r--src/invidious/jsonify/api_v1/video_json.cr15
-rw-r--r--src/invidious/routes/account.cr2
-rw-r--r--src/invidious/routes/api/v1/authenticated.cr59
-rw-r--r--src/invidious/routes/video_playback.cr8
-rw-r--r--src/invidious/routing.cr5
-rw-r--r--src/invidious/videos.cr7
-rw-r--r--src/invidious/videos/music.cr3
-rw-r--r--src/invidious/videos/parser.cr10
-rw-r--r--src/invidious/views/watch.ecr6
34 files changed, 730 insertions, 124 deletions
diff --git a/.gitignore b/.gitignore
index 1779a73d..7a26e1a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-/doc/
+/docs/
/dev/
/lib/
/bin/
diff --git a/README.md b/README.md
index 0744ac50..602ad2e2 100644
--- a/README.md
+++ b/README.md
@@ -149,12 +149,13 @@ Weblate also allows you to log-in with major SSO providers like Github, Gitlab,
- [CloudTube](https://sr.ht/~cadence/tube/): A JavaScript-rich alternate YouTube player.
- [PeerTubeify](https://gitlab.com/Cha_de_L/peertubeify): On YouTube, displays a link to the same video on PeerTube, if it exists.
- [MusicPiped](https://github.com/deep-gaurav/MusicPiped): A material design music player that streams music from YouTube.
-- [HoloPlay](https://github.com/stephane-r/HoloPlay): Funny Android application connecting on Invidious API's with search, playlists and favorites.
+- [HoloPlay](https://github.com/stephane-r/holoplay-wa): Progressive Web App connecting on Invidious API's with search, playlists and favorites.
- [WatchTube](https://github.com/WatchTubeTeam/WatchTube): Powerful YouTube client for Apple Watch.
- [Yattee](https://github.com/yattee/yattee): Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV.
- [TubiTui](https://codeberg.org/777/TubiTui): A lightweight, libre, TUI-based YouTube client.
- [Ytfzf](https://github.com/pystardust/ytfzf): A posix script to find and watch youtube videos from the terminal. (Without API)
- [Playlet](https://github.com/iBicha/playlet): Unofficial Youtube client for Roku TV
+- [Clipious](https://github.com/lamarios/clipious): Unofficial Invidious client for Android
## Liability
diff --git a/assets/css/default.css b/assets/css/default.css
index 3deaebe1..f8b1c9f7 100644
--- a/assets/css/default.css
+++ b/assets/css/default.css
@@ -515,7 +515,7 @@ hr {
#descexpansionbutton ~ div {
overflow: hidden;
- height: 8.3em;
+ max-height: 8.3em;
}
#descexpansionbutton:checked ~ div {
@@ -583,3 +583,7 @@ p,
/* Wider settings name to less word wrap */
.pure-form-aligned .pure-control-group label { width: 19em; }
+
+.channel-emoji {
+ margin: 0 2px;
+}
diff --git a/locales/ar.json b/locales/ar.json
index 181ff933..3ce34c2d 100644
--- a/locales/ar.json
+++ b/locales/ar.json
@@ -543,5 +543,7 @@
"channel_tab_channels_label": "القنوات",
"Music in this video": "الموسيقى في هذا الفيديو",
"Album: ": "الألبوم: ",
- "Artist: ": "الفنان: "
+ "Artist: ": "الفنان: ",
+ "Song: ": "أغنية: ",
+ "Channel Sponsor": "راعي القناة"
}
diff --git a/locales/ca.json b/locales/ca.json
index 2ba6ae39..54a0b177 100644
--- a/locales/ca.json
+++ b/locales/ca.json
@@ -75,7 +75,7 @@
"Title": "Títol",
"Belarusian": "Bielorús",
"Enable web notifications": "Activa notificacions web",
- "search": "busca",
+ "search": "cerca",
"Catalan": "Català",
"Croatian": "Croat",
"preferences_category_admin": "Preferències d'administrador",
@@ -99,5 +99,387 @@
"Music": "Música",
"search_filters_sort_option_relevance": "Rellevància",
"search_filters_date_option_hour": "Última hora",
- "search_filters_date_option_today": "Avui"
+ "search_filters_date_option_today": "Avui",
+ "preferences_volume_label": "Volum del reproductor: ",
+ "invidious": "Invidious",
+ "preferences_quality_dash_option_144p": "144p",
+ "Turkish (auto-generated)": "Turc (generat automàticament)",
+ "Urdu": "Urdú",
+ "Vietnamese (auto-generated)": "Vietnamita (generat automàticament)",
+ "Welsh": "Gal·lès",
+ "Yoruba": "Ioruba",
+ "YouTube comment permalink": "Enllaç permanent de comentari de YouTube",
+ "Channel Sponsor": "Patrocinador del canal",
+ "Audio mode": "Mode d'àudio",
+ "search_filters_date_option_none": "Qualsevol data",
+ "search_filters_type_option_playlist": "Llista de reproducció",
+ "search_filters_type_option_movie": "Pel·lícula",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_subtitles": "Subtítols/CC",
+ "search_filters_features_option_live": "Directe",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_hdr": "HDR",
+ "search_filters_features_option_location": "Ubicació",
+ "search_filters_apply_button": "Aplica els filtres seleccionats",
+ "videoinfo_started_streaming_x_ago": "Ha començat el directe fa `x`",
+ "next_steps_error_message_go_to_youtube": "Anar a YouTube",
+ "footer_donate_page": "Donar",
+ "footer_original_source_code": "Codi font original",
+ "videoinfo_watch_on_youTube": "Veure a YouTube",
+ "user_saved_playlists": "`x` llistes de reproducció guardades",
+ "adminprefs_modified_source_code_url_label": "URL al repositori de codi font modificat",
+ "none": "cap",
+ "footer_modfied_source_code": "Codi font modificat",
+ "videoinfo_invidious_embed_link": "Incrusta l'enllaç",
+ "download_subtitles": "Subtítols - `x` (.vtt)",
+ "user_created_playlists": "`x`llistes de reproducció creades",
+ "Video unavailable": "Vídeo no disponible",
+ "channel_tab_channels_label": "Canals",
+ "channel_tab_playlists_label": "Llistes de reproducció",
+ "channel_tab_community_label": "Comunitat",
+ "Invalid TFA code": "Codi TFA no vàlid",
+ "Czech": "Txec",
+ "Default": "Per defecte",
+ "Amharic": "Amàric",
+ "preferences_automatic_instance_redirect_label": "Redirecció automàtica d'instàncies (retorna a redirect.invidious.io): ",
+ "Login enabled: ": "Activa inici de sessió: ",
+ "Registration enabled: ": "Activa registre: ",
+ "Whitelisted regions: ": "Regions a la llista blanca: ",
+ "Chinese (Simplified)": "Xinès (Simplificat)",
+ "Corsican": "Cors",
+ "Estonian": "Estonià",
+ "Japanese (auto-generated)": "Japonès (generat automàticament)",
+ "English (United States)": "Anglès (Estats Units)",
+ "English (auto-generated)": "Anglès (generat automàticament)",
+ "Cebuano": "Cebuà",
+ "Esperanto": "Esperanto",
+ "Scottish Gaelic": "Gaèlic escocès",
+ "Playlists": "Llistes de reproducció",
+ "search_filters_title": "Filtres",
+ "search_filters_type_option_all": "Qualsevol tipus",
+ "search_filters_duration_option_none": "Qualsevol duració",
+ "next_steps_error_message": "Després d'això, hauríeu d'intentar: ",
+ "next_steps_error_message_refresh": "Recarregar la pàgina",
+ "crash_page_refresh": "ha intentat <a href=\"`x`\">actualitzar la pàgina</a>",
+ "crash_page_report_issue": "Si cap de les anteriors no ha ajudat, <a href=\"`x`\">obre un nou issue a GitHub</a> (preferiblement en anglès) i inclou el text següent al missatge (NO tradueixis aquest text):",
+ "generic_subscriptions_count": "{{count}} subscripció",
+ "generic_subscriptions_count_plural": "{{count}} subscripcions",
+ "error_video_not_in_playlist": "El vídeo sol·licitat no existeix en aquesta llista de reproducció. <a href=\"`x`\">Fes clic aquí per a la pàgina d'inici de la llista de reproducció.</a>",
+ "comments_points_count": "{{count}} punt",
+ "comments_points_count_plural": "{{count}} punts",
+ "%A %B %-d, %Y": "%A %B %-d, %Y",
+ "Create playlist": "Crear llista de reproducció",
+ "Text CAPTCHA": "Text CAPTCHA",
+ "Next page": "Pàgina següent",
+ "preferences_category_visual": "Preferències visuals",
+ "preferences_unseen_only_label": "Mostra només no vistos: ",
+ "preferences_listen_label": "Escolta per defecte: ",
+ "Import": "Importar",
+ "Token": "Senyal",
+ "Wilson score: ": "Puntuació de Wilson: ",
+ "search_filters_date_label": "Data de càrrega",
+ "search_filters_features_option_three_sixty": "360°",
+ "source": "font",
+ "preferences_default_home_label": "Pàgina d'inici per defecte: ",
+ "preferences_comments_label": "Comentaris per defecte: ",
+ "`x` uploaded a video": "`x` ha penjat un vídeo",
+ "Released under the AGPLv3 on Github.": "Publicat sota l'AGPLv3 a GitHub.",
+ "Token manager": "Gestor de tokens",
+ "Watch history": "Historial de reproduccions",
+ "Cannot change password for Google accounts": "No es pot canviar la contrasenya dels comptes de Google",
+ "Authorize token?": "Autoritzar senyal?",
+ "Source available here.": "Font disponible aquí.",
+ "Export subscriptions as OPML (for NewPipe & FreeTube)": "Exporta subscripcions com a OPML (per a NewPipe i FreeTube)",
+ "Log in": "Inicia sessió",
+ "search_filters_sort_option_date": "Data de càrrega",
+ "Unlisted": "No llistat",
+ "View privacy policy.": "Veure política de privadesa.",
+ "Public": "Públic",
+ "View all playlists": "Veure totes les llistes de reproducció",
+ "reddit": "Reddit",
+ "Manage tokens": "Gestiona senyals",
+ "Not a playlist.": "No és una llista de reproducció.",
+ "preferences_local_label": "Vídeos de Proxy: ",
+ "View channel on YouTube": "Veure canal a Youtube",
+ "preferences_quality_dash_option_1080p": "1080p",
+ "Top enabled: ": "Activa top: ",
+ "Delete playlist `x`?": "Eliminar llista de reproducció `x`?",
+ "View JavaScript license information.": "Consulta la informació de la llicència de JavaScript.",
+ "Playlist privacy": "Privacitat de la llista de reproducció",
+ "search_message_no_results": "No s'han trobat resultats.",
+ "search_message_use_another_instance": " També es pot <a href=\"`x`\">buscar en una altra instància</a>.",
+ "Genre: ": "Gènere: ",
+ "Hidden field \"challenge\" is a required field": "El camp ocult \"repte\" és un camp obligatori",
+ "Burmese": "Birmà",
+ "View as playlist": "Mostra com a llista de reproducció",
+ "preferences_category_subscription": "Preferències de subscripció",
+ "Music in this video": "Música en aquest vídeo",
+ "Artist: ": "Artista: ",
+ "Album: ": "Àlbum: ",
+ "Shared `x`": "Compartit `x`",
+ "Premieres `x`": "Estrena `x`",
+ "View more comments on Reddit": "Veure més comentaris a Reddit",
+ "View `x` comments": {
+ "([^.,0-9]|^)1([^.,0-9]|$)": "Veure `x` comentari",
+ "": "Veure `x` comentaris"
+ },
+ "View Reddit comments": "Veure comentaris de Reddit",
+ "Incorrect password": "Contrasenya incorrecta",
+ "Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "No es pot iniciar la sessió, assegureu-vos que l'autenticació de dos factors (Autenticador o SMS) estigui activada.",
+ "Erroneous CAPTCHA": "CAPTCHA erroni",
+ "CAPTCHA is a required field": "El CAPTCHA és un camp obligatori",
+ "Korean (auto-generated)": "Coreà (generat automàticament)",
+ "Kyrgyz": "Kirguís",
+ "Latin": "Llatí",
+ "Malagasy": "Malgaix",
+ "Maori": "Maori",
+ "Marathi": "Marathi",
+ "Norwegian Bokmål": "Bokmål Noruec",
+ "Nyanja": "Nyanja",
+ "Portuguese (Brazil)": "Portuguès (Brazil)",
+ "Punjabi": "Panjabi",
+ "Russian (auto-generated)": "Rus (generat automàticament)",
+ "Samoan": "Samoà",
+ "Somali": "Somali",
+ "Southern Sotho": "Sesotho",
+ "Spanish (Mexico)": "Espanyol (Mèxic)",
+ "Spanish (Spain)": "Espanyol (Espanya)",
+ "Sundanese": "Sondanès",
+ "Swahili": "Suahili",
+ "Tamil": "Tàmil",
+ "Telugu": "Telugu",
+ "Zulu": "Zulu",
+ "generic_count_months": "{{count}} mes",
+ "generic_count_months_plural": "{{count}} mesos",
+ "generic_count_weeks": "{{count}} setmana",
+ "generic_count_weeks_plural": "{{count}} setmanes",
+ "About": "Sobre",
+ "`x` marked it with a ❤": "`x`marca'l amb un ❤",
+ "Video mode": "Mode de vídeo",
+ "search_filters_features_label": "Característiques",
+ "search_filters_features_option_c_commons": "Creative Commons",
+ "search_filters_features_option_vr180": "VR180",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_purchased": "Comprat",
+ "Chinese (Hong Kong)": "Xinès (Hong Kong)",
+ "Chinese (Taiwan)": "Xinès (Taiwan)",
+ "Hmong": "Hmong",
+ "Kazakh": "Kazakh",
+ "Igbo": "Igbo",
+ "Javanese": "Javanès",
+ "Indonesian (auto-generated)": "Indonesi (generat automàticament)",
+ "Interlingue": "Interlingüe",
+ "Khmer": "Khmer",
+ "This channel does not exist.": "Aquest canal no existeix.",
+ "Song: ": "Cançó: ",
+ "Login failed. This may be because two-factor authentication is not turned on for your account.": "Error a l'iniciar sessió. Això pot ser perquè l'autenticació de dos factors no està activada per al vostre compte.",
+ "channel:`x`": "canal: `x`",
+ "Deleted or invalid channel": "Canal suprimit o no vàlid",
+ "Could not get channel info.": "No s'ha pogut obtenir la informació del canal.",
+ "Could not pull trending pages.": "No s'han pogut extreure les pàgines de tendència.",
+ "comments_view_x_replies": "Veure {{count}} resposta",
+ "comments_view_x_replies_plural": "Veure {{count}} respostes",
+ "Subscriptions": "Subscripcions",
+ "generic_count_seconds": "{{count}} segon",
+ "generic_count_seconds_plural": "{{count}} segons",
+ "channel_tab_shorts_label": "Vídeos curts",
+ "preferences_save_player_pos_label": "Desa la posició de reproducció: ",
+ "crash_page_before_reporting": "Abans d'informar d'un error, assegureu-vos que teniu:",
+ "crash_page_switch_instance": "ha intentat <a href=\"`x`\">utilitzar una altra instància</a>",
+ "crash_page_read_the_faq": "heu llegit les <a href=\"`x`\">Preguntes més freqüents (FAQ)</a>",
+ "crash_page_search_issue": "ha cercat <a href=\"`x`\">problemes existents a GitHub</a>",
+ "User ID is a required field": "L'identificador d'usuari és un camp obligatori",
+ "Password is a required field": "La contrasenya és un camp obligatori",
+ "Wrong username or password": "Nom d'usuari o contrasenya incorrectes",
+ "Please sign in using 'Log in with Google'": "Si us plau, inicieu la sessió amb 'Inicieu sessió amb Google'",
+ "Password cannot be longer than 55 characters": "La contrasenya no pot tenir més de 55 caràcters",
+ "Invidious Private Feed for `x`": "Feed privat Invidious per a `x`",
+ "generic_views_count": "{{count}} visualització",
+ "generic_views_count_plural": "{{count}} visualitzacions",
+ "generic_videos_count": "{{count}} vídeo",
+ "generic_videos_count_plural": "{{count}} vídeos",
+ "Token is expired, please try again": "La senyal ha caducat, torna-ho a provar",
+ "English": "Anglès",
+ "Kannada": "Kanarès",
+ "Erroneous token": "Senyal errònia",
+ "`x` ago": "fa `x`",
+ "Empty playlist": "Llista de reproducció buida",
+ "Playlist does not exist.": "La llista de reproducció no existeix.",
+ "No such user": "No hi ha tal usuari",
+ "Afrikaans": "Afrikàans",
+ "Azerbaijani": "Azerbaidjana",
+ "Cantonese (Hong Kong)": "Cantonès (Hong Kong)",
+ "Chinese": "Xinès",
+ "Chinese (China)": "Xinès (Xina)",
+ "Chinese (Traditional)": "Xinès (Tradicional)",
+ "Dutch": "Holandès",
+ "Dutch (auto-generated)": "Holandès (generat automàticament)",
+ "French (auto-generated)": "Francès (generat automàticament)",
+ "Georgian": "Georgià",
+ "German (auto-generated)": "Alemany (generat automàticament)",
+ "Gujarati": "Gujarati",
+ "Hawaiian": "Hawaià",
+ "generic_count_years": "{{count}} any",
+ "generic_count_years_plural": "{{count}} anys",
+ "Popular": "Popular",
+ "Rating: ": "Valoració: ",
+ "permalink": "enllaç permanent",
+ "preferences_quality_dash_option_worst": "Pitjor",
+ "Yiddish": "Ídix",
+ "preferences_quality_dash_option_auto": "Automàtic",
+ "Western Frisian": "Frisó occidental",
+ "Swedish": "Suec",
+ "Only show latest unwatched video from channel: ": "Mostra només l'últim vídeo no vist del canal: ",
+ "preferences_continue_label": "Reprodueix el següent per defecte: ",
+ "Import YouTube subscriptions": "Importar subscripcions de YouTube",
+ "search_filters_sort_option_rating": "Valoració",
+ "preferences_thin_mode_label": "Mode prim: ",
+ "preferences_quality_option_small": "Petit",
+ "CAPTCHA enabled: ": "activa CAPTCHA: ",
+ "Import and Export Data": "Importar i exportar dades",
+ "preferences_quality_dash_option_360p": "360p",
+ "Popular enabled: ": "Activa popular: ",
+ "Password": "Contrasenya",
+ "Blacklisted regions: ": "Regions a la llista negra: ",
+ "Register": "Registra't",
+ "Shared `x` ago": "Compartit fa `x`",
+ "search_filters_sort_option_views": "Recompte de visualitzacions",
+ "Import Invidious data": "Importa dades JSON d'Invidious",
+ "preferences_related_videos_label": "Mostra vídeos relacionats: ",
+ "preferences_show_nick_label": "Mostra l'àlies a la part superior: ",
+ "Time (h:mm:ss):": "Temps (h:mm:ss):",
+ "Could not fetch comments": "No s'han pogut obtenir els comentaris",
+ "New password": "Nova contrasenya",
+ "preferences_notifications_only_label": "Mostra només notificacions (si n'hi ha): ",
+ "preferences_annotations_label": "Mostra anotacions per defecte: ",
+ "Import FreeTube subscriptions (.db)": "Importar subscripcions de FreeTube (.db)",
+ "Fallback captions: ": "Subtítols alternatius: ",
+ "Log out": "Tancar sessió",
+ "preferences_quality_dash_option_2160p": "2160p",
+ "Unsubscribe": "Cancel·la la subscripció",
+ "Log in/register": "Inicia sessió/registra't",
+ "Nepali": "Nepalí",
+ "Xhosa": "Xosa",
+ "preferences_captions_label": "Subtítols per defecte: ",
+ "preferences_autoplay_label": "Reproducció automàtica: ",
+ "`x` is live": "`x` està en directe",
+ "Uzbek": "Uzbek",
+ "Hausa": "Haussa",
+ "Bosnian": "Bosnià",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Hola! Sembla que tens JavaScript desactivat. Feu clic aquí per veure els comentaris, tingueu en compte que poden trigar una mica més a carregar-se.",
+ "Password cannot be empty": "La contrasenya no pot estar buida",
+ "preferences_video_loop_label": "Sempre en bucle: ",
+ "preferences_quality_option_dash": "DASH (qualitat adaptativa)",
+ "Change password": "Canvia la contrasenya",
+ "Export data as JSON": "Exporta dades d'Invidious com a JSON",
+ "Wrong answer": "Resposta incorrecta",
+ "Clear watch history": "Neteja l'historial de reproduccions",
+ "Mongolian": "Mongol",
+ "preferences_quality_dash_option_best": "Millor",
+ "Authorize token for `x`?": "Autoritzar senyal per a `x`?",
+ "Report statistics: ": "Estadístiques de l'informe: ",
+ "Switch Invidious Instance": "Canvia la instància d'Invidious",
+ "History": "Historial",
+ "Portuguese (auto-generated)": "Portuguès (generat automàticament)",
+ "footer_source_code": "Codi font",
+ "videoinfo_youTube_embed_link": "Insereix",
+ "generic_count_minutes": "{{count}} minut",
+ "generic_count_minutes_plural": "{{count}} minuts",
+ "preferences_category_player": "Preferències del reproductor",
+ "Sign In": "Inicia Sessió",
+ "preferences_continue_autoplay_label": "Reprodueix automàticament el següent vídeo: ",
+ "generic_playlists_count": "{{count}} llista de reproducció",
+ "generic_playlists_count_plural": "{{count}} llistes de reproducció",
+ "Delete account?": "Esborrar compte?",
+ "Please log in": "Si us plau inicieu sessió",
+ "Import NewPipe data (.zip)": "Importar dades de NewPipe (.zip)",
+ "Image CAPTCHA": "Imatge CAPTCHA",
+ "channel_tab_streams_label": "Transmissions en directe",
+ "preferences_category_misc": "Preferències diverses",
+ "preferences_annotations_subscribed_label": "Mostra les anotacions per defecte dels canals subscrits? ",
+ "Tajik": "Tadjik",
+ "preferences_player_style_label": "Estil del reproductor: ",
+ "Load more": "Carrega més",
+ "preferences_vr_mode_label": "Vídeos interactius de 360 graus (requereix WebGL): ",
+ "Manage subscriptions": "Gestionar les subscripcions",
+ "preferences_quality_option_medium": "Mitjà",
+ "Editing playlist `x`": "Editant la llista de reproducció `x`",
+ "search_filters_duration_option_medium": "Mitjà (4 - 20 minuts)",
+ "E-mail": "Correu electrònic",
+ "Spanish (auto-generated)": "Castellà (generat automàticament)",
+ "Export": "Exportar",
+ "preferences_quality_dash_option_4320p": "4320p",
+ "JavaScript license information": "Informació de la llicència de JavaScript",
+ "Hidden field \"token\" is a required field": "El camp ocult \"senyal\" és un camp obligatori",
+ "Shona": "Xona",
+ "Family friendly? ": "Apte per a tots els públics? ",
+ "preferences_quality_dash_label": "Qualitat de vídeo DASH preferida: ",
+ "Hindi": "Hindi",
+ "An alternative front-end to YouTube": "Una interfície alternativa a YouTube",
+ "Export subscriptions as OPML": "Exporta subscripcions com a OPML",
+ "Watch on YouTube": "Veure a YouTube",
+ "Lao": "Laosià",
+ "search_message_change_filters_or_query": "Proveu d'ampliar la vostra consulta de cerca i/o canviar els filtres.",
+ "View YouTube comments": "Veure comentaris de YouTube",
+ "New passwords must match": "Les contrasenyes noves han de coincidir",
+ "Subscription manager": "Gestor de subscripcions",
+ "Premieres in `x`": "Estrena en `x`",
+ "youtube": "YouTube",
+ "Latvian": "Letó",
+ "LIVE": "EN VIU",
+ "Could not create mix.": "No s'ha pogut crear la barreja.",
+ "preferences_speed_label": "Velocitat per defecte: ",
+ "preferences_extend_desc_label": "Amplieu automàticament la descripció del vídeo: ",
+ "popular": "popular",
+ "Erroneous challenge": "Repte erroni",
+ "last": "darrer",
+ "preferences_quality_dash_option_240p": "240p",
+ "preferences_quality_dash_option_720p": "720p",
+ "preferences_quality_dash_option_480p": "480p",
+ "Log in with Google": "Inicia sessió amb Google",
+ "preferences_quality_dash_option_1440p": "1440p",
+ "Previous page": "Pàgina anterior",
+ "Only show latest video from channel: ": "Mostra només l'últim vídeo del canal: ",
+ "unsubscribe": "cancel·la la subscripció",
+ "View playlist on YouTube": "Veure llista de reproducció a YouTube",
+ "Import NewPipe subscriptions (.json)": "Importar subscripcions de NewPipe (.json)",
+ "crash_page_you_found_a_bug": "Sembla que has trobat un error a Invidious!",
+ "Subscribe": "Subscriu-me",
+ "Quota exceeded, try again in a few hours": "S'ha superat la quota, torna-ho a provar d'aquí a unes hores",
+ "generic_count_days": "{{count}} dia",
+ "generic_count_days_plural": "{{count}} dies",
+ "Trending": "Tendència",
+ "Updated `x` ago": "Actualitzat fa `x`",
+ "Haitian Creole": "Crioll Haitià",
+ "preferences_watch_history_label": "Habilita historial de reproduccions: ",
+ "generic_count_hours": "{{count}} hora",
+ "generic_count_hours_plural": "{{count}} hores",
+ "Malayalam": "Maialàiam",
+ "Clear watch history?": "Neteja historial de reproduccions?",
+ "Import/export data": "Importa/exporta dades",
+ "Sinhala": "Singalès",
+ "Delete playlist": "Eliminar llista de reproducció",
+ "Bangla": "Bengalí",
+ "Italian (auto-generated)": "Italià (generat automàticament)",
+ "License: ": "Llicència: ",
+ "(edited)": "(editat)",
+ "Pashto": "Paixtu",
+ "preferences_dark_mode_label": "Tema: ",
+ "revoke": "revocar",
+ "English (United Kingdom)": "Anglès (Regne Unit)",
+ "preferences_quality_option_hd720": "HD720",
+ "tokens_count": "{{count}} senyal",
+ "tokens_count_plural": "{{count}} senyals",
+ "subscriptions_unseen_notifs_count": "{{count}} notificació no vista",
+ "subscriptions_unseen_notifs_count_plural": "{{count}} notificacions no vistes",
+ "generic_subscribers_count": "{{count}} subscriptor",
+ "generic_subscribers_count_plural": "{{count}} subscriptors",
+ "Sindhi": "Sindhi",
+ "Slovenian": "Eslovè",
+ "preferences_feed_menu_label": "Menú del feed: ",
+ "Fallback comments: ": "Comentaris alternatius: ",
+ "Top": "Millors",
+ "preferences_max_results_label": "Nombre de vídeos mostrats al feed: ",
+ "Engagement: ": "Atracció: ",
+ "Redirect homepage to feed: ": "Redirigeix la pàgina d'inici al feed: "
}
diff --git a/locales/cs.json b/locales/cs.json
index 51db1550..4611c4fd 100644
--- a/locales/cs.json
+++ b/locales/cs.json
@@ -495,5 +495,7 @@
"channel_tab_streams_label": "Živé přenosy",
"Music in this video": "Hudba v tomto videu",
"Artist: ": "Umělec: ",
- "Album: ": "Album: "
+ "Album: ": "Album: ",
+ "Channel Sponsor": "Sponzor kanálu",
+ "Song: ": "Skladba: "
}
diff --git a/locales/de.json b/locales/de.json
index 55c40905..c2941d6d 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -472,5 +472,12 @@
"search_filters_duration_option_none": "Beliebige Länge",
"search_filters_date_label": "Upload-Datum",
"search_filters_date_option_none": "Beliebiges Datum",
- "error_video_not_in_playlist": "Das angeforderte Video existiert nicht in dieser Wiedergabeliste. <a href=\"`x`\">Klicken Sie hier, um zur Startseite der Wiedergabeliste zu gelangen.</a>"
+ "error_video_not_in_playlist": "Das angeforderte Video existiert nicht in dieser Wiedergabeliste. <a href=\"`x`\">Klicken Sie hier, um zur Startseite der Wiedergabeliste zu gelangen.</a>",
+ "channel_tab_shorts_label": "Shorts",
+ "channel_tab_streams_label": "Livestreams",
+ "Music in this video": "Musik in diesem Video",
+ "Artist: ": "Künstler: ",
+ "Album: ": "Album: ",
+ "channel_tab_playlists_label": "Wiedergabelisten",
+ "channel_tab_channels_label": "Kanäle"
}
diff --git a/locales/el.json b/locales/el.json
index 3448a4dc..8d0c84dd 100644
--- a/locales/el.json
+++ b/locales/el.json
@@ -366,7 +366,7 @@
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "Μεσαία",
"preferences_quality_option_small": "Μικρό",
- "preferences_quality_option_dash": "DASH (προσαρμοστική ποιότητα)",
+ "preferences_quality_option_dash": "DASH (προσαρμόσιμη ποιότητα)",
"preferences_quality_dash_option_4320p": "4320p",
"preferences_quality_dash_option_720p": "720p",
"invidious": "Invidious",
@@ -450,5 +450,5 @@
"search_filters_type_option_show": "Μπάρα προόδου διαβάσματος",
"preferences_watch_history_label": "Ενεργοποίηση ιστορικού παρακολούθησης: ",
"search_filters_title": "Φίλτρο",
- "search_message_no_results": "Δεν"
+ "search_message_no_results": "Δε βρέθηκαν αποτελέσματα."
}
diff --git a/locales/en-US.json b/locales/en-US.json
index fbcc1341..a3c195ff 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -191,6 +191,7 @@
"Blacklisted regions: ": "Blacklisted regions: ",
"Music in this video": "Music in this video",
"Artist: ": "Artist: ",
+ "Song: ": "Song: ",
"Album: ": "Album: ",
"Shared `x`": "Shared `x`",
"Premieres in `x`": "Premieres in `x`",
@@ -406,6 +407,7 @@
"YouTube comment permalink": "YouTube comment permalink",
"permalink": "permalink",
"`x` marked it with a ❤": "`x` marked it with a ❤",
+ "Channel Sponsor": "Channel Sponsor",
"Audio mode": "Audio mode",
"Video mode": "Video mode",
"Playlists": "Playlists",
@@ -455,7 +457,7 @@
"footer_documentation": "Documentation",
"footer_source_code": "Source code",
"footer_original_source_code": "Original source code",
- "footer_modfied_source_code": "Modified Source code",
+ "footer_modfied_source_code": "Modified source code",
"adminprefs_modified_source_code_url_label": "URL to modified source code repository",
"none": "none",
"videoinfo_started_streaming_x_ago": "Started streaming `x` ago",
diff --git a/locales/es.json b/locales/es.json
index 6cf721f3..bb082c06 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -52,21 +52,21 @@
"preferences_video_loop_label": "Repetir siempre: ",
"preferences_autoplay_label": "Reproducción automática: ",
"preferences_continue_label": "Reproducir siguiente por defecto: ",
- "preferences_continue_autoplay_label": "Reproducir automáticamente el vídeo siguiente: ",
+ "preferences_continue_autoplay_label": "Reproducir automáticamente el video siguiente: ",
"preferences_listen_label": "Activar el sonido por defecto: ",
- "preferences_local_label": "¿Usar un proxy para los vídeos? ",
+ "preferences_local_label": "¿Usar un proxy para los videos? ",
"preferences_speed_label": "Velocidad por defecto: ",
- "preferences_quality_label": "Calidad de vídeo preferida: ",
+ "preferences_quality_label": "Calidad de video preferida: ",
"preferences_volume_label": "Volumen del reproductor: ",
"preferences_comments_label": "Comentarios por defecto: ",
"youtube": "YouTube",
"reddit": "Reddit",
"preferences_captions_label": "Subtítulos por defecto: ",
"Fallback captions: ": "Subtítulos alternativos: ",
- "preferences_related_videos_label": "¿Mostrar vídeos relacionados? ",
+ "preferences_related_videos_label": "¿Mostrar videos relacionados? ",
"preferences_annotations_label": "¿Mostrar anotaciones por defecto? ",
- "preferences_extend_desc_label": "Extender automáticamente la descripción del vídeo: ",
- "preferences_vr_mode_label": "Vídeos interactivos de 360 grados (necesita WebGL): ",
+ "preferences_extend_desc_label": "Extender automáticamente la descripción del video: ",
+ "preferences_vr_mode_label": "Videos interactivos de 360 grados (necesita WebGL): ",
"preferences_category_visual": "Preferencias visuales",
"preferences_player_style_label": "Estilo de reproductor: ",
"Dark mode: ": "Modo oscuro: ",
@@ -79,16 +79,16 @@
"preferences_category_subscription": "Preferencias de la suscripción",
"preferences_annotations_subscribed_label": "¿Mostrar anotaciones por defecto para los canales suscritos? ",
"Redirect homepage to feed: ": "Redirigir la página de inicio a la fuente: ",
- "preferences_max_results_label": "Número de vídeos mostrados en la fuente: ",
- "preferences_sort_label": "Ordenar los vídeos por: ",
+ "preferences_max_results_label": "Número de videos mostrados en la fuente: ",
+ "preferences_sort_label": "Ordenar los videos por: ",
"published": "fecha de publicación",
"published - reverse": "fecha de publicación: orden inverso",
"alphabetically": "alfabéticamente",
"alphabetically - reverse": "alfabéticamente: orden inverso",
"channel name": "nombre del canal",
"channel name - reverse": "nombre del canal: orden inverso",
- "Only show latest video from channel: ": "Mostrar solo el último vídeo del canal: ",
- "Only show latest unwatched video from channel: ": "Mostrar solo el último vídeo sin ver del canal: ",
+ "Only show latest video from channel: ": "Mostrar solo el último video del canal: ",
+ "Only show latest unwatched video from channel: ": "Mostrar solo el último video sin ver del canal: ",
"preferences_unseen_only_label": "Mostrar solo los no vistos: ",
"preferences_notifications_only_label": "Mostrar solo notificaciones (si hay alguna): ",
"Enable web notifications": "Habilitar notificaciones web",
@@ -139,7 +139,7 @@
"Editing playlist `x`": "Editando la lista de reproducción 'x'",
"Show more": "Mostrar más",
"Show less": "Mostrar menos",
- "Watch on YouTube": "Ver el vídeo en YouTube",
+ "Watch on YouTube": "Ver en YouTube",
"Switch Invidious Instance": "Cambiar Instancia de Invidious",
"Hide annotations": "Ocultar anotaciones",
"Show annotations": "Mostrar anotaciones",
@@ -153,7 +153,7 @@
"Shared `x`": "Compartido `x`",
"Premieres in `x`": "Se estrena en `x`",
"Premieres `x`": "Estrenos `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.": "¡Hola! Parece que tiene JavaScript desactivado. Haga clic aquí para ver los comentarios, pero tenga en cuenta que pueden tardar un poco más en cargarse.",
+ "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "¡Hola! Parece que tienes JavaScript desactivado. Haz clic aquí para ver los comentarios, pero tengas en cuenta que pueden tardar un poco más en cargarse.",
"View YouTube comments": "Ver los comentarios de YouTube",
"View more comments on Reddit": "Ver más comentarios en Reddit",
"View `x` comments": {
@@ -164,7 +164,7 @@
"Hide replies": "Ocultar las respuestas",
"Show replies": "Mostrar las respuestas",
"Incorrect password": "Contraseña incorrecta",
- "Quota exceeded, try again in a few hours": "Cuota excedida, pruebe otra vez en unas horas",
+ "Quota exceeded, try again in a few hours": "Cuota excedida, prueba otra vez en unas horas",
"Unable to log in, make sure two-factor authentication (Authenticator or SMS) is turned on.": "No se puede iniciar sesión, asegúrese de que la autentificación de dos factores (autentificador o SMS) esté habilitada.",
"Invalid TFA code": "Código TFA no válido",
"Login failed. This may be because two-factor authentication is not turned on for your account.": "Error de inicio de sesion. Puede deberse a que la autentificación de dos factores no está habilitada en su cuenta.",
@@ -176,7 +176,7 @@
"Wrong username or password": "Nombre o contraseña incorrecto",
"Please sign in using 'Log in with Google'": "Inicie sesión con «Iniciar sesión con Google»",
"Password cannot be empty": "La contraseña no puede estar en blanco",
- "Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres",
+ "Password cannot be longer than 55 characters": "La contraseña no debe tener más de 55 caracteres",
"Please log in": "Inicie sesión, por favor",
"Invidious Private Feed for `x`": "Fuente privada de Invidious para `x`",
"channel:`x`": "canal: `x`",
@@ -198,7 +198,7 @@
"No such user": "Usuario no existe",
"Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo",
"English": "Inglés",
- "English (auto-generated)": "Inglés (generados automáticamente)",
+ "English (auto-generated)": "Inglés (generado automáticamente)",
"Afrikaans": "Afrikáans",
"Albanian": "Albanés",
"Amharic": "Amárico",
@@ -324,50 +324,51 @@
"permalink": "enlace permanente",
"`x` marked it with a ❤": "`x` lo ha marcado con un ❤",
"Audio mode": "Modo de audio",
- "Video mode": "Modo de vídeo",
- "channel_tab_videos_label": "Vídeos",
+ "Video mode": "Modo de video",
+ "channel_tab_videos_label": "Videos",
"Playlists": "Listas de reproducción",
"channel_tab_community_label": "Comunidad",
- "search_filters_sort_option_relevance": "relevancia",
- "search_filters_sort_option_rating": "valoración",
- "search_filters_sort_option_date": "fecha",
- "search_filters_sort_option_views": "visualizaciones",
- "search_filters_type_label": "content_type",
+ "search_filters_sort_option_relevance": "Relevancia",
+ "search_filters_sort_option_rating": "Valoración",
+ "search_filters_sort_option_date": "Fecha de subida",
+ "search_filters_sort_option_views": "Visualizaciones",
+ "search_filters_type_label": "tipo de contenido",
"search_filters_duration_label": "duración",
"search_filters_features_label": "funcionalidades",
"search_filters_sort_label": "ordenar",
- "search_filters_date_option_hour": "hora",
- "search_filters_date_option_today": "hoy",
- "search_filters_date_option_week": "semana",
- "search_filters_date_option_month": "mes",
- "search_filters_date_option_year": "año",
- "search_filters_type_option_video": "vídeo",
- "search_filters_type_option_channel": "canal",
- "search_filters_type_option_playlist": "lista de reproducción",
- "search_filters_type_option_movie": "película",
- "search_filters_type_option_show": "programa",
- "search_filters_features_option_hd": "hd",
- "search_filters_features_option_subtitles": "subtítulos",
- "search_filters_features_option_c_commons": "creative_commons",
- "search_filters_features_option_three_d": "3d",
- "search_filters_features_option_live": "directo",
- "search_filters_features_option_four_k": "4k",
- "search_filters_features_option_location": "ubicación",
- "search_filters_features_option_hdr": "hdr",
+ "search_filters_date_option_hour": "Última hora",
+ "search_filters_date_option_today": "Hoy",
+ "search_filters_date_option_week": "Esta semana",
+ "search_filters_date_option_month": "Este mes",
+ "search_filters_date_option_year": "Este año",
+ "search_filters_type_option_video": "Video",
+ "search_filters_type_option_channel": "Canal",
+ "search_filters_type_option_playlist": "Lista de reproducción",
+ "search_filters_type_option_movie": "Película",
+ "search_filters_type_option_show": "Programa",
+ "search_filters_features_option_hd": "HD",
+ "search_filters_features_option_subtitles": "Subtítulos",
+ "search_filters_features_option_c_commons": "Creative Commons",
+ "search_filters_features_option_three_d": "3D",
+ "search_filters_features_option_live": "En directo",
+ "search_filters_features_option_four_k": "4K",
+ "search_filters_features_option_location": "Ubicación",
+ "search_filters_features_option_hdr": "HDR",
"Current version: ": "Versión actual: ",
"next_steps_error_message": "Después de lo cual debes intentar: ",
"next_steps_error_message_refresh": "Recargar la página",
"next_steps_error_message_go_to_youtube": "Ir a YouTube",
- "search_filters_duration_option_short": "Corto (< 4 minutos)",
- "search_filters_duration_option_long": "Largo (> 20 minutos)",
+ "search_filters_duration_option_short": "Menos de 4 minutos",
+ "search_filters_duration_option_medium": "De 4 a 20 minutos",
+ "search_filters_duration_option_long": "Más de 20 minutos",
"footer_documentation": "Documentación",
"footer_original_source_code": "Código fuente original",
- "adminprefs_modified_source_code_url_label": "URL al repositorio de código fuente modificado",
+ "adminprefs_modified_source_code_url_label": "Enlace al repositorio de código fuente modificado",
"footer_source_code": "Código fuente",
"footer_modfied_source_code": "Código fuente modificado",
"footer_donate_page": "Donar",
"preferences_region_label": "País del contenido: ",
- "preferences_quality_dash_label": "Calidad de vídeo DASH preferida: ",
+ "preferences_quality_dash_label": "Calidad de video DASH preferida: ",
"preferences_quality_option_hd720": "HD720",
"preferences_quality_option_medium": "Intermedia",
"preferences_quality_dash_option_auto": "Automática",
@@ -376,7 +377,7 @@
"download_subtitles": "Subtítulos- `x` (.vtt)",
"user_created_playlists": "`x` listas de reproducción creadas",
"user_saved_playlists": "`x` listas de reproducción guardadas",
- "Video unavailable": "Vídeo no disponible",
+ "Video unavailable": "Video no disponible",
"videoinfo_youTube_embed_link": "Insertar",
"preferences_quality_dash_option_2160p": "2160p",
"preferences_quality_dash_option_4320p": "4320p",
@@ -413,8 +414,9 @@
"generic_count_weeks_plural": "{{count}} semanas",
"generic_playlists_count": "{{count}} lista de reproducción",
"generic_playlists_count_plural": "{{count}} listas de reproducción",
- "generic_videos_count": "{{count}} vídeo",
- "generic_videos_count_plural": "{{count}} vídeos",
+ "generic_videos_count_0": "{{count}} video",
+ "generic_videos_count_1": "{{count}} videos",
+ "generic_videos_count_2": "{{count}} videos",
"generic_count_months": "{{count}} mes",
"generic_count_months_plural": "{{count}} meses",
"comments_points_count": "{{count}} punto",
@@ -433,7 +435,7 @@
"crash_page_search_issue": "buscado <a href=\"`x`\">problemas existentes en GitHub</a>",
"crash_page_you_found_a_bug": "¡Parece que has encontrado un error en Invidious!",
"crash_page_refresh": "probado a <a href=\"`x`\">recargar la página</a>",
- "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye el siguiente texto en tu mensaje (NO traduzcas este texto):",
+ "crash_page_report_issue": "Si nada de lo anterior ha sido de ayuda, por favor, <a href=\"`x`\">abre una nueva incidencia en GitHub</a> (preferiblemente en inglés) e incluye verbatim el siguiente texto en tu mensaje:",
"English (United States)": "Inglés (Estados Unidos)",
"Cantonese (Hong Kong)": "Cantonés (Hong Kong)",
"Dutch (auto-generated)": "Neerlandés (generados automáticamente)",
@@ -461,23 +463,24 @@
"search_message_no_results": "No se han encontrado resultados.",
"search_message_change_filters_or_query": "Pruebe ampliar la consulta de búsqueda y/o a cambiar los filtros.",
"search_filters_title": "Filtros",
- "search_filters_date_label": "Fecha de subida",
+ "search_filters_date_label": "fecha de subida",
"search_filters_date_option_none": "Cualquier fecha",
"search_filters_type_option_all": "Cualquier tipo",
"search_filters_duration_option_none": "Cualquier duración",
"search_filters_features_option_vr180": "VR180",
- "search_filters_apply_button": "Aplicar filtros seleccionados",
+ "search_filters_apply_button": "Aplicar filtros",
"tokens_count": "{{count}} ficha",
"tokens_count_plural": "{{count}} fichas",
"search_message_use_another_instance": " También puede <a href=\"`x`\">buscar en otra instancia</a>.",
- "search_filters_duration_option_medium": "Medio (4 - 20 minutes)",
"Popular enabled: ": "¿Habilitar la sección popular? ",
- "error_video_not_in_playlist": "El vídeo solicitado no existe en esta lista de reproducción. <a href=\"`x`\">Haga clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
+ "error_video_not_in_playlist": "El video que solicitaste no existe en esta lista de reproducción. <a href=\"`x`\">Haz clic aquí para acceder a la página de inicio de la lista de reproducción.</a>",
"channel_tab_streams_label": "Directos",
"channel_tab_channels_label": "Canales",
"channel_tab_shorts_label": "Cortos",
"channel_tab_playlists_label": "Listas de reproducción",
- "Music in this video": "Música en este vídeo",
+ "Music in this video": "Música en este video",
"Artist: ": "Artista: ",
- "Album: ": "Álbum: "
+ "Album: ": "Álbum: ",
+ "Song: ": "Canción: ",
+ "Channel Sponsor": "Patrocinador del canal"
}
diff --git a/locales/fa.json b/locales/fa.json
index fe72a1e8..56685f64 100644
--- a/locales/fa.json
+++ b/locales/fa.json
@@ -26,15 +26,15 @@
"No": "خیر",
"Import and Export Data": "درون‌برد و برون‌برد داده",
"Import": "درون‌برد",
- "Import Invidious data": "درون‌برد داده اینویدیوس",
- "Import YouTube subscriptions": "درون‌برد اشتراک‌های یوتیوب",
+ "Import Invidious data": "وارد کردن داده JSON اینویدیوس",
+ "Import YouTube subscriptions": "وارد کردن اشتراک OPML/ یوتیوب",
"Import FreeTube subscriptions (.db)": "درون‌برد اشتراک‌های فری‌تیوب (.db)",
"Import NewPipe subscriptions (.json)": "درون‌برد اشتراک‌های نیوپایپ (.json)",
"Import NewPipe data (.zip)": "درون‌برد داده نیوپایپ (.zip)",
"Export": "برون‌برد",
"Export subscriptions as OPML": "برون‌برد اشتراک‌ها در قالب OPML",
"Export subscriptions as OPML (for NewPipe & FreeTube)": "برون‌برد اشتراک‌ها در قالب OPML (برای نیوپایپ و فری‌تیوب)",
- "Export data as JSON": "برون‌برد داده در قالب JSON",
+ "Export data as JSON": "گرفتن(خارج کردن) اطلاعات اینویدیوس با فرمت JSON",
"Delete account?": "حذف حساب کاربری؟",
"History": "تاریخچه",
"An alternative front-end to YouTube": "یک پیشانه جایگزین برای یوتیوب",
@@ -71,7 +71,7 @@
"preferences_related_videos_label": "نمایش ویدیو های مرتبط: ",
"preferences_annotations_label": "نمایش حاشیه نویسی ها به طور پیشفرض: ",
"preferences_extend_desc_label": "گسترش خودکار توضیحات ویدئو: ",
- "preferences_vr_mode_label": "ویدئوها ۳۶۰ درجه تعاملی: ",
+ "preferences_vr_mode_label": "ویدئوها ۳۶۰ درجه تعاملی(نیازمند WebGL): ",
"preferences_category_visual": "ترجیحات بصری",
"preferences_player_style_label": "حالت پخش کننده: ",
"Dark mode: ": "حالت تاریک: ",
@@ -80,7 +80,7 @@
"light": "روشن",
"preferences_thin_mode_label": "حالت نازک: ",
"preferences_category_misc": "ترجیحات متفرقه",
- "preferences_automatic_instance_redirect_label": "هدایت خودکار نمونه (به طور پیش‌فرض به redirect.invidious.io): ",
+ "preferences_automatic_instance_redirect_label": "هدایت خودکار نمونه (انتقال به redirect.invidious.io): ",
"preferences_category_subscription": "ترجیحات اشتراک",
"preferences_annotations_subscribed_label": "نمایش حاشیه نویسی ها به طور پیشفرض برای کانال های مشترک شده: ",
"Redirect homepage to feed: ": "تغییر مسیر صفحه خانه به خوراک: ",
@@ -157,7 +157,7 @@
"Engagement: ": "نامزدی: ",
"Whitelisted regions: ": "مناطق لیست سفید: ",
"Blacklisted regions: ": "مناطق لیست سیاه: ",
- "Shared `x`": "به اشتراک گذاشته شده `x`",
+ "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.": "سلام! مثل اینکه تو جاوا اسکریپت رو خاموش کرده ای. اینجا کلیک کن تا نظرات را ببینی، این رو یادت باشه که ممکنه بارگذاری اونها کمی طول بکشه.",
@@ -375,7 +375,7 @@
"next_steps_error_message_refresh": "تازه‌سازی",
"next_steps_error_message_go_to_youtube": "رفتن به یوتیوب",
"preferences_quality_option_hd720": "HD720",
- "preferences_quality_option_dash": "DASH (کیفیت قابل تطبیق)",
+ "preferences_quality_option_dash": "DASH (کیفیت تطبیفی)",
"preferences_quality_option_medium": "میانه",
"preferences_quality_option_small": "پایین",
"preferences_quality_dash_option_auto": "خودکار",
@@ -445,5 +445,10 @@
"Spanish (Spain)": "اسپانیایی (اسپانیا)",
"Turkish (auto-generated)": "ترکی (تولید خودکار)",
"search_filters_features_option_vr180": "VR180",
- "Spanish (Mexico)": "اسپانیایی (مکزیک)"
+ "Spanish (Mexico)": "اسپانیایی (مکزیک)",
+ "Popular enabled: ": "محبوب ها فعال شد: ",
+ "Music in this video": "آهنگ در این ویدیو",
+ "Artist: ": "هنرمند: ",
+ "Album: ": "آلبوم: ",
+ "Song: ": "آهنگ: "
}
diff --git a/locales/hi.json b/locales/hi.json
index 54e0fe84..41335266 100644
--- a/locales/hi.json
+++ b/locales/hi.json
@@ -472,5 +472,12 @@
"crash_page_refresh": "<a href=\"`x`\">पृष्ठ को एक बार साफ़ करें</a>",
"crash_page_search_issue": "<a href=\"`x`\">GitHub पर मौजूदा मुद्दे</a> ढूँढ़ें",
"Popular enabled: ": "लोकप्रिय सक्षम: ",
- "Artist: ": "कलाकार: "
+ "Artist: ": "कलाकार: ",
+ "Music in this video": "इस वीडियो में संगीत",
+ "Album: ": "एल्बम: ",
+ "error_video_not_in_playlist": "अनुरोधित वीडियो इस प्लेलिस्ट में मौजूद नहीं है। <a href=\"`x`\">प्लेलिस्ट के मुखपृष्ठ पर जाने के लिए यहाँ क्लिक करें।</a>",
+ "channel_tab_shorts_label": "शॉर्ट्स",
+ "channel_tab_streams_label": "लाइवस्ट्रीम्स",
+ "channel_tab_playlists_label": "प्लेलिस्ट्स",
+ "channel_tab_channels_label": "चैनल्स"
}
diff --git a/locales/hr.json b/locales/hr.json
index 72cd6a8e..ade732ad 100644
--- a/locales/hr.json
+++ b/locales/hr.json
@@ -359,13 +359,13 @@
"next_steps_error_message_refresh": "Aktualiziraj stranicu",
"next_steps_error_message_go_to_youtube": "Idi na YouTube",
"footer_donate_page": "Doniraj",
- "adminprefs_modified_source_code_url_label": "URL do repozitorija izmijenjenog izvornog koda",
+ "adminprefs_modified_source_code_url_label": "URL do repozitorija prilagođenog izvornog koda",
"search_filters_duration_option_short": "Kratko (< 4 minute)",
"search_filters_duration_option_long": "Dugo (> 20 minute)",
"footer_source_code": "Izvorni kod",
- "footer_modfied_source_code": "Izmijenjeni izvorni kod",
+ "footer_modfied_source_code": "Prilagođen izvorni kod",
"footer_documentation": "Dokumentacija",
- "footer_original_source_code": "Izvoran izvorni kod",
+ "footer_original_source_code": "Prvobitan izvorni kod",
"preferences_region_label": "Zemlja sadržaja: ",
"preferences_quality_dash_label": "Preferirana DASH videokvaliteta: ",
"preferences_quality_option_dash": "DASH (adaptativna kvaliteta)",
@@ -495,5 +495,7 @@
"channel_tab_shorts_label": "Kratka videa",
"Music in this video": "Glazba u ovom videu",
"Album: ": "Album: ",
- "Artist: ": "Izvođač: "
+ "Artist: ": "Izvođač: ",
+ "Channel Sponsor": "Sponzor kanala",
+ "Song: ": "Pjesma: "
}
diff --git a/locales/ja.json b/locales/ja.json
index 3ad4b494..8a4537d4 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -5,7 +5,7 @@
"generic_subscribers_count_0": "{{count}} 人の登録者",
"generic_subscriptions_count_0": "{{count}} 個の登録チャンネル",
"LIVE": "ライブ",
- "Shared `x` ago": "`x`前に共有",
+ "Shared `x` ago": "`x`前に公開",
"Unsubscribe": "登録解除",
"Subscribe": "登録",
"View channel on YouTube": "YouTube でチャンネルを見る",
@@ -56,17 +56,17 @@
"preferences_category_player": "プレイヤーの設定",
"preferences_video_loop_label": "常にループ: ",
"preferences_autoplay_label": "自動再生: ",
- "preferences_continue_label": "デフォルトで次を再生: ",
+ "preferences_continue_label": "次の動画を再生: ",
"preferences_continue_autoplay_label": "次の動画を自動再生: ",
"preferences_listen_label": "デフォルトで音声モードを使用: ",
- "preferences_local_label": "動画をプロキシーに通す: ",
- "preferences_speed_label": "デフォルトの再生速度: ",
+ "preferences_local_label": "動画視聴にプロキシーを経由: ",
+ "preferences_speed_label": "標準の再生速度: ",
"preferences_quality_label": "優先する画質: ",
"preferences_volume_label": "プレイヤーの音量: ",
"preferences_comments_label": "デフォルトのコメント: ",
"youtube": "YouTube",
"reddit": "Reddit",
- "preferences_captions_label": "デフォルトの字幕: ",
+ "preferences_captions_label": "優先する字幕: ",
"Fallback captions: ": "フォールバック時の字幕: ",
"preferences_related_videos_label": "関連動画を表示: ",
"preferences_annotations_label": "デフォルトでアノテーションを表示: ",
@@ -108,7 +108,7 @@
"Watch history": "再生履歴",
"Delete account": "アカウントを削除",
"preferences_category_admin": "管理者設定",
- "preferences_default_home_label": "デフォルトのホーム: ",
+ "preferences_default_home_label": "ホームに表示するページ: ",
"preferences_feed_menu_label": "フィードメニュー: ",
"preferences_show_nick_label": "ニックネームを一番上に表示する: ",
"Top enabled: ": "トップページを有効化: ",
@@ -157,7 +157,7 @@
"Engagement: ": "エンゲージメント: ",
"Whitelisted regions: ": "ホワイトリストの地域: ",
"Blacklisted regions: ": "ブラックリストの地域: ",
- "Shared `x`": "`x`に共有",
+ "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.": "やあ!君は JavaScript を無効にしているのかな?ここをクリックしてコメントを見れるけど、読み込みには少し時間がかかることがあるのを覚えておいてね。",
@@ -191,9 +191,9 @@
"This channel does not exist.": "このチャンネルは存在しません。",
"Could not get channel info.": "チャンネル情報を取得できませんでした。",
"Could not fetch comments": "コメントを取得できませんでした",
- "comments_view_x_replies_0": "{{count}} 件の返信を見る",
+ "comments_view_x_replies_0": "{{count}}件の返信を表示",
"`x` ago": "`x`前",
- "Load more": "もっと読み込む",
+ "Load more": "もっと見る",
"comments_points_count_0": "{{count}}点",
"Could not create mix.": "ミックスを作成できませんでした。",
"Empty playlist": "空の再生リスト",
@@ -366,7 +366,7 @@
"search_filters_features_option_subtitles": "字幕",
"search_filters_features_option_c_commons": "クリエイティブ・コモンズ",
"search_filters_features_option_three_d": "3D",
- "search_filters_features_option_live": "生配信",
+ "search_filters_features_option_live": "ライブ",
"search_filters_features_option_four_k": "4K",
"search_filters_features_option_location": "場所",
"search_filters_features_option_hdr": "HDR",
@@ -377,8 +377,8 @@
"search_filters_duration_option_short": "4 分未満",
"footer_documentation": "文書",
"footer_source_code": "ソースコード",
- "footer_original_source_code": "ソースコード (元)",
- "footer_modfied_source_code": "ソースコード (改変)",
+ "footer_original_source_code": "元のソースコード",
+ "footer_modfied_source_code": "改変して使用",
"adminprefs_modified_source_code_url_label": "改変されたソースコードのレポジトリのURL",
"search_filters_duration_option_long": "20 分以上",
"preferences_region_label": "地域: ",
@@ -445,7 +445,7 @@
"search_message_use_another_instance": " <a href=\"`x`\">別のインスタンス上でも検索</a>できます。",
"search_filters_apply_button": "選択したフィルターを適用",
"user_saved_playlists": "`x` 個の保存した再生リスト",
- "crash_page_you_found_a_bug": "Invidious でバグを見つけたようです。",
+ "crash_page_you_found_a_bug": "Invidious のバグのようです!",
"crash_page_refresh": "<a href=\"`x`\">ページを更新</a>を試す",
"preferences_watch_history_label": "再生履歴を有効化 ",
"search_filters_date_option_none": "すべて",
@@ -463,5 +463,7 @@
"channel_tab_channels_label": "チャンネル",
"Music in this video": "この動画の音楽",
"Artist: ": "アーティスト: ",
- "Album: ": "アルバム: "
+ "Album: ": "アルバム: ",
+ "Song: ": "曲: ",
+ "Channel Sponsor": "チャンネルのスポンサー"
}
diff --git a/locales/pl.json b/locales/pl.json
index 2dd3ed87..3ca78e43 100644
--- a/locales/pl.json
+++ b/locales/pl.json
@@ -495,5 +495,7 @@
"channel_tab_shorts_label": "Shorts",
"Music in this video": "Muzyka w tym filmie",
"Artist: ": "Wykonawca: ",
- "Album: ": "Album: "
+ "Album: ": "Album: ",
+ "Song: ": "Piosenka: ",
+ "Channel Sponsor": "Sponsor kanału"
}
diff --git a/locales/pt-BR.json b/locales/pt-BR.json
index afd31ede..ec00d46e 100644
--- a/locales/pt-BR.json
+++ b/locales/pt-BR.json
@@ -381,7 +381,7 @@
"footer_documentation": "Documentação",
"footer_source_code": "Código fonte",
"footer_original_source_code": "Código fonte original",
- "footer_modfied_source_code": "Código Fonte Modificado",
+ "footer_modfied_source_code": "Código-fonte modificado",
"preferences_quality_dash_label": "Qualidade de vídeo do painel preferida: ",
"preferences_region_label": "País do conteúdo: ",
"preferences_quality_dash_option_4320p": "4320p",
@@ -476,5 +476,8 @@
"channel_tab_channels_label": "Canais",
"channel_tab_playlists_label": "Listas de reprodução",
"channel_tab_shorts_label": "Curtos",
- "channel_tab_streams_label": "Ao Vivo"
+ "channel_tab_streams_label": "Ao Vivo",
+ "Music in this video": "Música neste vídeo",
+ "Artist: ": "Artista: ",
+ "Album: ": "Álbum: "
}
diff --git a/locales/pt-PT.json b/locales/pt-PT.json
index 1788deb1..43834d70 100644
--- a/locales/pt-PT.json
+++ b/locales/pt-PT.json
@@ -472,5 +472,12 @@
"search_message_change_filters_or_query": "Tente alargar os termos genéricos da pesquisa e/ou alterar os filtros.",
"crash_page_refresh": "tentou <a href=\"`x`\">recarregar a página</a>",
"crash_page_switch_instance": "tentou <a href=\"`x`\">usar outra instância</a>",
- "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>"
+ "error_video_not_in_playlist": "O vídeo pedido não existe nesta lista de reprodução. <a href=\"`x`\">Clique aqui para a página inicial da lista de reprodução.</a>",
+ "Artist: ": "Artista: ",
+ "Album: ": "Álbum: ",
+ "channel_tab_streams_label": "Diretos",
+ "channel_tab_playlists_label": "Listas de reprodução",
+ "channel_tab_channels_label": "Canais",
+ "Music in this video": "Música neste vídeo",
+ "channel_tab_shorts_label": "Curtos"
}
diff --git a/locales/pt.json b/locales/pt.json
index b6b6c110..310381ae 100644
--- a/locales/pt.json
+++ b/locales/pt.json
@@ -479,5 +479,7 @@
"channel_tab_streams_label": "Diretos",
"Music in this video": "Música neste vídeo",
"Artist: ": "Artista: ",
- "Album: ": "Álbum: "
+ "Album: ": "Álbum: ",
+ "Song: ": "Canção: ",
+ "Channel Sponsor": "Patrocinador do canal"
}
diff --git a/locales/sq.json b/locales/sq.json
index 15025750..7f29a035 100644
--- a/locales/sq.json
+++ b/locales/sq.json
@@ -286,7 +286,7 @@
"search_filters_type_option_show": "Shfaqe",
"search_filters_duration_option_short": "E shkurtër (< 4 minuta)",
"search_filters_features_option_purchased": "Të blera",
- "footer_modfied_source_code": "Kod Burim i ndryshuar",
+ "footer_modfied_source_code": "Kod burim i ndryshuar",
"adminprefs_modified_source_code_url_label": "URL e depos së ndryshuar të kodit burim",
"none": "asnjë",
"videoinfo_started_streaming_x_ago": "Filloi transmetimin `x` më parë",
@@ -468,5 +468,7 @@
"Artist: ": "Artist: ",
"Album: ": "Album: ",
"channel_tab_channels_label": "Kanale",
- "Music in this video": "Muzikë në këtë video"
+ "Music in this video": "Muzikë në këtë video",
+ "channel_tab_shorts_label": "Të shkurtra",
+ "channel_tab_streams_label": "Transmetime të drejtpërdrejta"
}
diff --git a/locales/tr.json b/locales/tr.json
index d98e2038..6e0bc175 100644
--- a/locales/tr.json
+++ b/locales/tr.json
@@ -363,7 +363,7 @@
"footer_documentation": "Belgelendirme",
"footer_source_code": "Kaynak Kodları",
"footer_original_source_code": "Orijinal Kaynak Kodları",
- "footer_modfied_source_code": "Değiştirilmiş Kaynak Kodları",
+ "footer_modfied_source_code": "Değiştirilmiş kaynak kodları",
"adminprefs_modified_source_code_url_label": "Değiştirilmiş Kaynak Kodları Deposunun URL'si",
"footer_donate_page": "Bağış Yap",
"preferences_region_label": "İçerik Ülkesi: ",
@@ -479,5 +479,7 @@
"channel_tab_playlists_label": "Oynatma Listeleri",
"Album: ": "Albüm: ",
"Music in this video": "Bu videodaki müzik",
- "Artist: ": "Sanatçı: "
+ "Artist: ": "Sanatçı: ",
+ "Channel Sponsor": "Kanal Sponsoru",
+ "Song: ": "Şarkı: "
}
diff --git a/locales/uk.json b/locales/uk.json
index b44d237f..4d748e7f 100644
--- a/locales/uk.json
+++ b/locales/uk.json
@@ -495,5 +495,7 @@
"channel_tab_channels_label": "Канали",
"Music in this video": "Музика в цьому відео",
"Artist: ": "Виконавець: ",
- "Album: ": "Альбом: "
+ "Album: ": "Альбом: ",
+ "Song: ": "Пісня: ",
+ "Channel Sponsor": "Спонсор каналу"
}
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index aff6dd3e..f202cf88 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -463,5 +463,7 @@
"channel_tab_streams_label": "直播",
"Album: ": "专辑: ",
"channel_tab_shorts_label": "短视频",
- "channel_tab_channels_label": "频道"
+ "channel_tab_channels_label": "频道",
+ "Song: ": "歌曲: ",
+ "Channel Sponsor": "频道赞助者"
}
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index 8aa9869a..54090d3d 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -463,5 +463,7 @@
"channel_tab_streams_label": "直播",
"Artist: ": "藝術家: ",
"Album: ": "專輯: ",
- "Music in this video": "此影片中的音樂"
+ "Music in this video": "此影片中的音樂",
+ "Channel Sponsor": "頻道贊助者",
+ "Song: ": "歌曲: "
}
diff --git a/src/invidious/channels/community.cr b/src/invidious/channels/community.cr
index 13af2d8b..ce34ff82 100644
--- a/src/invidious/channels/community.cr
+++ b/src/invidious/channels/community.cr
@@ -1,3 +1,5 @@
+private IMAGE_QUALITIES = {320, 560, 640, 1280, 2000}
+
# TODO: Add "sort_by"
def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
response = YT_POOL.client &.get("/channel/#{ucid}/community?gl=US&hl=en")
@@ -108,6 +110,8 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
like_count = post["actionButtons"]["commentActionButtonsRenderer"]["likeButton"]["toggleButtonRenderer"]["accessibilityData"]["accessibilityData"]["label"]
.try &.as_s.gsub(/\D/, "").to_i? || 0
+ reply_count = short_text_to_number(post.dig?("actionButtons", "commentActionButtonsRenderer", "replyButton", "buttonRenderer", "text", "simpleText").try &.as_s || "0")
+
json.field "content", html_to_content(content_html)
json.field "contentHtml", content_html
@@ -115,6 +119,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
json.field "likeCount", like_count
+ json.field "replyCount", reply_count
json.field "commentId", post["postId"]? || post["commentId"]? || ""
json.field "authorIsChannelOwner", post["authorEndpoint"]["browseEndpoint"]["browseId"] == ucid
@@ -174,9 +179,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
aspect_ratio = (width.to_f / height.to_f)
url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
- qualities = {320, 560, 640, 1280, 2000}
-
- qualities.each do |quality|
+ IMAGE_QUALITIES.each do |quality|
json.object do
json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
json.field "width", quality
@@ -185,10 +188,39 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
end
end
end
- # TODO
- # when .has_key?("pollRenderer")
- # attachment = attachment["pollRenderer"]
- # json.field "type", "poll"
+ when .has_key?("pollRenderer")
+ attachment = attachment["pollRenderer"]
+ json.field "type", "poll"
+ json.field "totalVotes", short_text_to_number(attachment["totalVotes"]["simpleText"].as_s.split(" ")[0])
+ json.field "choices" do
+ json.array do
+ attachment["choices"].as_a.each do |choice|
+ json.object do
+ json.field "text", choice.dig("text", "runs", 0, "text").as_s
+ # A choice can have an image associated with it.
+ # Ex post: https://www.youtube.com/post/UgkxD4XavXUD4NQiddJXXdohbwOwcVqrH9Re
+ if choice["image"]?
+ thumbnail = choice["image"]["thumbnails"][0].as_h
+ width = thumbnail["width"].as_i
+ height = thumbnail["height"].as_i
+ aspect_ratio = (width.to_f / height.to_f)
+ url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
+ json.field "image" do
+ json.array do
+ IMAGE_QUALITIES.each do |quality|
+ json.object do
+ json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
+ json.field "width", quality
+ json.field "height", (quality / aspect_ratio).ceil.to_i
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ end
when .has_key?("postMultiImageRenderer")
attachment = attachment["postMultiImageRenderer"]
json.field "type", "multiImage"
@@ -202,9 +234,7 @@ def fetch_channel_community(ucid, continuation, locale, format, thin_mode)
aspect_ratio = (width.to_f / height.to_f)
url = thumbnail["url"].as_s.gsub(/=w\d+-h\d+(-p)?(-nd)?(-df)?(-rwa)?/, "=s640")
- qualities = {320, 560, 640, 1280, 2000}
-
- qualities.each do |quality|
+ IMAGE_QUALITIES.each do |quality|
json.object do
json.field "url", url.gsub(/=s\d+/, "=s#{quality}")
json.field "width", quality
diff --git a/src/invidious/comments.cr b/src/invidious/comments.cr
index 357a461c..b15d63d4 100644
--- a/src/invidious/comments.cr
+++ b/src/invidious/comments.cr
@@ -182,7 +182,11 @@ def fetch_youtube_comments(id, cursor, format, locale, thin_mode, region, sort_b
json.field "contentHtml", content_html
json.field "isPinned", (node_comment["pinnedCommentBadge"]? != nil)
-
+ json.field "isSponsor", (node_comment["sponsorCommentBadge"]? != nil)
+ if node_comment["sponsorCommentBadge"]?
+ # Sponsor icon thumbnails always have one object and there's only ever the url property in it
+ json.field "sponsorIconUrl", node_comment.dig("sponsorCommentBadge", "sponsorCommentBadgeRenderer", "customBadge", "thumbnails", 0, "url").to_s
+ end
json.field "published", published.to_unix
json.field "publishedText", translate(locale, "`x` ago", recode_date(published, locale))
@@ -324,11 +328,21 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
end
author_name = HTML.escape(child["author"].as_s)
+ sponsor_icon = ""
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
+
+ if child["isSponsor"]?.try &.as_bool
+ sponsor_icon = String.build do |str|
+ str << %(<img alt="" )
+ str << %(src="/ggpht) << URI.parse(child["sponsorIconUrl"].as_s).request_target << "\" "
+ str << %(title=") << translate(locale, "Channel Sponsor") << "\" "
+ str << %(width="16" height="16" />)
+ end
+ end
html << <<-END_HTML
<div class="pure-g" style="width:100%">
<div class="channel-profile pure-u-4-24 pure-u-md-2-24">
@@ -339,6 +353,7 @@ def template_youtube_comments(comments, locale, thin_mode, is_replies = false)
<b>
<a class="#{child["authorIsChannelOwner"] == true ? "channel-owner" : ""}" href="#{child["authorUrl"]}">#{author_name}</a>
</b>
+ #{sponsor_icon}
<p style="white-space:pre-wrap">#{child["contentHtml"]}</p>
END_HTML
@@ -675,6 +690,27 @@ def content_to_comment_html(content, video_id : String? = "")
text = "<s>#{text}</s>" if run["strikethrough"]?
text = "<i>#{text}</i>" if run["italics"]?
+ # check for custom emojis
+ if run["emoji"]?
+ if run["emoji"]["isCustomEmoji"]?.try &.as_bool
+ if emojiImage = run.dig?("emoji", "image")
+ emojiAlt = emojiImage.dig?("accessibility", "accessibilityData", "label").try &.as_s || text
+ emojiThumb = emojiImage["thumbnails"][0]
+ text = String.build do |str|
+ str << %(<img alt=") << emojiAlt << "\" "
+ str << %(src="/ggpht) << URI.parse(emojiThumb["url"].as_s).request_target << "\" "
+ str << %(title=") << emojiAlt << "\" "
+ str << %(width=") << emojiThumb["width"] << "\" "
+ str << %(height=") << emojiThumb["height"] << "\" "
+ str << %(class="channel-emoji"/>)
+ end
+ else
+ # Hide deleted channel emoji
+ text = ""
+ end
+ end
+ end
+
text
end
diff --git a/src/invidious/jsonify/api_v1/video_json.cr b/src/invidious/jsonify/api_v1/video_json.cr
index a2b1a35c..fe4b5223 100644
--- a/src/invidious/jsonify/api_v1/video_json.cr
+++ b/src/invidious/jsonify/api_v1/video_json.cr
@@ -197,6 +197,21 @@ module Invidious::JSONify::APIv1
end
end
+ if !video.music.empty?
+ json.field "musicTracks" do
+ json.array do
+ video.music.each do |music|
+ json.object do
+ json.field "song", music.song
+ json.field "artist", music.artist
+ json.field "album", music.album
+ json.field "license", music.license
+ end
+ end
+ end
+ end
+ end
+
json.field "recommendedVideos" do
json.array do
video.related_videos.each do |rv|
diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr
index 8f69df94..5aa4452c 100644
--- a/src/invidious/routes/account.cr
+++ b/src/invidious/routes/account.cr
@@ -262,7 +262,7 @@ module Invidious::Routes::Account
end
query["token"] = access_token
- query["username"] = user.email
+ query["username"] = URI.encode_path_segment(user.email)
url.query = query.to_s
env.redirect url.to_s
diff --git a/src/invidious/routes/api/v1/authenticated.cr b/src/invidious/routes/api/v1/authenticated.cr
index 6b935312..ce2ee812 100644
--- a/src/invidious/routes/api/v1/authenticated.cr
+++ b/src/invidious/routes/api/v1/authenticated.cr
@@ -54,6 +54,65 @@ module Invidious::Routes::API::V1::Authenticated
env.response.status_code = 204
end
+ def self.get_history(env)
+ env.response.content_type = "application/json"
+ user = env.get("user").as(User)
+
+ page = env.params.query["page"]?.try &.to_i?.try &.clamp(0, Int32::MAX)
+ page ||= 1
+
+ max_results = env.params.query["max_results"]?.try &.to_i?.try &.clamp(0, MAX_ITEMS_PER_PAGE)
+ max_results ||= user.preferences.max_results
+ max_results ||= CONFIG.default_user_preferences.max_results
+
+ start_index = (page - 1) * max_results
+ if user.watched[start_index]?
+ watched = user.watched.reverse[start_index, max_results]
+ end
+ watched ||= [] of String
+
+ return watched.to_json
+ end
+
+ def self.mark_watched(env)
+ user = env.get("user").as(User)
+
+ if !user.preferences.watch_history
+ return error_json(409, "Watch history is disabled in preferences.")
+ end
+
+ id = env.params.url["id"]
+ if !id.match(/^[a-zA-Z0-9_-]{11}$/)
+ return error_json(400, "Invalid video id.")
+ end
+
+ Invidious::Database::Users.mark_watched(user, id)
+ env.response.status_code = 204
+ end
+
+ def self.mark_unwatched(env)
+ user = env.get("user").as(User)
+
+ if !user.preferences.watch_history
+ return error_json(409, "Watch history is disabled in preferences.")
+ end
+
+ id = env.params.url["id"]
+ if !id.match(/^[a-zA-Z0-9_-]{11}$/)
+ return error_json(400, "Invalid video id.")
+ end
+
+ Invidious::Database::Users.mark_unwatched(user, id)
+ env.response.status_code = 204
+ end
+
+ def self.clear_history(env)
+ user = env.get("user").as(User)
+
+ Invidious::Database::Users.clear_watch_history(user)
+ env.response.status_code = 204
+ end
+
def self.feed(env)
env.response.content_type = "application/json"
diff --git a/src/invidious/routes/video_playback.cr b/src/invidious/routes/video_playback.cr
index 1e932d11..9641e01a 100644
--- a/src/invidious/routes/video_playback.cr
+++ b/src/invidious/routes/video_playback.cr
@@ -256,7 +256,7 @@ module Invidious::Routes::VideoPlayback
return error_template(400, "Invalid video ID")
end
- if itag.nil? || itag <= 0 || itag >= 1000
+ if !itag.nil? && (itag <= 0 || itag >= 1000)
return error_template(400, "Invalid itag")
end
@@ -277,7 +277,11 @@ module Invidious::Routes::VideoPlayback
return error_template(500, ex)
end
- fmt = video.fmt_stream.find(nil) { |f| f["itag"].as_i == itag } || video.adaptive_fmts.find(nil) { |f| f["itag"].as_i == itag }
+ if itag.nil?
+ fmt = video.fmt_stream[-1]?
+ else
+ fmt = video.fmt_stream.find(nil) { |f| f["itag"].as_i == itag } || video.adaptive_fmts.find(nil) { |f| f["itag"].as_i == itag }
+ end
url = fmt.try &.["url"]?.try &.as_s
if !url
diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr
index dca2f117..9e2ade3d 100644
--- a/src/invidious/routing.cr
+++ b/src/invidious/routing.cr
@@ -257,6 +257,11 @@ module Invidious::Routing
get "/api/v1/auth/export/invidious", {{namespace}}::Authenticated, :export_invidious
post "/api/v1/auth/import/invidious", {{namespace}}::Authenticated, :import_invidious
+ get "/api/v1/auth/history", {{namespace}}::Authenticated, :get_history
+ post "/api/v1/auth/history/:id", {{namespace}}::Authenticated, :mark_watched
+ delete "/api/v1/auth/history/:id", {{namespace}}::Authenticated, :mark_unwatched
+ delete "/api/v1/auth/history", {{namespace}}::Authenticated, :clear_history
+
get "/api/v1/auth/feed", {{namespace}}::Authenticated, :feed
get "/api/v1/auth/subscriptions", {{namespace}}::Authenticated, :get_subscriptions
diff --git a/src/invidious/videos.cr b/src/invidious/videos.cr
index 436ac82d..0038a97a 100644
--- a/src/invidious/videos.cr
+++ b/src/invidious/videos.cr
@@ -249,7 +249,12 @@ struct Video
def music : Array(VideoMusic)
info["music"].as_a.map { |music_json|
- VideoMusic.new(music_json["album"].as_s, music_json["artist"].as_s, music_json["license"].as_s)
+ VideoMusic.new(
+ music_json["song"].as_s,
+ music_json["album"].as_s,
+ music_json["artist"].as_s,
+ music_json["license"].as_s
+ )
}
end
diff --git a/src/invidious/videos/music.cr b/src/invidious/videos/music.cr
index 402ae46f..08d88a3e 100644
--- a/src/invidious/videos/music.cr
+++ b/src/invidious/videos/music.cr
@@ -3,10 +3,11 @@ require "json"
struct VideoMusic
include JSON::Serializable
+ property song : String
property album : String
property artist : String
property license : String
- def initialize(@album : String, @artist : String, @license : String)
+ def initialize(@song : String, @album : String, @artist : String, @license : String)
end
end
diff --git a/src/invidious/videos/parser.cr b/src/invidious/videos/parser.cr
index efc4b2e5..608ae99d 100644
--- a/src/invidious/videos/parser.cr
+++ b/src/invidious/videos/parser.cr
@@ -327,17 +327,25 @@ def parse_video_info(video_id : String, player_response : Hash(String, JSON::Any
album = nil
music_license = nil
+ # Used when the video has multiple songs
+ if song_title = music_desc.dig?("carouselLockupRenderer", "videoLockup", "compactVideoRenderer", "title")
+ # "simpleText" for plain text / "runs" when song has a link
+ song = song_title["simpleText"]? || song_title.dig("runs", 0, "text")
+ end
+
music_desc.dig?("carouselLockupRenderer", "infoRows").try &.as_a.each do |desc|
desc_title = extract_text(desc.dig?("infoRowRenderer", "title"))
if desc_title == "ARTIST"
artist = extract_text(desc.dig?("infoRowRenderer", "defaultMetadata"))
+ elsif desc_title == "SONG"
+ song = extract_text(desc.dig?("infoRowRenderer", "defaultMetadata"))
elsif desc_title == "ALBUM"
album = extract_text(desc.dig?("infoRowRenderer", "defaultMetadata"))
elsif desc_title == "LICENSES"
music_license = extract_text(desc.dig?("infoRowRenderer", "expandedMetadata"))
end
end
- music_list << VideoMusic.new(album.to_s, artist.to_s, music_license.to_s)
+ music_list << VideoMusic.new(song.to_s, album.to_s, artist.to_s, music_license.to_s)
end
# Author infos
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr
index 1fc79495..a3ec94e8 100644
--- a/src/invidious/views/watch.ecr
+++ b/src/invidious/views/watch.ecr
@@ -252,9 +252,9 @@ we're going to need to do it here in order to allow for translations.
<div id="music-description-box">
<% video.music.each do |music| %>
<div class="music-item">
- <p id="music-artist"><%= translate(locale, "Artist: ") %><%= music.artist %></p>
- <p id="music-album"><%= translate(locale, "Album: ") %><%= music.album %></p>
- <p id="music-license"><%= translate(locale, "License: ") %><%= music.license %></p>
+ <p class="music-song"><%= translate(locale, "Song: ") %><%= music.song %></p>
+ <p class="music-artist"><%= translate(locale, "Artist: ") %><%= music.artist %></p>
+ <p class="music-album"><%= translate(locale, "Album: ") %><%= music.album %></p>
</div>
<% end %>
</div>