Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ There are three ways to install Gentle.

By default, the aligner listens at http://localhost:8765. That page has a graphical interface for transcribing audio, viewing results, and downloading data.

Alignment result pages can also drive transcript playback from a YouTube video instead of the uploaded audio file. Add a `youtube` query parameter with a video ID or URL to the result page:

```text
http://localhost:8765/transcriptions/<id>/?youtube=M7lc1UVf-VE
http://localhost:8765/transcriptions/<id>/?youtube=https://www.youtube.com/watch?v=M7lc1UVf-VE
```

Clicking aligned transcript words will seek and play the YouTube video at the matching timestamp.

There is also a REST API so you can use Gentle in your programs. Here's an example of how to use the API with CURL:

```bash
Expand Down
149 changes: 143 additions & 6 deletions www/view_alignment.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,15 @@
.home:hover a {
background: #555;
}
#audio {
#audio, #youtube-player {
margin-top: 9px;
width: 50%;
display: inline-block;
}
#youtube-player {
height: 28px;
display: none;
}
#transcript {
margin: 0 15px;
margin-top: 70px;
Expand Down Expand Up @@ -116,13 +120,25 @@
font-style: italic;
font-family: Helvetica, sans-serif;
padding: 10px;
}
body.youtube #header {
height: 190px;
}
body.youtube #youtube-player {
width: 267px;
height: 150px;
margin-top: 20px;
}
body.youtube #transcript {
margin-top: 210px;
}
</style>
</head>
<body>
<div id="header">
<h1 class="home"><a href="/">Gentle</a></h1>
<audio id="audio" src="a.wav" controls="true"></audio>
<div id="youtube-player"></div>
<img src="/preloader.gif" id="preloader" alt="loading...">
<span id="downloads"> </div>
</div>
Expand All @@ -148,10 +164,131 @@ <h1 class="home"><a href="/">Gentle</a></h1>
}

var $a = document.getElementById("audio");
var $youtube = document.getElementById("youtube-player");
var youtube_player = null;
var youtube_ready = false;
var youtube_pending_seek = null;

function get_query_param(name) {
var pairs = window.location.search.replace(/^\?/, '').split('&');
for(var i = 0; i < pairs.length; i++) {
var idx = pairs[i].indexOf('=');
var key = idx >= 0 ? pairs[i].slice(0, idx) : pairs[i];
var value = idx >= 0 ? pairs[i].slice(idx + 1) : '';
if(decodeURIComponent(key || '') == name) {
return decodeURIComponent(value.replace(/\+/g, ' '));
}
}
}

function parse_youtube_id(value) {
if(!value) {
return null;
}
var match = value.match(/^[A-Za-z0-9_-]{11}$/);
if(match) {
return value;
}
match = value.match(/[?&]v=([A-Za-z0-9_-]{11})/);
if(match) {
return match[1];
}
match = value.match(/youtu\.be\/([A-Za-z0-9_-]{11})/);
if(match) {
return match[1];
}
match = value.match(/\/embed\/([A-Za-z0-9_-]{11})/);
if(match) {
return match[1];
}
return null;
}

var youtube_id = parse_youtube_id(get_query_param('youtube') || get_query_param('youtube_id'));

var media = {
currentTime: function() {
return $a.currentTime;
},
seek: function(t) {
$a.currentTime = t;
},
play: function() {
$a.play();
},
pause: function() {
$a.pause();
},
hide: function() {
$a.style.visibility = 'hidden';
}
};

function enable_youtube_player(video_id) {
document.body.className += (document.body.className ? ' ' : '') + 'youtube';
$a.style.display = 'none';
$youtube.style.display = 'inline-block';

media.currentTime = function() {
if(youtube_ready && youtube_player && youtube_player.getCurrentTime) {
return youtube_player.getCurrentTime();
}
return 0;
};
media.seek = function(t) {
youtube_pending_seek = t;
if(youtube_ready && youtube_player && youtube_player.seekTo) {
youtube_player.seekTo(t, true);
}
};
media.play = function() {
if(youtube_ready && youtube_player && youtube_player.playVideo) {
youtube_player.playVideo();
}
};
media.pause = function() {
if(youtube_ready && youtube_player && youtube_player.pauseVideo) {
youtube_player.pauseVideo();
}
};
media.hide = function() {
$youtube.style.visibility = 'hidden';
};

window.onYouTubeIframeAPIReady = function() {
youtube_player = new YT.Player('youtube-player', {
height: '150',
width: '267',
videoId: video_id,
playerVars: {
controls: 1,
modestbranding: 1,
rel: 0
},
events: {
onReady: function() {
youtube_ready = true;
if(youtube_pending_seek !== null) {
youtube_player.seekTo(youtube_pending_seek, true);
}
}
}
});
};

var tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
document.body.appendChild(tag);
}

if(youtube_id) {
enable_youtube_player(youtube_id);
}

window.onkeydown = function(ev) {
if(ev.keyCode == 32) {
ev.preventDefault();
$a.pause();
media.pause();
}
}

Expand Down Expand Up @@ -223,7 +360,7 @@ <h1 class="home"><a href="/">Gentle</a></h1>
}

function highlight_word() {
var t = $a.currentTime;
var t = media.currentTime();
// XXX: O(N); use binary search
var hits = wds.filter(function(x) {
return (t - x.start) > 0.01 && (x.end - t) > 0.01;
Expand Down Expand Up @@ -285,8 +422,8 @@ <h1 class="home"><a href="/">Gentle</a></h1>
$wd.onclick = function() {
if(wd.start !== undefined) {
console.log(wd.start);
$a.currentTime = wd.start;
$a.play();
media.seek(wd.start);
media.play();
}
};
$trans.appendChild($wd);
Expand Down Expand Up @@ -372,7 +509,7 @@ <h1 class="home"><a href="/">Gentle</a></h1>
else {
// Show the status
get_json('status.json', function(ret) {
$a.style.visibility = 'hidden';
media.hide();
if (ret.status == 'ERROR') {
$preloader.style.visibility = 'hidden';
$trans.innerHTML = '<b>' + ret.status + ': ' + ret.error + '</b>';
Expand Down