diff options
author | lonkaars <loek@pipeframe.xyz> | 2021-07-25 10:42:42 +0200 |
---|---|---|
committer | lonkaars <loek@pipeframe.xyz> | 2021-07-25 10:42:42 +0200 |
commit | da26ec8a8cec5406850a2529b861201148592835 (patch) | |
tree | 9dc2163e4cd06e26a65406a9d82d3f7f345dddac | |
parent | 9cf44906d3d20ef72379d36b7ccdc2241b1a17ff (diff) |
remove project info from timeline type
-rw-r--r-- | pages/editor.tsx | 62 | ||||
-rw-r--r-- | pages/present.tsx | 40 | ||||
-rw-r--r-- | project.ts | 47 | ||||
-rw-r--r-- | timeline.ts | 8 |
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> @@ -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; |