diff --git a/src/obsidianMarkdownPreprocessor.ts b/src/obsidianMarkdownPreprocessor.ts
index d89e3758..28e680cb 100644
--- a/src/obsidianMarkdownPreprocessor.ts
+++ b/src/obsidianMarkdownPreprocessor.ts
@@ -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';
@@ -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;
@@ -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();
@@ -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);
@@ -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);
diff --git a/src/obsidianUtils.ts b/src/obsidianUtils.ts
index c81f46c6..0de39bba 100644
--- a/src/obsidianUtils.ts
+++ b/src/obsidianUtils.ts
@@ -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 {
@@ -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;
diff --git a/src/processors/videoProcessor.ts b/src/processors/videoProcessor.ts
new file mode 100644
index 00000000..17403719
--- /dev/null
+++ b/src/processors/videoProcessor.ts
@@ -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 
+ 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  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 ${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 = ``;
+ result = result + videoHtml;
+
+ }
+ return result + '\n';
+ }
+
+ private transformAbsoluteFilePath(path: string) {
+ const pathURL = new URL(path);
+ if (pathURL) {
+ return '/localFileSlash' + pathURL.pathname;
+ }
+ return path;
+ }
+}
diff --git a/src/revealExporter.ts b/src/revealExporter.ts
index 6093ce22..249b0a1e 100644
--- a/src/revealExporter.ts
+++ b/src/revealExporter.ts
@@ -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);
@@ -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);
}
}
diff --git a/src/revealRenderer.ts b/src/revealRenderer.ts
index 31dac840..b3d3440d 100644
--- a/src/revealRenderer.ts
+++ b/src/revealRenderer.ts
@@ -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';
@@ -49,6 +50,8 @@ export class RevealRenderer {
if (renderForExport) {
ImageCollector.getInstance().reset();
ImageCollector.getInstance().enable();
+ VideoCollector.getInstance().reset();
+ VideoCollector.getInstance().enable();
}
const content = (await readFile(filePath.toString())).toString();
@@ -56,7 +59,8 @@ export class RevealRenderer {
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);
}
@@ -89,6 +93,9 @@ export class RevealRenderer {
if (!ImageCollector.getInstance().shouldCollect()) {
base = '/';
}
+ if (!VideoCollector.getInstance().shouldCollect()) {
+ base = '/';
+ }
const context = Object.assign(options, {
title,
diff --git a/src/videoCollector.ts b/src/videoCollector.ts
new file mode 100644
index 00000000..83b22110
--- /dev/null
+++ b/src/videoCollector.ts
@@ -0,0 +1,38 @@
+export class VideoCollector {
+ private videos = new Set();
+ 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;
+ }
+}