This repository has been archived by the owner on Jun 6, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e4bf320
Showing
14 changed files
with
1,289 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"browser": true, | ||
"devel": true, | ||
"eqeqeq": true, | ||
"esversion": 8, | ||
"globals": { | ||
"browser": false, // global variable in Firefox | ||
"self": false | ||
}, | ||
"laxbreak": true, | ||
"newcap": false, | ||
"nonew": false, | ||
"strict": "global", | ||
"sub": true, | ||
"undef": true, | ||
"unused": true, | ||
"validthis": true | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
## CCaptioner | ||
|
||
A very simple extension which purpose is to assign a text track to a [HTML5 | ||
`video` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video) | ||
in a web page. | ||
|
||
Many HTML5 video players do not offer the ability to import text track for | ||
captions/subtitles purpose. The purpose of this extension is to remediate | ||
this problem. | ||
|
||
When you want to assign a text track to a video element in a web page: | ||
|
||
- Open the popup menu and click __"Assign text track to..."__ | ||
- Move the mouse over the target video element | ||
- Click the video element if needed | ||
- A file picker will appear | ||
- Pick the `.srt` or `.vtt` file to use as text track | ||
|
||
The video should now render the captions/subtitles of the file you | ||
selected. | ||
|
||
The content scripts of CCaptioner are injected **if and only if** you click on | ||
its toolbar icon while on a specific web site, and only for that web site. | ||
Once the text track is embedded, the content script terminates and should be | ||
garbage-collected by your browser's JavaScript engine. | ||
|
||
Once a text track has been assigned to a video element on a given page, you | ||
can time-shift the text track through CCaptioner's popup panel -- this is | ||
useful when the text track is not well synchronized with the video content. | ||
|
||
### Permissions | ||
|
||
#### `activeTab` | ||
|
||
This permission means that the extension will be able to interact | ||
with a web page **only** when you click its icon in the toolbar; so | ||
CCaptioner's content script is injected **only** when you demand it by clicking | ||
CCaptioner's toolbar icon. | ||
|
||
#### `<all_urls>` | ||
|
||
This permission is necessary to ensure CCaptioner's content script can also be | ||
injected in embedded `iframe` elements in a page -- it is not uncommon for | ||
video players to be inside an `iframe` which origin is different from the | ||
origin of the root document. | ||
|
||
### Credits | ||
|
||
The CCaptioner's icon is from <https://en.wikipedia.org/wiki/File:Closed_captioning_symbol.svg>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/usr/bin/env bash | ||
# | ||
# This script assumes a linux environment | ||
|
||
echo "*** CCaptioner: Creating extension packages" | ||
|
||
DES=~/Downloads/ccaptioner | ||
rm -rf $DES | ||
|
||
# Chromium | ||
echo "*** Creating ccaptioner.chromium" | ||
DESCH=$DES/ccaptioner.chromium | ||
mkdir -p $DESCH | ||
cp -R ./src/* $DESCH/ | ||
cp ./LICENSE.txt $DESCH/ | ||
cp ./manifest-chromium.json $DESCH/manifest.json | ||
|
||
# Firefox | ||
echo "*** Creating ccaptioner.firefox" | ||
DESFF=$DES/ccaptioner.firefox | ||
mkdir -p $DESFF | ||
cp -R ./src/* $DESFF/ | ||
cp ./LICENSE.txt $DESFF/ | ||
cp ./manifest-firefox.json $DESFF/manifest.json | ||
pushd $DESFF > /dev/null | ||
zip ../$(basename $DESFF).xpi -qr * | ||
popd > /dev/null |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"author": "Raymond Hill", | ||
"browser_action": { | ||
"default_icon": { | ||
"64": "icon-64.png" | ||
}, | ||
"default_title": "CCaptioner", | ||
"default_popup": "popup.html" | ||
}, | ||
"description": "Assign your own text track to a video element in a web page", | ||
"icons": { | ||
"64": "icon-64.png" | ||
}, | ||
"manifest_version": 2, | ||
"name": "CCaptioner", | ||
"permissions": [ | ||
"activeTab", | ||
"<all_urls>" | ||
], | ||
"version": "1.0.0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"author": "Raymond Hill", | ||
"browser_action": { | ||
"default_icon": { | ||
"64": "icon-64.png" | ||
}, | ||
"default_title": "CCaptioner", | ||
"default_popup": "popup.html" | ||
}, | ||
"browser_specific_settings": { | ||
"gecko": { | ||
"id": "[email protected]", | ||
"strict_min_version": "68.0" | ||
} | ||
}, | ||
"description": "Assign your own text track to a video element in a web page", | ||
"icons": { | ||
"64": "icon-64.png" | ||
}, | ||
"manifest_version": 2, | ||
"name": "CCaptioner", | ||
"permissions": [ | ||
"activeTab", | ||
"<all_urls>" | ||
], | ||
"version": "1.0.0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/******************************************************************************* | ||
CCaptioner - a browser extension to block requests. | ||
Copyright (C) 2020-present Raymond Hill | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see {http://www.gnu.org/licenses/}. | ||
Home: https://github.com/gorhill/CCaptioner | ||
*/ | ||
|
||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track | ||
|
||
'use strict'; | ||
|
||
(( ) => { | ||
if ( document.querySelector('video') === null ) { return; } | ||
if ( | ||
self.closedCaptioner instanceof Object && | ||
self.closedCaptioner.closedCaptioner === true | ||
) { | ||
return; | ||
} | ||
self.closedCaptioner = { closedCaptioner: true }; | ||
|
||
let clientX = 0; | ||
let clientY = 0; | ||
|
||
const handleMouseEvent = function(ev) { | ||
clientX = ev.clientX; | ||
clientY = ev.clientY; | ||
findVideoUnderMouse(); | ||
}; | ||
const listenerOptions = { | ||
passive: true, | ||
}; | ||
|
||
document.addEventListener('click', handleMouseEvent, listenerOptions); | ||
document.addEventListener('mousedown', handleMouseEvent, listenerOptions); | ||
document.addEventListener('mousemove', handleMouseEvent, listenerOptions); | ||
|
||
let timer = setTimeout( | ||
( ) => { | ||
timer = undefined; | ||
stop(); | ||
}, | ||
10000 | ||
); | ||
|
||
const stop = function() { | ||
document.removeEventListener('click', handleMouseEvent, listenerOptions); | ||
document.removeEventListener('mousedown', handleMouseEvent, listenerOptions); | ||
document.removeEventListener('mousemove', handleMouseEvent, listenerOptions); | ||
if ( timer !== undefined ) { | ||
clearTimeout(timer); | ||
timer = undefined; | ||
} | ||
self.closedCaptioner = undefined; | ||
}; | ||
|
||
const findVideoUnderMouse = function() { | ||
const elems = document.elementsFromPoint(clientX, clientY); | ||
for ( const elem of elems ) { | ||
if ( elem.localName !== 'video' ) { continue; } | ||
stop(); | ||
srtPick(elem); | ||
} | ||
}; | ||
|
||
const srtPick = function(video) { | ||
const input = document.createElement('input'); | ||
input.setAttribute('type', 'file'); | ||
input.setAttribute('accept', '.srt,.vtt'); | ||
input.style.display = 'none'; | ||
|
||
input.addEventListener('change', ev => { | ||
const button = ev.target; | ||
const file = button.files[0]; | ||
if ( file === undefined || file.name === '' ) { return; } | ||
if ( | ||
file.type.indexOf('text') !== 0 && | ||
file.type.indexOf('subrip') === -1 | ||
) { | ||
return; | ||
} | ||
const fr = new FileReader(); | ||
fr.onload = ( ) => { | ||
srtInstall(video, srtParse(fr.result)); | ||
}; | ||
fr.readAsText(file); | ||
}); | ||
|
||
input.click(); | ||
video.pause(); | ||
}; | ||
|
||
const srtParse = function(raw) { | ||
const vtt = [ 'WEBVTT', '' ]; | ||
const entries = raw.replace(/(\r\n|\n\r)/g, '\n') | ||
.trim() | ||
.split(/\s*\n\n+\s*/); | ||
for ( const entry of entries ) { | ||
const lines = entry.split(/\s*\n\s*/); | ||
if ( /^\d+$/.test(lines[0]) === false ) { continue; } | ||
const times = /(\S+)\s+--+>\s+(\S+)/.exec(lines[1]); | ||
if ( times === null ) { continue; } | ||
vtt.push( | ||
lines[0], | ||
times[1].replace(/,/g, '.') + ' --> ' + times[2].replace(/,/g, '.'), | ||
lines.slice(2).join('\n'), | ||
'' | ||
); | ||
} | ||
return vtt.join('\n'); | ||
}; | ||
|
||
const srtInstall = function(video, vtt) { | ||
for ( const elem of video.querySelectorAll('track') ) { | ||
elem.remove(); | ||
} | ||
const blob = new Blob([ vtt ], { type: 'text/vtt' }); | ||
const blobURL = URL.createObjectURL(blob); | ||
const track = document.createElement('track'); | ||
track.setAttribute('default', ''); | ||
track.setAttribute('kind', 'subtitles'); | ||
track.setAttribute('label', 'CCaptioner'); | ||
track.setAttribute('srclang', 'zz'); | ||
track.setAttribute('src', blobURL); | ||
track.setAttribute('data-vtt-delta', '0'); | ||
track.setAttribute('data-vtt', vtt); | ||
video.appendChild(track); | ||
track.track.mode = 'showing'; | ||
}; | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
/******************************************************************************* | ||
CCaptioner - a browser extension to block requests. | ||
Copyright (C) 2020-present Raymond Hill | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see {http://www.gnu.org/licenses/}. | ||
Home: https://github.com/gorhill/ccaptioner | ||
*/ | ||
|
||
'use strict'; | ||
|
||
(( ) => { | ||
const track = document.querySelector('video > track[label="CCaptioner"][data-vtt]'); | ||
if ( track === null ) { return; } | ||
return parseFloat(track.getAttribute('data-vtt-offset') || '0') || 0; | ||
})(); |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/******************************************************************************* | ||
CCaptioner - a browser extension to block requests. | ||
Copyright (C) 2020-present Raymond Hill | ||
This program is free software: you can redistribute it and/or modify | ||
it under the terms of the GNU General Public License as published by | ||
the Free Software Foundation, either version 3 of the License, or | ||
(at your option) any later version. | ||
This program is distributed in the hope that it will be useful, | ||
but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
GNU General Public License for more details. | ||
You should have received a copy of the GNU General Public License | ||
along with this program. If not, see {http://www.gnu.org/licenses/}. | ||
Home: https://github.com/gorhill/ccaptioner | ||
*/ | ||
|
||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track | ||
|
||
'use strict'; | ||
|
||
// Offset text track as per `data-vtt-offset` attribute | ||
|
||
(( ) => { | ||
const oldTrack = document.querySelector('video > track[label="CCaptioner"][data-vtt]'); | ||
if ( oldTrack === null ) { return; } | ||
|
||
const timeDelta = parseInt(oldTrack.getAttribute('data-vtt-offset') || '0', 10); | ||
|
||
let vtt = oldTrack.getAttribute('data-vtt'); | ||
if ( typeof vtt !== 'string' || vtt === '' ) { return; } | ||
|
||
const timeShift = function(timecode) { | ||
const fields = /(\d+):(\d+):(\d+).(\d+)/.exec(timecode); | ||
let seconds = parseInt(fields[1], 10) * 3600 + | ||
parseInt(fields[2], 10) * 60 + | ||
parseInt(fields[3], 10) * 1 + | ||
timeDelta; | ||
if ( seconds < 0 ) { return '00:00:00.000'; } | ||
const hh = Math.floor(seconds / 3600).toString().padStart(2, '0'); | ||
seconds %= 3600; | ||
const mm = Math.floor(seconds / 60).toString().padStart(2, '0'); | ||
seconds %= 60; | ||
const ss = seconds.toString().padStart(2, '0'); | ||
return `${hh}:${mm}:${ss}.${fields[4]}`; | ||
}; | ||
|
||
const entries = vtt.trim().split(/\n\n/); | ||
|
||
for ( let i = 0; i < entries.length; i++ ) { | ||
const lines = entries[i].split(/\n/); | ||
const times = /(\S+) --> (\S+)/.exec(lines[1]); | ||
if ( times === null ) { continue; } | ||
const t0 = timeShift(times[1]); | ||
if ( t0 === '' ) { return; } | ||
const t1 = timeShift(times[2]); | ||
lines[1] = `${t0} --> ${t1}`; | ||
entries[i] = lines.join('\n'); | ||
} | ||
vtt = entries.join('\n\n'); | ||
|
||
const blob = new Blob([ vtt ], { type: 'text/vtt' }); | ||
const blobURL = URL.createObjectURL(blob); | ||
const newTrack = oldTrack.cloneNode(false); | ||
newTrack.setAttribute('src', blobURL); | ||
oldTrack.replaceWith(newTrack); | ||
newTrack.track.mode = 'showing'; | ||
})(); |
Oops, something went wrong.