-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutils.js
More file actions
142 lines (123 loc) · 3.78 KB
/
Copy pathutils.js
File metadata and controls
142 lines (123 loc) · 3.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Utility Functions Module
// Shared utility functions used across the extension
const TranscriptUtils = (function() {
'use strict';
/**
* Get video ID from current URL
* @returns {string|null} Video ID or null if not found
*/
function getVideoId() {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('v');
}
/**
* Format seconds to timestamp (MM:SS or HH:MM:SS)
* @param {number} seconds - Time in seconds
* @returns {string} Formatted timestamp
*/
function formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
/**
* Decode HTML entities and clean up text
* @param {string} text - Text with HTML entities
* @returns {string} Decoded text
*/
function decodeHTMLEntities(text) {
const textarea = document.createElement('textarea');
textarea.innerHTML = text;
let decoded = textarea.value;
// Clean up newlines and extra spaces
decoded = decoded.replace(/\n/g, ' ').replace(/\s+/g, ' ');
return decoded;
}
/**
* Escape special regex characters
* @param {string} string - String to escape
* @returns {string} Escaped string
*/
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Wait for an element to exist in the DOM
* @param {string} selector - CSS selector
* @param {number} timeout - Maximum wait time in milliseconds
* @returns {Promise<Element>} Promise that resolves with the element
*/
function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const existingElement = document.querySelector(selector);
if (existingElement) {
resolve(existingElement);
return;
}
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
obs.disconnect();
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
setTimeout(() => {
observer.disconnect();
reject(new Error(`Timeout waiting for element: ${selector}`));
}, timeout);
});
}
/**
* Get active subtitle language from video player
* @returns {string|null} Language code or null
*/
function getActiveSubtitleLanguage() {
try {
const video = document.querySelector('video');
if (!video) return null;
const textTracks = video.textTracks;
if (!textTracks) return null;
for (let i = 0; i < textTracks.length; i++) {
const track = textTracks[i];
if (track.mode === 'showing') {
return track.language;
}
}
return null;
} catch (error) {
console.error('Error getting active subtitle language:', error);
return null;
}
}
/**
* Get all available languages from caption tracks
* @param {Array} tracks - Array of caption track objects
* @returns {Array} Array of language objects
*/
function getAvailableLanguages(tracks) {
if (!tracks || !Array.isArray(tracks)) return [];
return tracks.map(track => ({
code: track.languageCode,
name: track.name?.simpleText || track.languageCode,
isTranslatable: track.isTranslatable || false
}));
}
// Public API
return {
getVideoId,
formatTime,
decodeHTMLEntities,
escapeRegex,
waitForElement,
getActiveSubtitleLanguage,
getAvailableLanguages
};
})();