I'm trying to write a program that will read the names of files in a directory, and parse them for info which it will then write into that file's metadata (seems pointless, I know, but it's more of a trial run for a larger project. If I can figure this out, then I'll be ready to move on to what I actually want to do). All of my filenames are formatted:
Title, Article* - Subtitle* {Cut}* [Year] *if present
The four test videos I'm using are titled:
Test Video 1 [2000]
Test Video 2, A [2000]
Test Video 3 {Test Cut} [2000]
Test Video 4 - The Testening [2000]
The code seems to be working fine on videos 1, 2, & 4; but video 3 is causing me a lot of headache.
//node C:\Users\User\Documents\Coding\Tools\testMDG.js
const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const async = require('async');
const directory = path.normalize('F:\\Movies & TV\\Movies\\testDir');
let x = 0;
const fileArray = [];
const succArray = [];
const failArray = [];
// Set the path to the ffmpeg executable
ffmpeg.setFfmpegPath(path.normalize('C:\\ffmpeg\\bin\\ffmpeg.exe'));
// Add this near the start of your script
const tempDir = path.join(directory, 'temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const progresscsv = path.normalize('C:\\Users\\User\\Documents\\Coding\\Tools\\progress.csv');
if (fs.existsSync(progresscsv)) {
fs.unlinkSync(progresscsv);
};
const csvStream = fs.createWriteStream(progresscsv);
csvStream.write('File Name,Status\n');
const CONCURRENCY_LIMIT = 3; // Adjust based on your system capabilities
// Add at the start of your script
const processedFiles = new Set();
function sanitizeFilePath(filePath) {
return filePath.replace(/([{}])/g, '\\$1');
}
// Create a queue
const queue = async.queue(async (task, callback) => {
const { file, filePath, tempFilePath, movieMetadata } = task;
try {
await new Promise((resolve, reject) => {
console.log(`ffmpeg reading: ${sanitizeFilePath(filePath)}`);
ffmpeg(sanitizeFilePath(filePath))
.outputOptions([
'-y',
'-c', 'copy',
'-map', '0',
'-metadata', `title=${movieMetadata.title}`,
'-metadata', `subtitle=${movieMetadata.subtitle || ''}`,
'-metadata', `comment=${movieMetadata.cut || ''}`,
'-metadata', `year=${movieMetadata.year}`
])
.on('error', (err) => reject(err))
.on('end', () => resolve())
.saveToFile(tempFilePath);
});
// Handle success
console.log(`Successfully processed: ${file}`);
succArray.push(file);
// Only call callback once
if (callback && typeof callback === 'function') {
callback();
}
} catch (err) {
// Handle error
console.error(`Error processing ${file}: ${err.message}`);
failArray.push(file);
// Only call callback once with error
if (callback && typeof callback === 'function') {
callback(err);
}
}
}, CONCURRENCY_LIMIT);
fs.readdir(directory, (err, files) => {
if (err) {
console.error(`Error reading directory: ${err.message}`);
return;
}
console.log(directory);
// Filter for files only
files = files.filter(file => fs.statSync(path.join(directory, file)).isFile());
console.log(files);
for (const file of files) {
x++;
const filePath = path.join(directory, file);
let desort = file.replace(/(.*),\s(the\s|an\s|a\s)/i, '$2'+'$1 ') || file;
// Create task object for queue
const task = {
file,
filePath: filePath,
tempFilePath: path.join(directory, 'temp', `temp_${x}_${path.parse(file).name
.replace(/[^a-zA-Z0-9]/g, '_')}${path.extname(file)}`),
movieMetadata: {
title: desort.replace(/(\s[\-\{\[].*)/gi, ``),
subtitle: desort.includes('-') ? desort.replace(/(.*)\-\s(.*?)[\{\[].*/gi, '$2') : null,
cut: desort.includes('{') ? desort.replace(/.*\{(.*)\}.*/gi, '$1') : null,
year: desort.replace(/.*\[(.*)\].*/gi, '$1')
}
};
// Add to processing queue
queue.push(task, (err) => {
if (!processedFiles.has(task.file)) {
processedFiles.add(task.file);
if (err) {
csvStream.write(`${task.file},Failed\n`);
} else {
csvStream.write(`${task.file},Processed\n`);
}
}
});
}
});
// Add queue completion handler
queue.drain(() => {
console.log('All files have been processed');
console.log(`Success: ${succArray.length} files: ${succArray}`);
console.log(`Failed: ${failArray.length} files: ${failArray}`);
});
//node C:\Users\User\Documents\Coding\Tools\testMDG.js
And the console is logging:
PS C:\Users\User> node C:\Users\User\Documents\Coding\Tools\testMDG.js
F:\Movies & TV\Movies\testDir
[
'Test Video 1 [2020].mp4',
'Test Video 2, A [2020].mp4',
'Test Video 3 {Test Cut} [2020].mp4',
'Test Video 4 - The Testening [2020].mp4'
]
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 1 [2020].mp4
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 2, A [2020].mp4
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 3 \{Test Cut\} [2020].mp4
Error processing Test Video 3 {Test Cut} [2020].mp4: ffmpeg exited with code 4294967294: Error opening input file F:\Movies & TV\Movies\testDir\Test Video 3 \{Test Cut\} [2020].mp4.
Error opening input files: No such file or directory
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 4 - The Testening [2020].mp4
Successfully processed: Test Video 1 [2020].mp4
Successfully processed: Test Video 2, A [2020].mp4
Successfully processed: Test Video 4 - The Testening [2020].mp4
All files have been processed
Success: 3 files: Test Video 1 [2020].mp4,Test Video 2, A [2020].mp4,Test Video 4 - The Testening [2020].mp4
Failed: 1 files: Test Video 3 {Test Cut} [2020].mp4
I've tried so many different solutions in the sanitizeFilePath function. Escaping the {} characters (as included below), escaping all non-alphanumeric characters, putting the filepath in quotes, etc. VSCode's CoPilot is just pulling me round in circles, suggesting solutions I've already tried.
I'm trying to write a program that will read the names of files in a directory, and parse them for info which it will then write into that file's metadata (seems pointless, I know, but it's more of a trial run for a larger project. If I can figure this out, then I'll be ready to move on to what I actually want to do). All of my filenames are formatted:
Title, Article* - Subtitle* {Cut}* [Year] *if present
The four test videos I'm using are titled:
Test Video 1 [2000]
Test Video 2, A [2000]
Test Video 3 {Test Cut} [2000]
Test Video 4 - The Testening [2000]
The code seems to be working fine on videos 1, 2, & 4; but video 3 is causing me a lot of headache.
//node C:\Users\User\Documents\Coding\Tools\testMDG.js
const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const async = require('async');
const directory = path.normalize('F:\\Movies & TV\\Movies\\testDir');
let x = 0;
const fileArray = [];
const succArray = [];
const failArray = [];
// Set the path to the ffmpeg executable
ffmpeg.setFfmpegPath(path.normalize('C:\\ffmpeg\\bin\\ffmpeg.exe'));
// Add this near the start of your script
const tempDir = path.join(directory, 'temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const progresscsv = path.normalize('C:\\Users\\User\\Documents\\Coding\\Tools\\progress.csv');
if (fs.existsSync(progresscsv)) {
fs.unlinkSync(progresscsv);
};
const csvStream = fs.createWriteStream(progresscsv);
csvStream.write('File Name,Status\n');
const CONCURRENCY_LIMIT = 3; // Adjust based on your system capabilities
// Add at the start of your script
const processedFiles = new Set();
function sanitizeFilePath(filePath) {
return filePath.replace(/([{}])/g, '\\$1');
}
// Create a queue
const queue = async.queue(async (task, callback) => {
const { file, filePath, tempFilePath, movieMetadata } = task;
try {
await new Promise((resolve, reject) => {
console.log(`ffmpeg reading: ${sanitizeFilePath(filePath)}`);
ffmpeg(sanitizeFilePath(filePath))
.outputOptions([
'-y',
'-c', 'copy',
'-map', '0',
'-metadata', `title=${movieMetadata.title}`,
'-metadata', `subtitle=${movieMetadata.subtitle || ''}`,
'-metadata', `comment=${movieMetadata.cut || ''}`,
'-metadata', `year=${movieMetadata.year}`
])
.on('error', (err) => reject(err))
.on('end', () => resolve())
.saveToFile(tempFilePath);
});
// Handle success
console.log(`Successfully processed: ${file}`);
succArray.push(file);
// Only call callback once
if (callback && typeof callback === 'function') {
callback();
}
} catch (err) {
// Handle error
console.error(`Error processing ${file}: ${err.message}`);
failArray.push(file);
// Only call callback once with error
if (callback && typeof callback === 'function') {
callback(err);
}
}
}, CONCURRENCY_LIMIT);
fs.readdir(directory, (err, files) => {
if (err) {
console.error(`Error reading directory: ${err.message}`);
return;
}
console.log(directory);
// Filter for files only
files = files.filter(file => fs.statSync(path.join(directory, file)).isFile());
console.log(files);
for (const file of files) {
x++;
const filePath = path.join(directory, file);
let desort = file.replace(/(.*),\s(the\s|an\s|a\s)/i, '$2'+'$1 ') || file;
// Create task object for queue
const task = {
file,
filePath: filePath,
tempFilePath: path.join(directory, 'temp', `temp_${x}_${path.parse(file).name
.replace(/[^a-zA-Z0-9]/g, '_')}${path.extname(file)}`),
movieMetadata: {
title: desort.replace(/(\s[\-\{\[].*)/gi, ``),
subtitle: desort.includes('-') ? desort.replace(/(.*)\-\s(.*?)[\{\[].*/gi, '$2') : null,
cut: desort.includes('{') ? desort.replace(/.*\{(.*)\}.*/gi, '$1') : null,
year: desort.replace(/.*\[(.*)\].*/gi, '$1')
}
};
// Add to processing queue
queue.push(task, (err) => {
if (!processedFiles.has(task.file)) {
processedFiles.add(task.file);
if (err) {
csvStream.write(`${task.file},Failed\n`);
} else {
csvStream.write(`${task.file},Processed\n`);
}
}
});
}
});
// Add queue completion handler
queue.drain(() => {
console.log('All files have been processed');
console.log(`Success: ${succArray.length} files: ${succArray}`);
console.log(`Failed: ${failArray.length} files: ${failArray}`);
});
//node C:\Users\User\Documents\Coding\Tools\testMDG.js
And the console is logging:
PS C:\Users\User> node C:\Users\User\Documents\Coding\Tools\testMDG.js
F:\Movies & TV\Movies\testDir
[
'Test Video 1 [2020].mp4',
'Test Video 2, A [2020].mp4',
'Test Video 3 {Test Cut} [2020].mp4',
'Test Video 4 - The Testening [2020].mp4'
]
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 1 [2020].mp4
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 2, A [2020].mp4
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 3 \{Test Cut\} [2020].mp4
Error processing Test Video 3 {Test Cut} [2020].mp4: ffmpeg exited with code 4294967294: Error opening input file F:\Movies & TV\Movies\testDir\Test Video 3 \{Test Cut\} [2020].mp4.
Error opening input files: No such file or directory
ffmpeg reading: F:\Movies & TV\Movies\testDir\Test Video 4 - The Testening [2020].mp4
Successfully processed: Test Video 1 [2020].mp4
Successfully processed: Test Video 2, A [2020].mp4
Successfully processed: Test Video 4 - The Testening [2020].mp4
All files have been processed
Success: 3 files: Test Video 1 [2020].mp4,Test Video 2, A [2020].mp4,Test Video 4 - The Testening [2020].mp4
Failed: 1 files: Test Video 3 {Test Cut} [2020].mp4
I've tried so many different solutions in the sanitizeFilePath function. Escaping the {} characters (as included below), escaping all non-alphanumeric characters, putting the filepath in quotes, etc. VSCode's CoPilot is just pulling me round in circles, suggesting solutions I've already tried.
Share Improve this question edited Feb 16 at 20:19 genpfault 52.1k12 gold badges91 silver badges148 bronze badges asked Feb 16 at 17:12 Holly WilsonHolly Wilson 153 bronze badges 5 |1 Answer
Reset to default 0File names and windows are historically an adventure. Try changing the path to this construct:
tempFilePath: path.join(tempDir, `temp_${x}_${sanitizeFilePath(path.parse(file).name)}${path.extname(file)}`),
You could also cover more characters in the sanitize:
// Replace invalid characters with an underscore
function sanitizeFilePath(filePath) {
return filePath.replace(/([{}<>:"\/\\|?*])/g, '_'); }
You want to use sanitizeFilePath
everywhere a file path is referenced. You can also multi-purpose your sanitize for the metadata.
const task = {
file,
filePath: sanitizeFilePath(filePath),
tempFilePath: path.join(tempDir, `temp_${x}_${sanitizeFilePath(path.parse(file).name)}${path.extname(file)}`),
movieMetadata: {
title: sanitizeFilePath(desort.replace(/(\s[\-\{\[].*)/gi, ``)),
subtitle: desort.includes('-') ? sanitizeFilePath(desort.replace(/(.*)\-\s(.*?)[\{\[].*/gi, '$2')) : null,
cut: desort.includes('{') ? sanitizeFilePath(desort.replace(/.*\{(.*)\}.*/gi, '$1')) : null,
year: sanitizeFilePath(desort.replace(/.*\[(.*)\].*/gi, '$1'))
}
};
`
. Example:const resetLink = `http://localhost:3000/reset?token=${resetToken}`;
But you're already using them, so... I don't know. – JayCravens Commented Feb 16 at 17:28${filePath}
(in quotes which aren't showing bc it's being read as code). Been beating my head against this for days now – Holly Wilson Commented Feb 16 at 17:39