aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlonkaars <loek@pipeframe.xyz>2021-07-25 10:42:42 +0200
committerlonkaars <loek@pipeframe.xyz>2021-07-25 10:42:42 +0200
commitda26ec8a8cec5406850a2529b861201148592835 (patch)
tree9dc2163e4cd06e26a65406a9d82d3f7f345dddac
parent9cf44906d3d20ef72379d36b7ccdc2241b1a17ff (diff)
remove project info from timeline type
-rw-r--r--pages/editor.tsx62
-rw-r--r--pages/present.tsx40
-rw-r--r--project.ts47
-rw-r--r--timeline.ts8
4 files changed, 65 insertions, 92 deletions
diff --git a/pages/editor.tsx b/pages/editor.tsx
index f07d915..1ae1587 100644
--- a/pages/editor.tsx
+++ b/pages/editor.tsx
@@ -131,34 +131,20 @@ var global = createState<globalState>({
update: {
refreshLiveTimeline: () => {
if (typeof player.timeline === 'undefined') return;
- player.timeline.slides = Array(...(global.timeline.workingTimeline.value));
- player.timeline.slides = player.timeline.slides.filter(slide => slide != null);
- player.timeline.slides.sort((a, b) => a.frame - b.frame);
- player.timeline.slides[-1] = { // TODO: dry
+ player.timeline = Array(...(global.timeline.workingTimeline.value));
+ player.timeline = player.timeline.filter(slide => slide != null);
+ player.timeline.sort((a, b) => a.frame - b.frame);
+ player.timeline[-1] = { // TODO: dry
id: '00000000-0000-0000-0000-000000000000',
frame: 0,
type: 'default',
clickThroughBehaviour: 'ImmediatelySkip',
};
- project.timeline.slides.set(player.timeline.slides);
- player.timeline = project.timeline.attach(Downgraded).value;
+ projectFile.timeline = player.timeline;
},
},
});
-interface project {
- timeline: timeline;
-}
-
-var project = createState<project>({
- timeline: {
- name: '',
- slides: [],
- framerate: 0,
- framecount: 0,
- },
-});
-
var settings = {
'default': {
node: <DefaultSettings />,
@@ -511,7 +497,7 @@ function TimelineSelection(props: { selectionDragArea: Ref<ReactNode>; }) {
selectionPosAPI.start({ visibility: 0 });
} else {
var endingFrame = startingFrame + frameWidth;
- var expandedTimeline = new Array(...project.timeline.slides.value);
+ var expandedTimeline = new Array(...projectFile.timeline);
for (let i = 0; i < expandedTimeline.length; i++) {
var slide = expandedTimeline[i];
if (slide.type != 'loop') continue;
@@ -670,7 +656,7 @@ function divs(int: number) {
function getMarkerSpacing() {
var zoom = global.timeline.zoom.value;
var frameWidth = zoomToPx(zoom);
- var divvable = divs(Math.round(project.timeline.framerate.value));
+ var divvable = divs(Math.round(projectFile?.video?.framerate));
if (divvable.length == 0) return 30;
var minSpacing = 120;
var multiply = 1;
@@ -693,7 +679,6 @@ function TimelineEditor() {
var mouseX = 0;
var ready = useHookstate(global).ready;
- var proj = useHookstate(project).timeline;
var timelineRef = useRef(null);
var selectionDragArea = useRef(null);
@@ -724,7 +709,7 @@ function TimelineEditor() {
useMousetrap(['.'], () => { // TODO: dry
if (!global.ready.timeline.value) return;
- var frame = Math.min(project.timeline.framecount.value, global.timeline.frame.value + 1);
+ var frame = Math.min(projectFile?.video?.framecount, global.timeline.frame.value + 1);
global.timeline.frame.set(frame);
scrubberSpring.start({ frame });
});
@@ -934,7 +919,7 @@ function TimelineEditor() {
</animated.div>
<div
className='keyframes'
- style={{ '--total-frames': proj.framecount.get() } as CSSProperties}
+ style={{ '--total-frames': projectFile?.video?.framecount } as CSSProperties}
>
<div className='selectionarea posabs v0' ref={selectionDragArea} />
{workingTimeline.map(slide =>
@@ -1113,9 +1098,9 @@ function DefaultSettings() {
reader.addEventListener('load', async ev => {
await projectFile.openProject(ev.target.result as ArrayBuffer);
- player.loadSlides(JSON.stringify(projectFile.project));
- project.timeline.set(player.timeline);
- global.timeline.workingTimeline.set(player.timeline.slides);
+ player.loadSlides(projectFile.timeline);
+ projectFile.timeline = player.timeline;
+ global.timeline.workingTimeline.set(player.timeline);
global.update.refreshLiveTimeline.value();
global.ready.timeline.set(true);
@@ -1146,7 +1131,7 @@ function DefaultSettings() {
children='Download .prspr'
startIcon={<DescriptionRoundedIcon />}
onClick={async () => {
- projectFile.loadProject(player.timeline);
+ projectFile.timeline = player.timeline;
projectFile.saveProject();
projectFile.downloadProjectFile();
}}
@@ -1156,15 +1141,10 @@ function DefaultSettings() {
color='default'
children='New project'
onClick={() => {
- var newProj: timeline = {
- slides: [],
- name: 'New project',
- framerate: 0,
- framecount: 0,
- };
- player.loadSlides(JSON.stringify(newProj));
- project.timeline.set(player.timeline);
- global.timeline.workingTimeline.set(player.timeline.slides);
+ player.loadSlides([]);
+ projectFile.timeline = player.timeline;
+ projectFile.name = 'New project';
+ global.timeline.workingTimeline.set(player.timeline);
global.update.refreshLiveTimeline.value();
global.ready.timeline.set(true);
}}
@@ -1292,7 +1272,6 @@ function Tools() {
var tool = useHookstate(global).timeline.tool;
var timelineZoom = useHookstate(global).timeline.zoom;
var ready = useHookstate(global).ready;
- var framerate = useHookstate(project).timeline.framerate;
useMousetrap(['v'], switchToTool('cursor'));
useMousetrap(['d'], switchToTool('default'));
@@ -1304,7 +1283,7 @@ function Tools() {
return <div className='tools'>
<div className={'time posrel ' + (ready.timeline.get() ? '' : 'disabled')}>
- <span className='framerate numbers posabs l0 t0'>@{framerate.get()}fps</span>
+ <span className='framerate numbers posabs l0 t0'>@{projectFile?.video?.framerate}fps</span>
<h2 className='timecode numbers posabs r0 t0'>
{player.frameToTimestampString(frame.get(), false)}
</h2>
@@ -1439,7 +1418,6 @@ function Player() {
function TitleBar() {
var ready = useHookstate(global).ready;
- var proj = useHookstate(project).timeline;
var nameRef = useRef(null);
@@ -1455,8 +1433,8 @@ function TitleBar() {
contentEditable
spellCheck={false}
ref={nameRef}
- onBlur={() => proj.name.set((nameRef.current as HTMLSpanElement).textContent.trim())}
- children={proj.name.get()}
+ onBlur={() => projectFile.name = (nameRef.current as HTMLSpanElement).textContent.trim()}
+ children={projectFile.name}
/>
</div>
</Toolbar>
diff --git a/pages/present.tsx b/pages/present.tsx
index a99d239..f1fbc58 100644
--- a/pages/present.tsx
+++ b/pages/present.tsx
@@ -1,9 +1,8 @@
import Button from '@material-ui/core/Button';
-import Ajv from 'ajv';
import { useEffect, useState } from 'react';
import Timecode from 'timecode-boss';
+import Project from '../project';
import timeline, { delaySlide, loopSlide, slide, speedChangeSlide } from '../timeline';
-import * as timelineSchema from '../timeline.schema.json';
import ExitToAppRoundedIcon from '@material-ui/icons/ExitToAppRounded';
import PlayArrowRoundedIcon from '@material-ui/icons/PlayArrowRounded';
@@ -21,6 +20,7 @@ export class TimedVideoPlayer {
video: string;
registeredEventListeners: boolean;
frame: number;
+ project: Project;
constructor() {
this.slide = -1;
@@ -30,7 +30,7 @@ export class TimedVideoPlayer {
}
frameToTimestampString(frame: number, trim: boolean = true) {
- var timecodeString = new Timecode(frame, this.timeline?.framerate).toString();
+ var timecodeString = new Timecode(frame, this.project?.video?.framerate).toString();
if (trim) timecodeString = timecodeString.replace(/^(00:)+/, '');
timecodeString = timecodeString.replace(';', '.')
.replace(/(:)(\d+?)$/, '.$2')
@@ -40,11 +40,11 @@ export class TimedVideoPlayer {
}
timestampToFrame(timestamp: number): number {
- return Math.ceil((timestamp * 1e3) / (1e3 / this.timeline?.framerate));
+ return Math.ceil(timestamp * this.project?.video?.framerate);
}
frameToTimestamp(frame: number): number {
- return frame / this.timeline?.framerate;
+ return frame / this.project?.video?.framerate;
}
registerPlayer(player: HTMLVideoElement) {
@@ -69,11 +69,11 @@ export class TimedVideoPlayer {
getPlaybackSpeed(slide: number) {
for (var i = slide; i > -1; i--) {
- var previousSlide = this.timeline.slides[i];
+ var previousSlide = this.timeline[i];
if (previousSlide.type != 'speedChange') {
continue;
}
- return this.timeline?.framerate / (previousSlide as speedChangeSlide).newFramerate;
+ return this.project?.video?.framerate / (previousSlide as speedChangeSlide).newFramerate;
}
return 1;
}
@@ -87,7 +87,7 @@ export class TimedVideoPlayer {
case 'delay': {
this.player.playbackRate = 0;
this.slide++;
- var event = new CustomEvent('TimedVideoPlayerSlide', { detail: this.timeline.slides[this.slide] });
+ var event = new CustomEvent('TimedVideoPlayerSlide', { detail: this.timeline[this.slide] });
this.dispatchEvent(event);
setTimeout(() => {
this.player.playbackRate = this.getPlaybackSpeed(this.slide - 1);
@@ -96,9 +96,9 @@ export class TimedVideoPlayer {
}
case 'speedChange': {
this.slide++;
- var event = new CustomEvent('TimedVideoPlayerSlide', { detail: this.timeline.slides[this.slide] });
+ var event = new CustomEvent('TimedVideoPlayerSlide', { detail: this.timeline[this.slide] });
this.dispatchEvent(event);
- this.player.playbackRate = this.timeline?.framerate / (slide as speedChangeSlide).newFramerate;
+ this.player.playbackRate = this.project?.video?.framerate / (slide as speedChangeSlide).newFramerate;
break;
}
default: {
@@ -134,13 +134,13 @@ export class TimedVideoPlayer {
var event = new CustomEvent('TimedVideoPlayerOnFrame', { detail: this.frame });
this.dispatchEvent(event);
- var slide = this.timeline.slides[this.slide];
+ var slide = this.timeline[this.slide];
if (!slide) return;
if (this.frame >= slide.frame) {
this.handleSlide(slide);
}
- }, 1e3 / (this.precision * this.timeline?.framerate));
+ }, 1e3 / (this.precision * this.project?.video?.framerate));
this.registeredEventListeners = true;
}
@@ -151,10 +151,10 @@ export class TimedVideoPlayer {
this.registerEventListeners();
}
- loadSlides(jsonString: string) {
- this.timeline = JSON.parse(jsonString) as timeline;
+ loadSlides(timeline: timeline) {
+ this.timeline = timeline;
- this.timeline.slides[-1] = {
+ this.timeline[-1] = {
id: '00000000-0000-0000-0000-000000000000',
frame: 0,
type: 'default',
@@ -165,7 +165,7 @@ export class TimedVideoPlayer {
}
skip() {
- var slide = this.timeline.slides[this.slide - 1];
+ var slide = this.timeline[this.slide - 1];
if (slide.clickThroughBehaviour == 'ImmediatelySkip') this.jumpToSlide(slide);
}
@@ -174,7 +174,7 @@ export class TimedVideoPlayer {
this.slide++;
- var slide = this.timeline.slides[this.slide];
+ var slide = this.timeline[this.slide];
var event = new CustomEvent('TimedVideoPlayerSlide', { detail: slide });
this.dispatchEvent(event);
@@ -188,7 +188,7 @@ export class TimedVideoPlayer {
this.slide = Math.max(this.slide - 1, -1);
- var slide = this.timeline.slides[this.slide];
+ var slide = this.timeline[this.slide];
if (!slide) return;
var event = new CustomEvent('TimedVideoPlayerSlide', { detail: slide });
@@ -255,7 +255,7 @@ export default function Present() {
<div className='info sidebyside posabs h0 b0'>
<div className='timetitle floatb'>
<h3 className='time numbers nobr' id='time'>14:00:41</h3>
- <h1 className='title nobr'>{player.timeline?.name || '???'}</h1>
+ <h1 className='title nobr'>{player.project?.name || '???'}</h1>
</div>
<div className='buttons floatb'>
<div className='inner center'>
@@ -336,7 +336,7 @@ export default function Present() {
</div>
<div className='slide posrel floatb'>
<h3 className='time numbers nobr posrel'>
- slide {player.slide + 1}/{player.timeline?.slides.length || 0}
+ slide {player.slide + 1}/{player.timeline?.length || 0}
</h3>
</div>
</div>
diff --git a/project.ts b/project.ts
index be62416..0fbdeb4 100644
--- a/project.ts
+++ b/project.ts
@@ -1,7 +1,7 @@
import { MediaInfo as Mediainfo, ResultObject } from 'mediainfo.js/dist/types';
import JSZip from 'jszip';
import semver from 'semver';
-import { TimedVideoPlayer } from './pages/present';
+import timeline from './timeline';
import { LocalVideoSettings } from './components/videosourcesettings';
@@ -18,7 +18,7 @@ export function arrayBufferToBase64(buffer: ArrayBuffer, mimetype?: string) {
const filext = '.prspr';
interface VideoSource {
- load: (data: ArrayBuffer) => void;
+ load: (dir: JSZip) => void;
save: (dir: JSZip) => void;
}
@@ -34,16 +34,14 @@ export class LocalVideo implements VideoSource {
fullyBuffer: boolean;
};
- constructor(video?: ArrayBuffer) {
+ constructor() {
this.type = 'local';
this.config = {
fullyBuffer: false,
};
- if (video) this.load(video);
}
- async load(data: ArrayBuffer) {
- this.source = data;
+ async getVideoInfo() {
var mediainfo = await MediaInfo();
var result = await mediainfo.analyzeData(
() => this.source.byteLength,
@@ -59,6 +57,12 @@ export class LocalVideo implements VideoSource {
this.framerate = Number(meta.FrameRate);
}
+ async load(dir: JSZip) {
+ this.source = await dir.file('video').async('arraybuffer');
+ this.framerate = Number(await dir.file('framerate').async('string'));
+ this.framecount = Number(await dir.file('framecount').async('string'));
+ }
+
save(dir: JSZip) {
dir.file('video', this.source);
dir.file('framecount', this.framecount.toString());
@@ -108,7 +112,8 @@ export default class {
version: string = '0.2.0';
fileVersion: string;
zip: JSZip;
- project: TimedVideoPlayer['timeline'];
+ timeline: timeline;
+ name: string;
video: VideoSourceClass;
settings: PresentationSettings;
@@ -122,29 +127,25 @@ export default class {
var blob = new Blob([zip], { type: 'application/octet-stream' });
var a = document.createElement('a');
a.href = URL.createObjectURL(blob);
- a.download = this.project.name
+ a.download = this.name
.toLowerCase()
.replace(/\s/g, '-') + filext;
a.click();
}
- loadProject(project: TimedVideoPlayer['timeline']) {
- this.project = project;
- }
-
saveProject() {
this.zip = new JSZip();
var meta = this.zip.folder('meta');
meta.file('version', this.version);
- meta.file('name', this.project.name);
+ meta.file('name', this.name);
var source = this.zip.folder('source');
this.video.save(source);
source.file('mimetype', this.video.mimetype);
source.file('config', JSON.stringify(this.video.config, null, 4));
- this.zip.file('slides', JSON.stringify(this.project.slides, null, 4));
+ this.zip.file('slides', JSON.stringify(this.timeline, null, 4));
this.zip.file('settings', JSON.stringify(this.settings, null, 4));
}
@@ -153,18 +154,16 @@ export default class {
await this.zip.loadAsync(data);
this.fileVersion = await this.zip.file('meta/version').async('string');
- this.project = {
- name: await this.zip.file('meta/name').async('string'),
- slides: JSON.parse(await this.zip.file('slides').async('string')),
- framerate: Number(await this.zip.file('source/framerate').async('string')),
- framecount: Number(await this.zip.file('source/framecount').async('string')),
- } as TimedVideoPlayer['timeline'];
-
- var type = await this.zip.file('source/type').async('string');
+ this.timeline = JSON.parse(await this.zip.file('slides').async('string'));
+ this.name = await this.zip.file('meta/name').async('string');
+
+ var source = this.zip.folder('source');
+ var type = await source.file('type').async('string');
var videoSourceType = VideoSources.find(s => s.type == type);
- this.video = new videoSourceType.class(await this.zip.file('source/video').async('arraybuffer'));
- this.video.mimetype = await this.zip.file('source/mimetype').async('string');
+ this.video = new videoSourceType.class();
+ await this.video.load(source);
+ this.video.mimetype = await source.file('mimetype').async('string');
if (semver.lt('0.1.1', this.fileVersion)) {
this.video.config = JSON.parse(await this.zip.file('source/config').async('string'));
diff --git a/timeline.ts b/timeline.ts
index 052ae8d..bd1ccd4 100644
--- a/timeline.ts
+++ b/timeline.ts
@@ -50,9 +50,5 @@ export var toolToSlide = {
loop: loopSlide,
};
-export default interface timeline {
- slides: Array<anySlide>;
- framecount: number;
- framerate: number;
- name: string;
-}
+type timeline = Array<anySlide>;
+export default timeline;