Skip to content

Commit

Permalink
Merge pull request #12 from 2jun0/dev
Browse files Browse the repository at this point in the history
Dev: 1.3.2
  • Loading branch information
2jun0 authored Nov 30, 2021
2 parents e469887 + c34b925 commit 3456cfd
Show file tree
Hide file tree
Showing 23 changed files with 391 additions and 158 deletions.
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
<p align="center">
<img src="https://github.com/2jun0/yt-subtitle-filter/blob/main/chrome/asset/logo/logo-big-128.png" width="75" height="75"/>
<img src="chrome/asset/logo/logo-big-128.png" width="75" height="75"/>
</p>

<h1 align="center">YouTube Subtitle Filter</h1>

We support Korean version!
follow the below page to read it in multiple languages.

[한국어 보기](README_KO.md)
[한국어](README_KO.md)

Add a subtitle tag language that you want on the video thumbnail in the YouTube.

## Download

- [Chrome Web Store](https://chrome.google.com/webstore/detail/Youtube-subtitle-filter/onmelgncdnoihoaopmkcacadlmjmcehd)

- [Firefox Add On](https://addons.mozilla.org/ko/firefox/addon/youtube-subtitle-filter)

---

## Showcase

![Showcase Videos](chrome/asset/showcase/showcase_videos.jpg)

![Showcase In Video](chrome/asset/showcase/showcase_invideo.jpg)

---

## Customize

- You can customize tag color in popup menu (background and text color)
- You can resize the subtitle tags
- You can search for subtitles by grouping regions. (ex en-US + en-GB)
<img src="chrome/asset/showcase/showcase_popup.jpg" align="right" width="200">

![Showcase Popup](chrome/asset/showcase/showcase_popup_1.jpg) ![Showcase Popup](chrome/asset/showcase/showcase_popup_2.jpg)
- You can customize tag color in popup menu (background and text color)

## Download
- You can resize the subtitle tags

- [Chrome Web Store](https://chrome.google.com/webstore/detail/Youtube-subtitle-filter/onmelgncdnoihoaopmkcacadlmjmcehd)
- [Firefox Add On](https://addons.mozilla.org/ko/firefox/addon/youtube-subtitle-filter)
- You can search for subtitles by grouping regions. (ex en-US + en-GB)
25 changes: 16 additions & 9 deletions README_KO.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
<p align="center">
<img src="https://github.com/2jun0/yt-subtitle-filter/blob/main/chrome/asset/logo/logo-big-128.png" width="75" height="75"/>
<img src="chrome/asset/logo/logo-big-128.png" width="75" height="75"/>
</p>

<h1 align="center">유튜브 자막 필터</h1>

README 페이지를 영문로 보시려면 아래를 참고하세요.

[영문 보기](README.md)
[영문](README.md)

유튜브 내 동영상 썸네일에 원하는 언어의 자막 태그를 추가해줍니다.

## 다운로드

- [Chrome 웹 스토어](https://chrome.google.com/webstore/detail/Youtube-subtitle-filter/onmelgncdnoihoaopmkcacadlmjmcehd)

- [Firefox Add On](https://addons.mozilla.org/ko/firefox/addon/youtube-subtitle-filter)

---

## 예시화면

![동영상 목록 예시화면](chrome/asset/showcase/showcase_videos.jpg)

![동영상 실행 중 예시화면](chrome/asset/showcase/showcase_invideo.jpg)

---

## 사용자 설정

- 팝업 메뉴에서 태그의 색상을 선택할 수 있습니다. (배경색상, 글자색상)
- 태그의 크기를 조절 할 수 있습니다.
- 지역언어 통합 기능으로 여러지역으로 나눠진 자막을 하나로 검색할 수 도 있습니다. (예: 영어(영국) + 영어(미국))
<img src="chrome/asset/showcase/showcase_popup.jpg" align="right" width="200">

![팝업 예시화면](chrome/asset/showcase/showcase_popup_ko_1.jpg) ![팝업 예시화면](chrome/asset/showcase/showcase_popup_ko_2.jpg)
- 팝업 메뉴에서 태그의 색상을 선택할 수 있습니다. (배경색상, 글자색상)

## 다운로드
- 태그의 크기를 조절 할 수 있습니다.

- [Chrome 웹 스토어](https://chrome.google.com/webstore/detail/Youtube-subtitle-filter/onmelgncdnoihoaopmkcacadlmjmcehd)
- [Firefox Add On](https://addons.mozilla.org/ko/firefox/addon/youtube-subtitle-filter)
- 지역언어 통합 기능으로 여러지역으로 나눠진 자막을 하나로 검색할 수 도 있습니다. (예: 영어(영국) + 영어(미국))
Binary file modified chrome/asset/showcase/showcase_popup.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed chrome/asset/showcase/showcase_popup_1.jpg
Binary file not shown.
Binary file removed chrome/asset/showcase/showcase_popup_2.jpg
Binary file not shown.
Binary file removed chrome/asset/showcase/showcase_popup_ko.jpg
Binary file not shown.
Binary file removed chrome/asset/showcase/showcase_popup_ko_1.jpg
Binary file not shown.
Binary file removed chrome/asset/showcase/showcase_popup_ko_2.jpg
Binary file not shown.
Binary file modified chrome/asset/showcase/showcase_videos.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions chrome/html/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
<popup-multi-page-menu class="popup-multi-page-menu">
<div class="items">
<popup-item-page>
<img
src="../asset/logo/logo-little.png"
style="margin-right: 5px; width: 40px"
/>
<span style="font-weight: 600" data-locale="appName"
>Youtube Subtitle Filter</span
>
Expand Down
167 changes: 104 additions & 63 deletions chrome/js/background/ytVideo.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,129 @@
import { FIELD_VIDEO_LANGS, loadData, saveData } from '../storage.js';
import {
getLangListUrl,
getTabId,
getYTVideoId,
requestAysnc,
} from '../common.js';
import {
FIELD_VIDEO_LANG_LIST_URL,
loadDataAsync,
saveData,
} from '../storage.js';

let TabId;
getTabId(tabId => (TabId = tabId));

let waittingIntervals = {};

// Load YouTube Video Iframe Url
function loadYtPlayer(videoId, callback) {
// Already exists
if (document.getElementById(`player-${videoId}`)) return;

let playerElm = document.createElement('div');
playerElm.id = `player-${videoId}`;
document.body.appendChild(playerElm);

let ytPlayer = new YT.Player(`player-${videoId}`, {
videoId: videoId,
playerVars: {
autoplay: 1,
cc_load_policy: 1,
suggestedQuality: 'tiny',
},
events: {
onReady: ({ target, data }) => {
ytPlayer.pauseVideo();

// Wait until the option is loaded.
let count = 0;
let intervalId = setInterval(() => {
let ccList = ytPlayer.getOption('captions', 'tracklist');
// over 60 sec => video doesn't have any captions
if (ccList || count > 600) {
clearInterval(intervalId);
callback(ytPlayer);
return;
}
count++;
}, 100);
async function loadYtPlayerAsync(videoId) {
return new Promise(resolve => {
// Already exists
if (document.getElementById(`player-${videoId}`)) return;

let playerElm = document.createElement('div');
playerElm.id = `player-${videoId}`;
document.body.appendChild(playerElm);

let ytPlayer = new YT.Player(`player-${videoId}`, {
videoId: videoId,
playerVars: {
autoplay: 1,
cc_load_policy: 1,
suggestedQuality: 'tiny',
},
events: {
onReady: ({ target, data }) => {
ytPlayer.pauseVideo();
resolve();
},
},
},
});
});
}

function checkLangCodes(videoId, langs, callback) {
const langCodeCheck = RegExp(`(${langs.join('|')})`);
const vLangField = `${FIELD_VIDEO_LANGS}_${videoId}`;
async function createWattingIntervalAsync(videoId) {
const vLangListUrlField = `${FIELD_VIDEO_LANG_LIST_URL}_${videoId}`;

loadData(vLangField, items => {
if (items[vLangField]) {
const langCodes = items[vLangField].langCodes;
const searchTime = items[vLangField].searchTime;
return new Promise(resolve => {
let count = 0;
waittingIntervals[videoId] = {
id: setInterval(() => {
// during 1 sec, wait web request
if (count >= 60) {
resolve();
return;
}

// After one day, the search starts again.
if (Date.now() - searchTime < 86400000) {
callback(langCodeCheck.test(langCodes));
return;
}
}
// when find timedtext url, save url and return
if (waittingIntervals[videoId].langListUrl) {
saveData(vLangListUrlField, waittingIntervals[videoId].langListUrl);
resolve(waittingIntervals[videoId].langListUrl);
return;
}
count++;
}, 1000),
};
}).then(langListUrl => {
// remove yt player and interval
document.getElementById(`player-${videoId}`).remove();
clearInterval(waittingIntervals[videoId].id);
return langListUrl;
});
}

// The subtitle search start
loadYtPlayer(videoId, ytPlayer => {
let langCodeList = (
ytPlayer.getOption('captions', 'tracklist') || []
).map(cc => cc.languageCode);
async function getLangListUrlAsync(videoId) {
const vLangListUrlField = `${FIELD_VIDEO_LANG_LIST_URL}_${videoId}`;

let hasSubtitles = false;
langCodeList.forEach(langCode => {
hasSubtitles ||= langCodeCheck.test(langCode);
});
return loadDataAsync(vLangListUrlField).then(items => {
let langListUrl = items[vLangListUrlField];

saveData(vLangField, {
langCodes: langCodeList.join(','),
searchTime: Date.now(),
if (langListUrl) {
// already saved
return langListUrl;
} else {
// load Youtube Player (wait until the "onReady" event occurs)
return loadYtPlayerAsync(videoId).then(() => {
return createWattingIntervalAsync(videoId);
});
// remove yt player
document.getElementById(`player-${videoId}`).remove();
}
});
}

callback(hasSubtitles);
});
async function hasSubtitlesAsync(videoId, langs) {
const langCodeCheck = RegExp(`lang_code="(${langs.join('|')})"`);

return getLangListUrlAsync(videoId).then(langListUrl => {
if (!langListUrl) {
return false;
} else {
return requestAysnc('GET', langListUrl).then(res => {
return langCodeCheck.test(res);
});
}
});
}

// Get web Request
chrome.webRequest.onBeforeRequest.addListener(
details => {
let videoId = getYTVideoId(details.url);
let langListUrl = getLangListUrl(details.url);

if (waittingIntervals[videoId])
waittingIntervals[videoId].langListUrl = langListUrl;
},
{ tabId: TabId, urls: ['*://*.youtube.com/api/timedtext*'] },
['requestBody'],
);

// Get content script message
chrome.runtime.onMessage.addListener(({ type, value }, sender, sendRes) => {
if (type === 'has-subtitles') {
let langs = value.langs;
let videoId = value.videoId;

checkLangCodes(videoId, langs, sendRes);
hasSubtitlesAsync(videoId, langs).then(sendRes);
}
return true; // Inform Chrome that we will make a delayed sendResponse
});
37 changes: 37 additions & 0 deletions chrome/js/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export function getYTVideoId(url) {
return url.match(/\?v=([\w-]+)/)[1];
}

export function getLangListUrl(timedtextUrl) {
// remove '&fmt=json3&xorb=2&xobt=3&xovt=3' and add '&type=list'
return timedtextUrl.substr(0, timedtextUrl.length - 31) + '&type=list';
}

// Get current tab id
export function getTabId(callback) {
const intervalId = setInterval(() => {
chrome.tabs.query({ currentWindow: true }, tabs => {
if (tabs) {
callback(tabs[0].id);
clearInterval(intervalId);
}
});
}, 1);
}

export function requestAysnc(method, url, msg = null) {
return new Promise(resolve => {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState == this.DONE) {
if (this.status == 200) {
resolve(this.responseText);
}
}
};

xhr.open(method, url);
console.log(method, url, msg);
xhr.send(msg);
});
}
10 changes: 8 additions & 2 deletions chrome/js/content_script/subtitleFilter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(async () => {
// dynamic import
const { getYTVideoId } = await import(chrome.runtime.getURL('js/common.js'));
const { getRelatedLangCodes } = await import(
chrome.runtime.getURL('js/lang.js')
);
Expand All @@ -13,6 +14,7 @@
loadData,
} = await import(chrome.runtime.getURL('js/storage.js'));

// tag const values
let ccLang = DEFAULT_VALUE[FIELD_LANG];
let ccColorBg = DEFAULT_VALUE[FIELD_COLOR_BG];
let ccColorTxt = DEFAULT_VALUE[FIELD_COLOR_TXT];
Expand Down Expand Up @@ -74,7 +76,11 @@
// To avoid deleting the ccLoading,
// Wait loading video overlays
waitOverlayLoaded(e, overlays => {
if (overlays.querySelector('#cc-loading')) return;
if (
overlays.querySelector('#cc-loading') ||
overlays.querySelector('#cc-status')
)
return;

let ccLoading = document.createElement('div');
ccLoading.id = 'cc-loading';
Expand Down Expand Up @@ -143,7 +149,7 @@

function hasSubtitles(videoUrl, langs, callback) {
// URL example : /watch?v=[video_id]
const videoId = videoUrl.match(/\?v=([\w-]+)/)[1];
const videoId = getYTVideoId(videoUrl);

function sendMsg() {
chrome.runtime.sendMessage(
Expand Down
2 changes: 1 addition & 1 deletion chrome/js/popup/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ colorTxtPicker.on('color:change', color => {
tagSizeRange.oninput = () => {
let idx = tagSizeRange.value;
setTagFontSize(TagFontSizes[idx]);
sendMessage(FIELD_TAG_FONT_SIZE, TagFontSizes[ix]);
sendMessage(FIELD_TAG_FONT_SIZE, TagFontSizes[idx]);
};

combineRegionCheckbox.onchange = () => {
Expand Down
Loading

0 comments on commit 3456cfd

Please sign in to comment.