Skip to content

Instantly share code, notes, and snippets.

@rheajt
Created October 11, 2016 15:04
Show Gist options
  • Select an option

  • Save rheajt/20c99d1a452d8a85faf2cbce9c053d40 to your computer and use it in GitHub Desktop.

Select an option

Save rheajt/20c99d1a452d8a85faf2cbce9c053d40 to your computer and use it in GitHub Desktop.
Caption Creator for Drive

This is the Caption Creator for Google Drive App

Find out more about creating accessibility in the classroom at:

Check out the working app at: Caption Creator for Google Drive

Project idea @maoliver17

Coded by @rheajt

Open an MP4 file from your google drive and create an SRT file while watching the video. Option to download the srt as a text file or save to drive.

TODO:

  1. Why are some video types not playing in the
  2. Create keyboard shortcuts for starting/stopping video and changing the time signatures of the SRT
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<base target="_top">
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').setSandboxMode(HtmlService.SandboxMode.IFRAME).getContent() ?>
</head>
<body class="container">
<div class="row">
<div class="six columns">
<h4>Caption Creator for Google Drive</h4>
<h5><?= title ?></h5>
<input type="hidden" id="vid-title" value="<?= title ?>" />
<video id="video-player" width="100%" src="https://drive.google.com/uc?export=download&id=<?= vidId ?>" controls="controls"></video>
</div>
<div class="six columns" id="controls">
<label>Type your captions below</label>
<textarea id="caption-text" class="u-full-width"></textarea>
<label>
<b>SRT File Viewer </b>
<input type="checkbox" id="time-stamps">
<span class="label-body">Show time Stamps?</span>
</label>
<textarea id="caption-viewer"></textarea>
<div id="download-btns">
<button type="click" class="button-primary" id="download-srt">Download</button>
<button type="click" id="save-to-drive">Save SRT into a Google Doc</button>
</div>
</div>
</div> <!-- end of the row -->
<footer>
<h6><a href="//www.createaccessibility.com">www.createaccessibility.com</a> Created by <a href="https://twitter.com/maoliver17">@maoliver17</a> Coded by<a href="https://twitter.com/rheajt">@rheajt</a></h6>
</footer>
<script>
//a couple of global variables
var vid = document.getElementById('video-player');
var timerId;
var captionData = {
count: 1,
time: 0
};
//runs when user starts typing
function onType() {
clearTimeout(timerId);
if(!vid.paused) {
vid.pause();
}
startTimer();
}
//start or restart the timer when there is typing, set to run the doneType function after 3 seconds
function startTimer() {
timerId = setTimeout(doneType, 3000);
}
//function will run if there has been no typing for 3 seconds
function doneType() {
//get the current srt string as an array
var srt = getSrtArray();
var saveTimes = document.getElementById('time-stamps').checked;
var currentText = document.getElementById('caption-text');
srt.push(captionData.count + '\r\n');
if(saveTimes) {
srt.push(secondsToHms(captionData.time) + ' --> ' + secondsToHms(vid.currentTime) + '\r\n');
}
srt.push(currentText.value + '\r\n\r\n');
//increment the caption count and set the start time for the next caption
captionData.count++;
captionData.time = vid.currentTime;
//clear the textarea
currentText.value = '';
//set the textarea viewer value to the updated srt string and restart the video
document.getElementById('caption-viewer').value = srt.join('');
vid.play();
}
//function to get the textarea value and break it into an array
function getSrtArray() {
return document.getElementById('caption-viewer').value.split('\r\n');
}
//slightly modified function from stackoverflow, used to convert the seconds of the video into the
//format required by an srt file.
//i was advised that the srt file comes out better when it isn't used by @maoliver17 which is why
//the checkbox allows you to disable the need for this
function secondsToHms(d) {
d = Number(d);
var h = Math.floor(d / 3600);
var m = Math.floor(d % 3600 / 60);
var s = Math.floor(d % 3600 % 60);
var milli = d.toString().split('.')[1] || '000';
//added this to make sure the hour is always two digits. not sure when anyone will ever be captioning
//a 10+ hour video with this tool, but just in case...
if(h < 10) {
h = '0' + h;
}
//complicated ternary operation!
return ((h >= 0 ? h + ":" + (m < 10 ? "0" : "") : "") + m + ":" + (s < 10 ? "0" : "") + s + ',' + milli.substr(0, 3));
}
//another modified function i found on stackoverflow that sends a download. allows the user
//to trigger a download of the srt file that can be opened in a text editor
function downloadSrt() {
var srt = getSrtArray();
var text = srt.join('');
var filename = document.getElementById('vid-title').value + '.srt';
vid.pause();
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
if (document.createEvent) {
var event = document.createEvent('MouseEvents');
event.initEvent('click', true, true);
pom.dispatchEvent(event);
}
else {
pom.click();
}
}
//for the chromebook users, you have the option of saving the text to your drive and editing with docs
function saveToDrive() {
var srt = getSrtArray();
vid.pause();
google.script.run
.withSuccessHandler(function(resp) {
var btns = document.getElementById('download-btns');
//when the file is created, a link is added for the user
btns.innerHTML += '<br><a href="' + resp + '" target="_blank">Go to file</a>';
})
.saveToDrive(srt.join(''), document.getElementById('vid-title').value);
}
//event handlers for the user actions
document.getElementById('download-srt').addEventListener('click', downloadSrt);
document.getElementById('save-to-drive').addEventListener('click', saveToDrive);
document.getElementById('caption-text').addEventListener('keydown', onType);
</script>
</body>
</html>
/**
# This is the Caption Creator for Google Drive App
## Find out more about creating accessibility in the classroom at:
### [http://www.createaccessibility.com/](http://www.createaccessibility.com/)
### Check out the working app at: [Caption Creator for Google Drive](http://bit.ly/captioncreatorforgoogledrive)
### Project idea [@maoliver17](https://twitter.com/maoliver17)
### Coded by [@rheajt](https://twitter.com/rheajt)
Open an MP4 file from your google drive and create an SRT file while watching the video. Option to download the srt as a text file or save to drive.
### TODO:
1. Why are some video types not playing in the <video> tag?
2. Create keyboard shortcuts for starting/stopping video and changing the time signatures of the SRT
*/
function doGet(e) {
if(e.parameter.vidId) {
var html = HtmlService.createTemplateFromFile('Caption');
html.vidId = e.parameter.vidId;
html.title = e.parameter.title;
return html.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
} else {
return HtmlService.createTemplateFromFile('Picker').evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
}
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
}
function saveToDrive(srt, title) {
var doc = DocumentApp.create(title + '.srt');
var body = doc.getBody();
body.appendParagraph(srt);
return doc.getUrl();
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<?!= HtmlService.createHtmlOutputFromFile('Stylesheet').setSandboxMode(HtmlService.SandboxMode.IFRAME).getContent() ?>
<script>
// IMPORTANT: Replace the value for DEVELOPER_KEY with the API key obtained
// from the Google Developers Console.
var DEVELOPER_KEY = 'AIzaSyAA7NDasSelVPveibkzuVhYPjvbYONXMes';
var DIALOG_DIMENSIONS = {width: 600, height: 425};
var pickerApiLoaded = false;
/**
* Loads the Google Picker API.
*/
function onApiLoad() {
gapi.load('picker', {'callback': function() {
pickerApiLoaded = true;
}});
}
/**
* Gets the user's OAuth 2.0 access token from the server-side script so that
* it can be passed to Picker. This technique keeps Picker from needing to
* show its own authorization dialog, but is only possible if the OAuth scope
* that Picker needs is available in Apps Script. Otherwise, your Picker code
* will need to declare its own OAuth scopes.
*/
function getOAuthToken() {
google.script.run.withSuccessHandler(createPicker)
.withFailureHandler(showError).getOAuthToken();
}
/**
* Creates a Picker that can access the user's spreadsheets. This function
* uses advanced options to hide the Picker's left navigation panel and
* default title bar.
*
* @param {string} token An OAuth 2.0 access token that lets Picker access the
* file type specified in the addView call.
*/
function createPicker(token) {
if (pickerApiLoaded && token) {
var picker = new google.picker.PickerBuilder()
// Instruct Picker to display only spreadsheets in Drive. For other
// views, see https://developers.google.com/picker/docs/#otherviews
.addView(google.picker.ViewId.DOCS_VIDEOS)
// Hide the navigation panel so that Picker fills more of the dialog.
.enableFeature(google.picker.Feature.NAV_HIDDEN)
// Hide the title bar since an Apps Script dialog already has a title.
.hideTitleBar()
.setOAuthToken(token)
.setDeveloperKey(DEVELOPER_KEY)
.setCallback(pickerCallback)
.setOrigin(google.script.host.origin)
// Instruct Picker to fill the dialog, minus 2 pixels for the border.
.setSize(DIALOG_DIMENSIONS.width - 2,
DIALOG_DIMENSIONS.height - 2)
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
}
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* @param {object} data The response object.
*/
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
var title = doc[google.picker.Document.NAME];
window.location.href = 'https://script.google.com/macros/s/AKfycbwRMgzKtBzlqF-dRCy5AdELuOlh-KPeVi0jXOqrsfqdTa6Ka94d/exec?vidId=' + id + '&title=' + encodeURIComponent(title);
} else if (action == google.picker.Action.CANCEL) {
document.getElementById('result').innerHTML = 'Picker canceled.';
}
}
/**
* Displays an error message within the #result element.
*
* @param {string} message The error message to display.
*/
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body class="container">
<h4>Caption Generator for Google Drive</h4>
<h6>Select an MP4 file from your google drive to get started.</h6>
<div>
<button onclick='getOAuthToken()'>Select a file</button>
<p id='result'></p>
</div>
<script src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
<link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
<style>
/* Skeleton CSS www.getskeleton.com */
.container {
position: relative;
width: 100%;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box; }
.column,
.columns {
width: 100%;
float: left;
box-sizing: border-box; }
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0; }
}
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%; }
.column,
.columns {
margin-left: 4%; }
.column:first-child,
.columns:first-child {
margin-left: 0; }
.one.column,
.one.columns { width: 4.66666666667%; }
.two.columns { width: 13.3333333333%; }
.three.columns { width: 22%; }
.four.columns { width: 30.6666666667%; }
.five.columns { width: 39.3333333333%; }
.six.columns { width: 48%; }
.seven.columns { width: 56.6666666667%; }
.eight.columns { width: 65.3333333333%; }
.nine.columns { width: 74.0%; }
.ten.columns { width: 82.6666666667%; }
.eleven.columns { width: 91.3333333333%; }
.twelve.columns { width: 100%; margin-left: 0; }
.one-third.column { width: 30.6666666667%; }
.two-thirds.column { width: 65.3333333333%; }
.one-half.column { width: 48%; }
/* Offsets */
.offset-by-one.column,
.offset-by-one.columns { margin-left: 8.66666666667%; }
.offset-by-two.column,
.offset-by-two.columns { margin-left: 17.3333333333%; }
.offset-by-three.column,
.offset-by-three.columns { margin-left: 26%; }
.offset-by-four.column,
.offset-by-four.columns { margin-left: 34.6666666667%; }
.offset-by-five.column,
.offset-by-five.columns { margin-left: 43.3333333333%; }
.offset-by-six.column,
.offset-by-six.columns { margin-left: 52%; }
.offset-by-seven.column,
.offset-by-seven.columns { margin-left: 60.6666666667%; }
.offset-by-eight.column,
.offset-by-eight.columns { margin-left: 69.3333333333%; }
.offset-by-nine.column,
.offset-by-nine.columns { margin-left: 78.0%; }
.offset-by-ten.column,
.offset-by-ten.columns { margin-left: 86.6666666667%; }
.offset-by-eleven.column,
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
.offset-by-one-third.column,
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
.offset-by-two-thirds.column,
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
.offset-by-one-half.column,
.offset-by-one-half.columns { margin-left: 52%; }
}
/* Base Styles
–––––––––––––––––––––––––––––––––––––––––––––––––– */
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
font-size: 62.5%; }
body {
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
line-height: 1.6;
font-weight: 400;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #222; }
/* Typography
–––––––––––––––––––––––––––––––––––––––––––––––––– */
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 2rem;
font-weight: 300; }
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
/* Larger than phablet */
@media (min-width: 550px) {
h1 { font-size: 5.0rem; }
h2 { font-size: 4.2rem; }
h3 { font-size: 3.6rem; }
h4 { font-size: 3.0rem; }
h5 { font-size: 2.4rem; }
h6 { font-size: 1.5rem; }
}
p {
margin-top: 0; }
/* Links
–––––––––––––––––––––––––––––––––––––––––––––––––– */
a {
color: #1EAEDB; }
a:hover {
color: #0FA0CE; }
/* Buttons
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
color: #333;
border-color: #888;
outline: 0; }
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
color: #FFF;
background-color: #33C3F0;
border-color: #33C3F0; }
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
color: #FFF;
background-color: #1EAEDB;
border-color: #1EAEDB; }
/* Forms
–––––––––––––––––––––––––––––––––––––––––––––––––– */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea,
select {
height: 38px;
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
background-color: #fff;
border: 1px solid #D1D1D1;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box; }
/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none; }
textarea {
min-height: 65px;
padding-top: 6px;
padding-bottom: 6px; }
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="text"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
border: 1px solid #33C3F0;
outline: 0; }
label,
legend {
display: block;
margin-bottom: .5rem;
font-weight: 600; }
fieldset {
padding: 0;
border-width: 0; }
input[type="checkbox"],
input[type="radio"] {
display: inline; }
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal; }
/* Lists
–––––––––––––––––––––––––––––––––––––––––––––––––– */
ul {
list-style: circle inside; }
ol {
list-style: decimal inside; }
ol, ul {
padding-left: 0;
margin-top: 0; }
ul ul,
ul ol,
ol ol,
ol ul {
margin: 1.5rem 0 1.5rem 3rem;
font-size: 90%; }
li {
margin-bottom: 1rem; }
/* Code
–––––––––––––––––––––––––––––––––––––––––––––––––– */
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
white-space: nowrap;
background: #F1F1F1;
border: 1px solid #E1E1E1;
border-radius: 4px; }
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre; }
/* Tables
–––––––––––––––––––––––––––––––––––––––––––––––––– */
th,
td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #E1E1E1; }
th:first-child,
td:first-child {
padding-left: 0; }
th:last-child,
td:last-child {
padding-right: 0; }
/* Spacing
–––––––––––––––––––––––––––––––––––––––––––––––––– */
button,
.button {
margin-bottom: 1rem; }
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem; }
pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
margin-bottom: 2.5rem; }
/* Utilities
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.u-full-width {
width: 100%;
box-sizing: border-box; }
.u-max-full-width {
max-width: 100%;
box-sizing: border-box; }
.u-pull-right {
float: right; }
.u-pull-left {
float: left; }
/* Misc
–––––––––––––––––––––––––––––––––––––––––––––––––– */
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #E1E1E1; }
/* Clearing
–––––––––––––––––––––––––––––––––––––––––––––––––– */
/* Self Clearing Goodness */
.container:after,
.row:after,
.u-cf {
content: "";
display: table;
clear: both; }
/* Media Queries
–––––––––––––––––––––––––––––––––––––––––––––––––– */
/*
Note: The best way to structure the use of media queries is to create the queries
near the relevant code. For example, if you wanted to change the styles for buttons
on small devices, paste the mobile query code up in the buttons section and style it
there.
*/
/* Larger than mobile */
@media (min-width: 400px) {}
/* Larger than phablet (also point when grid becomes active) */
@media (min-width: 550px) {}
/* Larger than tablet */
@media (min-width: 750px) {}
/* Larger than desktop */
@media (min-width: 1000px) {}
/* Larger than Desktop HD */
@media (min-width: 1200px) {}
/* CUSTOM CSS STYLES */
#controls .caption-text {
width: 100%;
height: 25vh;
}
#controls #caption-viewer {
width: 100%;
height: 20vh;
min-height: 400px;
border: 1px solid black;
overflow: auto;
}
.row {
height: 95vh;
}
footer {
text-align: center;
position: fixed;
bottom: 0;
height: 5vh;
background-color: white;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment