Greasy Fork is available in English.

Youtube 翻译中文字幕下载 v12

Youtube 播放器右下角有个 Auto-tranlsate,可以把视频字幕翻成中文。这个脚本是下载这个中文字幕

2021-11-13 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name Youtube 翻译中文字幕下载 v12
  3. // @include https://*youtube.com/*
  4. // @author Cheng Zheng
  5. // @copyright 2018-2021 Cheng Zheng;
  6. // @license GNU GPL v3.0 or later. http://www.gnu.org/copyleft/gpl.html
  7. // @require https://code.jquery.com/jquery-1.12.4.min.js
  8. // @version 12
  9. // @grant GM_xmlhttpRequest
  10. // @namespace https://greasyfork.org/users/5711
  11. // @description Youtube 播放器右下角有个 Auto-tranlsate,可以把视频字幕翻成中文。这个脚本是下载这个中文字幕
  12. // ==/UserScript==
  13.  
  14. /*
  15. 作者 : 郑诚
  16. 新浪微博: 糖醋陈皮 https://weibo.com/u/2004104451/home?wvr=5
  17. 邮箱 : guokrfans@gmail.com
  18. Github: https://github.com/1c7/Youtube-Auto-Subtitle-Download
  19.  
  20. 测试视频:
  21. https://www.youtube.com/watch?v=nGlQkaoIfBI 1门语言
  22. https://www.youtube.com/watch?v=O5nskjZ_GoI 13门语言
  23. https://www.youtube.com/watch?v=VfEz3DIbkvo 测试自动字幕(西班牙语)
  24. https://www.youtube.com/watch?v=WSnKbcfsT1E
  25. */
  26.  
  27. (function () {
  28. // 配置项
  29. const NO_SUBTITLE = '无字幕';
  30. const HAVE_SUBTITLE = '下载翻译的中文字幕';
  31. const TEXT_LOADING = '载入中...';
  32. const BUTTON_ID = 'youtube-translate-to-chinese-subtitle-downloader-by-1c7'
  33. // 配置项
  34.  
  35. var HASH_BUTTON_ID = `#${BUTTON_ID}`
  36. var first_load = true;
  37.  
  38. // return true / false
  39. // Detect [new version UI(material design)] OR [old version UI]
  40. // I tested this, accurated.
  41. function new_material_design_version() {
  42. var old_title_element = document.getElementById('watch7-headline');
  43. if (old_title_element) {
  44. return false;
  45. } else {
  46. return true;
  47. }
  48. }
  49.  
  50. // trigger when first load (hit refresh button)
  51. $(document).ready(function () {
  52. // because document ready still not enough
  53. // it's still too early, we have to wait certain element exist, then execute function.
  54. if (new_material_design_version()) {
  55. var material_checkExist = setInterval(function () {
  56. if (document.querySelectorAll('.title.style-scope.ytd-video-primary-info-renderer').length) {
  57. init();
  58. clearInterval(material_checkExist);
  59. }
  60. }, 330);
  61. } else {
  62. var checkExist = setInterval(function () {
  63. if ($('#watch7-headline').length) {
  64. init();
  65. clearInterval(checkExist);
  66. }
  67. }, 330);
  68. }
  69.  
  70. });
  71.  
  72. // trigger when loading new page (actually this would also trigger when first loading, that's not what we want, that's why we need to use firsr_load === false)
  73. // (new Material design version would trigger this "yt-navigate-finish" event. old version would not.)
  74. var body = document.getElementsByTagName("body")[0];
  75. body.addEventListener("yt-navigate-finish", function (event) {
  76. if (first_load === false) {
  77. remove_subtitle_download_button();
  78. init();
  79. }
  80. });
  81.  
  82. // trigger when loading new page
  83. // (old version would trigger this "spfdone" event. new Material design version not sure yet.)
  84. window.addEventListener("spfdone", function (e) {
  85. if (current_page_is_video_page()) {
  86. remove_subtitle_download_button();
  87. var checkExist = setInterval(function () {
  88. if ($('#watch7-headline').length) {
  89. init();
  90. clearInterval(checkExist);
  91. }
  92. }, 330);
  93. }
  94.  
  95. });
  96.  
  97. // return true / false
  98. function current_page_is_video_page() {
  99. return get_video_id() !== null;
  100. }
  101.  
  102. // return string like "RW1ChiWyiZQ", from "https://www.youtube.com/watch?v=RW1ChiWyiZQ"
  103. // or null
  104. function get_video_id() {
  105. return getURLParameter('v');
  106. }
  107.  
  108. //https://stackoverflow.com/questions/11582512/how-to-get-url-parameters-with-javascript/11582513#11582513
  109. function getURLParameter(name) {
  110. return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
  111. }
  112.  
  113. function remove_subtitle_download_button() {
  114. $(HASH_BUTTON_ID).remove();
  115. }
  116.  
  117. function init() {
  118. unsafeWindow.caption_array = [];
  119. inject_our_script();
  120. first_load = false;
  121. }
  122.  
  123. function inject_our_script() {
  124. var div = document.createElement('div'),
  125. select = document.createElement('select'),
  126. option = document.createElement('option'),
  127. controls = document.getElementById('watch7-headline'); // Youtube video title DIV
  128.  
  129. if (new_material_design_version()) {
  130. div.setAttribute('style', `display: table;
  131. margin-top:4px;
  132. border: 1px solid rgb(0, 183, 90);
  133. cursor: pointer; color: rgb(255, 255, 255);
  134. border-top-left-radius: 3px;
  135. border-top-right-radius: 3px;
  136. border-bottom-right-radius: 3px;
  137. border-bottom-left-radius: 3px;
  138. background-color: #00B75A;
  139. padding: 4px;
  140. padding-right: 8px;
  141. `);
  142. } else {
  143. div.setAttribute('style', `display: table;
  144. margin-top:4px;
  145. border: 1px solid rgb(0, 183, 90);
  146. cursor: pointer; color: rgb(255, 255, 255);
  147. border-top-left-radius: 3px;
  148. border-top-right-radius: 3px;
  149. border-bottom-right-radius: 3px;
  150. border-bottom-left-radius: 3px;
  151. background-color: #00B75A;
  152. padding: 3px;
  153. padding-right: 8px;
  154. `);
  155. }
  156.  
  157. div.id = BUTTON_ID;
  158.  
  159. select.id = 'captions_selector';
  160. select.disabled = true;
  161. select.setAttribute('style', 'display:block; border: 1px solid rgb(0, 183, 90); cursor: pointer; color: rgb(255, 255, 255); background-color: #00B75A;');
  162.  
  163. option.textContent = TEXT_LOADING;
  164. option.selected = true;
  165. select.appendChild(option);
  166.  
  167. // 下拉菜单中选择后的事件侦听
  168. select.addEventListener('change', function () {
  169. download_subtitle(this);
  170. }, false);
  171.  
  172. div.appendChild(select);
  173. // put <select> into <div>
  174.  
  175. // put the div into page: new material design
  176. var title_element = document.querySelectorAll('.title.style-scope.ytd-video-primary-info-renderer');
  177. if (title_element) {
  178. $(title_element[0]).after(div);
  179. }
  180. // put the div into page: old version
  181. if (controls) {
  182. controls.appendChild(div);
  183. }
  184.  
  185. load_language_list(select);
  186.  
  187. // <a> element is for download
  188. var a = document.createElement('a');
  189. a.style.cssText = 'display:none;';
  190. a.setAttribute("id", "ForSubtitleDownload");
  191. var body = document.getElementsByTagName('body')[0];
  192. body.appendChild(a);
  193. }
  194.  
  195. // Trigger when user select <option>
  196. async function download_subtitle(selector) {
  197. // if user select first <option>, we just return, do nothing.
  198. if (selector.selectedIndex == 0) {
  199. return;
  200. }
  201.  
  202. var caption = caption_array[selector.selectedIndex - 1]; // because first <option> is for display, so index-1
  203. if (!caption) return;
  204.  
  205. var lang_code = caption.lang_code;
  206. var lang_name = caption.lang_name;
  207.  
  208. // if user choose auto subtitle // 如果用户选的是自动字幕
  209. if (caption.lang_code == 'AUTO') {
  210. var file_name = get_file_name(lang_name);
  211. download_auto_subtitle(file_name);
  212. selector.options[0].selected = true; // after download, select first <option>
  213. return
  214. }
  215.  
  216. // 如果用户选的是完整字幕
  217. // 原文
  218. // sub mean "subtitle"
  219. var sub_original_url = await get_closed_subtitle_url(lang_code)
  220.  
  221. // 中文
  222. var sub_translated_url = sub_original_url + "&tlang=" + "zh-Hans"
  223. var sub_translated_xml = await get(sub_translated_url);
  224.  
  225. var sub_translated_srt = parse_youtube_XML_to_object_list(sub_translated_xml)
  226.  
  227. var srt_string = object_array_to_SRT_string(sub_translated_srt)
  228. var title = get_file_name(lang_name);
  229. downloadString(srt_string, "text/plain", title);
  230.  
  231. // after download, select first <option>
  232. selector.options[0].selected = true;
  233. }
  234.  
  235. // Return something like: "(English)How Did Python Become A Data Science Powerhouse?.srt"
  236. function get_file_name(x) {
  237. return `(${x})${get_title()}.srt`;
  238. }
  239.  
  240. // 载入有多少种语言, 然后加到 <select> 里
  241. function load_language_list(select) {
  242. // auto
  243. var auto_subtitle_exist = false;
  244.  
  245. // closed
  246. var closed_subtitle_exist = false;
  247.  
  248. // get auto subtitle
  249. var auto_subtitle_url = get_auto_subtitle_xml_url();
  250. if (auto_subtitle_url != false) {
  251. auto_subtitle_exist = true;
  252. }
  253.  
  254. var captionTracks = get_captionTracks()
  255. if (captionTracks.length > 0) {
  256. closed_subtitle_exist = true;
  257. }
  258.  
  259. // if no subtitle at all, just say no and stop
  260. if (auto_subtitle_exist == false && closed_subtitle_exist == false) {
  261. select.options[0].textContent = NO_SUBTITLE;
  262. disable_download_button();
  263. return false;
  264. }
  265.  
  266. // if at least one type of subtitle exist
  267. select.options[0].textContent = HAVE_SUBTITLE;
  268. select.disabled = false;
  269.  
  270. // if at least one type of subtitle exist
  271. select.options[0].textContent = HAVE_SUBTITLE;
  272. select.disabled = false;
  273.  
  274. var caption = null; // for inside loop
  275. var option = null; // for <option>
  276. var caption_info = null; // for our custom object
  277.  
  278. // 自动字幕
  279. if (auto_subtitle_exist) {
  280. var auto_sub_name = get_auto_subtitle_name()
  281. var lang_name = `${auto_sub_name} 翻译成 中文`
  282. caption_info = {
  283. lang_code: 'AUTO', // later we use this to know if it's auto subtitle
  284. lang_name: lang_name // for display only
  285. };
  286. caption_array.push(caption_info);
  287.  
  288. option = document.createElement('option');
  289. option.textContent = caption_info.lang_name;
  290. select.appendChild(option);
  291. }
  292.  
  293. // if closed_subtitle_exist
  294. if (closed_subtitle_exist) {
  295. for (var i = 0, il = captionTracks.length; i < il; i++) {
  296. var caption = captionTracks[i];
  297. if (caption.kind == 'asr') {
  298. continue
  299. }
  300. let lang_code = caption.languageCode
  301. let lang_translated = caption.name.simpleText
  302. var lang_name = `${lang_code_to_local_name(lang_code, lang_translated)} 翻译成 中文`
  303. caption_info = {
  304. lang_code: lang_code, // for AJAX request
  305. lang_name: lang_name, // display to user
  306. };
  307. caption_array.push(caption_info);
  308. // 注意这里是加到 caption_array, 一个全局变量, 待会要靠它来下载
  309. option = document.createElement('option');
  310. option.textContent = caption_info.lang_name;
  311. select.appendChild(option);
  312. }
  313. }
  314. }
  315.  
  316. // 处理时间. 比如 start="671.33" start="37.64" start="12" start="23.029"
  317. // 处理成 srt 时间, 比如 00:00:00,090 00:00:08,460 00:10:29,350
  318. function process_time(s) {
  319. s = s.toFixed(3);
  320. // 超棒的函数, 不论是整数还是小数都给弄成3位小数形式
  321. // 举个柚子:
  322. // 671.33 -> 671.330
  323. // 671 -> 671.000
  324. // 注意函数会四舍五入. 具体读文档
  325.  
  326. var array = s.split('.');
  327. // 把开始时间根据句号分割
  328. // 671.330 会分割成数组: [671, 330]
  329.  
  330. var Hour = 0;
  331. var Minute = 0;
  332. var Second = array[0]; // 671
  333. var MilliSecond = array[1]; // 330
  334. // 先声明下变量, 待会把这几个拼好就行了
  335.  
  336. // 我们来处理秒数. 把"分钟"和"小时"除出来
  337. if (Second >= 60) {
  338. Minute = Math.floor(Second / 60);
  339. Second = Second - Minute * 60;
  340. // 把 秒 拆成 分钟和秒, 比如121秒, 拆成2分钟1秒
  341.  
  342. Hour = Math.floor(Minute / 60);
  343. Minute = Minute - Hour * 60;
  344. // 把 分钟 拆成 小时和分钟, 比如700分钟, 拆成11小时40分钟
  345. }
  346. // 分钟,如果位数不够两位就变成两位,下面两个if语句的作用也是一样。
  347. if (Minute < 10) {
  348. Minute = '0' + Minute;
  349. }
  350. // 小时
  351. if (Hour < 10) {
  352. Hour = '0' + Hour;
  353. }
  354. // 秒
  355. if (Second < 10) {
  356. Second = '0' + Second;
  357. }
  358. return Hour + ':' + Minute + ':' + Second + ',' + MilliSecond;
  359. }
  360.  
  361. // https://css-tricks.com/snippets/javascript/unescape-html-in-js/
  362. // turn HTML entity back to text, example: &quot; should be "
  363. function htmlDecode(input) {
  364. var e = document.createElement('div');
  365. e.class = 'dummy-element-for-tampermonkey-Youtube-Subtitle-Downloader-script-to-decode-html-entity';
  366. e.innerHTML = input;
  367. return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
  368. }
  369.  
  370. // return URL or null;
  371. // later we can send a AJAX and get XML subtitle
  372. function get_auto_subtitle_xml_url() {
  373. try {
  374. var captionTracks = get_captionTracks()
  375. for (var index in captionTracks) {
  376. var caption = captionTracks[index];
  377. if (typeof caption.kind === 'string' && caption.kind == 'asr') {
  378. return captionTracks[index].baseUrl;
  379. }
  380. // ASR – A caption track generated using automatic speech recognition.
  381. // https://developers.google.com/youtube/v3/docs/captions
  382. }
  383. return false;
  384. } catch (error) {
  385. return false;
  386. }
  387. }
  388.  
  389. function disable_download_button() {
  390. $(HASH_BUTTON_ID)
  391. .css('border', '#95a5a6')
  392. .css('cursor', 'not-allowed')
  393. .css('background-color', '#95a5a6');
  394. $('#captions_selector')
  395. .css('border', '#95a5a6')
  396. .css('cursor', 'not-allowed')
  397. .css('background-color', '#95a5a6');
  398.  
  399. if (new_material_design_version()) {
  400. $(HASH_BUTTON_ID).css('padding', '6px');
  401. } else {
  402. $(HASH_BUTTON_ID).css('padding', '5px');
  403. }
  404. }
  405.  
  406. // 下载自动字幕的中英双语
  407. // 输入: file_name: 保存的文件名
  408. // 输出: 无 (会触发浏览器下载一个文件)
  409. async function download_auto_subtitle(file_name) {
  410. var auto_sub_url = get_auto_subtitle_xml_url();
  411. var format_json3_url = auto_sub_url + '&fmt=json3'
  412. var cn_url = format_json3_url + '&tlang=zh-Hans'
  413.  
  414. var cn_srt = await auto_sub_in_chinese_fmt_json3_to_srt(cn_url)
  415. var srt_string = to_srt(cn_srt)
  416.  
  417. downloadString(srt_string, "text/plain", file_name);
  418. }
  419.  
  420. function to_srt(srt_array) {
  421. // var srt_array_item_example = {
  422. // "startTime": "00:00:06,640",
  423. // "endTime": "00:00:09,760",
  424. // "text": "在与朋友的长时间交谈中以及与陌生人的简短交谈中",
  425. // "tStartMs": 6640,
  426. // "dDurationMs": 3120,
  427. // "words": ["in", " a", " long", " conversation", " with", " a", " friend", " and", "a", " short", " chat", " with", " a", " stranger", "the", " endless", " streams"]
  428. // }
  429. var result_array = []
  430. for (let i = 0; i < srt_array.length; i++) {
  431. const line = srt_array[i];
  432. var text = line.text; // 中文
  433. var item = {
  434. startTime: line.startTime,
  435. endTime: line.endTime,
  436. text: text
  437. }
  438. result_array.push(item)
  439. }
  440.  
  441. var srt_string = object_array_to_SRT_string(result_array)
  442. return srt_string
  443. }
  444.  
  445. // return "English (auto-generated)" or a default name;
  446. function get_auto_subtitle_name() {
  447. const name = "自动字幕"
  448. try {
  449. var captionTracks = get_captionTracks()
  450. for (var index in captionTracks) {
  451. var caption = captionTracks[index];
  452. if (typeof caption.kind === 'string' && caption.kind == 'asr') {
  453. return captionTracks[index].name.simpleText;
  454. }
  455. }
  456. return name;
  457. } catch (error) {
  458. console.log(error);
  459. return name;
  460. }
  461. }
  462.  
  463. // Usage: var result = await get(url)
  464. function get(url) {
  465. return $.ajax({
  466. url: url,
  467. type: 'get',
  468. success: function (r) {
  469. return r
  470. },
  471. fail: function (error) {
  472. return error
  473. }
  474. });
  475. }
  476.  
  477.  
  478. // 输入: url (String)
  479. // 输出: SRT (Array)
  480. async function auto_sub_in_chinese_fmt_json3_to_srt(url) {
  481. var srt_array = []
  482.  
  483. var json = await get(url);
  484. var events = json.events;
  485. for (let index = 0; index < events.length; index++) {
  486. const event = events[index];
  487.  
  488. if(event.segs === undefined){
  489. continue
  490. }
  491. if(event.segs.length === 1 && event.segs[0].utf8 === '\n'){
  492. continue
  493. }
  494.  
  495. var tStartMs = event.tStartMs
  496. var dDurationMs = event.dDurationMs
  497. var segs = event.segs
  498. var text = segs.map(seg => seg.utf8).join("")
  499.  
  500. var item = {
  501. startTime: ms_to_srt(tStartMs),
  502. endTime: ms_to_srt(tStartMs + dDurationMs),
  503. text: text,
  504.  
  505. tStartMs: tStartMs,
  506. dDurationMs: dDurationMs,
  507. }
  508. srt_array.push(item);
  509. }
  510. return srt_array
  511. }
  512.  
  513. // 毫秒转成 srt 时间
  514. function ms_to_srt($milliseconds) {
  515. var $seconds = Math.floor($milliseconds / 1000);
  516. var $minutes = Math.floor($seconds / 60);
  517. var $hours = Math.floor($minutes / 60);
  518. var $milliseconds = $milliseconds % 1000;
  519. var $seconds = $seconds % 60;
  520. var $minutes = $minutes % 60;
  521. return ($hours < 10 ? '0' : '') + $hours + ':' +
  522. ($minutes < 10 ? '0' : '') + $minutes + ':' +
  523. ($seconds < 10 ? '0' : '') + $seconds + ',' +
  524. ($milliseconds < 100 ? '0' : '') + ($milliseconds < 10 ? '0' : '') + $milliseconds;
  525. }
  526.  
  527. /*
  528. Input: [ {startTime: "", endTime: "", text: ""}, {...}, {...} ]
  529. Output: SRT
  530. */
  531. function object_array_to_SRT_string(object_array) {
  532. var result = '';
  533. var BOM = '\uFEFF';
  534. result = BOM + result; // store final SRT result
  535.  
  536. for (var i = 0; i < object_array.length; i++) {
  537. var item = object_array[i]
  538. var index = i + 1;
  539. var start_time = item.startTime
  540. var end_time = item.endTime
  541. var text = item.text
  542.  
  543. var new_line = "\n";
  544. result = result + index + new_line;
  545.  
  546. result = result + start_time;
  547. result = result + ' --> ';
  548. result = result + end_time + new_line;
  549.  
  550. result = result + text + new_line + new_line;
  551. }
  552.  
  553. return result;
  554. }
  555.  
  556. // Copy from: https://gist.github.com/danallison/3ec9d5314788b337b682
  557. // Thanks! https://github.com/danallison
  558. // Work in Chrome 66
  559. // Test passed: 2018-5-19
  560. function downloadString(text, fileType, fileName) {
  561. var blob = new Blob([text], {
  562. type: fileType
  563. });
  564. var a = document.createElement('a');
  565. a.download = fileName;
  566. a.href = URL.createObjectURL(blob);
  567. a.dataset.downloadurl = [fileType, a.download, a.href].join(':');
  568. a.style.display = "none";
  569. document.body.appendChild(a);
  570. a.click();
  571. document.body.removeChild(a);
  572. setTimeout(function () {
  573. URL.revokeObjectURL(a.href);
  574. }, 1500);
  575. }
  576.  
  577. // Input: lang_code like 'en'
  578. // Output: URL (String)
  579. async function get_closed_subtitle_url(lang_code) {
  580. try {
  581. var captionTracks = get_captionTracks()
  582. for (var index in captionTracks) {
  583. var caption = captionTracks[index];
  584. if (caption.languageCode === lang_code && caption.kind != 'asr') {
  585. var url = captionTracks[index].baseUrl;
  586. return url
  587. }
  588. }
  589. } catch (error) {
  590. console.log(error);
  591. return false;
  592. }
  593. }
  594.  
  595. // Input: XML (provide by Youtube)
  596. // Output: Array of object
  597. // each object look like:
  598. /*
  599. {
  600. startTime: "",
  601. endTime: "",
  602. text: ""
  603. }
  604. */
  605. // it's intermediate representation for SRT
  606. function parse_youtube_XML_to_object_list(youtube_xml_string) {
  607. if (youtube_xml_string === '' || youtube_xml_string === undefined || youtube_xml_string === null) {
  608. return false;
  609. }
  610. var result_array = []
  611. var text_nodes = youtube_xml_string.getElementsByTagName('text');
  612. var len = text_nodes.length;
  613. for (var i = 0; i < len; i++) {
  614. var text = text_nodes[i].textContent.toString();
  615. text = text.replace(/(<([^>]+)>)/ig, ""); // remove all html tag.
  616. text = htmlDecode(text);
  617.  
  618. var start = text_nodes[i].getAttribute('start');
  619. var end = parseFloat(text_nodes[i].getAttribute('start')) + parseFloat(text_nodes[i].getAttribute('dur'));
  620.  
  621. // if (i + 1 >= len) {
  622. // end = parseFloat(text_nodes[i].getAttribute('start')) + parseFloat(text_nodes[i].getAttribute('dur'));
  623. // } else {
  624. // end = text_nodes[i + 1].getAttribute('start');
  625. // }
  626.  
  627. var start_time = process_time(parseFloat(start));
  628. var end_time = process_time(parseFloat(end));
  629.  
  630. var item = {
  631. startTime: start_time,
  632. endTime: end_time,
  633. text: text
  634. }
  635. result_array.push(item)
  636. }
  637.  
  638. return result_array
  639. }
  640.  
  641. function get_youtube_data(){
  642. return document.getElementsByTagName("ytd-app")[0].data.playerResponse
  643. }
  644.  
  645. function get_captionTracks() {
  646. let data = get_youtube_data();
  647. var captionTracks = data.captions.playerCaptionsTracklistRenderer.captionTracks
  648. return captionTracks
  649. }
  650.  
  651. // Input a language code, output that language name in current locale
  652. // 如果当前语言是中文简体, Input: "de" Output: 德语
  653. // if current locale is English(US), Input: "de" Output: "Germany"
  654. function lang_code_to_local_name(languageCode, fallback_name) {
  655. try {
  656. var captionTracks = get_captionTracks()
  657. for (var i in captionTracks) {
  658. var caption = captionTracks[i];
  659. if (caption.languageCode === languageCode) {
  660. let simpleText = captionTracks[i].name.simpleText;
  661. if (simpleText) {
  662. return simpleText
  663. } else {
  664. return fallback_name
  665. }
  666. }
  667. }
  668. } catch (error) {
  669. return fallback_name
  670. }
  671. }
  672.  
  673. function get_title() {
  674. return ytplayer.config.args.title;
  675. }
  676. })();