\n\n';
24 |
25 | return ttmlOut;
26 | };
27 |
28 | export default ttmlGeneratorPremiere;
--------------------------------------------------------------------------------
/packages/index.js:
--------------------------------------------------------------------------------
1 | import TranscriptEditor from './components/transcript-editor/index.js';
2 | import TimedTextEditor from './components/timed-text-editor/index.js';
3 | import Settings from './components/settings/index.js';
4 | import KeyboardShortcuts from './components/keyboard-shortcuts/index.js';
5 | import VideoPlayer from './components/video-player/index.js';
6 | import MediaPlayer from './components/media-player/index.js';
7 | import PlayerControls from './components/media-player/src/PlayerControls/index.js';
8 | import groupWordsInParagraphsBySpeakersDPE from './stt-adapters/digital-paper-edit/group-words-by-speakers.js';
9 |
10 | import {
11 | secondsToTimecode,
12 | timecodeToSeconds,
13 | shortTimecode
14 | } from './util/timecode-converter/index.js';
15 |
16 | import exportAdapter from './export-adapters/index.js';
17 | import sttJsonAdapter from './stt-adapters/index.js';
18 |
19 | export default TranscriptEditor;
20 |
21 | export {
22 | TranscriptEditor,
23 | TimedTextEditor,
24 | VideoPlayer,
25 | MediaPlayer,
26 | PlayerControls,
27 | Settings,
28 | KeyboardShortcuts,
29 | secondsToTimecode,
30 | timecodeToSeconds,
31 | shortTimecode,
32 | exportAdapter,
33 | sttJsonAdapter,
34 | groupWordsInParagraphsBySpeakersDPE
35 | };
36 |
--------------------------------------------------------------------------------
/packages/util/timecode-converter/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Wrapping around "time stamps" and timecode conversion modules
3 | * To provide more support for variety of formats.
4 | */
5 | import secondsToTimecode from './src/secondsToTimecode';
6 | import timecodeToSecondsHelper from './src/timecodeToSeconds';
7 | import padTimeToTimecode from './src/padTimeToTimecode';
8 |
9 | /**
10 | * @param {*} time
11 | * Can take as input timecodes in the following formats
12 | * - hh:mm:ss:ff
13 | * - mm:ss
14 | * - m:ss
15 | * - ss - seconds --> if it's already in seconds then it just returns seconds
16 | * - hh:mm:ff
17 | * @todo could be refactored with some helper functions for clarity
18 | */
19 | const timecodeToSeconds = (time) => {
20 | if (typeof time === 'string') {
21 | const resultPadded = padTimeToTimecode(time);
22 | const resultConverted = timecodeToSecondsHelper(resultPadded);
23 |
24 | return resultConverted;
25 | }
26 |
27 | // assuming it receive timecode as seconds as string '600'
28 | return parseFloat(time);
29 | };
30 |
31 | const shortTimecode = (time) => {
32 | const timecode = secondsToTimecode(time);
33 |
34 | return timecode.slice(0, -3);
35 | };
36 |
37 | export { secondsToTimecode, timecodeToSeconds, shortTimecode };
38 |
--------------------------------------------------------------------------------
/demo/local-storage.js:
--------------------------------------------------------------------------------
1 | const localSave = (mediaUrl, fileName, data) => {
2 | let mediaUrlName = mediaUrl;
3 | // if using local media instead of using random blob name
4 | // that makes it impossible to retrieve from on page refresh
5 | // use file name
6 | if (mediaUrlName.includes('blob')) {
7 | mediaUrlName = fileName;
8 | }
9 |
10 | localStorage.setItem(`draftJs-${ mediaUrlName }`, JSON.stringify(data));
11 | };
12 |
13 | // eslint-disable-next-line class-methods-use-this
14 | const isPresentInLocalStorage = (mediaUrl, fileName) => {
15 | if (mediaUrl !== null) {
16 | let mediaUrlName = mediaUrl;
17 | if (mediaUrl.includes('blob')) {
18 | mediaUrlName = fileName;
19 | }
20 |
21 | const data = localStorage.getItem(`draftJs-${ mediaUrlName }`);
22 | if (data !== null) {
23 | return true;
24 | }
25 |
26 | return false;
27 | }
28 |
29 | return false;
30 | };
31 |
32 | const loadLocalSavedData = (mediaUrl, fileName) => {
33 | let mediaUrlName = mediaUrl;
34 | if (mediaUrl.includes('blob')) {
35 | mediaUrlName = fileName;
36 | }
37 | const data = JSON.parse(localStorage.getItem(`draftJs-${ mediaUrlName }`));
38 | return data;
39 | };
40 |
41 | export { loadLocalSavedData, isPresentInLocalStorage, localSave };
42 |
--------------------------------------------------------------------------------
/packages/components/settings/index.module.css:
--------------------------------------------------------------------------------
1 | .settings {
2 | position: absolute;
3 | left: 0;
4 | right: 0;
5 | margin: 0 auto;
6 | width: 30%;
7 | min-width: 300px;
8 | min-height: 300px;
9 | text-align: center;
10 | vertical-align: middle;
11 | color: white;
12 | background: #4a4a4a;
13 | padding: 1em;
14 | font-weight: lighter;
15 | z-index: 2;
16 | }
17 |
18 | .header {
19 | margin-top: 0;
20 | margin-bottom: 1em;
21 | }
22 |
23 | .closeButton {
24 | position: absolute;
25 | top: 0;
26 | right: 0;
27 | padding: 1em;
28 | cursor: pointer;
29 | }
30 |
31 | .controlsContainer {
32 | display: flex;
33 | flex-direction: column;
34 | align-content: flex-start;
35 | align-items: center;
36 | margin: 0 auto;
37 | }
38 |
39 | .settingElement {
40 | text-align: left;
41 | align-self: auto;
42 | margin-bottom: 0.5em;
43 | }
44 |
45 | .label {
46 | display: inline-block;
47 | min-width: 200px;
48 | width: 200px;
49 | }
50 |
51 | .rollbackValue {
52 | height: 2em;
53 | width: 48px;
54 | box-sizing: border-box;
55 | border: none;
56 | text-align: center;
57 | font-weight: bold;
58 | margin-right: 16px;
59 | vertical-align: middle;
60 | }
61 |
62 | .timecodeLabel {
63 | display: block;
64 | text-align: center;
65 | }
66 |
--------------------------------------------------------------------------------
/packages/components/settings/Toggle/index.module.css:
--------------------------------------------------------------------------------
1 | @import '../../../config/style-guide/colours.scss';
2 |
3 | .switchContainer {
4 | display: inline-block;
5 | position: relative;
6 | width: 48px;
7 | display: inline-block;
8 | vertical-align: middle;
9 | margin-right: 1em;
10 | }
11 |
12 | .switch {
13 | position: relative;
14 | display: inline-block;
15 | width: 48px;
16 | height: 28px;
17 | }
18 |
19 | .switch input {
20 | opacity: 0;
21 | width: 0;
22 | height: 0;
23 | }
24 |
25 | .slider {
26 | position: absolute;
27 | cursor: pointer;
28 | top: 0;
29 | left: 0;
30 | right: 0;
31 | bottom: 0;
32 | background-color: #ccc;
33 | -webkit-transition: 0.4s;
34 | transition: 0.4s;
35 | }
36 |
37 | .slider:before {
38 | position: absolute;
39 | content: '';
40 | height: 20px;
41 | width: 20px;
42 | left: 4px;
43 | bottom: 4px;
44 | background-color: white;
45 | -webkit-transition: 0.4s;
46 | transition: 0.4s;
47 | }
48 |
49 | input:checked + .slider {
50 | background-color: $color-labs-red;
51 | }
52 |
53 | input:focus + .slider {
54 | box-shadow: 0 0 1px $color-labs-red;
55 | }
56 |
57 | input:checked + .slider:before {
58 | -webkit-transform: translateX(20px);
59 | -ms-transform: translateX(20px);
60 | transform: translateX(20px);
61 | }
62 |
--------------------------------------------------------------------------------
/packages/export-adapters/txt/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert DraftJS to plain text without timecodes or speaker names
3 | * Text+speaker+timecode
4 | * TODO: have a separate one or some logic to get text without timecodes?
5 | *
6 | * Export looks like
7 | ```
8 | 00:00:13 F_S12
9 | There is a day. About ten years ago when I asked a friend to hold a baby dinosaur called plea. All
10 |
11 | 00:00:24 F_S1
12 | that
13 |
14 | 00:00:24 F_S12
15 | he'd ordered and I was really excited about it because I've always loved about this one has really caught technical features. It had more orders and touch sensors. It had an infra red camera and one of the things that had was a tilt sensor so it. Knew what direction. It was facing. If and when you held it upside down.
16 |
17 | 00:00:46 U_UKN
18 | I thought.
19 | ```
20 | */
21 | // import { shortTimecode } from '../../util/timecode-converter/';
22 |
23 | export default (blockData) => {
24 | // TODO: to export text without speaker or timecodes use line below
25 | // const lines = blockData.blocks.map(paragraph => paragraph.text);
26 | const lines = blockData.blocks.map(paragraph => {
27 | // return `${ shortTimecode(paragraph.data.words[0].start) }\t${ paragraph.data.speaker }\n${ paragraph.text }`;
28 | return `${ paragraph.text }`;
29 | });
30 |
31 | return lines.join('\n\n');
32 | };
33 |
--------------------------------------------------------------------------------
/demo/select-export-format.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const ExportFormatSelect = props => {
5 | return ;
22 | };
23 |
24 | ExportFormatSelect.propTypes = {
25 | className: PropTypes.string,
26 | name: PropTypes.string,
27 | value: PropTypes.string,
28 | handleChange: PropTypes.func
29 | };
30 |
31 | export default ExportFormatSelect;
32 |
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/line-break-between-sentences/index.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import addLineBreakBetweenSentences from './index.js';
3 |
4 | var sampleText = `Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
5 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
6 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.`;
7 |
8 | var expectedOutput = `Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
9 |
10 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
11 |
12 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.`;
13 |
14 | test('add line break between sentences', () => {
15 | var result = addLineBreakBetweenSentences(sampleText);
16 | expect(result).toBe(expectedOutput);
17 | });
18 |
--------------------------------------------------------------------------------
/docs/adr/2018-12-06-analytics.md:
--------------------------------------------------------------------------------
1 | # Analytics
2 |
3 | * Status: being evaluated
4 | * Deciders: Pietro, Luke, James
5 | * Date: 2018-12-06
6 |
7 | ## Context and Problem Statement
8 |
9 | It be great to be able to track some component level analytics in a way that is agnostic to the tracking provider (eg piwik/[matomo](https://developer.matomo.org/api-reference/tracking-javascript#using-the-tracker-object), google analytics etc..)
10 |
11 | ## Decision Drivers
12 |
13 | * easy to reason around
14 | * clean interface
15 | * flexible to integrate with variety of analytics providers
16 | * un-opinionated in regards to the API end point and how to make that request
17 |
18 | ## Considered Options
19 |
20 | 1. [npm analytics](https://www.npmjs.com/package/analytics) module.
21 | > This is a pluggable event driven analytics library designed to work with any third party analytics tool.
22 |
23 | 2. Making one from scratch - a class that takes in an option and handles the logic to call the end points
24 |
25 | 3. just raise events to the top parent component - see [notes here](../notes/2018-12-06-analytics-raise-events.md) then parent component can decide how to handle depending on the library in use for analytics.
26 |
27 | ## Decision Outcome
28 |
29 |
30 | Option 3 - as suggested by Luke, raise event to parent component.
31 |
32 |
33 | ## Other
34 |
35 | - [Working With Events in React](https://css-tricks.com/working-with-events-in-react/)
--------------------------------------------------------------------------------
/demo/select-stt-json-type.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const SttTypeSelect = props => {
5 | return ;
23 | };
24 |
25 | SttTypeSelect.propTypes = {
26 | className: PropTypes.string,
27 | name: PropTypes.string,
28 | value: PropTypes.string,
29 | handleChange: PropTypes.func
30 | };
31 |
32 | export default SttTypeSelect;
33 |
--------------------------------------------------------------------------------
/packages/components/media-player/src/Select.module.scss:
--------------------------------------------------------------------------------
1 | .selectPlayerControl[name='playbackRate'] {
2 | font-size: 1em;
3 | -webkit-appearance: none;
4 | -moz-appearance: none;
5 | appearance: none;
6 | height: 48px;
7 | width: 97px;
8 | border-radius: 0;
9 | background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEyNS4zMDQgMTI1LjMwNCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTI1LjMwNCAxMjUuMzA0OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCI+CjxnPgoJPGc+CgkJPHBvbHlnb24gcG9pbnRzPSI2Mi42NTIsMTAzLjg5NSAwLDIxLjQwOSAxMjUuMzA0LDIxLjQwOSAgICIgZmlsbD0iI0ZGRkZGRiIvPgoJPC9nPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=);
10 | background-repeat: no-repeat;
11 | background-position: 85% center;
12 | color: white;
13 | border-color: transparent;
14 | padding-left: 32px;
15 | }
16 |
17 | @media (max-width: 768px) {
18 |
19 | .selectPlayerControl[name='playbackRate'] {
20 | width: 100%;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/fold/index.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import foldWords from './index.js';
3 |
4 | const sampleText = `Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
5 |
6 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
7 |
8 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.`;
9 |
10 | const expectedOutput = `Hi there, my name is Ian police -
11 | are recording this video to talk
12 | about mercury for the folks at a
13 | tech daily conference in New York.
14 |
15 | Sorry, I can't be there in person,
16 | so we are building a prototype
17 | funded in part by Google DNI of a
18 | web-based computer, assisted
19 | transcription and translation tool
20 | with some video editing features.
21 |
22 | It does speech to text and then
23 | automated consistent translation
24 | and then text to speech generate
25 | synthetic voices at time codes
26 | that line up with the original
27 | original audio.`;
28 |
29 | test('fold words at 35 char', () => {
30 | const result = foldWords(sampleText, 35);
31 | expect(result).toBe(expectedOutput);
32 | });
33 |
--------------------------------------------------------------------------------
/packages/util/timecode-converter/src/padTimeToTimecode.js:
--------------------------------------------------------------------------------
1 | const countColon = timecode => timecode.split(':').length;
2 |
3 | const includesFullStop = timecode => timecode.includes('.');
4 |
5 | const isOneDigit = str => str.length === 1;
6 |
7 | const padTimeToTimecode = (time) => {
8 | if (typeof time === 'string') {
9 | switch (countColon(time)) {
10 | case 4:
11 | // is already in timecode format
12 | // hh:mm:ss:ff
13 | return time;
14 | case 2:
15 | // m:ss
16 | if (isOneDigit(time.split(':')[0])) {
17 | return `00:0${ time }:00`;
18 | }
19 |
20 | return `00:${ time }:00`;
21 | case 3:
22 | // hh:mm:ss
23 | return `${ time }:00`;
24 | default:
25 | // mm.ss
26 | if (includesFullStop(time)) {
27 | // m.ss
28 | if (isOneDigit(time.split('.')[0])) {
29 | return `00:0${ time.split('.')[0] }:${ time.split('.')[1] }:00`;
30 | }
31 |
32 | return `00:${ time.replace('.', ':') }:00`;
33 | }
34 |
35 | // if just int, then it's seconds
36 | // s
37 | if (isOneDigit(time)) {
38 | return `00:00:0${ time }:00`;
39 | }
40 |
41 | return `00:00:${ time }:00`;
42 | }
43 | // edge case if it's number return a number coz cannot refactor
44 | // TODO: might need to refactor and move this elsewhere
45 | } else {
46 | return time;
47 | }
48 | };
49 |
50 | export default padTimeToTimecode;
51 |
--------------------------------------------------------------------------------
/packages/stt-adapters/amazon-transcribe/group-words-by-speakers.js:
--------------------------------------------------------------------------------
1 | export const groupWordsBySpeakerLabel = (words) => {
2 | const groupedWords = [];
3 | let currentSpeaker = '';
4 | words.forEach((word) => {
5 | if (word.speaker_label === currentSpeaker) {
6 | groupedWords[groupedWords.length - 1].words.push(word);
7 | } else {
8 | currentSpeaker = word.speaker_label;
9 | // start new speaker block
10 | groupedWords.push({
11 | speaker: word.speaker_label,
12 | words: [ word ] });
13 | }
14 | });
15 |
16 | return groupedWords;
17 | };
18 |
19 | export const findSpeakerForWord = (word, segments) => {
20 | const startTime = parseFloat(word.start_time);
21 | const endTime = parseFloat(word.end_time);
22 | const firstMatchingSegment = segments.find((seg) => {
23 | return startTime >= parseFloat(seg.start_time) && endTime <= parseFloat(seg.end_time);
24 | });
25 | if (firstMatchingSegment === undefined) {
26 | return 'UKN';
27 | } else {
28 | return firstMatchingSegment.speaker_label.replace('spk_', '');
29 | }
30 | };
31 |
32 | const addSpeakerLabelToWords = (words, segments) => {
33 | return words.map(w => Object.assign(w, { 'speaker_label': findSpeakerForWord(w, segments) }));
34 | };
35 |
36 | export const groupWordsBySpeaker = (words, speakerLabels) => {
37 | const wordsWithSpeakers = addSpeakerLabelToWords(words, speakerLabels.segments);
38 |
39 | return groupWordsBySpeakerLabel(wordsWithSpeakers);
40 | };
--------------------------------------------------------------------------------
/packages/components/keyboard-shortcuts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { faWindowClose } from '@fortawesome/free-solid-svg-icons';
4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5 |
6 | import returnHotKeys from './hot-keys';
7 |
8 | import style from './index.module.css';
9 |
10 | export const getHotKeys = returnHotKeys;
11 |
12 | class KeyboardShortcuts extends React.Component {
13 | render() {
14 | const hotKeys = returnHotKeys(this);
15 |
16 | const hotKeysCheatsheet = Object.keys(hotKeys).map(key => {
17 | const shortcut = hotKeys[key];
18 |
19 | return (
20 |
21 |
{shortcut.displayKeyCombination}
22 |
{shortcut.label}
23 |
24 | );
25 | });
26 |
27 | return (
28 |
29 |
Shortcuts
30 |
33 |
34 |
35 |
{hotKeysCheatsheet}
36 |
37 | );
38 | }
39 | }
40 |
41 | KeyboardShortcuts.propTypes = {
42 | handleShortcutsToggle: PropTypes.func
43 | };
44 |
45 | export default KeyboardShortcuts;
46 |
--------------------------------------------------------------------------------
/docs/qa/4-keyboard-shortcuts.md:
--------------------------------------------------------------------------------
1 | ### Item to test #4: Keyboard Shortcuts
2 |
3 | #### Item to test #4.1:Keyboard Shortcuts - show
4 |
5 | ##### Steps:
6 | - click on keyboard shortcut, icon top left
7 | ##### Expected Results:
8 | - [ ] Expect keyboard shortcut panel to come up
9 |
10 | #### Item to test #4.2: Keyboard Shortcuts - hide
11 |
12 | ##### Steps:
13 | - click on keyboard shortcut panel cross, top right
14 | ##### Expected Results:
15 | - [ ] Expect keyboard shortcut panel to hide
16 |
17 | #### Item to test #4.3: Keyboard Shortcuts
18 |
19 | | n | Functionality | Steps | Expected Results |
20 | |---|--- |--- |--- |
21 | | 1 |Play Media | click keyboard shortcut combination | to play/pause media
22 | | 2 | set current time | click keyboard shortcut combination | to prompt change current time dialog
23 | | 3 | Fast Forward | click keyboard shortcut combination | To fast forward media
24 | | 4 | Rewind | click keyboard shortcut combination | to rewind media
25 | | 5 | Increase playback speed | click keyboard shortcut combination | to Increase playback speed of the media, and show current playback speed in playback speed btn
26 | | 6 | Decrease playback speed | click keyboard shortcut combination | to Decrease playback speed of the media, and show current playback speed in playback speed btn
27 | | 6 | Roll Back | click keyboard shortcut combination | to RollBack playhead default amount of 15 sec, or custom amount set in settings
28 |
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/index.js:
--------------------------------------------------------------------------------
1 | import textSegmentation from './text-segmentation/index.js';
2 | import addLineBreakBetweenSentences from './line-break-between-sentences/index.js';
3 | import foldWords from './fold/index.js';
4 | import divideIntoTwoLines from './divide-into-two-lines/index.js';
5 |
6 | /**
7 | * Takes in array of word object,
8 | * and returns string containing all the text
9 | * @param {array} words - Words
10 | */
11 | function getTextFromWordsList(words) {
12 | return words.map((word) => {return word.text;}).join(' ');
13 | }
14 |
15 | /**
16 | *
17 | * @param {*} textInput - can be either plain text string or an array of word objects
18 | */
19 | function preSegmentText(textInput, tmpNumberOfCharPerLine = 35) {
20 | let text = textInput;
21 | if (typeof textInput === 'object') {
22 | text = getTextFromWordsList(textInput);
23 | }
24 | const segmentedText = textSegmentation(text);
25 | // - 2.Line brek between stentences
26 | const textWithLineBreakBetweenSentences = addLineBreakBetweenSentences(segmentedText);
27 | // - 3.Fold char limit per line
28 | const foldedText = foldWords(textWithLineBreakBetweenSentences, tmpNumberOfCharPerLine);
29 | // - 4.Divide into two lines
30 | // console.log(foldedText)
31 | const textDividedIntoTwoLines = divideIntoTwoLines(foldedText);
32 |
33 | return textDividedIntoTwoLines;
34 | }
35 |
36 | export {
37 | preSegmentText,
38 | getTextFromWordsList
39 | };
40 |
41 | export default preSegmentText;
--------------------------------------------------------------------------------
/docs/notes/2018-11-29-git-cheat-sheet.md:
--------------------------------------------------------------------------------
1 | # Git Cheat Sheet
2 |
3 | ## git create branch from un-staged changes
4 | ```
5 | git checkout -b new_branch_name
6 | ```
7 |
8 | ## checkout pull request locally
9 | ```
10 | git fetch origin pull/ID/head:BRANCHNAME
11 | ```
12 |
13 | ## git pull from remote to override local changes
14 |
15 | ```
16 | git fetch origin
17 | ```
18 | ```
19 | git reset --hard origin/master
20 | ```
21 |
22 | ## Git pull remote branch locally
23 | ```
24 | git fetch origin
25 | ```
26 | ```
27 | git checkout --track origin/
28 | ```
29 |
30 | ## git push local branch to remote
31 | ```
32 | git checkout -b
33 | ```
34 | ```
35 | git push -u origin
36 | ```
37 |
38 | ## Git merge master to update branch
39 | ```
40 | git checkout
41 | ```
42 | ```
43 | git merge master
44 | ```
45 |
46 | or [merging vs rebasing](https://www.atlassian.com/git/tutorials/merging-vs-rebasing )
47 |
48 |
49 | ## to view previous commits
50 |
51 | ```
52 | git log
53 | ```
54 | ## to add to previous commit
55 |
56 | ### If have not pushed
57 |
58 | ```
59 | git commit --amend
60 | ```
61 | - [Changing a commit message](https://help.github.com/articles/changing-a-commit-message/)
62 | - [rewriting history blog post](https://robots.thoughtbot.com/git-interactive-rebase-squash-amend-rewriting-history
63 | )
64 |
65 |
66 | ### if you have pushed
67 | if you have pushed already same as above but you can force a push to amend remote as well
68 |
69 | ```
70 | git push -f
71 | ```
72 |
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/divide-into-two-lines/index.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import divideIntoTwoLines from './index.js';
3 |
4 | var sampleText = `Hi there, my name is Ian police -
5 | are recording this video to talk
6 | about mercury for the folks at a
7 | tech daily conference in New York.
8 |
9 | Sorry, I can’t be there in person,
10 | so we are building a prototype
11 | funded in part by Google DNI of a
12 | web-based computer, assisted
13 | transcription and translation tool
14 | with some video editing features.
15 |
16 | It does speech to text and then
17 | automated consistent translation
18 | and then text to speech generate
19 | synthetic voices at time codes that
20 | line up with the original original
21 | audio.`;
22 |
23 | var expectedOutput = `Hi there, my name is Ian police -
24 | are recording this video to talk
25 |
26 | about mercury for the folks at a
27 | tech daily conference in New York.
28 |
29 | Sorry, I can’t be there in person,
30 | so we are building a prototype
31 |
32 | funded in part by Google DNI of a
33 | web-based computer, assisted
34 |
35 | transcription and translation tool
36 | with some video editing features.
37 |
38 | It does speech to text and then
39 | automated consistent translation
40 |
41 | and then text to speech generate
42 | synthetic voices at time codes that
43 |
44 | line up with the original original
45 | audio.`;
46 |
47 | test('divide into two lines', () => {
48 | var result = divideIntoTwoLines(sampleText);
49 | expect(result).toBe(expectedOutput);
50 | });
51 |
--------------------------------------------------------------------------------
/packages/util/timecode-converter/src/padTimeToTimecode.test.js:
--------------------------------------------------------------------------------
1 | import padTimeToTimecode from './padTimeToTimecode';
2 |
3 | describe('Timecode conversion TC- convertToSeconds', () => {
4 | it('Should be defined', ( ) => {
5 | const demoTimecode = '12:34:56:78';
6 | const result = padTimeToTimecode(demoTimecode);
7 | expect(result).toBeDefined();
8 | });
9 |
10 | it('hh:mm:ss:ff --> hh:mm:ss:ff ', ( ) => {
11 | const demoTimecode = '12:34:56:78';
12 | const result = padTimeToTimecode(demoTimecode);
13 | expect(result).toEqual(demoTimecode);
14 | });
15 |
16 | it('mm:ss --> convert to hh:mm:ss:ms', ( ) => {
17 | const demoTime = '34:56';
18 | const expectedTimecode = '00:34:56:00';
19 | const result = padTimeToTimecode(demoTime);
20 | expect(result).toEqual(expectedTimecode);
21 | });
22 |
23 | xit('hh:mm:ss --> convert to hh:mm:ss:ms', ( ) => {
24 | const demoTime = '34:56:78';
25 | const expectedTimecode = '00:34:56:78';
26 | const result = padTimeToTimecode(demoTime);
27 | expect(result).toEqual(expectedTimecode);
28 | });
29 |
30 | it('mm.ss--> convert to hh:mm:ss:ms', ( ) => {
31 | const demoTime = '34.56';
32 | const expectedTimecode = '00:34:56:00';
33 | const result = padTimeToTimecode(demoTime);
34 | expect(result).toEqual(expectedTimecode);
35 | });
36 |
37 | it('120 sec --> 120', ( ) => {
38 | const demoTime = 120;
39 | const expectedTimecode = 120;
40 | const result = padTimeToTimecode(demoTime);
41 | expect(result).toEqual(expectedTimecode);
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/packages/components/media-player/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import { action } from '@storybook/addon-actions';
5 | import { withKnobs, text, number } from '@storybook/addon-knobs';
6 |
7 | import MediaPlayer from '../index.js';
8 |
9 | storiesOf('MediaPlayer', module)
10 | .addDecorator(withKnobs)
11 | .add('default', () => {
12 | const videoRef = React.createRef();
13 |
14 | const mediaUrl = 'https://download.ted.com/talks/KateDarling_2018S-950k.mp4';
15 |
16 | const fixtureProps = {
17 | videoRef: videoRef,
18 | title: text('title', 'Ted Talk'),
19 | hookSeek: action('hookSeek'),
20 | hookPlayMedia: action('hookPlayMedia'),
21 | hookIsPlaying: action('hookIsPlaying'),
22 | mediaUrl: text('mediaUrl', mediaUrl),
23 | hookOnTimeUpdate: action('hookOnTimeUpdate'),
24 | rollBackValueInSeconds: number('rollBackValueInSeconds', 10),
25 | timecodeOffset: number('timecodeOffset', 0),
26 | handleAnalyticsEvents: action('handleAnalyticsEvents'),
27 | mediaDuration: text('mediaDuration', '01:00:00:00'),
28 | handleSaveTranscript: action('handleSaveTranscript')
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 |
41 |
42 | );
43 | });
44 |
--------------------------------------------------------------------------------
/docs/qa/2-timed-text-editor.md:
--------------------------------------------------------------------------------
1 | ### Item to test #2: Timed Text Editor
2 |
3 | #### Item to test #2.1: Timed Text Editor - double click on words
4 |
5 | 
6 |
7 | ##### Steps:
8 | - Double click on a word
9 | ##### Expected Results:
10 | - [ ] Expect play head and current time it to jump to the corresponding point in the media
11 | - [ ] Expect the text before the word to be darker color
12 | - [ ] Expect current word to be highlighted
13 | - [ ] Expect media to start playing and highlight and current word to continue
14 |
15 | #### Item to test #2.2: Timed Text Editor - click on timecodes
16 |
17 | ##### Steps:
18 | - Click on timecode next to the text of the paragraph
19 | ##### Expected Results:
20 | - [ ] Expect play head and current time it to jump to the corresponding point in the media beginning of paragraph
21 |
27 |
28 | #### Item to test #2.3: Timed Text Editor - Edit speakers labels
29 |
30 | #### Steps:
31 | - click on speaker icon next to default speaker names
32 | #### Expected Results:
33 | - [ ] Expect a prompt to come up,
34 | - [ ] on add text and click on expect name to change in Timed Text Editor next to speaker label icon clicked.
35 | - [ ] if instead of ok click cancel, expect nothing to happen
36 |
37 | ---
38 |
--------------------------------------------------------------------------------
/docs/notes/draftjs/2018-10-20-draftjs-6-local-storage.md:
--------------------------------------------------------------------------------
1 | # saving to local storage on keystroke
2 |
3 | Getting data
4 |
5 | ```js
6 | const data = convertToRaw(editorState.getCurrentContent());
7 | ```
8 |
9 |
10 | Import `convertToRaw` the same way you import `convertFromRaw` then you can iterate that data do to various things
11 |
12 | eg in bbc transcript model https://github.com/bbc/subtitalizer/blob/d102c233236b782011a4a94a21e6de13491abb45/src/components/TranscriptEditor.js#L105-L241
13 |
14 | so, you need to get JSON of that content state, store that in localstorage, and use that to load into draft back.
15 |
16 | So, basically
17 |
18 | https://github.com/bbc/subtitalizer/blob/d102c233236b782011a4a94a21e6de13491abb45/src/components/TranscriptEditor.js#L105-L241
19 |
20 | without the Immutable things
21 |
22 |
23 |
24 | ## how often to save? saving on every keystroke?
25 |
26 | it will make it slow, can do debounce to improve perfroamce
27 |
28 |
29 | ```js
30 | this.debouncedRebuildTranscript = debounce(this.rebuildTranscript, 100);
31 | ```
32 |
33 |
34 | https://github.com/bbc/subtitalizer/blob/d102c233236b782011a4a94a21e6de13491abb45/src/components/TranscriptEditor.js#L266
35 |
36 | ```js
37 | import debounce from 'lodash.debounce';
38 | ```
39 |
40 | [https://lodash.com/docs/4.17.11](https://lodash.com/docs/4.17.11) - [debounce](https://lodash.com/docs#debounce)
41 |
42 | This as you type, it postpones that until you do a 100ms pause in this example.
43 |
44 | Laurian
45 | > Do it without, then test and see if you need it, but there is a lot of computation to do on a keystroke, and JS is single threaded
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/text-segmentation/index.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import textSegmentation from './index.js';
3 |
4 | var sampleText = 'Hi there, my name is Mr. Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York. Sorry, I can\'t be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features. It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.';
5 |
6 | var expectedOutput = `Hi there, my name is Mr. Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
7 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
8 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.`;
9 |
10 | var optionalHonorificsSample = 'Mr';
11 |
12 | test('add line break between sentences', () => {
13 | var result = textSegmentation(sampleText);
14 | expect(result).toBe(expectedOutput);
15 | });
16 |
17 | test('add line break between sentences,with optional honorifics', () => {
18 | var result = textSegmentation(sampleText, optionalHonorificsSample);
19 | expect(result).toBe(expectedOutput);
20 | });
21 |
--------------------------------------------------------------------------------
/docs/qa/5-analytics.md:
--------------------------------------------------------------------------------
1 | ### Item to test #5: Analytics
2 |
3 | Analytics in[the demo app](https://bbc.github.io/react-transcript-editor/) are logged in a text area below the component for demonstration purposes.
4 | This can also be used to test tha they are fulling working.
5 |
6 | Every new event return an object into an array in the text area.
7 | Inspecting the array for new elements gives a good indication of whether all expected events are being tracked. eg if an event is not in the array list then the code might not be working as expected.
8 |
9 | Here's a list of events grouped by functionality. It might be easier to test one at a time, or if already done the test for above items, can just review the array generated so far to see if any of these are missing.
10 |
11 | ##### info
12 | - [ ] duration of media
13 | - [ ] number of words in transcript
14 |
15 | _these we'd expect to be triggered first_
16 |
17 | ##### actions
18 | - [ ] click on progress bar
19 | - [ ] double click on word // now also triggered when clicking on time-codes
20 | - [ ] click on time-codes, at paragraph level
21 | - [ ] Set current time, Jump to time, click on current time display
22 | - [ ] play/pause, click on media preview // but triggered by other events as well
23 | - [ ] playback speed change
24 | - [ ] use of keyboard shortcuts // see keyboard shortcut cheat sheet and test each individually
25 | - [ ] edit speaker label
26 | - [ ] ~skip forward~
27 | - [ ] ~skip backward~
28 |
29 | ##### settings
30 | - [ ] set timecode offset
31 | - [ ] pause while typing
32 | - [ ] scroll into view
33 |
34 | - [ ] rollback
35 | - [ ] Toggles speaker names - show/hide
36 |
--------------------------------------------------------------------------------
/docs/notes/2018-10-20-local-storage-draft-js.md:
--------------------------------------------------------------------------------
1 | 2018-11-20-local-saving-to-local-storage-on-keystroke.md
2 |
3 | # saving to local storage on keystroke
4 |
5 | Getting data
6 |
7 | ```js
8 | const data = convertToRaw(editorState.getCurrentContent());
9 | ```
10 |
11 |
12 | Import `convertToRaw` the same way you import `convertFromRaw` then you can iterate that data do to various things
13 |
14 | eg in bbc transcript model https://github.com/bbc/subtitalizer/blob/d102c233236b782011a4a94a21e6de13491abb45/src/components/TranscriptEditor.js#L105-L241
15 |
16 | so, you need to get JSON of that content state, store that in localstorage, and use that to load into draft back.
17 |
18 | So, basically
19 |
20 | https://github.com/bbc/subtitalizer/blob/d102c233236b782011a4a94a21e6de13491abb45/src/components/TranscriptEditor.js#L105-L241
21 |
22 | without the Immutable things
23 |
24 |
25 |
26 | ## how often to save? saving on every keystroke?
27 |
28 | it will make it slow, can do debounce to improve perfroamce
29 |
30 |
31 | ```js
32 | this.debouncedRebuildTranscript = debounce(this.rebuildTranscript, 100);
33 | ```
34 |
35 |
36 | https://github.com/bbc/subtitalizer/blob/d102c233236b782011a4a94a21e6de13491abb45/src/components/TranscriptEditor.js#L266
37 |
38 | ```js
39 | import debounce from 'lodash.debounce';
40 | ```
41 |
42 | [https://lodash.com/docs/4.17.11](https://lodash.com/docs/4.17.11) - [debounce](https://lodash.com/docs#debounce)
43 |
44 | This as you type, it postpones that until you do a 100ms pause in this example.
45 |
46 | Laurian
47 | > Do it without, then test and see if you need it, but there is a lot of computation to do on a keystroke, and JS is single threaded
--------------------------------------------------------------------------------
/packages/stt-adapters/google-stt/index.test.js:
--------------------------------------------------------------------------------
1 | import gcpSttToDraft, {
2 | getBestAlternativeSentence,
3 | trimLeadingAndTailingWhiteSpace
4 | } from './index';
5 | import draftTranscriptSample from './sample/googleSttToDraftJs.sample.js';
6 | import gcpSttTedTalkTranscript from './sample/gcpSttPunctuation.sample.json';
7 |
8 | describe('gcpSttToDraft', () => {
9 | const result = gcpSttToDraft(gcpSttTedTalkTranscript);
10 | it('Should be defined', () => {
11 | expect(result).toBeDefined();
12 | });
13 |
14 | it('Should be equal to expected value', () => {
15 | expect(result).toEqual(draftTranscriptSample);
16 | });
17 | });
18 |
19 | describe('leading and tailing white space should be removed from text block', () => {
20 | const sentence = ' this is a sentence ';
21 | const expected = 'this is a sentence';
22 |
23 | const result = trimLeadingAndTailingWhiteSpace(sentence);
24 | it('should be equal to expected value', () => {
25 | expect(result).toEqual(expected);
26 | });
27 | });
28 |
29 | describe('Best alternative sentence should be returned', () => {
30 | const sentences = {
31 | alternatives: [
32 | {
33 | 'transcript': 'this is the first sentence',
34 | 'confidence': 0.95,
35 | },
36 | {
37 | 'transcript': 'this is the first sentence alternative',
38 | 'confidence': 0.80,
39 | }
40 | ]
41 | };
42 | const expected = {
43 | 'transcript': 'this is the first sentence',
44 | 'confidence': 0.95
45 | };
46 |
47 | it('Should be equal to expected value', () => {
48 |
49 | const result = getBestAlternativeSentence(sentences);
50 | expect(result).toEqual(expected);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/line-break-between-sentences/README.md:
--------------------------------------------------------------------------------
1 | # Line break between sentences
2 |
3 |
4 | separates each line (a sentence) with an empty line.
5 |
6 |
7 | #### Input
8 |
9 | Text where each sentence that ends with full stop is on a new line. `\n`.
10 |
11 | ```
12 | Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
13 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
14 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.
15 | ```
16 |
17 | #### Output
18 |
19 | ```
20 | Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
21 |
22 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
23 |
24 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.
25 | ```
26 |
27 | #### algo
28 |
29 | ```bash
30 | # Add blank line after every new line
31 | sed -e 'G' test.txt > test2.txt
32 | ```
33 |
34 | Equivalent to
35 |
36 | ```js
37 | test.replace(/\n/g,"\n\n")
38 | ```
39 |
--------------------------------------------------------------------------------
/packages/stt-adapters/amazon-transcribe/group-words-by-speakers.test.js:
--------------------------------------------------------------------------------
1 | import amazonTodayInFocusTranscript from './sample/todayinfocus.sample.json';
2 | import wordsWithSpeakers from './sample/todayinfocuswords.sample.json';
3 |
4 | import { groupWordsBySpeakerLabel, findSpeakerForWord, groupWordsBySpeaker } from './group-words-by-speakers';
5 |
6 | const words = amazonTodayInFocusTranscript.results.items;
7 | const speakerLabels = amazonTodayInFocusTranscript.results.speaker_labels;
8 |
9 | describe('groupWordsBySpeakerLabel', () => {
10 |
11 | it('Should group speakers correctly', ( ) => {
12 |
13 | const groups = groupWordsBySpeakerLabel(wordsWithSpeakers);
14 | expect(groups[0].speaker).toBe('spk_0');
15 | expect(groups[0].words.length).toBe(1);
16 | expect(groups[1].speaker).toBe('spk_1');
17 | expect(groups[1].words.length).toBe(2);
18 | });
19 | });
20 |
21 | describe('findSpeakerForWord', () => {
22 |
23 | it('Should find correct speaker', ( ) => {
24 |
25 | const speaker = findSpeakerForWord({
26 | 'start_time': '8.65',
27 | 'end_time': '8.98',
28 | 'alternatives': [
29 | {
30 | 'confidence': '0.9999',
31 | 'content': 'one'
32 | }
33 | ],
34 | 'type': 'pronunciation'
35 | }, speakerLabels.segments);
36 |
37 | expect(speaker).toBe('0');
38 | });
39 | });
40 |
41 | describe('groupWordsBySpeaker', () => {
42 | /** Hopefully the other unit tests suffice.
43 | * this is a rather lazy one to check the full results
44 | */
45 | it('Should return expected number of groups', ( ) => {
46 |
47 | const groups = groupWordsBySpeaker(words, speakerLabels);
48 | expect(groups.length).toBe(173);
49 | });
50 | });
--------------------------------------------------------------------------------
/packages/export-adapters/draftjs-to-digital-paper-edit/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert DraftJS to Digital Paper Edit format
3 | * More details see
4 | * https://github.com/bbc/digital-paper-edit
5 | */
6 | export default (blockData) => {
7 | const result = { words: [], paragraphs: [] };
8 |
9 | blockData.blocks.forEach((block, index) => {
10 | if (block.data.words !== undefined) {
11 | // TODO: make sure that when restoring timecodes text attribute in block word data
12 | // should be updated as well
13 | const tmpParagraph = {
14 | id: index,
15 | start: block.data.words[0].start, //block.data.start,
16 | end: block.data.words[block.data.words.length - 1].end,
17 | speaker: block.data.speaker
18 | };
19 | result.paragraphs.push(tmpParagraph);
20 | // using data within a block to get words info
21 | const tmpWords = block.data.words.map((word) => {
22 | const tmpWord = {
23 | id: word.index,
24 | start: word.start,
25 | end: word.end,
26 | text: null
27 | };
28 | // TODO: need to normalise various stt adapters
29 | // so that when they create draftJs json, word text attribute
30 | // has got consistent naming. `text` and not `punct` or `word`.
31 | if (word.text) {
32 | tmpWord.text = word.text;
33 | }
34 | else if (word.punct) {
35 | tmpWord.text = word.punct;
36 | }
37 | else if (word.word) {
38 | tmpWord.text = word.punct;
39 | }
40 |
41 | return tmpWord;
42 | });
43 | // flattening the list of words
44 | result.words = result.words.concat(tmpWords);
45 | }
46 | });
47 |
48 | return result;
49 | };
50 |
--------------------------------------------------------------------------------
/packages/components/settings/stories/index.stories.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { storiesOf } from '@storybook/react';
4 | import { action } from '@storybook/addon-actions';
5 | import { withKnobs, number, boolean } from '@storybook/addon-knobs';
6 |
7 | import Settings from '../index.js';
8 |
9 | storiesOf('Settings', module)
10 | .addDecorator(withKnobs)
11 | .add('default', () => {
12 |
13 | const fixtureProps = {
14 | handleSettingsToggle: action('Toggle settings'),
15 | showTimecodes: boolean('showTimecodes', true),
16 | showSpeakers: boolean('showSpeakers', true),
17 | timecodeOffset: number('timecodeOffset', 3600),
18 | defaultValueScrollSync: boolean('defaultValueScrollSync', true),
19 | defaultValuePauseWhileTyping: boolean('defaultValuePauseWhileTyping', true),
20 | defaultRollBackValueInSeconds: number('defaultRollBackValueInSeconds', 10),
21 | previewIsDisplayed: boolean('previewIsDisplayed', true),
22 | handleShowTimecodes: action('handleShowTimecodes'),
23 | handleShowSpeakers: action('handleShowSpeakers'),
24 | handleSetTimecodeOffset: action('handleSetTimecodeOffset'),
25 | handleSettingsToggle: action('handleSettingsToggle'),
26 | handlePauseWhileTyping: action('handlePauseWhileTyping'),
27 | handleIsScrollIntoViewChange: action('handleIsScrollIntoViewChange'),
28 | handleRollBackValueInSeconds: action('handleRollBackValueInSeconds'),
29 | handlePreviewIsDisplayed: action('handlePreviewIsDisplayed'),
30 | handleChangePreviewViewWidth: action('handleChangePreviewViewWidth'),
31 | handleAnalyticsEvents: action('handleAnalyticsEvents')
32 | };
33 |
34 | return (
35 |
36 | );});
37 |
--------------------------------------------------------------------------------
/packages/util/timecode-converter/src/secondsToTimecode.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Raised in this comment https://github.com/bbc/react-transcript-editor/pull/9
3 | * abstracted from https://github.com/bbc/newslabs-cdn/blob/master/js/20-bbcnpf.utils.js
4 | * In broadcast VIDEO, timecode is NOT hh:mm:ss:ms, it's hh:mm:ss:ff where ff is frames,
5 | * dependent on the framerate of the media concerned.
6 | * `hh:mm:ss:ff`
7 | */
8 |
9 | /**
10 | * Helper function
11 | * Rounds to the 14milliseconds boundaries
12 | * Time in video can only "exist in" 14milliseconds boundaries.
13 | * This makes it possible for the HTML5 player to be frame accurate.
14 | * @param {*} seconds
15 | * @param {*} fps
16 | */
17 | const normalisePlayerTime = function (seconds, fps) {
18 | return Number((1.0 / fps * Math.floor(Number((fps * seconds).toPrecision(12)))).toFixed(2));
19 | };
20 |
21 | /*
22 | * @param {*} seconds
23 | * @param {*} fps
24 | */
25 | const secondsToTimecode = function (seconds, framePerSeconds) {
26 | // written for PAL non-drop timecode
27 | let fps = 25;
28 | if (framePerSeconds !== undefined) {
29 | fps = framePerSeconds;
30 | }
31 |
32 | const normalisedSeconds = normalisePlayerTime(seconds, fps);
33 |
34 | const wholeSeconds = Math.floor(normalisedSeconds);
35 | const frames = ((normalisedSeconds - wholeSeconds) * fps).toFixed(2);
36 |
37 | // prepends zero - example pads 3 to 03
38 | function _padZero(n) {
39 | if (n < 10) return `0${ parseInt(n) }`;
40 |
41 | return parseInt(n);
42 | }
43 |
44 | return `${ _padZero((wholeSeconds / 60 / 60) % 60)
45 | }:${
46 | _padZero((wholeSeconds / 60) % 60)
47 | }:${
48 | _padZero(wholeSeconds % 60)
49 | }:${
50 | _padZero(frames) }`;
51 | };
52 |
53 | export default secondsToTimecode;
54 |
--------------------------------------------------------------------------------
/docs/adr/2018-11-20-local-storage-save.md:
--------------------------------------------------------------------------------
1 | # Local storage save
2 |
3 | * Status: being evaluated
4 | * Deciders: Pietro, James
5 | * Date: 2018-11-20
6 |
7 |
8 | ## Context and Problem Statement
9 |
10 | It be good to be able to save in local storage as the user is editing the text.
11 | Some things to define
12 | - Is this done inside the component? or does the component return the data and the parent component handles saving to local storage?
13 | - how to be able to save multiple transcript for the same editor? eg with a unique id?
14 | - perhaps adding time stamps to the saving for versioning?
15 | - When loading transcript from server, if it's in local storage should we resume from that one instead?
16 | - ignoring multi users scenarios for now.
17 |
18 | How often to save
19 | - save button?
20 | - save on each new char?
21 | - save on time interval?
22 |
23 | ## Decision Drivers
24 |
25 | * simple to reason around
26 | * [driver 2, e.g., a force, facing concern, …]
27 | * …
28 |
29 | ## Considered Options
30 |
31 | * inside component TimedTextEditor
32 | * outside React TranscriptEditor component
33 | * Debouncing on save (Lodash helper function)
34 |
35 | ## Decision Outcome
36 |
37 | - Save inside component TimedTextEditor
38 | - Save button for local storage
39 | - Save on each new char | switch to timer if it effects performance for users
40 | - Save on char / multiple of char inputs
41 | - Can use "save-on-char" together with time intervals
42 | - using url of media as id to save to local storage
43 | - add time stamps
44 | - When loading transcript from server, if it's in local storage should resume from that one instead
45 |
46 |
47 | [See separate ADR for saving to server.](./2018-11-20-save-to-server.md)
48 |
--------------------------------------------------------------------------------
/packages/components/timed-text-editor/Word.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class Word extends Component {
5 |
6 | shouldComponentUpdate(nextProps) {
7 | if ( nextProps.decoratedText !== this.props.decoratedText) {
8 | return true;
9 | }
10 |
11 | return false;
12 | }
13 |
14 | generateConfidence = (data) => {
15 | // handling edge case where confidence score not present
16 | if (data.confidence) {
17 | return data.confidence > 0.6 ? 'high' : 'low';
18 | }
19 |
20 | return 'high';
21 | }
22 |
23 | generatePreviousTimes = (data) => {
24 | let prevTimes = '';
25 |
26 | for (let i = 0; i < data.start; i++) {
27 | prevTimes += `${ i } `;
28 | }
29 |
30 | if (data.start % 1 > 0) {
31 | // Find the closest quarter-second to the current time, for more dynamic results
32 | const dec = Math.floor((data.start % 1) * 4.0) / 4.0;
33 | prevTimes += ` ${ Math.floor(data.start) + dec }`;
34 | }
35 |
36 | return prevTimes;
37 | }
38 |
39 | render() {
40 | const data = this.props.entityKey
41 | ? this.props.contentState.getEntity(this.props.entityKey).getData()
42 | : {};
43 |
44 | return (
45 |
52 | {this.props.children}
53 |
54 | );
55 | }
56 | }
57 |
58 | Word.propTypes = {
59 | contentState: PropTypes.object,
60 | entityKey: PropTypes.string,
61 | children: PropTypes.array
62 | };
63 |
64 | export default Word;
65 |
--------------------------------------------------------------------------------
/packages/components/video-player/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styles from './index.module.css';
4 |
5 | class VideoPlayer extends React.Component {
6 |
7 | // to avoid unnecessary re-renders
8 | shouldComponentUpdate(nextProps) {
9 | if (nextProps.previewIsDisplayed !== this.props.previewIsDisplayed) {
10 | return true;
11 | }
12 |
13 | if (nextProps.mediaUrl !== this.props.mediaUrl) {
14 | return true;
15 | }
16 |
17 | return false;
18 | }
19 |
20 | handlePlayMedia = () => {
21 | if (this.props.videoRef.current !== null) {
22 | return this.props.videoRef.current.paused
23 | ? this.props.videoRef.current.play()
24 | : this.props.videoRef.current.pause();
25 | }
26 | };
27 | render() {
28 | const isDisplayed = this.props.previewIsDisplayed ? 'inline' : 'none';
29 |
30 | return (
31 |
46 | );
47 | }
48 | }
49 |
50 | VideoPlayer.propTypes = {
51 | mediaUrl: PropTypes.string,
52 | onTimeUpdate: PropTypes.func,
53 | onClick: PropTypes.func,
54 | videoRef: PropTypes.object.isRequired,
55 | onLoadedDataGetDuration: PropTypes.func,
56 | previewIsDisplayed: PropTypes.bool,
57 | previewViewWidth: PropTypes.string
58 | };
59 |
60 | export default VideoPlayer;
61 |
--------------------------------------------------------------------------------
/docs/notes/2018-11-29-auto-pause-while-typing.md:
--------------------------------------------------------------------------------
1 | # auto pause while typing implementation notes - draft
2 |
3 | Notes Pause while typing
4 |
5 | ## MediaPlayer
6 | Expose `isPlaying` and `playMedia` from `MediaPlayer` through parent component `TranscriptEditor` To other `TimedTextEditor` to allow the setup for external controls
7 |
8 | Change playMedia to accommodate optional `bool`
9 | `True` -> play
10 | `False` -> pause
11 |
12 | If `bool` not provided and if `playMedia` triggered by btn or video element then can use detecting `.target` attribute to distinguish who is calling the function.
13 |
14 | Play if paused | pause if playing.
15 |
16 | ### Toggle
17 | Added toggle for pause while typing to make it optional
18 |
19 |
20 | ### In timedTextEditor
21 | [from issue](https://github.com/facebook/draft-js/issues/1060)
22 | ```js
23 | onChange = (editorState) => {
24 | if(this.state.editorState.getCurrentContent() !== editorState.getCurrentContent()){
25 | ```
26 |
27 | Using comparison of previous and current content state to check if content has changed. and therefore if the user is typing.
28 |
29 | Proved more effective [then checking change type](https://draftjs.org/docs/api-reference-editor-state#lastchangetype) from [draft docs](https://draftjs.org/docs/api-reference-editor-change-type), which has more edge cases.
30 |
31 | another option is to use `editorStateChangeType === 'insert-characters'` but this seems to be triggered even outside of draftJS eg when clicking play button.
32 |
33 | at pseudo code level then we do
34 | - when onChange
35 | - Trigger pause, MediaPlayer
36 | - Clear timer
37 | - After 2 sec trigger playMedia
38 |
39 | This means that if user keeps typing, the timer keeps getting set and reset. Keeping the media paused.
40 |
41 | When the user stops typing after 2 sec it start playing again
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/README.md:
--------------------------------------------------------------------------------
1 | # Pre segmentation
2 |
3 | ## Input
4 | - either an array list of words objects
5 | example
6 | ```json
7 | [
8 | {
9 | "id": 0,
10 | "start": 13.02,
11 | "end": 13.17,
12 | "text": "There"
13 | },
14 | {
15 | "id": 1,
16 | "start": 13.17,
17 | "end": 13.38,
18 | "text": "is"
19 | },
20 | {
21 | "id": 2,
22 | "start": 13.38,
23 | "end": 13.44,
24 | "text": "a"
25 | },
26 | {
27 | "id": 3,
28 | "start": 13.44,
29 | "end": 13.86,
30 | "text": "day."
31 | },
32 | {
33 | "id": 4,
34 | "start": 13.86,
35 | "end": 14.13,
36 | "text": "About"
37 | },
38 | {
39 | "id": 5,
40 | "start": 14.13,
41 | "end": 14.38,
42 | "text": "ten"
43 | },
44 | {
45 | "id": 6,
46 | "start": 14.38,
47 | "end": 14.61,
48 | "text": "years"
49 | },
50 | {
51 | "id": 7,
52 | "start": 14.61,
53 | "end": 15.15,
54 | "text": "ago"
55 | },
56 | ```
57 | - or a string of text
58 | Example
59 | ```
60 | There is a day. About ten years ago
61 | ```
62 |
63 | ## Output:
64 | - segmented plain text
65 |
66 | example
67 |
68 | ```
69 | There is a day.
70 |
71 | About ten years ago when I asked a
72 |
73 | friend to hold a baby dinosaur
74 | robot upside down.
75 |
76 | It was a toy called plea.
77 |
78 | All It's a super courts are
79 |
80 | showing off to my friend and I
81 | said to hold it, but he'll see
82 |
83 | ...
84 | ```
85 |
86 |
87 | This allows for flexibility in giving the input either to aeneas forced aligner to produce subtitles or to another algorithm to restore timecodes from STT word timings output if available.
--------------------------------------------------------------------------------
/packages/components/transcript-editor/src/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | faCog,
4 | faKeyboard,
5 | faShare
6 | } from '@fortawesome/free-solid-svg-icons';
7 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
8 |
9 | import style from '../index.module.css';
10 |
11 | class Header extends React.Component {
12 |
13 | // to avoid unnecessary re-renders
14 | shouldComponentUpdate(nextProps) {
15 | if (nextProps !== this.props) return true;
16 |
17 | return false;
18 | }
19 | render() {
20 | const props = this.props;
21 |
22 | return (<>
23 |
24 | {props.showSettings ? props.settings : null}
25 | {props.showShortcuts ? props.shortcuts : null}
26 | {props.showExportOptions ? props.exportOptions : null}
27 | {props.tooltip}
28 |
29 |
32 |
33 |
\n\n';
46 |
47 | return ittOut;
48 | };
49 |
50 | export default ittGenerator;
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/example-usage.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import subtitlesGenerator from './index.js';
3 | // import transcript from './sample/words-list.sample.json';
4 | import transcript from './sample/words-list-2.sample.json';
5 | const sampleWords = transcript.words;
6 |
7 | function getTextFromWordsList(words) {
8 | return words.map((word) => {return word.text;}).join(' ');
9 | }
10 |
11 | const plainText = getTextFromWordsList(sampleWords);
12 |
13 | const subtitlesJson = subtitlesGenerator({ words: sampleWords, type: 'json' });
14 | // const ttmlPremiere = subtitlesGenerator({ words: sampleWords, type: 'premiere' });
15 | // const ittData = subtitlesGenerator({ words: sampleWords, type: 'itt' });
16 | // const ttmlData = subtitlesGenerator({ words: sampleWords, type: 'ttml' });
17 | // const srtData = subtitlesGenerator({ words: sampleWords, type: 'srt', numberOfCharPerLine: 35 });
18 | // const vttData = subtitlesGenerator({ words: sampleWords, type: 'vtt' });
19 | // const csvData = subtitlesGenerator({ words: sampleWords, type: 'csv' });
20 | // const preSegmentTextData = subtitlesGenerator({ words: sampleWords, type: 'pre-segment-txt' });
21 | // const testTet = subtitlesGenerator({ words: plainText, type: 'txt' });
22 |
23 | console.log(subtitlesJson);
24 |
25 | // fs.writeFileSync('./example-output/test.json', JSON.stringify(subtitlesJson, null, 2));
26 | // fs.writeFileSync('./example-output/test-premiere.xml', ttmlPremiere);
27 | // fs.writeFileSync('./example-output/test.itt', ittData);
28 | // fs.writeFileSync('./example-output/test.ttml', ttmlData);
29 | // fs.writeFileSync('./example-output/test.srt', srtData);
30 | // fs.writeFileSync('./example-output/test.vtt', vttData);
31 | // fs.writeFileSync('./example-output/test.csv', csvData);
32 | // fs.writeFileSync('./example-output/test-presegment.txt', preSegmentTextData);
33 | // fs.writeFileSync('./example-output/test.txt', testTet);
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/divide-into-two-lines/README.md:
--------------------------------------------------------------------------------
1 | # Divide into two lines
2 |
3 | Take these new chunks and separate them further so that there are no more than two consecutive lines before an empty line.
4 |
5 | Creating block of text, with one or two consecutive lines.
6 |
7 | Groups “paragraphs” by `\n`.
8 |
9 | Of “paragraphs” if they are more then 1 line.
10 | break/add line break `\n` every two or more line breaks.
11 |
12 |
13 | #### Input
14 | is output of previous section
15 |
16 | ```
17 | Hi there, my name is Ian police -
18 | are recording this video to talk
19 | about mercury for the folks at a
20 | tech daily conference in New York.
21 |
22 | Sorry, I can’t be there in person,
23 | so we are building a prototype
24 | funded in part by Google DNI of a
25 | web-based computer, assisted
26 | transcription and translation tool
27 | with some video editing features.
28 |
29 | It does speech to text and then
30 | automated consistent translation
31 | and then text to speech generate
32 | synthetic voices at time codes that
33 | line up with the original original
34 | audio.
35 | ```
36 |
37 | #### output
38 |
39 | ```
40 | Hi there, my name is Ian police -
41 | are recording this video to talk
42 |
43 | about mercury for the folks at a
44 | tech daily conference in New York.
45 |
46 | Sorry, I can’t be there in person,
47 | so we are building a prototype
48 |
49 | funded in part by Google DNI of a
50 | web-based computer, assisted
51 |
52 | transcription and translation tool
53 | with some video editing features.
54 |
55 | It does speech to text and then
56 | automated consistent translation
57 |
58 | and then text to speech generate
59 | synthetic voices at time codes that
60 |
61 | line up with the original original
62 | audio.
63 | ```
64 |
65 | #### algo
66 | ```perl
67 | # Insert new line for every two lines, preserving paragraphs
68 | perl -00 -ple 's/.*\n.*\n/$&\n/mg' test3.txt > "$f"
69 | ```
70 |
--------------------------------------------------------------------------------
/packages/components/transcript-editor/src/index.module.css:
--------------------------------------------------------------------------------
1 | @import '../../../config/style-guide/colours.scss';
2 |
3 | .settings {
4 | position: absolute;
5 | left: 0;
6 | right: 0;
7 | margin: 0 auto;
8 | width: 30%;
9 | min-width: 300px;
10 | min-height: 300px;
11 | text-align: center;
12 | vertical-align: middle;
13 | color: white;
14 | background: #4a4a4a;
15 | padding: 1em;
16 | font-weight: lighter;
17 | z-index: 2;
18 | }
19 |
20 | .header {
21 | margin-top: 0;
22 | margin-bottom: 1em;
23 | }
24 |
25 | .closeButton {
26 | position: absolute;
27 | top: 0;
28 | right: 0;
29 | padding: 1em;
30 | cursor: pointer;
31 | }
32 |
33 | .controlsContainer {
34 | display: flex;
35 | flex-direction: column;
36 | align-content: flex-start;
37 | align-items: center;
38 | margin: 0 auto;
39 | }
40 |
41 | .settingElement {
42 | text-align: left;
43 | align-self: auto;
44 | margin-bottom: 0.5em;
45 | }
46 |
47 | .label {
48 | display: inline-block;
49 | min-width: 200px;
50 | width: 200px;
51 | }
52 |
53 | .rollbackValue {
54 | height: 2em;
55 | width: 48px;
56 | box-sizing: border-box;
57 | border: none;
58 | text-align: center;
59 | font-weight: bold;
60 | margin-right: 16px;
61 | vertical-align: middle;
62 | }
63 |
64 | .timecodeLabel {
65 | display: block;
66 | text-align: center;
67 | }
68 |
69 |
70 | .playerButton {
71 | width: 48px;
72 | height: 48px;
73 | padding: 0.5em;
74 | border: 0;
75 | color: white;
76 | background: $color-darkest-grey;
77 | font-size: 1em;
78 | cursor: pointer;
79 | margin-right: 0.3rem;
80 | margin-top: 0.3rem;
81 | white-space: nowrap;
82 | overflow: hidden;
83 | text-overflow: ellipsis;
84 | }
85 |
86 | .playerButton {
87 | width: 100%;
88 | height: 48px;
89 | margin: 0;
90 | }
91 |
92 |
93 | .playerButton:hover {
94 | background-color: gray;
95 | }
96 |
--------------------------------------------------------------------------------
/docs/guides/analytics.md:
--------------------------------------------------------------------------------
1 | # Analytics
2 |
3 | The `TranscriptEditor` component has an optional setup to track some analytics events around the usage of the main functionalities.
4 |
5 | As you can see in the demo app at `/src/index.js` there is an optional `handleAnalyticsEvents`.
6 |
7 | ```js
8 |
16 | ```
17 |
18 | It returns an object, which in the example we are adding to an array. and displaying at the [bottom of the demo page](https://bbc.github.io/react-transcript-editor/) in a `textarea`.
19 |
20 | Here's an example of the output
21 |
22 | ```json
23 | [
24 | {
25 | "category": "TimedTextEditor",
26 | "action": "setEditorContentState",
27 | "name": "getWordCount",
28 | "value": 1712
29 | },
30 | {
31 | "category": "MediaPlayer",
32 | "action": "onLoadedDataGetDuration",
33 | "name": "durationInSeconds-WithoutOffset",
34 | "value": "00:11:51:07"
35 | },
36 | {
37 | "category": "WrapperBlock",
38 | "action": "handleTimecodeClick",
39 | "name": "onWordClick",
40 | "value": "00:00:31:20"
41 | },
42 | {
43 | "category": "MediaPlayer",
44 | "action": "pauseMedia",
45 | "name": "pauseMedia",
46 | "value": "00:00:31:20"
47 | }
48 | ]
49 | ```
50 |
51 | This data is what you can send to your analytics system/provider. Eg if you are using [piwik/matomo](https://matomo.org/free-software/) with the js sdk then you could setup an handler like this to track individual events with their dashboard.
52 |
53 | ```js
54 | handleAnalyticsEvents = (event) => {
55 | if (window.location.hostname !== "localhost") {
56 | _paq.push([
57 | "trackEvent",
58 | event.category,
59 | event.action,
60 | event.name,
61 | event.value,
62 | ]);
63 | }
64 | };
65 | ```
66 |
--------------------------------------------------------------------------------
/packages/export-adapters/docx/index.js:
--------------------------------------------------------------------------------
1 | import { Document, Paragraph, TextRun, Packer } from 'docx';
2 | import { shortTimecode } from '../../util/timecode-converter/';
3 |
4 | export default (blockData, transcriptTitle) => {
5 | // const lines = blockData.blocks.map(x => x.text);
6 |
7 | return generateDocxFromDraftJs(blockData, transcriptTitle);
8 | // return lines.join('\n\n');
9 | };
10 |
11 | function generateDocxFromDraftJs(blockData, transcriptTitle) {
12 |
13 | const doc = new Document({
14 | creator: 'Test',
15 | description: 'Test Description',
16 | title: transcriptTitle,
17 | });
18 |
19 | // Transcript Title
20 | // TODO: get title in programmatically - optional value
21 | const textTitle = new TextRun(transcriptTitle);
22 | const paragraphTitle = new Paragraph();
23 | paragraphTitle.addRun(textTitle);
24 | paragraphTitle.heading1().center();
25 | doc.addParagraph(paragraphTitle);
26 |
27 | // add spacing
28 | var paragraphEmpty = new Paragraph();
29 | doc.addParagraph(paragraphEmpty);
30 |
31 | blockData.blocks.forEach((draftJsParagraph) => {
32 | // TODO: use timecode converter module to convert from seconds to timecode
33 | const paragraphSpeakerTimecodes = new Paragraph(shortTimecode(draftJsParagraph.data.words[0].start));
34 | const speaker = new TextRun(draftJsParagraph.data.speaker).bold().tab();
35 | const textBreak = new TextRun('').break();
36 | paragraphSpeakerTimecodes.addRun(speaker);
37 | doc.addParagraph(paragraphSpeakerTimecodes);
38 | const paragraphText = new Paragraph(draftJsParagraph.text);
39 | paragraphText.addRun(textBreak);
40 | doc.addParagraph(paragraphText);
41 | });
42 |
43 | const packer = new Packer();
44 |
45 | packer.toBlob(doc).then(blob => {
46 | const filename = `${ transcriptTitle }.docx`;
47 | // // const type = 'application/octet-stream';
48 | const a = document.createElement('a');
49 | a.href = window.URL.createObjectURL(blob);
50 | a.download = filename;
51 | a.click();
52 |
53 | return blob;
54 | });
55 |
56 | }
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/fold/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Fold char limit per line
3 |
4 | folds each line at char limit. eg 35 char.
5 |
6 | he 2nd line (pictured) takes each of sentences (now separated by an empty line) and places a new line mark at the end of the word that exceeds > 35 characters (if the sentence exceeds that number)
7 |
8 |
9 |
14 |
15 | #### Input
16 |
17 | ```
18 | Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
19 |
20 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
21 |
22 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.
23 | ```
24 |
25 | #### Output
26 |
27 | ```
28 |
29 | Hi there, my name is Ian police -
30 | are recording this video to talk
31 | about mercury for the folks at a
32 | tech daily conference in New York.
33 |
34 | Sorry, I can’t be there in person,
35 | so we are building a prototype
36 | funded in part by Google DNI of a
37 | web-based computer, assisted
38 | transcription and translation tool
39 | with some video editing features.
40 |
41 | It does speech to text and then
42 | automated consistent translation
43 | and then text to speech generate
44 | synthetic voices at time codes that
45 | line up with the original original
46 | audio.
47 | ```
48 |
49 | #### algo
50 |
51 | ```bash
52 | # Break each line at 35 characters
53 | fold -w 35 -s test2.txt > test3.txt
54 | ```
55 |
--------------------------------------------------------------------------------
/docs/notes/2018-10-07-component-will-receive-props-deprecated.md:
--------------------------------------------------------------------------------
1 | # `componentWillReceiveProps` deprecated
2 |
3 | - [Update on Async Rendering](https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html)
4 | - [Replacing ‘componentWillReceiveProps’ with ‘getDerivedStateFromProps’](https://hackernoon.com/replacing-componentwillreceiveprops-with-getderivedstatefromprops-c3956f7ce607)
5 |
6 |
7 |
8 | > Use `getDerivedStateFromProps`. Don't re-update the transcript if not chnaged basically for now just don't update transcriptData if that is not null we can massage that later for the odd case when you want to load another transcript `this.state.transcriptData === null`
9 |
10 | ---
11 | refactored this
12 |
13 | ```js
14 | componentWillReceiveProps(nextProps) {
15 | // TODO: use currentTime info to highlight text in draftJs
16 | console.log('nextProps.currentTime', nextProps.currentTime);
17 |
18 | if(this.state.transcriptData === null){
19 | this.setState({
20 | transcriptData: nextProps.transcriptData
21 | },() => {
22 | this.loadData();
23 | }
24 | );
25 | }
26 | }
27 | ```
28 | to
29 |
30 | ```js
31 | // can use to determine if you need to update the state
32 | // return state object
33 | // if no changes needed, return null
34 | static getDerivedStateFromProps(nextProps, prevState){
35 | if(nextProps.transcriptData !== null){
36 | return {
37 | transcriptData: nextProps.transcriptData
38 | }
39 | }
40 | return null;
41 | }
42 |
43 | // if you need to trigger something after state update on new prop can use
44 | // this function to do that
45 | componentDidUpdate(prevProps, prevState) {
46 | if(prevState.transcriptData !== this.state.transcriptData){
47 | this.loadData();
48 | }
49 | }
50 | ```
51 |
52 |
53 |
54 | ---
55 |
56 | > later `this.state.transcriptData !== nexProps.transcriptData` would be nicer but comparing objects like that is trickier unless you go to immutable.js or something for now just care about loading transcript once
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/packages/export-adapters/subtitles-generator/presegment-text/text-segmentation/README.md:
--------------------------------------------------------------------------------
1 | # Text segmention
2 |
3 | #### Input
4 |
5 | Plain text, **with punctuation** all on one line
6 |
7 | ```
8 | Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York. Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features. It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.
9 | ```
10 |
11 | #### Out
12 |
13 | Puts each sentence that ends with full stop on new line. `\n`. Without getting fulled by `HONOFIFICS`.
14 |
15 | ```
16 | Hi there, my name is Ian police - are recording this video to talk about mercury for the folks at a tech daily conference in New York.
17 | Sorry, I can't be there in person, so we are building a prototype funded in part by Google DNI of a web-based computer, assisted transcription and translation tool with some video editing features.
18 | It does speech to text and then automated consistent translation and then text to speech generate synthetic voices at time codes that line up with the original original audio.
19 | ```
20 |
21 | #### algo
22 |
23 | [Joseph Polizzotto's perl script identify sentence boundaries sentence-boundary.pl ](https://github.com/polizoto/segment_transcript/blob/master/sentence-boundary.pl)
24 |
25 | ```perl
26 | # segment transcript into sentences
27 | perl sentence-boundary.pl -d HONORIFICS -i "$f" -o test.txt
28 | ```
29 |
30 | list of [`HONORIFICS` here](https://github.com/polizoto/align_transcript/blob/master/HONORIFICS)
31 |
32 |
33 | ## Dependency
34 |
35 | - [sbd](https://www.npmjs.com/package/sbd)
36 |
37 |
38 | ## TODO:
39 | - [ ] Do further tests with honorifics, see `HONORIFICS` here](./HONORIFICS.txt)
40 | - [ ] if packagins `text_segmengation` as separate module, add `package.json` with `sbd` dependency.
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # component to distribute to npm
15 | /dist
16 |
17 | # misc
18 | .DS_Store
19 | .env.local
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 | .vscode
24 |
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
29 |
30 | # Node specific .gitignore
31 | # Logs
32 | logs
33 | *.log
34 | npm-debug.log*
35 | yarn-debug.log*
36 | yarn-error.log*
37 |
38 | # Runtime data
39 | pids
40 | *.pid
41 | *.seed
42 | *.pid.lock
43 |
44 | # Directory for instrumented libs generated by jscoverage/JSCover
45 | lib-cov
46 |
47 | # Coverage directory used by tools like istanbul
48 | coverage
49 |
50 | # nyc test coverage
51 | .nyc_output
52 |
53 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
54 | .grunt
55 |
56 | # Bower dependency directory (https://bower.io/)
57 | bower_components
58 |
59 | # node-waf configuration
60 | .lock-wscript
61 |
62 | # Compiled binary addons (https://nodejs.org/api/addons.html)
63 | build/Release
64 |
65 | # Dependency directories
66 | node_modules/
67 | jspm_packages/
68 |
69 | # TypeScript v1 declaration files
70 | typings/
71 |
72 | # Optional npm cache directory
73 | .npm
74 |
75 | # Optional eslint cache
76 | .eslintcache
77 |
78 | # Optional REPL history
79 | .node_repl_history
80 |
81 | # Output of 'npm pack'
82 | *.tgz
83 |
84 | # Yarn Integrity file
85 | .yarn-integrity
86 |
87 | # dotenv environment variables file
88 | .env
89 |
90 | # next.js build output
91 | .next
92 |
93 | stats.json
94 |
95 | #ignore all video files
96 | *.wmv
97 | *.mpg
98 | *.mpeg
99 | *.mp4
100 | *.mov
101 | *.flv
102 | *.avi
103 | *.ogv
104 | *.ogg
105 | *.webm
106 |
107 | #ignore audio file
108 | *.wav
109 | *.mp3
110 |
111 | #ingore subtitles files
112 | *.srt
113 | *.sbv
114 | *.vtt
115 | *.ttml
116 | *.itt
117 |
--------------------------------------------------------------------------------
/packages/components/media-player/src/ProgressBar.module.scss:
--------------------------------------------------------------------------------
1 | @import '../../../config/style-guide/colours.scss';
2 |
3 | $slider-width-number: 1440;
4 | $slider-width: #{$slider-width-number}px;
5 | $slider-height: 10px;
6 | $background-slider: $color-light-grey;
7 | $bar-slider-filled: $color-labs-red;
8 | $thumb-width: 16px;
9 | $thumb-height: 24px;
10 | $shadow-size: -7px;
11 | $fit-thumb-in-slider: -7px;
12 |
13 | @function strip-units($number) {
14 | @return $number / ($number * 0 + 1);
15 | }
16 |
17 | @function makelongshadow($color, $size) {
18 | $val: 1px 0 0 $size $color;
19 |
20 | @for $i from 1 through $slider-width-number {
21 | $val: #{$val}, #{$i}px 0 0 $size #{$color};
22 | }
23 |
24 | @return $val;
25 | }
26 |
27 | .wrapper {
28 | width: 100%;
29 | overflow-x: hidden;
30 | position: absolute;
31 | left: 0;
32 | bottom: -16px;
33 | }
34 |
35 | .bar {
36 | -webkit-appearance: none;
37 | -moz-appearance: none;
38 | appearance: none;
39 | background: none;
40 | cursor: pointer;
41 | width: 100%;
42 | height: 30px;
43 | margin: 0;
44 |
45 | // Chrome
46 | &::-webkit-slider-runnable-track {
47 | height: $slider-height;
48 | width: $slider-width;
49 | content: '';
50 | pointer-events: none;
51 | background: $bar-slider-filled;
52 | }
53 |
54 | &::-webkit-slider-thumb {
55 | -webkit-appearance: none;
56 | height: $thumb-height;
57 | width: $thumb-width;
58 | margin-top: $fit-thumb-in-slider;
59 | background: $color-labs-red;
60 | box-shadow: makelongshadow($color-light-grey, $shadow-size);
61 | }
62 |
63 | // Firefox
64 | &::-moz-range-track {
65 | height: $slider-height;
66 | width: $slider-width;
67 | content: '';
68 | pointer-events: none;
69 | background: $bar-slider-filled;
70 | }
71 |
72 | &::-moz-range-thumb {
73 | -moz-appearance: none;
74 | height: $thumb-height;
75 | width: $thumb-width;
76 | margin-top: $fit-thumb-in-slider;
77 | border: 0;
78 | border-radius: 0;
79 | background: $color-labs-red;
80 | box-shadow: makelongshadow($color-light-grey, $shadow-size);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/components/transcript-editor/src/HowDoesThisWork.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | faKeyboard,
4 | faQuestionCircle,
5 | faMousePointer,
6 | faICursor,
7 | faUserEdit,
8 | faSave
9 | } from '@fortawesome/free-solid-svg-icons';
10 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11 | import Tooltip from 'react-simple-tooltip';
12 | import style from '../index.module.css';
13 |
14 | const helpMessage = (
15 |
16 |
17 |
18 | Double click on a word or timestamp to jump to that point in the
19 | video.
20 |
21 |
22 |
23 | Start typing to edit text.
24 |
25 |
26 |
27 | You can add and change names of speakers in your transcript.
28 |
29 |
30 |
31 | Use keyboard shortcuts for quick control.
32 |
33 |
34 |
35 | Save & export to get a copy to your desktop.
36 |
37 |