diff options
author | bobzel <zzzman@gmail.com> | 2024-01-04 13:11:22 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-01-04 13:11:22 -0500 |
commit | ae27dd1689ae1716591aab094e6d41f3a0160fef (patch) | |
tree | 09735c798abde4fd0b8f234c48375bd6cb1112a4 /src | |
parent | ebf32ac65d35053f847fb2cf60f915eb29d6fdd5 (diff) |
fixed ffmpeg for uploading videos. fixed getView() to take focus options so that if a sidebar or timeline has to be opened, the didMove flag can be set properly. added video snapshot frame to top menu. fixed using '^' to stop and start a selection region on timelines.
Diffstat (limited to 'src')
21 files changed, 129 insertions, 215 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 79285deb5..92a9ddfe8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -209,7 +209,7 @@ export class DocumentOptions { author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); annotationOn?: DOCt = new DocInfo('document annotated by this document', false); - embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument', false); + _embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument', false); rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)'); color?: STRt = new StrInfo('foreground color data doc', false); hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 3f28f44fc..4391e87d6 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -309,7 +309,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} }, { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '<ScriptingRepl />' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script - // { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack>" as any, openFactoryLocation: OpenWhere.overlay}, + // { toolTip: "Toggle an UndoStack", title: "undostacker", icon: "calculator", clickFactory: "<UndoStack />" as any, openFactoryLocation: OpenWhere.overlay}, ].map(tuple => ( { openFactoryLocation: OpenWhere.addRight, scripts: { onClick: 'openDoc(copyDragFactory(this.clickFactory,this.openFactoryAsDelegate), this.openFactoryLocation)', @@ -695,11 +695,16 @@ export class CurrentUserUtils { static webTools() { return [ - { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(_readOnly_); }' }}, - { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(_readOnly_); }'}}, + { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(); }' }}, + { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(); }'}}, { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, ]; } + static videoTools() { + return [ + { title: "Snapshot",toolTip: "Take snapshot of current frame", btnType: ButtonType.ClickButton, icon: "camera", scripts: { onClick: '{ return videoSnapshot(); }' }}, + ]; + } static contextMenuTools():Button[] { return [ { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, @@ -721,6 +726,7 @@ export class CurrentUserUtils { { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected + { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected ]; } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 0fab42895..7fef5cc79 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -281,7 +281,7 @@ export class DocumentManager { docContextPath.shift(); const childViewIterator = async (docView: DocumentView) => { const innerDoc = docContextPath.shift(); - return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc)) ?? this.getDocumentView(innerDoc) : undefined }; + return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc) : undefined }; }; if (rootContextView) { diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts index 865f8bc02..7638e2ce0 100644 --- a/src/client/util/PingManager.ts +++ b/src/client/util/PingManager.ts @@ -4,11 +4,20 @@ import { CurrentUserUtils } from './CurrentUserUtils'; export class PingManager { // create static instance and getter for global use @observable static _instance: PingManager; + @observable IsBeating = true; static get Instance(): PingManager { return PingManager._instance; } - @observable IsBeating = true; + // not used now, but may need to clear interval + private _interval: NodeJS.Timeout | null = null; + INTERVAL_SECONDS = 1; + constructor() { + makeObservable(this); + PingManager._instance = this; + this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000); + } + private setIsBeating = action((status: boolean) => { this.IsBeating = status; setTimeout(this.showAlert, 100); @@ -28,13 +37,4 @@ export class PingManager { } } }; - - // not used now, but may need to clear interval - private _interval: NodeJS.Timeout | null = null; - INTERVAL_SECONDS = 1; - constructor() { - makeObservable(this); - PingManager._instance = this; - this._interval = setInterval(this.sendPing, this.INTERVAL_SECONDS * 1000); - } } diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 77b831e51..d134d9e7b 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -128,10 +128,12 @@ export class KeyManager { var doDeselect = true; if (SnappingManager.IsDragging) { DragManager.AbortDrag(); - } else if (CollectionDockingView.Instance?.HasFullScreen) { + } + if (CollectionDockingView.Instance?.HasFullScreen) { CollectionDockingView.Instance?.CloseFullScreen(); - } else if (CollectionStackedTimeline.SelectingRegion) { - CollectionStackedTimeline.SelectingRegion = undefined; + } + if (CollectionStackedTimeline.SelectingRegions.size) { + CollectionStackedTimeline.StopSelecting(); doDeselect = false; } else { doDeselect = !ContextMenu.Instance.closeMenu(); diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx index ea038250e..068143225 100644 --- a/src/client/views/UndoStack.tsx +++ b/src/client/views/UndoStack.tsx @@ -8,19 +8,13 @@ import { SettingsManager } from '../util/SettingsManager'; import { UndoManager } from '../util/UndoManager'; import './UndoStack.scss'; -interface UndoStackProps { - width?: number; - height?: number; - inline?: boolean; -} +interface UndoStackProps {} @observer export class UndoStack extends React.Component<UndoStackProps> { - @observable static HideInline: boolean = false; - @observable static Expand: boolean = false; render() { const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor; const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor; - return this.props.inline && UndoStack.HideInline ? null : ( + return ( <Tooltip title={'undo stack (if it stays yellow, undo is broken - you should reload Dash)'}> <div> <div className="undoStack-outerContainer"> diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index c46f54c70..874cdffd9 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -615,7 +615,7 @@ ScriptingGlobals.add( // prettier-ignore switch (doc) { case '<ScriptingRepl />': return OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); - case "<UndoStack>": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Scripting REPL' }); + case "<UndoStack />": return OverlayView.Instance.addWindow(<UndoStack />, { x: 300, y: 100, width: 200, height: 200, title: 'Undo stack' }); } Doc.AddToMyOverlay(doc); } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index d99b4f9de..22a67c501 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -56,8 +56,11 @@ export enum TrimScope { @observer export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { - @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; - @observable public static CurrentlyPlaying: DocumentView[] = []; + public static SelectingRegions: Set<CollectionStackedTimeline> = new Set(); + public static StopSelecting() { + this.SelectingRegions.forEach(action(region => (region._selectingRegion = false))); + this.SelectingRegions.clear(); + } constructor(props: any) { super(props); makeObservable(this); @@ -69,6 +72,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack private _timeline: HTMLDivElement | null = null; // ref to actual timeline div private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling private _markerStart: number = 0; + @observable public static CurrentlyPlaying: DocumentView[] = []; + @observable _selectingRegion = false; @observable _markerEnd: number | undefined = undefined; @observable _trimming: number = TrimScope.None; @observable _trimStart: number = 0; // trim controls start pos @@ -123,8 +128,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack componentWillUnmount() { document.removeEventListener('keydown', this.keyEvents, true); - if (CollectionStackedTimeline.SelectingRegion === this) { - runInAction(() => (CollectionStackedTimeline.SelectingRegion = undefined)); + if (this._selectingRegion) { + runInAction(() => (this._selectingRegion = false)); + CollectionStackedTimeline.SelectingRegions.delete(this); } } @@ -154,6 +160,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._zoomFactor = zoom; } + makeDocUnfiltered = (doc: Doc) => this.childDocList?.some(item => item === doc); + + getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> => + new Promise<Opt<DocumentView>>(res => { + if (doc.hidden) options.didMove = !(doc.hidden = false); + const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); + findDoc(dv => res(dv)); + }); + anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag])); anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null); @@ -191,14 +206,16 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._props.playing() ? this._props.Pause() : this._props.Play(); break; case '^': - if (!CollectionStackedTimeline.SelectingRegion) { + if (!this._selectingRegion) { this._markerStart = this._markerEnd = this.currentTime; - CollectionStackedTimeline.SelectingRegion = this; + this._selectingRegion = true; + CollectionStackedTimeline.SelectingRegions.add(this); } else { this._markerEnd = this.currentTime; CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true); this._markerEnd = undefined; - CollectionStackedTimeline.SelectingRegion = undefined; + this._selectingRegion = false; + CollectionStackedTimeline.SelectingRegions.delete(this); } e.stopPropagation(); break; @@ -405,6 +422,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack backgroundColor: 'rgba(128, 128, 128, 0.5)', layout_hideLinkButton: true, onClick: FollowLinkScript(), + _embedContainer: doc, annotationOn: doc, _isTimelineLabel: true, layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined, @@ -511,7 +529,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // renders selection region on timeline @computed get selectionContainer() { - const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd; + const markerEnd = this._selectingRegion ? this.currentTime : this._markerEnd; return markerEnd === undefined ? null : ( <div className="collectionStackedTimeline-selector" @@ -532,9 +550,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack anchorEndTime: number; level: number; }[] = []; - const drawAnchors = this.childDocs.map(anchor => ({ - level: this.getLevel(anchor, overlaps), - anchor, + const drawAnchors = this.childLayoutPairs.map(pair => ({ + level: this.getLevel(pair.layout, overlaps), + anchor: pair.layout, })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; return this.clipDuration === 0 ? null : ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a3cedb9a0..8268a47d8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -321,9 +321,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; - getView = async (doc: Doc): Promise<Opt<DocumentView>> => + getView = async (doc: Doc, options: DocFocusOptions): Promise<Opt<DocumentView>> => new Promise<Opt<DocumentView>>(res => { - if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false; + if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false); const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index f1fb68003..d105b04f7 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -147,7 +147,7 @@ export class CollectionLinearView extends CollectionSubView() { switch (doc.layout) { case '<LinkingUI>': return this.getLinkUI(); case '<CurrentlyPlayingUI>': return this.getCurrentlyPlayingUI(); - case '<UndoStack>': return <UndoStack key={doc[Id]} width={200} height={40} inline={true} />; + case '<UndoStack>': return <UndoStack />; case '<Branching>': return Doc.UserDoc().isBranchingMode ? <BranchingTrailManager /> : null; } diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 989dd1db0..e57ef4871 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -19,6 +19,7 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum import { DocumentView } from '../nodes/DocumentView'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { WebBox } from '../nodes/WebBox'; +import { VideoBox } from '../nodes/VideoBox'; ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; @@ -390,14 +391,16 @@ ScriptingGlobals.add(function webForward(checkResult?: boolean) { } selected?.forward(); }); -ScriptingGlobals.add(function webBack(checkResult?: boolean) { +ScriptingGlobals.add(function webBack() { const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox; - if (checkResult) { - return selected?.back(checkResult) ? undefined : 'lightGray'; - } selected?.back(); }); +ScriptingGlobals.add(function videoSnapshot() { + const selected = SelectionManager.Views.lastElement()?.ComponentView as VideoBox; + selected?.Snapshot(); +}); + /** Schema * toggleSchemaPreview **/ diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 76cc010f6..5a55ca764 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -20,7 +20,7 @@ import { SidebarAnnos } from '../../SidebarAnnos'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup'; -import { DocumentView } from '../DocumentView'; +import { DocFocusOptions, DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { PinProps } from '../trails'; import './DataVizBox.scss'; @@ -228,8 +228,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar') ); }; - getView = async (doc: Doc) => { - if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); + getView = async (doc: Doc, options: DocFocusOptions) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { + options.didMove = true; + this.toggleSidebar(); + } return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; @computed get sidebarWidthPercent() { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7f1e547e4..6cc61ec62 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -43,7 +43,6 @@ import { GestureOverlay } from '../GestureOverlay'; import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { StyleProp } from '../StyleProvider'; -import { UndoStack } from '../UndoStack'; import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; @@ -119,7 +118,7 @@ export interface DocComponentView { restoreView?: (viewSpec: Doc) => boolean; scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms) - getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined + getView?: (doc: Doc, options: DocFocusOptions) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) select?: (ctrlKey: boolean, shiftKey: boolean) => void; @@ -520,11 +519,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (this._props.isGroupActive?.() === 'child' && !this._props.isDocumentActive?.()) return; this._longPressSelector = setTimeout(() => { if (DocumentView.LongPress) { - if (this.Document.undoIgnoreFields) { - runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline)); - } else { - this._props.select(false); - } + this._props.select(false); } }, 1000); if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._props.DocumentView(); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 41befbbfe..3575b21e4 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -27,7 +27,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocC import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; -import { DocumentView } from '../DocumentView'; +import { DocFocusOptions, DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../trails'; @@ -504,8 +504,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } }; - getView = async (doc: Doc) => { - if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); + getView = async (doc: Doc, options: DocFocusOptions) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { + this.toggleSidebar(); + options.didMove = true; + } return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; /* diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx index 8b22a1531..ea8496c99 100644 --- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx +++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx @@ -20,7 +20,7 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocC import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; -import { DocumentView } from '../DocumentView'; +import { DocFocusOptions, DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { MapAnchorMenu } from '../MapBox/MapAnchorMenu'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; @@ -374,8 +374,11 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent<ViewBoxAnnotata } }; - getView = async (doc: Doc) => { - if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(); + getView = async (doc: Doc, options: DocFocusOptions) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { + this.toggleSidebar(); + options.didMove = true; + } return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; /* diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 55a459a5c..959d5d88d 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -224,8 +224,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this._pdfViewer?.scrollFocus(anchor, NumCast(anchor.y, NumCast(anchor.config_scrollTop)), options); }; - getView = async (doc: Doc) => { - if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); + getView = async (doc: Doc, options: DocFocusOptions) => { + if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) { + options.didMove = true; + this.toggleSidebar(false); + } return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index ae923ad60..460155446 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -180,9 +180,9 @@ } } -video::-webkit-media-controls { - display: none !important; -} +// video::-webkit-media-controls { +// display: none !important; +// } input[type='range'] { -webkit-appearance: none; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ce73d9f37..fb42286af 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -1,9 +1,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { basename } from 'path'; import * as React from 'react'; -import { Doc, StrListCast } from '../../../fields/Doc'; +import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; @@ -12,23 +12,20 @@ import { AudioField, ImageField, VideoField } from '../../../fields/URLField'; import { emptyFunction, formatTime, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; -import { Networking } from '../../Network'; import { DocumentManager } from '../../util/DocumentManager'; import { FollowLinkScript } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ReplayMovements } from '../../util/ReplayMovements'; -import { SelectionManager } from '../../util/SelectionManager'; -import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { StyleProp } from '../StyleProvider'; -import { DocFocusOptions, DocumentView, OpenWhere } from './DocumentView'; +import { DocFocusOptions, DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; import { PinProps, PresBox } from './trails'; @@ -40,7 +37,7 @@ import './VideoBox.scss'; * Supporting Components: CollectionStackedTimeline * * VideoBox is a node that supports the playback of video files in Dash. - * When a video file or YouTube video is importeed into Dash, it is immediately rendered as a VideoBox document. + * When a video file is importeed into Dash, it is immediately rendered as a VideoBox document. * CollectionStackedTimline handles AudioBox and VideoBox shared behavior, but VideoBox handles playing, pausing, etc because it contains <video> element * User can trim video: nondestructive, just sets new bounds for playback and rendering timeline * Like images, users can zoom and pan and it has an overlay layer allowing for annotations on top of the video at different times @@ -51,16 +48,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } - static _youtubeIframeCounter: number = 0; static heightPercent = 80; // height of video relative to videoBox when timeline is open static numThumbnails = 20; private unmounting = false; private _disposers: { [name: string]: IReactionDisposer } = {}; - private _youtubePlayer: YT.Player | undefined = undefined; private _videoRef: HTMLVideoElement | null = null; // <video> ref private _contentRef: HTMLDivElement | null = null; // ref to div that wraps video and controls for full screen - private _youtubeIframeId: number = -1; - private _youtubeContentCreated = false; private _audioPlayer: HTMLAudioElement | null = null; private _marqueeref = React.createRef<MarqueeAnnotator>(); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); // outermost div @@ -74,12 +67,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._props.setContentView?.(this); } - @observable _stackedTimeline: any = undefined; // CollectionStackedTimeline ref - @observable static _nativeControls: boolean = false; // default html controls + @observable _stackedTimeline: CollectionStackedTimeline | undefined = undefined; // CollectionStackedTimeline ref @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @observable _screenCapture = false; @observable _clicking = false; // used for transition between showing/hiding timeline - @observable _forceCreateYouTubeIFrame = false; @observable _playTimer?: NodeJS.Timeout = undefined; @observable _fullScreen = false; @observable _playing = false; @@ -99,11 +90,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // @computed get rawDuration() { return NumCast(this.dataDoc[this.fieldKey + "_duration"]); } @observable rawDuration: number = 0; - @computed get youtubeVideoId() { - const field = Cast(this.dataDoc[this._props.fieldKey], VideoField); - return field && field.url.href.indexOf('youtube') !== -1 ? ((arr: string[]) => arr[arr.length - 1])(field.url.href.split('/')) : ''; - } - // returns the path of the audio file @computed get audiopath() { const field = Cast(this.Document[this._props.fieldKey + '_audio'], AudioField, null); @@ -130,17 +116,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { componentDidMount() { this.unmounting = false; this._props.setContentView?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link. - if (this.youtubeVideoId) { - const youtubeaspect = 400 / 315; - const nativeWidth = Doc.NativeWidth(this.layoutDoc); - const nativeHeight = Doc.NativeHeight(this.layoutDoc); - if (!nativeWidth || !nativeHeight) { - if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 600); - Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 600) / youtubeaspect); - this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect; - } - } - this.player && this.setPlayheadTime(this.timeline.clipStart || 0); + this.player && this.setPlayheadTime(this.timeline?.clipStart || 0); document.addEventListener('keydown', this.keyEvents, true); if (this.presentation) { @@ -201,8 +177,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime); update && this.player && this.playFrom(start, undefined, true); update && this._audioPlayer?.play(); - update && this._youtubePlayer?.playVideo(); - this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); } catch (e) { console.log('Video Play Exception:', e); } @@ -212,11 +186,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { // goes to time @action public Seek(time: number) { - try { - this._youtubePlayer?.seekTo(Math.round(time), true); - } catch (e) { - console.log('Video Seek Exception:', e); - } this.player && (this.player.currentTime = time); this._audioPlayer && (this._audioPlayer.currentTime = time); // TODO: revisit this and clean it @@ -242,13 +211,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { try { update && this.player?.pause(); update && this._audioPlayer?.pause(); - update && this._youtubePlayer?.pauseVideo(); - this._youtubePlayer && this._playTimer && clearInterval(this._playTimer); - this._youtubePlayer?.seekTo(this._youtubePlayer?.getCurrentTime(), true); } catch (e) { console.log('Video Pause Exception:', e); } - this._youtubePlayer && SelectionManager.DeselectAll(); // if we don't deselect the player, then we get an annoying YouTube spinner I guess telling us we're paused. this._playTimer = undefined; this.updateTimecode(); if (!this._finished) { @@ -270,11 +235,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this._fullScreen = true; this.player && this._contentRef && this._contentRef.requestFullscreen(); } - try { - this._youtubePlayer && this._props.addDocTab(this.Document, OpenWhere.add); - } catch (e) { - console.log('Video FullScreen Exception:', e); - } }; // fades out controls in fullscreen after mouse stops moving @@ -334,22 +294,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }); this._props.addDocument?.(b); DocUtils.MakeLink(b, this.Document, { link_relationship: 'video snapshot' }); - Networking.PostToServer('/youtubeScreenshot', { - id: this.youtubeVideoId, - timecode: this.layoutDoc._layout_currentTimecode, - }).then(response => { - const resolved = response?.accessPaths?.agnostic?.client; - if (resolved) { - this._props.removeDocument?.(b); - this.createSnapshotLink(resolved); - } - }); } else { //convert to desired file format const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, const retitled = StrCast(this.Document.title).replace(/[ -\.:]/g, ''); - const encodedFilename = encodeURIComponent('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString().replace(/\./, '_')); + const encodedFilename = encodeURIComponent(('snapshot' + retitled + '_' + (this.layoutDoc._layout_currentTimecode || 0).toString()).replace(/[\.\/\?\=]/g, '_')); const filename = basename(encodedFilename); Utils.convertDataUri(dataUrl, filename).then((returnedFilename: string) => returnedFilename && (cb ?? this.createSnapshotLink)(returnedFilename, downX, downY)); } @@ -416,16 +366,18 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @action updateTimecode = () => { !this.unmounting && this.player && (this.layoutDoc._layout_currentTimecode = this.player.currentTime); - try { - this._youtubePlayer && (this.layoutDoc._layout_currentTimecode = this._youtubePlayer.getCurrentTime?.()); - } catch (e) { - console.log('Video Timecode Exception:', e); - } }; - // getView = async (doc: Doc) => { - // return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); - // }; + getView = (doc: Doc, options: DocFocusOptions) => { + if (this._stackedTimeline?.makeDocUnfiltered(doc)) { + if (this.heightPercent === 100) { + this.layoutDoc._layout_timelineHeightPercent = VideoBox.heightPercent; + options.didMove = true; + } + return this._stackedTimeline.getView(doc, options); + } + return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); + }; // extracts video thumbnails and saves them as field of doc getVideoThumbnails = () => { @@ -518,7 +470,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { icon: 'expand-arrows-alt', }); subitems.push({ description: (this.layoutDoc.autoPlayAnchors ? "Don't auto play" : 'Auto play') + ' anchors onClick', event: () => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), icon: 'expand-arrows-alt' }); - // subitems.push({ description: "Toggle Native Controls", event: action(() => VideoBox._nativeControls = !VideoBox._nativeControls), icon: "expand-arrows-alt" }); // subitems.push({ description: "Start Trim All", event: () => this.startTrim(TrimScope.All), icon: "expand-arrows-alt" }); // subitems.push({ description: "Start Trim Clip", event: () => this.startTrim(TrimScope.Clip), icon: "expand-arrows-alt" }); // subitems.push({ description: "Stop Trim", event: () => this.finishTrim(), icon: "expand-arrows-alt" }); @@ -581,7 +532,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ref={this.setVideoRef} style={this._fullScreen ? this.fullScreenSize() : this.isCropped ? { width: 'max-content', height: 'max-content', transform: `scale(${1 / NumCast(this.layoutDoc._freeform_scale)})`, transformOrigin: 'top left' } : {}} onCanPlay={this.videoLoad} - controls={VideoBox._nativeControls} + controls={false} onPlay={() => this.Play()} onSeeked={this.updateTimecode} onPause={() => this.Pause()} @@ -600,54 +551,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); } - @action youtubeIframeLoaded = (e: any) => { - if (!this._youtubeContentCreated) { - this._forceCreateYouTubeIFrame = !this._forceCreateYouTubeIFrame; - return; - } else this._youtubeContentCreated = false; - - this.loadYouTube(e.target); - }; - - loadYouTube = (iframe: any) => { - let started = true; - const onYoutubePlayerStateChange = (event: any) => - runInAction(() => { - if (started && event.data === YT.PlayerState.PLAYING) { - started = false; - this._youtubePlayer?.unMute(); - //this.Pause(); - return; - } - if (event.data === YT.PlayerState.PLAYING && !this._playing) this.Play(false); - if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false); - }); - const onYoutubePlayerReady = (event: any) => { - this._disposers.reactionDisposer?.(); - this._disposers.youtubeReactionDisposer?.(); - this._disposers.reactionDisposer = reaction( - () => this.layoutDoc._layout_currentTimecode, - () => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode)) - ); - this._disposers.youtubeReactionDisposer = reaction( - () => Doc.ActiveTool === InkTool.None && this._props.isSelected() && !SnappingManager.IsDragging && !SnappingManager.IsResizing, - interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'), - { fireImmediately: true } - ); - }; - if (typeof YT === undefined) setTimeout(() => this.loadYouTube(iframe), 100); - else { - (YT as any)?.ready(() => { - this._youtubePlayer = new YT.Player(`${this.youtubeVideoId + this._youtubeIframeId}-player`, { - events: { - onReady: this._props.dontRegisterView ? undefined : onYoutubePlayerReady, - onStateChange: this._props.dontRegisterView ? undefined : onYoutubePlayerStateChange, - }, - }); - }); - } - }; - // for play button onPlayDown = () => (this._playing ? this.Pause() : this.Play()); @@ -723,25 +626,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; - @computed get youtubeContent() { - this._youtubeIframeId = VideoBox._youtubeIframeCounter++; - this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; - const classname = 'videoBox-content-YouTube' + (this._fullScreen ? '-fullScreen' : ''); - const start = untracked(() => Math.round(NumCast(this.layoutDoc._layout_currentTimecode))); - return ( - <iframe - key={this._youtubeIframeId} - id={`${this.youtubeVideoId + this._youtubeIframeId}-player`} - onPointerLeave={this.updateTimecode} - onLoad={this.youtubeIframeLoaded} - className={classname} - width={Doc.NativeWidth(this.layoutDoc) || 640} - height={Doc.NativeHeight(this.layoutDoc) || 390} - src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=0&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._nativeControls ? 1 : 0}`} - /> - ); - } - // for annotating, adds doc with time info @action.bound addDocWithTimecode(doc: Doc | Doc[]): boolean { @@ -1090,7 +974,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { removeDocument={this.removeDocument} moveDocument={this.moveDocument} addDocument={this.addDocWithTimecode}> - {this.youtubeVideoId ? this.youtubeContent : this.content} + {this.content} </CollectionFreeFormView> </div> {this.annotationLayer} @@ -1225,5 +1109,3 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); } } - -VideoBox._nativeControls = false; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 3ad9f7634..c3be2b390 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -296,7 +296,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; @action - getView = (doc: Doc) => { + getView = (doc: Doc, options: DocFocusOptions) => { if (Doc.AreProtosEqual(doc, this.Document)) return new Promise<Opt<DocumentView>>(res => res(this._props.DocumentView?.())); if (this.Document.layout_fieldKey === 'layout_icon') this._props.DocumentView?.().iconify(); const webUrl = WebCast(doc.config_data)?.url; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 66802d198..3be3aeba3 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1056,9 +1056,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return anchorDoc ?? this.Document; } - getView = async (doc: Doc) => { + getView = async (doc: Doc, options: DocFocusOptions) => { if (DocListCast(this.dataDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { - !this.SidebarShown && this.toggleSidebar(false); + if (!this.SidebarShown) { + this.toggleSidebar(false); + options.didMove = true; + } setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc)); } return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 50f202e69..b1a7a9c5e 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { green, red } from 'colors'; import { ExifImage } from 'exif'; import * as exifr from 'exifr'; -import { FfmpegCommand, ffprobe } from 'fluent-ffmpeg'; +import * as ffmpeg from 'fluent-ffmpeg'; import * as formidable from 'formidable'; import { File } from 'formidable'; import * as fs from 'fs'; @@ -95,9 +95,8 @@ export namespace DashUploadUtils { const outputFilePath = path.join(pathToDirectory(Directory.videos), outputFileName); // concatenate the videos - await new Promise((resolve, reject) => { - var merge = new FfmpegCommand(); - merge + await new Promise((resolve, reject) => + ffmpeg() .input(textFilePath) .inputOptions(['-f concat', '-safe 0']) // .outputOptions('-c copy') @@ -107,8 +106,8 @@ export namespace DashUploadUtils { console.log(err); reject(); }) - .on('end', resolve); - }); + .on('end', resolve) + ); // delete concat.txt from the file system unlinkSync(textFilePath); @@ -221,7 +220,7 @@ export namespace DashUploadUtils { if (format.includes('x-matroska')) { console.log('case video'); await new Promise(res => - new FfmpegCommand(file.filepath) + ffmpeg(file.filepath) .videoCodec('copy') // this will copy the data instead of reencode it .save(file.filepath.replace('.mkv', '.mp4')) .on('end', res) @@ -233,7 +232,7 @@ export namespace DashUploadUtils { if (format.includes('quicktime')) { let abort = false; await new Promise<void>(res => - ffprobe(file.filepath, (err: any, metadata: any) => { + ffmpeg.ffprobe(file.filepath, (err: any, metadata: any) => { if (metadata.streams.some((stream: any) => stream.codec_name === 'hevc')) { abort = true; } |