Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy videos at export time and support videos using obsidian syntax #261

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/obsidianMarkdownPreprocessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FormatProcessor } from './processors/formatProcessor';
import { FragmentProcessor } from './processors/fragmentProcessor';
import { GridProcessor } from './processors/gridProcessor';
import { ImageProcessor } from './processors/imageProcessor';
import { VideoProcessor } from './processors/videoProcessor';
import { InternalLinkProcessor } from './processors/internalLinkProcessor';
import { LatexProcessor } from './processors/latexProcessor';
import { MermaidProcessor } from './processors/mermaidProcessor';
Expand All @@ -29,6 +30,7 @@ export class ObsidianMarkdownPreprocessor {
private multipleFileProcessor: MultipleFileProcessor;
private blockProcessor: BlockProcessor;
private imageProcessor: ImageProcessor;
private videoProcessor: VideoProcessor;
private internalLinkProcessor: InternalLinkProcessor;
private footnoteProcessor: FootnoteProcessor;
private latexProcessor: LatexProcessor;
Expand All @@ -54,6 +56,7 @@ export class ObsidianMarkdownPreprocessor {
this.multipleFileProcessor = new MultipleFileProcessor(utils);
this.blockProcessor = new BlockProcessor();
this.imageProcessor = new ImageProcessor(utils);
this.videoProcessor = new VideoProcessor(utils);
this.internalLinkProcessor = new InternalLinkProcessor(utils);
this.footnoteProcessor = new FootnoteProcessor();
this.latexProcessor = new LatexProcessor();
Expand Down Expand Up @@ -112,7 +115,8 @@ export class ObsidianMarkdownPreprocessor {
const afterFootNoteProcessor = this.footnoteProcessor.process(afterBlockProcessor, options);
const afterExcalidrawProcessor = this.excalidrawProcessor.process(afterFootNoteProcessor);
const afterImageProcessor = this.imageProcessor.process(afterExcalidrawProcessor);
const afterInternalLinkProcessor = this.internalLinkProcessor.process(afterImageProcessor, options);
const afterVideoProcessor = this.videoProcessor.process(afterImageProcessor);
const afterInternalLinkProcessor = this.internalLinkProcessor.process(afterVideoProcessor, options);
const afterLatexProcessor = this.latexProcessor.process(afterInternalLinkProcessor);
const afterFormatProcessor = this.formatProcessor.process(afterLatexProcessor);
const afterFragmentProcessor = this.fragmentProcessor.process(afterFormatProcessor, options);
Expand All @@ -137,7 +141,8 @@ export class ObsidianMarkdownPreprocessor {
this.log('afterFootNoteProcessor', afterBlockProcessor, afterFootNoteProcessor);
this.log('afterExcalidrawProcessor', afterFootNoteProcessor, afterExcalidrawProcessor);
this.log('afterImageProcessor', afterExcalidrawProcessor, afterImageProcessor);
this.log('afterInternalLinkProcessor', afterImageProcessor, afterInternalLinkProcessor);
this.log('afterVideoProcessor', afterImageProcessor, afterVideoProcessor);
this.log('afterInternalLinkProcessor', afterVideoProcessor, afterInternalLinkProcessor);
this.log('afterLatexProcessor', afterInternalLinkProcessor, afterLatexProcessor);
this.log('afterFormatProcessor', afterLatexProcessor, afterFormatProcessor);
this.log('afterFragmentProcessor', afterFormatProcessor, afterFragmentProcessor);
Expand Down
4 changes: 4 additions & 0 deletions src/obsidianUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFileSync } from 'fs-extra';
import { App, FileSystemAdapter, resolveSubpath, TFile } from 'obsidian';
import path from 'path';
import { ImageCollector } from './imageCollector';
import { VideoCollector } from './videoCollector';
import { AdvancedSlidesSettings } from './main';

export class ObsidianUtils {
Expand Down Expand Up @@ -115,6 +116,9 @@ export class ObsidianUtils {
if (!ImageCollector.getInstance().shouldCollect()) {
base = '/';
}
if (!VideoCollector.getInstance().shouldCollect()) {
base = '/';
}
const file: TFile = this.getTFile(path);
if (file) {
return base + file.path;
Expand Down
198 changes: 198 additions & 0 deletions src/processors/videoProcessor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/* eslint-disable no-var */
import { VideoCollector } from 'src/videoCollector';
import { CommentParser } from '../comment';
import { ObsidianUtils } from '../obsidianUtils';

export class VideoProcessor {
private utils: ObsidianUtils;
private parser: CommentParser;

private markdownVideoRegex = /^[ ]{0,3}!\[([^\]]*)\]\((.*(?:mp4)?)\)\s?(<!--.*-->)?/gim;

private obsidianVideoRegex = /!\[\[(.*?(?:mp4))\s*\|?\s*([^\]]*)??\]\]\s?(<!--.*-->)?/ig;
private obsidianVideoReferenceRegex = /\[\[(.*?(?:mp4))\|?([^\]]*)??\]\]/gi;

private htmlVideoRegex = /<\s*video[^>]*>(\s*)(.*?)(\s*)<\s*\/\s*video>/gim;
private htmlSrcRegex = /src="[^\s]*/im;

constructor(utils: ObsidianUtils) {
this.utils = utils;
this.parser = new CommentParser();
}

process(markdown: string) {
return markdown
.split('\n')
.map(line => {
// Transform ![[myVideo.mp4]] to ![](myVideo.mp4)
if (this.obsidianVideoRegex.test(line)) {
return this.transformVideoString(line);
}
// Transform referenced videos to absolute paths (ex. in bg annotation)
if (this.obsidianVideoReferenceRegex.test(line)) {
return this.transformVideoReferenceString(line);
}
return line;
})
.map(line => {
// Transform ![](myVideo.mp4) to html
if (this.markdownVideoRegex.test(line)) {
return this.htmlify(line);
} else if (this.htmlVideoRegex.test(line) && this.htmlSrcRegex.test(line)) {
// The video is inserted as html already. Just add it to the collector
// Find the source tag to get the file path
// Remove the absolute path from the html
if (VideoCollector.getInstance().shouldCollect()) {
const srcMatch = this.htmlSrcRegex.exec(line);
const srcString = srcMatch[0];
const filePath = srcString.substring("src=/\"".length, srcString.length - 1);
VideoCollector.getInstance().addVideo(filePath);
const newVideoHtml = line.slice(0, srcMatch["index"] + "src=\"".length) + line.slice(srcMatch["index"] + "src=\"/".length);
return newVideoHtml;
}
return line;
} else {
return line;
}
})
.join('\n');
}
transformVideoReferenceString(line: string): string {
let result = line;

let m;
this.obsidianVideoReferenceRegex.lastIndex = 0;

while ((m = this.obsidianVideoReferenceRegex.exec(result)) !== null) {
if (m.index === this.obsidianVideoReferenceRegex.lastIndex) {
this.obsidianVideoReferenceRegex.lastIndex++;
}

const [match, image] = m;
const filePath = this.utils.findFile(image);
result = result.replaceAll(match, filePath);
}

return result;
}

private transformVideoString(line: string) {

let result = "";

let m;
this.obsidianVideoRegex.lastIndex = 0;

while ((m = this.obsidianVideoRegex.exec(line)) !== null) {
if (m.index === this.obsidianVideoRegex.lastIndex) {
this.obsidianVideoRegex.lastIndex++;
}
const [, image, ext, comment] = m;

const filePath = this.utils.findFile(image);
const commentAsString = this.buildComment(ext, comment) ?? '';
result = result + `\n![](${filePath}) ${commentAsString}`;
}
return result;
}

private buildComment(ext: string, commentAsString: string) {
const comment = commentAsString ? this.parser.parseComment(commentAsString) : this.parser.buildComment('element');

if (ext) {
if (ext.includes('x')) {
var [width, height] = ext.split('x');
} else {
var width = ext;
}
comment.addStyle('width', `${width}px`);

if (height) {
comment.addStyle('height', `${height}px`);
}
}
return this.parser.commentToString(comment);
}

private htmlify(line: string) {

let result = "";

let m;
this.markdownVideoRegex.lastIndex = 0;

while ((m = this.markdownVideoRegex.exec(line)) !== null) {
if (m.index === this.markdownVideoRegex.lastIndex) {
this.markdownVideoRegex.lastIndex++;
}
// eslint-disable-next-line prefer-const
let [, alt, filePath, commentString] = m;

if (alt && alt.includes('|')) {
commentString = this.buildComment(alt.split('|')[1], commentString) ?? '';
}

const comment = this.parser.parseLine(commentString) ?? this.parser.buildComment('element');

if (result.length > 0) {
result = result + '\n';
}

if (VideoCollector.getInstance().shouldCollect()) {
VideoCollector.getInstance().addVideo(filePath);
}

if (filePath.startsWith('file:/')) {
filePath = this.transformAbsoluteFilePath(filePath);
}

if (comment.hasStyle('width')) {
comment.addStyle('object-fit', 'fill');
}

if (!comment.hasStyle('align-self')) {
if (comment.hasAttribute('align')) {

const align = comment.getAttribute('align');

switch (align) {
case 'left':
comment.addStyle('align-self', 'start');
break;
case 'right':
comment.addStyle('align-self', 'end');
break;
case 'center':
comment.addStyle('align-self', 'center');
break;
case 'stretch':
comment.addStyle('align-self', 'stretch');
comment.addStyle('object-fit', 'cover');
comment.addStyle('height', '100%');
comment.addStyle('width', '100%');
break;
default:
break;
}
comment.deleteAttribute('align');
}
}

if (!comment.hasStyle('object-fit')) {
comment.addStyle('object-fit', 'scale-down');
}
const videoHtml = `<video ${this.parser.buildAttributes(comment)}> <source src="${filePath}" alt="${alt}" type="video/mp4"></video>`;
result = result + videoHtml;

}
return result + '\n';
}

private transformAbsoluteFilePath(path: string) {
const pathURL = new URL(path);
if (pathURL) {
return '/localFileSlash' + pathURL.pathname;
}
return path;
}
}
10 changes: 9 additions & 1 deletion src/revealExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export class RevealExporter {
this.vaultDirectory = utils.getVaultDirectory();
}

public async export(filePath: string, html: string, imgList: string[]) {
public async export(filePath: string, html: string, imgList: string[], vidList: string[]) {

const ext = path.extname(filePath);
const folderName = path.basename(filePath).replaceAll(ext, '');
const folderDir = path.join(this.exportDirectory, folderName);
Expand All @@ -32,6 +33,13 @@ export class RevealExporter {
await copy(path.join(this.vaultDirectory, img), path.join(folderDir, img));
}

for (const vid of vidList) {
if (vid.startsWith('http')) {
continue;
}
await copy(path.join(this.vaultDirectory, vid), path.join(folderDir, vid));
}

window.open('file://' + folderDir);
}
}
9 changes: 8 additions & 1 deletion src/revealRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { basename, extname, join } from 'path';

import { ImageCollector } from './imageCollector';
import { VideoCollector } from './videoCollector';
import Mustache from 'mustache';
import { ObsidianMarkdownPreprocessor } from './obsidianMarkdownPreprocessor';
import { ObsidianUtils } from './obsidianUtils';
Expand Down Expand Up @@ -49,14 +50,17 @@ export class RevealRenderer {
if (renderForExport) {
ImageCollector.getInstance().reset();
ImageCollector.getInstance().enable();
VideoCollector.getInstance().reset();
VideoCollector.getInstance().enable();
}

const content = (await readFile(filePath.toString())).toString();
let rendered = await this.render(content, renderForPrint, renderForEmbed);

if (renderForExport) {
ImageCollector.getInstance().disable();
await this.exporter.export(filePath, rendered, ImageCollector.getInstance().getAll());
VideoCollector.getInstance().disable();
await this.exporter.export(filePath, rendered, ImageCollector.getInstance().getAll(), VideoCollector.getInstance().getAll());
rendered = await this.render(content, renderForPrint, renderForEmbed);
}

Expand Down Expand Up @@ -89,6 +93,9 @@ export class RevealRenderer {
if (!ImageCollector.getInstance().shouldCollect()) {
base = '/';
}
if (!VideoCollector.getInstance().shouldCollect()) {
base = '/';
}

const context = Object.assign(options, {
title,
Expand Down
38 changes: 38 additions & 0 deletions src/videoCollector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export class VideoCollector {
private videos = new Set<string>();
private isCollecting = false;

private static instance: VideoCollector;
private constructor() {}

public static getInstance(): VideoCollector {
if (!VideoCollector.instance) {
VideoCollector.instance = new VideoCollector();
}
return VideoCollector.instance;
}

public reset() {
this.videos.clear();
}

public addVideo(filePath: string) {
this.videos.add(filePath);
}

public getAll(): string[] {
return Array.of(...this.videos);
}

public enable() {
this.isCollecting = true;
}

public disable() {
this.isCollecting = false;
}

public shouldCollect(): boolean {
return this.isCollecting;
}
}