1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-22 10:22:31 +01:00

in-house the file-uri implementation

fixes #1941
This commit is contained in:
Mikael Finstad 2024-04-01 13:49:22 +01:00
parent 66f6a8e4b6
commit a1422ad264
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
7 changed files with 73 additions and 14 deletions

View File

@ -4,9 +4,13 @@ on: [push, pull_request]
jobs: jobs:
test: test:
runs-on: ubuntu-latest runs-on: ${{ matrix.os }}
timeout-minutes: 60 timeout-minutes: 60
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -126,7 +126,6 @@
"express": "^4.19.2", "express": "^4.19.2",
"express-async-handler": "^1.2.0", "express-async-handler": "^1.2.0",
"file-type": "16", "file-type": "16",
"file-url": "^3.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"i18next": "^22.4.10", "i18next": "^22.4.10",
"i18next-fs-backend": "^2.1.1", "i18next-fs-backend": "^2.1.1",

View File

@ -70,7 +70,7 @@ import {
isStoreBuild, dragPreventer, isStoreBuild, dragPreventer,
havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile, havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile,
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName, mustDisallowVob, readVideoTs, readDirRecursively, getImportProjectType, deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName, mustDisallowVob, readVideoTs, readDirRecursively, getImportProjectType,
calcShouldShowWaveform, calcShouldShowKeyframes, mediaSourceQualities, calcShouldShowWaveform, calcShouldShowKeyframes, mediaSourceQualities, isWindows,
} from './util'; } from './util';
import { toast, errorToast } from './swal'; import { toast, errorToast } from './swal';
import { formatDuration } from './util/duration'; import { formatDuration } from './util/duration';
@ -89,11 +89,11 @@ import isDev from './isDev';
import { ChromiumHTMLVideoElement, EdlFileType, FfmpegCommandLog, FormatTimecode, PlaybackMode, SegmentColorIndex, SegmentTags, SegmentToExport, StateSegment, Thumbnail, TunerType } from './types'; import { ChromiumHTMLVideoElement, EdlFileType, FfmpegCommandLog, FormatTimecode, PlaybackMode, SegmentColorIndex, SegmentTags, SegmentToExport, StateSegment, Thumbnail, TunerType } from './types';
import { CaptureFormat, KeyboardAction, Html5ifyMode } from '../../../types'; import { CaptureFormat, KeyboardAction, Html5ifyMode } from '../../../types';
import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../ffprobe'; import { FFprobeChapter, FFprobeFormat, FFprobeStream } from '../../../ffprobe';
import filePathToUrl from './util/fileUri';
const electron = window.require('electron'); const electron = window.require('electron');
const { exists } = window.require('fs-extra'); const { exists } = window.require('fs-extra');
const { lstat } = window.require('fs/promises'); const { lstat } = window.require('fs/promises');
const filePathToUrl = window.require('file-url');
const { parse: parsePath, join: pathJoin, basename, dirname } = window.require('path'); const { parse: parsePath, join: pathJoin, basename, dirname } = window.require('path');
const { focusWindow, hasDisabledNetworking, quitApp } = window.require('@electron/remote').require('./index.js'); const { focusWindow, hasDisabledNetworking, quitApp } = window.require('@electron/remote').require('./index.js');
@ -449,7 +449,7 @@ function App() {
const effectiveFilePath = previewFilePath || filePath; const effectiveFilePath = previewFilePath || filePath;
const fileUri = useMemo(() => { const fileUri = useMemo(() => {
if (!effectiveFilePath) return ''; // Setting video src="" prevents memory leak in chromium if (!effectiveFilePath) return ''; // Setting video src="" prevents memory leak in chromium
const uri = filePathToUrl(effectiveFilePath); const uri = filePathToUrl(effectiveFilePath, isWindows);
// https://github.com/mifi/lossless-cut/issues/1674 // https://github.com/mifi/lossless-cut/issues/1674
if (cacheBuster !== 0) { if (cacheBuster !== 0) {
const qs = new URLSearchParams(); const qs = new URLSearchParams();

View File

@ -42,7 +42,6 @@ declare global {
require: <T extends string>(module: T) => ( require: <T extends string>(module: T) => (
T extends '@electron/remote' ? TypedRemote : T extends '@electron/remote' ? TypedRemote :
T extends 'electron' ? typeof Electron : T extends 'electron' ? typeof Electron :
T extends 'file-url' ? typeof import('file-url') :
// todo more // todo more
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
any any

View File

@ -0,0 +1,48 @@
import { test, expect, describe } from 'vitest';
import fileUriRaw from './fileUri.js';
describe('file uri windows only', () => {
test('converts path to file url', () => {
expect(fileUriRaw('C:\\Users\\sindresorhus\\dev\\te^st.jpg', true)).toEqual('file:///C:/Users/sindresorhus/dev/te%5Est.jpg');
});
});
describe('file uri non-windows', () => {
// https://github.com/mifi/lossless-cut/issues/1941
test('file with backslash', () => {
expect(fileUriRaw('/has/back\\slash', false)).toEqual('file:///has/back%5Cslash');
});
});
// taken from https://github.com/sindresorhus/file-url
describe.each([{ isWindows: false }, { isWindows: true }])('file uri both platforms isWindows=$isWindows', ({ isWindows }) => {
const fileUri = (path) => fileUriRaw(path, isWindows);
test('converts path to file url', () => {
expect(fileUri('/test.jpg')).toMatch(/file:\/{3}test\.jpg/);
expect(fileUri('/Users/sindresorhus/dev/te^st.jpg')).toEqual('file:///Users/sindresorhus/dev/te%5Est.jpg');
});
test('escapes more special characters in path', () => {
expect(fileUri('/a?!@#$%^&\'";<>')).toEqual('file:///a%3F!@%23$%25%5E&\'%22;%3C%3E');
});
test('escapes whitespace characters in path', () => {
expect(fileUri('/file with\r\nnewline')).toEqual('file:///file%20with%0D%0Anewline');
});
test('relative path', () => {
expect(fileUri('relative/test.jpg')).toEqual('file:///relative/test.jpg');
});
test('empty', () => {
expect(fileUri('')).toEqual('file:///');
});
test('slash', () => {
expect(fileUri('/')).toEqual('file:///');
});
});

View File

@ -0,0 +1,17 @@
export default function fileUri(filePath: string, isWindows: boolean) {
let pathName = filePath;
if (isWindows) {
pathName = pathName.replaceAll('\\', '/');
}
// Windows drive letter must be prefixed with a slash.
// also relative paths will be converted to absolute
if (pathName[0] !== '/') {
pathName = `/${pathName}`;
}
// Escape required characters for path components.
// See: https://tools.ietf.org/html/rfc3986#section-3.3
return encodeURI(`file://${pathName}`).replaceAll(/[#?]/g, encodeURIComponent);
}

View File

@ -5804,13 +5804,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"file-url@npm:^3.0.0":
version: 3.0.0
resolution: "file-url@npm:3.0.0"
checksum: f15c1bdd81df1a09238f3411f877274d7849703df837ec327c4d1df631314f60036cb700a59d826d8c96b79ff66429d3c758480005e1899c00961541b98d5bfe
languageName: node
linkType: hard
"filelist@npm:^1.0.1": "filelist@npm:^1.0.1":
version: 1.0.4 version: 1.0.4
resolution: "filelist@npm:1.0.4" resolution: "filelist@npm:1.0.4"
@ -7956,7 +7949,6 @@ __metadata:
express-async-handler: "npm:^1.2.0" express-async-handler: "npm:^1.2.0"
fast-xml-parser: "npm:^4.2.5" fast-xml-parser: "npm:^4.2.5"
file-type: "npm:16" file-type: "npm:16"
file-url: "npm:^3.0.0"
framer-motion: "npm:^9.0.3" framer-motion: "npm:^9.0.3"
fs-extra: "npm:^8.1.0" fs-extra: "npm:^8.1.0"
i18next: "npm:^22.4.10" i18next: "npm:^22.4.10"