Skip to content

Commit

Permalink
refactor(titles): Use player API as primary source for main title
Browse files Browse the repository at this point in the history
- Use movie_player.getPlayerResponse() as primary method to fetch original title
- Keep oembed API as fallback method for reliability
- Add script injection and custom event handling
- Better error handling with multiple fallback levels
  • Loading branch information
YouG-o committed Mar 2, 2025
1 parent 08904c1 commit df8c874
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 46 deletions.
3 changes: 2 additions & 1 deletion manifest.chrome.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"resources": [
"dist/content/audioTranslation/audioScript.js",
"dist/content/descriptionTranslation/descriptionScript.js",
"dist/content/subtitlesTranslation/subtitlesScript.js"
"dist/content/subtitlesTranslation/subtitlesScript.js",
"dist/content/titleTranslation/mainTitleScript.js"
],
"matches": ["*://*.youtube.com/*"]
}]
Expand Down
3 changes: 2 additions & 1 deletion manifest.firefox.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@
"resources": [
"dist/content/audioTranslation/audioScript.js",
"dist/content/descriptionTranslation/descriptionScript.js",
"dist/content/subtitlesTranslation/subtitlesScript.js"
"dist/content/subtitlesTranslation/subtitlesScript.js",
"dist/content/titleTranslation/mainTitleScript.js"
],
"matches": ["*://*.youtube.com/*"]
}]
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"watch:ts": "tsc --watch",
"watch:css": "tailwindcss -i ./src/styles/main.css -o ./dist/styles/main.css --watch",
"copy:assets": "cp -r assets/* dist/assets/",
"copy:scripts": "mkdir -p dist/content/audioTranslation dist/content/descriptionTranslation dist/content/subtitlesTranslation && cp src/content/audioTranslation/audioScript.js dist/content/audioTranslation/ && cp src/content/descriptionTranslation/descriptionScript.js dist/content/descriptionTranslation/ && cp src/content/subtitlesTranslation/subtitlesScript.js dist/content/subtitlesTranslation/",
"copy:scripts": "mkdir -p dist/content/audioTranslation dist/content/descriptionTranslation dist/content/subtitlesTranslation dist/content/titleTranslation && cp src/content/audioTranslation/audioScript.js dist/content/audioTranslation/ && cp src/content/descriptionTranslation/descriptionScript.js dist/content/descriptionTranslation/ && cp src/content/subtitlesTranslation/subtitlesScript.js dist/content/subtitlesTranslation/ && cp src/content/titleTranslation/mainTitleScript.js dist/content/titleTranslation/",
"pre:web-ext:firefox": "cp manifest.firefox.json manifest.json",
"pre:web-ext:chrome": "cp manifest.chrome.json manifest.json",
"web-ext:firefox": "web-ext build --overwrite-dest -a web-ext-artifacts/firefox",
Expand Down
28 changes: 9 additions & 19 deletions src/content/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,25 +120,15 @@ function setupMainTitleObserver() {
mainTitleLog('Video ID changed:', newVideoId);
mainTitleLog('Cache cleared');

// --- Get the current page URL to check against
const currentUrl = window.location.href;
/*mainTitleLog('Current URL:', currentUrl);*/

// --- Wait for title element and monitor its changes
const titleElement = await waitForElement('ytd-watch-metadata yt-formatted-string.style-scope.ytd-watch-metadata');
let attempts = 0;
const maxAttempts = 20;

while (attempts < maxAttempts) {
const pageUrl = window.location.href;

if (pageUrl === currentUrl && titleElement.textContent) {
await refreshMainTitle();
break;
}

await new Promise(resolve => setTimeout(resolve, 100));
attempts++;
// Wait for movie_player and title element
const [player, titleElement] = await Promise.all([
waitForElement('#movie_player'),
waitForElement('ytd-watch-metadata yt-formatted-string.style-scope.ytd-watch-metadata')
]);

// Only proceed if we're still on the same page
if (titleElement.textContent) {
await refreshMainTitle();
}
}
}
Expand Down
71 changes: 48 additions & 23 deletions src/content/titleTranslation/mainTitle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,38 +120,63 @@ function updatePageTitle(mainTitle: string): void {

// --- Main Title Function
async function refreshMainTitle(): Promise<void> {
const mainTitle = document.querySelector('h1.ytd-watch-metadata > yt-formatted-string') as HTMLElement;
const mainTitle = document.querySelector('h1.ytd-watch-metadata > yt-formatted-string') as HTMLElement;
if (mainTitle && window.location.pathname === '/watch' && !titleCache.hasElement(mainTitle)) {
mainTitleLog('Processing main title element');
const videoId = new URLSearchParams(window.location.search).get('v');
if (videoId) {
// --- Check if element has already been processed with this videoId
const currentTitle = mainTitle.textContent?.trim();
const originalTitle = await titleCache.getOriginalTitle(
`https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}`
);
// --- Translated title check is not working as the innerText is not modified by YouTube
// as soon as we modified it a first time.
// So we probably won't be able to detect if the title is already translated.
// Even if we could, it would be better to always update the title
// since YouTube won't update it.
let originalTitle: string | null = null;

// First try: Get title from player
try {
if (!originalTitle) {
// Extract current title from document.title
const currentPageTitle = document.title.replace(/ - YouTube$/, '');
mainTitleLog(`Failed to get original title from API: ${videoId}, using page title`);
updateMainTitleElement(mainTitle, currentPageTitle, videoId);
return;
}
if (currentTitle === originalTitle) {
//mainTitleLog('Title is not translated:', videoId);
return;
// Create and inject script
const mainTitleScript = document.createElement('script');
mainTitleScript.type = 'text/javascript';
mainTitleScript.src = browser.runtime.getURL('dist/content/titleTranslation/mainTitleScript.js');

// Set up event listener before injecting script
const playerTitle = await new Promise<string | null>((resolve) => {
const titleListener = (event: TitleDataEvent) => {
window.removeEventListener('ynt-title-data', titleListener as EventListener);
resolve(event.detail.title);
};
window.addEventListener('ynt-title-data', titleListener as EventListener);

// Inject script after listener is ready
document.head.appendChild(mainTitleScript);
});

if (playerTitle) {
//mainTitleLog('Got original title from player');
originalTitle = playerTitle;
}
//mainTitleLog('Main Title is translated:', videoId);
} catch (error) {
//mainTitleLog('Failed to get original title for comparison:', error);
}
mainTitleLog('Failed to get title from player:', error);
}

// Second try: Fallback to oembed API
if (!originalTitle) {
mainTitleLog('Falling back to oembed API');
originalTitle = await titleCache.getOriginalTitle(
`https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}`
);
}

// Last resort: Use page title
if (!originalTitle) {
const currentPageTitle = document.title.replace(/ - YouTube$/, '');
mainTitleLog(`Failed to get original title using both methods, using page title as last resort`);
updateMainTitleElement(mainTitle, currentPageTitle, videoId);
return;
}

// Skip if title is already correct
if (currentTitle === originalTitle) {
return;
}

// Apply the original title
try {
updateMainTitleElement(mainTitle, originalTitle, videoId);
updatePageTitle(originalTitle);
Expand Down
68 changes: 68 additions & 0 deletions src/content/titleTranslation/mainTitleScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2025-present YouGo (https://github.com/youg-o)
* This program is licensed under the GNU Affero General Public License v3.0.
* You may redistribute it and/or modify it under the terms of the license.
*
* Attribution must be given to the original author.
* This program is distributed without any warranty; see the license for details.
*/



(() => {
const LOG_PREFIX = '[YNT]';
const LOG_STYLES = {
MAIN_TITLE: { context: '[MAIN TITLE]', color: '#fcd34d' }
};

function createLogger(category) {
return (message, ...args) => {
console.log(
`%c${LOG_PREFIX}${category.context} ${message}`,
`color: ${category.color}`,
...args
);
};
}

const mainTitleLog = createLogger(LOG_STYLES.MAIN_TITLE);

function getOriginalTitle() {
const player = document.getElementById('movie_player');
if (!player) {
mainTitleLog('Player not found');
window.dispatchEvent(new CustomEvent('ynt-title-data', {
detail: { title: null }
}));
return;
}

try {
const response = player.getPlayerResponse();
// Debug logs
//mainTitleLog('Player response:', response);
//mainTitleLog('Video details:', response?.videoDetails);

const title = response?.videoDetails?.title;

if (title) {
mainTitleLog('Found title from player response:', title);
window.dispatchEvent(new CustomEvent('ynt-title-data', {
detail: { title }
}));
} else {
mainTitleLog('No title found in player response');
window.dispatchEvent(new CustomEvent('ynt-title-data', {
detail: { title: null }
}));
}
} catch (error) {
mainTitleLog('Error getting title:', error);
window.dispatchEvent(new CustomEvent('ynt-title-data', {
detail: { title: null }
}));
}
}

getOriginalTitle();
})();
10 changes: 9 additions & 1 deletion src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,12 @@ interface YouTubePlayerResponse {

interface Window {
ytInitialPlayerResponse?: YouTubePlayerResponse;
}
}

interface TitleData {
title: string | null;
}

interface TitleDataEvent extends CustomEvent {
detail: TitleData;
}

0 comments on commit df8c874

Please sign in to comment.