mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-15 13:51:35 +00:00
Merge 0f480cd517 into 90f4833c9e
This commit is contained in:
commit
ea06b10d5f
4 changed files with 336 additions and 126 deletions
120
package-lock.json
generated
120
package-lock.json
generated
|
|
@ -16,6 +16,7 @@
|
||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
|
"mediainfo.js": "^0.2.1",
|
||||||
"node-tone": "^1.0.1",
|
"node-tone": "^1.0.1",
|
||||||
"nodemailer": "^6.9.2",
|
"nodemailer": "^6.9.2",
|
||||||
"openid-client": "^5.6.1",
|
"openid-client": "^5.6.1",
|
||||||
|
|
@ -908,7 +909,6 @@
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -1358,7 +1358,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
},
|
},
|
||||||
|
|
@ -1369,8 +1368,7 @@
|
||||||
"node_modules/color-name": {
|
"node_modules/color-name": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/color-support": {
|
"node_modules/color-support": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
|
|
@ -1787,7 +1785,6 @@
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
|
|
@ -2099,7 +2096,6 @@
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "6.* || 8.* || >= 10.*"
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
}
|
}
|
||||||
|
|
@ -3052,6 +3048,58 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mediainfo.js": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mediainfo.js/-/mediainfo.js-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-xbTstvy34gDmxNLVytixbY8Uw4DGKKsQIMvX7q1K8FwIk/gwAVLd30EVvPh/g+QHVscATRuqrNtbTb7XUjDeyw==",
|
||||||
|
"dependencies": {
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"mediainfo.js": "dist/esm/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mediainfo.js/node_modules/cliui": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mediainfo.js/node_modules/yargs": {
|
||||||
|
"version": "17.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mediainfo.js/node_modules/yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
|
|
@ -4278,7 +4326,6 @@
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
|
@ -5247,7 +5294,6 @@
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
"string-width": "^4.1.0",
|
"string-width": "^4.1.0",
|
||||||
|
|
@ -5321,7 +5367,6 @@
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
|
|
@ -6128,7 +6173,6 @@
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
}
|
}
|
||||||
|
|
@ -6463,7 +6507,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
}
|
}
|
||||||
|
|
@ -6471,8 +6514,7 @@
|
||||||
"color-name": {
|
"color-name": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"color-support": {
|
"color-support": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
|
|
@ -6786,8 +6828,7 @@
|
||||||
"escalade": {
|
"escalade": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||||
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"escape-html": {
|
"escape-html": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
|
|
@ -7005,8 +7046,7 @@
|
||||||
"get-caller-file": {
|
"get-caller-file": {
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"get-func-name": {
|
"get-func-name": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|
@ -7735,6 +7775,45 @@
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
|
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="
|
||||||
},
|
},
|
||||||
|
"mediainfo.js": {
|
||||||
|
"version": "0.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mediainfo.js/-/mediainfo.js-0.2.1.tgz",
|
||||||
|
"integrity": "sha512-xbTstvy34gDmxNLVytixbY8Uw4DGKKsQIMvX7q1K8FwIk/gwAVLd30EVvPh/g+QHVscATRuqrNtbTb7XUjDeyw==",
|
||||||
|
"requires": {
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
|
"requires": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yargs": {
|
||||||
|
"version": "17.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
|
"requires": {
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"merge-descriptors": {
|
"merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
|
|
@ -8649,8 +8728,7 @@
|
||||||
"require-directory": {
|
"require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"require-main-filename": {
|
"require-main-filename": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
|
@ -9354,7 +9432,6 @@
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
"string-width": "^4.1.0",
|
"string-width": "^4.1.0",
|
||||||
|
|
@ -9401,8 +9478,7 @@
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@
|
||||||
"graceful-fs": "^4.2.10",
|
"graceful-fs": "^4.2.10",
|
||||||
"htmlparser2": "^8.0.1",
|
"htmlparser2": "^8.0.1",
|
||||||
"lru-cache": "^10.0.3",
|
"lru-cache": "^10.0.3",
|
||||||
|
"mediainfo.js": "^0.2.1",
|
||||||
"node-tone": "^1.0.1",
|
"node-tone": "^1.0.1",
|
||||||
"nodemailer": "^6.9.2",
|
"nodemailer": "^6.9.2",
|
||||||
"openid-client": "^5.6.1",
|
"openid-client": "^5.6.1",
|
||||||
|
|
|
||||||
97
server/utils/mediaInfoHelpers.js
Normal file
97
server/utils/mediaInfoHelpers.js
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
const Logger = require('../Logger')
|
||||||
|
const fsPromises = require('fs/promises')
|
||||||
|
const MediaInfoFactory = require('mediainfo.js').default
|
||||||
|
|
||||||
|
// File chunking logic pulled from the official cli example
|
||||||
|
// https://github.com/buzz/mediainfo.js/blob/main/src/cli.ts
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses mediainfo.js to dump a file's data
|
||||||
|
* @param {string} filepath
|
||||||
|
* @returns {Promise<import("mediainfo.js").MediaInfoType | null>}
|
||||||
|
*/
|
||||||
|
const mediaInfoDump = async (filepath) => {
|
||||||
|
let fileHandle
|
||||||
|
let fileSize
|
||||||
|
let mediaInfo
|
||||||
|
|
||||||
|
/** @type {import('mediainfo.js').ReadChunkFunc} */
|
||||||
|
const readChunk = async (size, offset) => {
|
||||||
|
if (fileHandle === undefined) throw new Error('File unavailable for Mediainfo reading')
|
||||||
|
const buffer = new Uint8Array(size)
|
||||||
|
await fileHandle.read(buffer, 0, size, offset)
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileHandle = await fsPromises.open(filepath, 'r')
|
||||||
|
fileSize = (await fileHandle.stat()).size
|
||||||
|
mediaInfo = await MediaInfoFactory({
|
||||||
|
format: 'object',
|
||||||
|
coverData: false,
|
||||||
|
full: false,
|
||||||
|
})
|
||||||
|
if (mediaInfo === undefined) {
|
||||||
|
Logger.warn('[MediaInfo] Failed to initialize MediaInfo parser for scanning media file', {
|
||||||
|
filepath,
|
||||||
|
})
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const result = await mediaInfo.analyzeData(() => fileSize, readChunk)
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(`[MediaInfo] MediaInfo failed while scanning media file`, error)
|
||||||
|
} finally {
|
||||||
|
fileHandle && (await fileHandle.close())
|
||||||
|
mediaInfo && mediaInfo.close()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts tags from a MediaInfoType object into a predictable internal object type
|
||||||
|
*
|
||||||
|
* Currently only returns "extra" fields as defined by mediainfo, but can easily consume normal tags too
|
||||||
|
*
|
||||||
|
* @param {import("mediainfo.js").MediaInfoType | null} mediaInfo
|
||||||
|
* @returns {{ fields: Record<string, string>, lowerCaseFields: Record<string, string> }} Object containing all lower-case
|
||||||
|
*/
|
||||||
|
const extractTags = (mediaInfo) => {
|
||||||
|
const defaultEmpty = { fields: {}, lowerCaseFields: {} }
|
||||||
|
if (!mediaInfo || !mediaInfo.media) return defaultEmpty
|
||||||
|
|
||||||
|
const generalTrack = mediaInfo.media.track?.find((track) => track['@type'] === 'General')
|
||||||
|
|
||||||
|
if (!generalTrack) return defaultEmpty
|
||||||
|
|
||||||
|
const fields = {}
|
||||||
|
const lowerCaseFields = {}
|
||||||
|
|
||||||
|
Object.entries(generalTrack.extra || {}).forEach(([key, value]) => {
|
||||||
|
// Lets keep it predictable... only supporting numbers and strings for now.
|
||||||
|
let cleanValue
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
cleanValue = value.trim()
|
||||||
|
}
|
||||||
|
if (typeof value === 'number' && !isNaN(value)) {
|
||||||
|
cleanValue = number.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanValue) {
|
||||||
|
fields[key] = cleanValue
|
||||||
|
lowerCaseFields[key.toLowerCase()] = key
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
fields,
|
||||||
|
lowerCaseFields,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mediaInfoDump,
|
||||||
|
extractTags,
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
const mediaInfoHelpers = require('./mediaInfoHelpers')
|
||||||
const ffprobe = require('../libs/nodeFfprobe')
|
const ffprobe = require('../libs/nodeFfprobe')
|
||||||
const MediaProbeData = require('../scanner/MediaProbeData')
|
const MediaProbeData = require('../scanner/MediaProbeData')
|
||||||
|
|
||||||
|
|
@ -20,7 +21,7 @@ function tryGrabBitRate(stream, all_streams, total_bit_rate) {
|
||||||
var tagDuration = stream.tags.DURATION || stream.tags['DURATION-eng'] || stream.tags['DURATION_eng']
|
var tagDuration = stream.tags.DURATION || stream.tags['DURATION-eng'] || stream.tags['DURATION_eng']
|
||||||
var tagBytes = stream.tags.NUMBER_OF_BYTES || stream.tags['NUMBER_OF_BYTES-eng'] || stream.tags['NUMBER_OF_BYTES_eng']
|
var tagBytes = stream.tags.NUMBER_OF_BYTES || stream.tags['NUMBER_OF_BYTES-eng'] || stream.tags['NUMBER_OF_BYTES_eng']
|
||||||
if (tagDuration && tagBytes && !isNaN(tagDuration) && !isNaN(tagBytes)) {
|
if (tagDuration && tagBytes && !isNaN(tagDuration) && !isNaN(tagBytes)) {
|
||||||
var bps = Math.floor(Number(tagBytes) * 8 / Number(tagDuration))
|
var bps = Math.floor((Number(tagBytes) * 8) / Number(tagDuration))
|
||||||
if (bps && !isNaN(bps)) {
|
if (bps && !isNaN(bps)) {
|
||||||
return bps
|
return bps
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +34,7 @@ function tryGrabBitRate(stream, all_streams, total_bit_rate) {
|
||||||
estimated_bit_rate -= Number(stream.bit_rate)
|
estimated_bit_rate -= Number(stream.bit_rate)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (!all_streams.find(s => s.codec_type === 'audio' && s.bit_rate && Number(s.bit_rate) > estimated_bit_rate)) {
|
if (!all_streams.find((s) => s.codec_type === 'audio' && s.bit_rate && Number(s.bit_rate) > estimated_bit_rate)) {
|
||||||
return estimated_bit_rate
|
return estimated_bit_rate
|
||||||
} else {
|
} else {
|
||||||
return total_bit_rate
|
return total_bit_rate
|
||||||
|
|
@ -70,17 +71,35 @@ function tryGrabChannelLayout(stream) {
|
||||||
return String(layout).split('(').shift()
|
return String(layout).split('(').shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryGrabTags(stream, ...tags) {
|
function tryGrabTags(rawTagData, ...tags) {
|
||||||
if (!stream.tags) return null
|
const { ffprobe, mediaInfo } = rawTagData
|
||||||
for (let i = 0; i < tags.length; i++) {
|
|
||||||
const tagKey = Object.keys(stream.tags).find(t => t.toLowerCase() === tags[i].toLowerCase())
|
if (!ffprobe && !mediaInfo) return null
|
||||||
const value = stream.tags[tagKey]
|
|
||||||
if (value && value.trim()) return value.trim()
|
// Try ffprobe tags first
|
||||||
|
if (ffprobe) {
|
||||||
|
for (let i = 0; i < tags.length; i++) {
|
||||||
|
const tagKey = Object.keys(ffprobe).find((t) => t.toLowerCase() === tags[i].toLowerCase())
|
||||||
|
const value = ffprobe[tagKey]
|
||||||
|
if (value && value.trim()) return value.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaInfo) {
|
||||||
|
// Try mediainfo tags as a backup only if ffprobe doesn't return anything first...
|
||||||
|
for (let i = 0; i < tags.length; i++) {
|
||||||
|
const tagKey = mediaInfo.lowerCaseFields[tags[i]]
|
||||||
|
if (tagKey) {
|
||||||
|
const value = mediaInfo.fields[tagKey]
|
||||||
|
if (value && value.trim()) return value.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMediaStreamInfo(stream, all_streams, total_bit_rate) {
|
function parseMediaStreamInfo(stream, all_streams, total_bit_rate) {
|
||||||
|
const tagData = { ffprobe: stream.tags }
|
||||||
var info = {
|
var info = {
|
||||||
index: stream.index,
|
index: stream.index,
|
||||||
type: stream.codec_type,
|
type: stream.codec_type,
|
||||||
|
|
@ -89,8 +108,8 @@ function parseMediaStreamInfo(stream, all_streams, total_bit_rate) {
|
||||||
codec_time_base: stream.codec_time_base || null,
|
codec_time_base: stream.codec_time_base || null,
|
||||||
time_base: stream.time_base || null,
|
time_base: stream.time_base || null,
|
||||||
bit_rate: tryGrabBitRate(stream, all_streams, total_bit_rate),
|
bit_rate: tryGrabBitRate(stream, all_streams, total_bit_rate),
|
||||||
language: tryGrabTags(stream, 'language'),
|
language: tryGrabTags(tagData, 'language'),
|
||||||
title: tryGrabTags(stream, 'title')
|
title: tryGrabTags(tagData, 'title'),
|
||||||
}
|
}
|
||||||
if (stream.tags) info.tags = stream.tags
|
if (stream.tags) info.tags = stream.tags
|
||||||
|
|
||||||
|
|
@ -101,7 +120,7 @@ function parseMediaStreamInfo(stream, all_streams, total_bit_rate) {
|
||||||
|
|
||||||
if (info.type === 'video') {
|
if (info.type === 'video') {
|
||||||
info.profile = stream.profile || null
|
info.profile = stream.profile || null
|
||||||
info.is_avc = (stream.is_avc !== '0' && stream.is_avc !== 'false')
|
info.is_avc = stream.is_avc !== '0' && stream.is_avc !== 'false'
|
||||||
info.pix_fmt = stream.pix_fmt || null
|
info.pix_fmt = stream.pix_fmt || null
|
||||||
info.frame_rate = tryGrabFrameRate(stream)
|
info.frame_rate = tryGrabFrameRate(stream)
|
||||||
info.width = !isNaN(stream.width) ? Number(stream.width) : null
|
info.width = !isNaN(stream.width) ? Number(stream.width) : null
|
||||||
|
|
@ -123,7 +142,6 @@ function isNullOrNaN(val) {
|
||||||
return val === null || isNaN(val)
|
return val === null || isNaN(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Example chapter object
|
/* Example chapter object
|
||||||
* {
|
* {
|
||||||
"id": 71,
|
"id": 71,
|
||||||
|
|
@ -140,7 +158,7 @@ function isNullOrNaN(val) {
|
||||||
function parseChapters(chapters) {
|
function parseChapters(chapters) {
|
||||||
if (!chapters) return []
|
if (!chapters) return []
|
||||||
let index = 0
|
let index = 0
|
||||||
return chapters.map(chap => {
|
return chapters.map((chap) => {
|
||||||
let title = chap['TAG:title'] || chap.title || ''
|
let title = chap['TAG:title'] || chap.title || ''
|
||||||
if (!title && chap.tags?.title) title = chap.tags.title
|
if (!title && chap.tags?.title) title = chap.tags.title
|
||||||
|
|
||||||
|
|
@ -151,66 +169,72 @@ function parseChapters(chapters) {
|
||||||
id: index++,
|
id: index++,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
title
|
title,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseTags(format, verbose) {
|
function parseTags(format, mediaInfoTags, verbose) {
|
||||||
if (!format.tags) {
|
if (!format.tags) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
Logger.debug('Tags', format.tags)
|
Logger.debug('ffprobe Tags', format.tags)
|
||||||
|
Logger.debug('mediainfo Tags', mediaInfoTags.fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawTagData = {
|
||||||
|
ffprobe: format.tags,
|
||||||
|
mediaInfo: mediaInfoTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
const tags = {
|
const tags = {
|
||||||
file_tag_encoder: tryGrabTags(format, 'encoder', 'tsse', 'tss'),
|
file_tag_encoder: tryGrabTags(rawTagData, 'encoder', 'tsse', 'tss'),
|
||||||
file_tag_encodedby: tryGrabTags(format, 'encoded_by', 'tenc', 'ten'),
|
file_tag_encodedby: tryGrabTags(rawTagData, 'encoded_by', 'tenc', 'ten'),
|
||||||
file_tag_title: tryGrabTags(format, 'title', 'tit2', 'tt2'),
|
file_tag_title: tryGrabTags(rawTagData, 'title', 'tit2', 'tt2'),
|
||||||
file_tag_titlesort: tryGrabTags(format, 'title-sort', 'tsot'),
|
file_tag_titlesort: tryGrabTags(rawTagData, 'title-sort', 'tsot'),
|
||||||
file_tag_subtitle: tryGrabTags(format, 'subtitle', 'tit3', 'tt3'),
|
file_tag_subtitle: tryGrabTags(rawTagData, 'subtitle', 'tit3', 'tt3'),
|
||||||
file_tag_track: tryGrabTags(format, 'track', 'trck', 'trk'),
|
file_tag_track: tryGrabTags(rawTagData, 'track', 'trck', 'trk'),
|
||||||
file_tag_disc: tryGrabTags(format, 'discnumber', 'disc', 'disk', 'tpos', 'tpa'),
|
file_tag_disc: tryGrabTags(rawTagData, 'discnumber', 'disc', 'disk', 'tpos', 'tpa'),
|
||||||
file_tag_album: tryGrabTags(format, 'album', 'talb', 'tal'),
|
file_tag_album: tryGrabTags(rawTagData, 'album', 'talb', 'tal'),
|
||||||
file_tag_albumsort: tryGrabTags(format, 'album-sort', 'tsoa'),
|
file_tag_albumsort: tryGrabTags(rawTagData, 'album-sort', 'tsoa'),
|
||||||
file_tag_artist: tryGrabTags(format, 'artist', 'tpe1', 'tp1'),
|
file_tag_artist: tryGrabTags(rawTagData, 'artist', 'tpe1', 'tp1'),
|
||||||
file_tag_artistsort: tryGrabTags(format, 'artist-sort', 'tsop'),
|
file_tag_artistsort: tryGrabTags(rawTagData, 'artist-sort', 'tsop'),
|
||||||
file_tag_albumartist: tryGrabTags(format, 'albumartist', 'album_artist', 'tpe2'),
|
file_tag_albumartist: tryGrabTags(rawTagData, 'albumartist', 'album_artist', 'tpe2'),
|
||||||
file_tag_date: tryGrabTags(format, 'date', 'tyer', 'tye'),
|
file_tag_date: tryGrabTags(rawTagData, 'date', 'tyer', 'tye'),
|
||||||
file_tag_composer: tryGrabTags(format, 'composer', 'tcom', 'tcm'),
|
file_tag_composer: tryGrabTags(rawTagData, 'composer', 'tcom', 'tcm'),
|
||||||
file_tag_publisher: tryGrabTags(format, 'publisher', 'tpub', 'tpb'),
|
file_tag_publisher: tryGrabTags(rawTagData, 'publisher', 'tpub', 'tpb'),
|
||||||
file_tag_comment: tryGrabTags(format, 'comment', 'comm', 'com'),
|
file_tag_comment: tryGrabTags(rawTagData, 'comment', 'comm', 'com'),
|
||||||
file_tag_description: tryGrabTags(format, 'description', 'desc'),
|
file_tag_description: tryGrabTags(rawTagData, 'description', 'desc'),
|
||||||
file_tag_genre: tryGrabTags(format, 'genre', 'tcon', 'tco'),
|
file_tag_genre: tryGrabTags(rawTagData, 'genre', 'tcon', 'tco'),
|
||||||
file_tag_series: tryGrabTags(format, 'series', 'show', 'mvnm'),
|
file_tag_series: tryGrabTags(rawTagData, 'series', 'show', 'mvnm'),
|
||||||
file_tag_seriespart: tryGrabTags(format, 'series-part', 'episode_id', 'mvin', 'part'),
|
file_tag_seriespart: tryGrabTags(rawTagData, 'series-part', 'episode_id', 'mvin', 'part'),
|
||||||
file_tag_isbn: tryGrabTags(format, 'isbn'), // custom
|
file_tag_isbn: tryGrabTags(rawTagData, 'isbn'), // custom
|
||||||
file_tag_language: tryGrabTags(format, 'language', 'lang'),
|
file_tag_language: tryGrabTags(rawTagData, 'language', 'lang'),
|
||||||
file_tag_asin: tryGrabTags(format, 'asin', 'audible_asin'), // custom
|
file_tag_asin: tryGrabTags(rawTagData, 'asin', 'audible_asin', 'cdek'), // custom
|
||||||
file_tag_itunesid: tryGrabTags(format, 'itunes-id'), // custom
|
file_tag_itunesid: tryGrabTags(rawTagData, 'itunes-id'), // custom
|
||||||
file_tag_podcasttype: tryGrabTags(format, 'podcast-type'), // custom
|
file_tag_podcasttype: tryGrabTags(rawTagData, 'podcast-type'), // custom
|
||||||
file_tag_episodetype: tryGrabTags(format, 'episode-type'), // custom
|
file_tag_episodetype: tryGrabTags(rawTagData, 'episode-type'), // custom
|
||||||
file_tag_originalyear: tryGrabTags(format, 'originalyear'),
|
file_tag_originalyear: tryGrabTags(rawTagData, 'originalyear'),
|
||||||
file_tag_releasecountry: tryGrabTags(format, 'MusicBrainz Album Release Country', 'releasecountry'),
|
file_tag_releasecountry: tryGrabTags(rawTagData, 'MusicBrainz Album Release Country', 'releasecountry'),
|
||||||
file_tag_releasestatus: tryGrabTags(format, 'MusicBrainz Album Status', 'releasestatus', 'musicbrainz_albumstatus'),
|
file_tag_releasestatus: tryGrabTags(rawTagData, 'MusicBrainz Album Status', 'releasestatus', 'musicbrainz_albumstatus'),
|
||||||
file_tag_releasetype: tryGrabTags(format, 'MusicBrainz Album Type', 'releasetype', 'musicbrainz_albumtype'),
|
file_tag_releasetype: tryGrabTags(rawTagData, 'MusicBrainz Album Type', 'releasetype', 'musicbrainz_albumtype'),
|
||||||
file_tag_isrc: tryGrabTags(format, 'tsrc', 'isrc'),
|
file_tag_isrc: tryGrabTags(rawTagData, 'tsrc', 'isrc'),
|
||||||
file_tag_musicbrainz_trackid: tryGrabTags(format, 'MusicBrainz Release Track Id', 'musicbrainz_releasetrackid'),
|
file_tag_musicbrainz_trackid: tryGrabTags(rawTagData, 'MusicBrainz Release Track Id', 'musicbrainz_releasetrackid'),
|
||||||
file_tag_musicbrainz_albumid: tryGrabTags(format, 'MusicBrainz Album Id', 'musicbrainz_albumid'),
|
file_tag_musicbrainz_albumid: tryGrabTags(rawTagData, 'MusicBrainz Album Id', 'musicbrainz_albumid'),
|
||||||
file_tag_musicbrainz_albumartistid: tryGrabTags(format, 'MusicBrainz Album Artist Id', 'musicbrainz_albumartistid'),
|
file_tag_musicbrainz_albumartistid: tryGrabTags(rawTagData, 'MusicBrainz Album Artist Id', 'musicbrainz_albumartistid'),
|
||||||
file_tag_musicbrainz_artistid: tryGrabTags(format, 'MusicBrainz Artist Id', 'musicbrainz_artistid'),
|
file_tag_musicbrainz_artistid: tryGrabTags(rawTagData, 'MusicBrainz Artist Id', 'musicbrainz_artistid'),
|
||||||
|
|
||||||
// Not sure if these are actually used yet or not
|
// Not sure if these are actually used yet or not
|
||||||
file_tag_creation_time: tryGrabTags(format, 'creation_time'),
|
file_tag_creation_time: tryGrabTags(rawTagData, 'creation_time'),
|
||||||
file_tag_wwwaudiofile: tryGrabTags(format, 'wwwaudiofile', 'woaf', 'waf'),
|
file_tag_wwwaudiofile: tryGrabTags(rawTagData, 'wwwaudiofile', 'woaf', 'waf'),
|
||||||
file_tag_contentgroup: tryGrabTags(format, 'contentgroup', 'tit1', 'tt1'),
|
file_tag_contentgroup: tryGrabTags(rawTagData, 'contentgroup', 'tit1', 'tt1'),
|
||||||
file_tag_releasetime: tryGrabTags(format, 'releasetime', 'tdrl'),
|
file_tag_releasetime: tryGrabTags(rawTagData, 'releasetime', 'tdrl'),
|
||||||
file_tag_movementname: tryGrabTags(format, 'movementname', 'mvnm'),
|
file_tag_movementname: tryGrabTags(rawTagData, 'movementname', 'mvnm'),
|
||||||
file_tag_movement: tryGrabTags(format, 'movement', 'mvin'),
|
file_tag_movement: tryGrabTags(rawTagData, 'movement', 'mvin'),
|
||||||
file_tag_genre1: tryGrabTags(format, 'tmp_genre1', 'genre1'),
|
file_tag_genre1: tryGrabTags(rawTagData, 'tmp_genre1', 'genre1'),
|
||||||
file_tag_genre2: tryGrabTags(format, 'tmp_genre2', 'genre2'),
|
file_tag_genre2: tryGrabTags(rawTagData, 'tmp_genre2', 'genre2'),
|
||||||
file_tag_overdrive_media_marker: tryGrabTags(format, 'OverDrive MediaMarkers'),
|
file_tag_overdrive_media_marker: tryGrabTags(rawTagData, 'OverDrive MediaMarkers'),
|
||||||
}
|
}
|
||||||
for (const key in tags) {
|
for (const key in tags) {
|
||||||
if (!tags[key]) {
|
if (!tags[key]) {
|
||||||
|
|
@ -224,12 +248,12 @@ function parseTags(format, verbose) {
|
||||||
function getDefaultAudioStream(audioStreams) {
|
function getDefaultAudioStream(audioStreams) {
|
||||||
if (!audioStreams || !audioStreams.length) return null
|
if (!audioStreams || !audioStreams.length) return null
|
||||||
if (audioStreams.length === 1) return audioStreams[0]
|
if (audioStreams.length === 1) return audioStreams[0]
|
||||||
var defaultStream = audioStreams.find(a => a.is_default)
|
var defaultStream = audioStreams.find((a) => a.is_default)
|
||||||
if (!defaultStream) return audioStreams[0]
|
if (!defaultStream) return audioStreams[0]
|
||||||
return defaultStream
|
return defaultStream
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseProbeData(data, verbose = false) {
|
function parseProbeData(data, mediaInfoTags, verbose = false) {
|
||||||
try {
|
try {
|
||||||
const { format, streams, chapters } = data
|
const { format, streams, chapters } = data
|
||||||
|
|
||||||
|
|
@ -242,15 +266,16 @@ function parseProbeData(data, verbose = false) {
|
||||||
size: sizeBytes,
|
size: sizeBytes,
|
||||||
sizeMb,
|
sizeMb,
|
||||||
bit_rate: !isNaN(format.bit_rate) ? Number(format.bit_rate) : null,
|
bit_rate: !isNaN(format.bit_rate) ? Number(format.bit_rate) : null,
|
||||||
tags: parseTags(format, verbose)
|
tags: parseTags(format, mediaInfoTags, verbose),
|
||||||
}
|
}
|
||||||
if (verbose && format.tags) {
|
if (verbose && format.tags) {
|
||||||
cleanedData.rawTags = format.tags
|
cleanedData.rawProbeTags = format.tags
|
||||||
|
cleanedData.rawMediaInfoTags = mediaInfoTags.fields
|
||||||
}
|
}
|
||||||
|
|
||||||
const cleaned_streams = streams.map(s => parseMediaStreamInfo(s, streams, cleanedData.bit_rate))
|
const cleaned_streams = streams.map((s) => parseMediaStreamInfo(s, streams, cleanedData.bit_rate))
|
||||||
cleanedData.video_stream = cleaned_streams.find(s => s.type === 'video')
|
cleanedData.video_stream = cleaned_streams.find((s) => s.type === 'video')
|
||||||
const audioStreams = cleaned_streams.filter(s => s.type === 'audio')
|
const audioStreams = cleaned_streams.filter((s) => s.type === 'audio')
|
||||||
cleanedData.audio_stream = getDefaultAudioStream(audioStreams)
|
cleanedData.audio_stream = getDefaultAudioStream(audioStreams)
|
||||||
|
|
||||||
if (cleanedData.audio_stream && cleanedData.video_stream) {
|
if (cleanedData.audio_stream && cleanedData.video_stream) {
|
||||||
|
|
@ -265,7 +290,7 @@ function parseProbeData(data, verbose = false) {
|
||||||
if (!format.tags && cleanedData.audio_stream && cleanedData.audio_stream.tags) {
|
if (!format.tags && cleanedData.audio_stream && cleanedData.audio_stream.tags) {
|
||||||
cleanedData = {
|
cleanedData = {
|
||||||
...cleanedData,
|
...cleanedData,
|
||||||
tags: parseTags(cleanedData.audio_stream, verbose)
|
tags: parseTags(cleanedData.audio_stream, mediaInfoTags, verbose),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,37 +307,49 @@ function parseProbeData(data, verbose = false) {
|
||||||
* Run ffprobe on audio filepath
|
* Run ffprobe on audio filepath
|
||||||
* @param {string} filepath
|
* @param {string} filepath
|
||||||
* @param {boolean} [verbose=false]
|
* @param {boolean} [verbose=false]
|
||||||
* @returns {import('../scanner/MediaProbeData')|{error:string}}
|
* @returns {Promise<import('../scanner/MediaProbeData')|{error:string}>}
|
||||||
*/
|
*/
|
||||||
function probe(filepath, verbose = false) {
|
async function probe(filepath, verbose = false) {
|
||||||
if (process.env.FFPROBE_PATH) {
|
if (process.env.FFPROBE_PATH) {
|
||||||
ffprobe.FFPROBE_PATH = process.env.FFPROBE_PATH
|
ffprobe.FFPROBE_PATH = process.env.FFPROBE_PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
return ffprobe(filepath)
|
let unparsedProbeData
|
||||||
.then(raw => {
|
try {
|
||||||
if (raw.error) {
|
const rawProbe = await ffprobe(filepath)
|
||||||
return {
|
if (rawProbe.error) {
|
||||||
error: raw.error.string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawProbeData = parseProbeData(raw, verbose)
|
|
||||||
if (!rawProbeData || (!rawProbeData.audio_stream && !rawProbeData.video_stream)) {
|
|
||||||
return {
|
|
||||||
error: rawProbeData ? 'Invalid media file: no audio or video streams found' : 'Probe Failed'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const probeData = new MediaProbeData()
|
|
||||||
probeData.setData(rawProbeData)
|
|
||||||
return probeData
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return {
|
return {
|
||||||
error: err
|
error: raw.error.string,
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
unparsedProbeData = rawProbe
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
error: error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaInfoTags
|
||||||
|
try {
|
||||||
|
let rawMediaInfo = await mediaInfoHelpers.mediaInfoDump(filepath)
|
||||||
|
mediaInfoTags = mediaInfoHelpers.extractTags(rawMediaInfo)
|
||||||
|
} catch (error) {
|
||||||
|
// Currently mediainfo will not throw, but keeping this here for posterity
|
||||||
|
return {
|
||||||
|
error: error.toString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawProbeData = parseProbeData(unparsedProbeData, mediaInfoTags, verbose)
|
||||||
|
if (!rawProbeData || (!rawProbeData.audio_stream && !rawProbeData.video_stream)) {
|
||||||
|
return {
|
||||||
|
error: rawProbeData ? 'Invalid media file: no audio or video streams found' : 'Probe Failed',
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const probeData = new MediaProbeData()
|
||||||
|
probeData.setData(rawProbeData)
|
||||||
|
return probeData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
module.exports.probe = probe
|
module.exports.probe = probe
|
||||||
|
|
||||||
|
|
@ -327,11 +364,10 @@ function rawProbe(filepath) {
|
||||||
ffprobe.FFPROBE_PATH = process.env.FFPROBE_PATH
|
ffprobe.FFPROBE_PATH = process.env.FFPROBE_PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
return ffprobe(filepath)
|
return ffprobe(filepath).catch((err) => {
|
||||||
.catch((err) => {
|
return {
|
||||||
return {
|
error: err,
|
||||||
error: err
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
module.exports.rawProbe = rawProbe
|
module.exports.rawProbe = rawProbe
|
||||||
Loading…
Add table
Add a link
Reference in a new issue