1
0
mirror of synced 2025-02-07 14:41:27 +01:00

Extract ID3 operation now returns a JSON blob and presents an HTML table

This commit is contained in:
n1474335 2021-02-11 18:01:08 +00:00
parent 19360391a6
commit 672b477751
2 changed files with 221 additions and 198 deletions

View File

@ -286,8 +286,8 @@
"JPath expression", "JPath expression",
"CSS selector", "CSS selector",
"Extract EXIF", "Extract EXIF",
"Extract Files", "Extract ID3",
"Extract ID3" "Extract Files"
] ]
}, },
{ {

View File

@ -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;