Extract ID3 operation now returns a JSON blob and presents an HTML table
This commit is contained in:
parent
19360391a6
commit
672b477751
@ -286,8 +286,8 @@
|
|||||||
"JPath expression",
|
"JPath expression",
|
||||||
"CSS selector",
|
"CSS selector",
|
||||||
"Extract EXIF",
|
"Extract EXIF",
|
||||||
"Extract Files",
|
"Extract ID3",
|
||||||
"Extract ID3"
|
"Extract Files"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* @author n1073645 [n1073645@gmail.com]
|
* @author n1073645 [n1073645@gmail.com]
|
||||||
|
* @author n1474335 [n1474335@gmail.com]
|
||||||
* @copyright Crown Copyright 2020
|
* @copyright Crown Copyright 2020
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Operation from "../Operation.mjs";
|
import Operation from "../Operation.mjs";
|
||||||
import OperationError from "../errors/OperationError.mjs";
|
import OperationError from "../errors/OperationError.mjs";
|
||||||
|
import Utils from "../Utils.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract ID3 operation
|
* Extract ID3 operation
|
||||||
@ -20,19 +22,144 @@ class ExtractID3 extends Operation {
|
|||||||
|
|
||||||
this.name = "Extract ID3";
|
this.name = "Extract ID3";
|
||||||
this.module = "Default";
|
this.module = "Default";
|
||||||
this.description = "ID3 is a metadata container most often used in conjunction with the MP3 audio file format. It allows information such as the title, artist, album, track number, and other information about the file to be stored in the file itself.";
|
this.description = "This operation extracts ID3 metadata from an MP3 file.<br><br>ID3 is a metadata container most often used in conjunction with the MP3 audio file format. It allows information such as the title, artist, album, track number, and other information about the file to be stored in the file itself.";
|
||||||
this.infoURL = "https://wikipedia.org/wiki/ID3";
|
this.infoURL = "https://wikipedia.org/wiki/ID3";
|
||||||
this.inputType = "ArrayBuffer";
|
this.inputType = "ArrayBuffer";
|
||||||
this.outputType = "string";
|
this.outputType = "JSON";
|
||||||
|
this.presentType = "html";
|
||||||
this.args = [];
|
this.args = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {ArrayBuffer} input
|
* @param {ArrayBuffer} input
|
||||||
* @param {Object[]} args
|
* @param {Object[]} args
|
||||||
* @returns {string}
|
* @returns {JSON}
|
||||||
*/
|
*/
|
||||||
run(input, args) {
|
run(input, args) {
|
||||||
|
input = new Uint8Array(input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the ID3 header fields.
|
||||||
|
*/
|
||||||
|
function extractHeader() {
|
||||||
|
if (!Array.from(input.slice(0, 3)).equals([0x49, 0x44, 0x33]))
|
||||||
|
throw new OperationError("No valid ID3 header.");
|
||||||
|
|
||||||
|
const header = {
|
||||||
|
"Type": "ID3",
|
||||||
|
// Tag version
|
||||||
|
"Version": input[3].toString() + "." + input[4].toString(),
|
||||||
|
// Header version
|
||||||
|
"Flags": input[5].toString()
|
||||||
|
};
|
||||||
|
|
||||||
|
input = input.slice(6);
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the size fields to a single integer.
|
||||||
|
*
|
||||||
|
* @param {number} num
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function readSize(num) {
|
||||||
|
let result = 0;
|
||||||
|
|
||||||
|
// The sizes are 7 bit numbers stored in 8 bit locations
|
||||||
|
for (let i = (num) * 7; i; i -= 7) {
|
||||||
|
result = (result << i) | input[0];
|
||||||
|
input = input.slice(1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads frame header based on ID.
|
||||||
|
*
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function readFrame(id) {
|
||||||
|
const frame = {};
|
||||||
|
|
||||||
|
// Size of frame
|
||||||
|
const size = readSize(4);
|
||||||
|
frame.Size = size.toString();
|
||||||
|
frame.Description = FRAME_DESCRIPTIONS[id];
|
||||||
|
input = input.slice(2);
|
||||||
|
|
||||||
|
// Read data from frame
|
||||||
|
let data = "";
|
||||||
|
for (let i = 1; i < size; i++)
|
||||||
|
data += String.fromCharCode(input[i]);
|
||||||
|
frame.Data = data;
|
||||||
|
|
||||||
|
// Move to next Frame
|
||||||
|
input = input.slice(size);
|
||||||
|
|
||||||
|
return [frame, size];
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = extractHeader();
|
||||||
|
|
||||||
|
const headerTagSize = readSize(4);
|
||||||
|
result.Size = headerTagSize.toString();
|
||||||
|
|
||||||
|
const tags = {};
|
||||||
|
let pos = 10;
|
||||||
|
|
||||||
|
// While the current element is in the header
|
||||||
|
while (pos < headerTagSize) {
|
||||||
|
|
||||||
|
// Frame Identifier of frame
|
||||||
|
let id = String.fromCharCode(input[0]) + String.fromCharCode(input[1]) + String.fromCharCode(input[2]);
|
||||||
|
input = input.slice(3);
|
||||||
|
|
||||||
|
// If the next character is non-zero it is an identifier
|
||||||
|
if (input[0] !== 0) {
|
||||||
|
id += String.fromCharCode(input[0]);
|
||||||
|
}
|
||||||
|
input = input.slice(1);
|
||||||
|
|
||||||
|
if (id in FRAME_DESCRIPTIONS) {
|
||||||
|
const [frame, size] = readFrame(id);
|
||||||
|
tags[id] = frame;
|
||||||
|
pos += 10 + size;
|
||||||
|
} else if (id === "\x00\x00\x00") { // end of header
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
throw new OperationError("Unknown Frame Identifier: " + id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Tags = tags;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the extracted data in a more accessible format for web apps.
|
||||||
|
* @param {JSON} data
|
||||||
|
* @returns {html}
|
||||||
|
*/
|
||||||
|
present(data) {
|
||||||
|
if (!data || !Object.prototype.hasOwnProperty.call(data, "Tags"))
|
||||||
|
return JSON.stringify(data, null, 4);
|
||||||
|
|
||||||
|
let output = `<table class="table table-hover table-sm table-bordered table-nonfluid">
|
||||||
|
<tr><th>Tag</th><th>Description</th><th>Data</th></tr>`;
|
||||||
|
|
||||||
|
for (const tagID in data.Tags) {
|
||||||
|
const description = data.Tags[tagID].Description,
|
||||||
|
contents = data.Tags[tagID].Data;
|
||||||
|
output += `<tr><td>${tagID}</td><td>${Utils.escapeHtml(description)}</td><td>${Utils.escapeHtml(contents)}</td></tr>`;
|
||||||
|
}
|
||||||
|
output += "</table>";
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Borrowed from https://github.com/aadsm/jsmediatags
|
// Borrowed from https://github.com/aadsm/jsmediatags
|
||||||
const FRAME_DESCRIPTIONS = {
|
const FRAME_DESCRIPTIONS = {
|
||||||
@ -194,108 +321,4 @@ class ExtractID3 extends Operation {
|
|||||||
"WXXX": "User defined URL link frame"
|
"WXXX": "User defined URL link frame"
|
||||||
};
|
};
|
||||||
|
|
||||||
input = new Uint8Array(input);
|
|
||||||
let result = "";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the ID3 header fields.
|
|
||||||
*/
|
|
||||||
function extractHeader() {
|
|
||||||
if (input.slice(0, 3).toString() !== [0x49, 0x44, 0x33].toString())
|
|
||||||
throw new OperationError("No valid ID3 header.");
|
|
||||||
result = "{\n";
|
|
||||||
result += " Type: \"ID3\",\n";
|
|
||||||
|
|
||||||
// Tag version.
|
|
||||||
result += " Version: \"" + input[3].toString() + "." + input[4].toString() + "\",\n";
|
|
||||||
|
|
||||||
// Header flags.
|
|
||||||
result += " Flags: " + input[5].toString() + ",\n";
|
|
||||||
input = input.slice(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the size fields to a single integer.
|
|
||||||
*
|
|
||||||
* @param {number} num
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function readSize(num) {
|
|
||||||
let result = 0;
|
|
||||||
|
|
||||||
// The sizes are 7 bit numbers stored in 8 bit locations.
|
|
||||||
for (let i = (num) * 7; i; i -= 7) {
|
|
||||||
result = (result << i) | input[0];
|
|
||||||
input = input.slice(1);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads frame header based on ID.
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
* @returns {number}
|
|
||||||
*/
|
|
||||||
function readFrame(id) {
|
|
||||||
result += " " + id + ": {\n";
|
|
||||||
result += " ID: \"" + id + "\",\n";
|
|
||||||
|
|
||||||
// Size of frame.
|
|
||||||
const size = readSize(4);
|
|
||||||
result += " Size: " + size.toString() + ",\n";
|
|
||||||
result += " Description: \"" + FRAME_DESCRIPTIONS[id] + "\",\n";
|
|
||||||
input = input.slice(2);
|
|
||||||
let data = "";
|
|
||||||
|
|
||||||
// Read data from frame.
|
|
||||||
for (let i = 1; i < size; i++)
|
|
||||||
data += String.fromCharCode(input[i]);
|
|
||||||
|
|
||||||
// Move to next Frame
|
|
||||||
input = input.slice(size);
|
|
||||||
result += " Data: \"" + data + "\",\n";
|
|
||||||
result += " },\n";
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
extractHeader();
|
|
||||||
|
|
||||||
const headerTagSize = readSize(4);
|
|
||||||
result += " Tags: {\n";
|
|
||||||
|
|
||||||
let pos = 10;
|
|
||||||
|
|
||||||
// While the current element is in the header.
|
|
||||||
while (pos < headerTagSize) {
|
|
||||||
|
|
||||||
// Frame Identifier of frame.
|
|
||||||
let id = String.fromCharCode(input[0]) + String.fromCharCode(input[1]) + String.fromCharCode(input[2]);
|
|
||||||
input = input.slice(3);
|
|
||||||
|
|
||||||
// If the next character is non-zero it is an identifier.
|
|
||||||
if (input[0] !== 0) {
|
|
||||||
id += String.fromCharCode(input[0]);
|
|
||||||
}
|
|
||||||
input = input.slice(1);
|
|
||||||
if (id in FRAME_DESCRIPTIONS) {
|
|
||||||
pos += 10 + readFrame(id);
|
|
||||||
// If end of header.
|
|
||||||
} else if (id === "\x00\x00\x00") {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
throw new OperationError("Unknown Frame Identifier: " + id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tidy up the output.
|
|
||||||
result += " },\n";
|
|
||||||
result += " Size: "+headerTagSize.toString() + ",\n";
|
|
||||||
result += "}";
|
|
||||||
return result;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ExtractID3;
|
export default ExtractID3;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user