You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
568tools/tools/webp2jpg-online/test/webp_vp8.html

315 lines
12 KiB

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<script>
var Groover = (function() {
// ensure webp is supported
function canEncode() {
var canvas = document.createElement("canvas");
canvas.width = 8;
canvas.height = 8;
return canvas.toDataURL("image/webp", 0.1).indexOf("image/webp") > -1;
}
if (!canEncode()) {
return undefined;
}
var webmData = null;
var clusterTimecode = 0;
var clusterCounter = 0;
var CLUSTER_MAX_DURATION = 30000;
var frameNumber = 0;
var width;
var height;
var frameDelay;
var quality;
var name;
const videoMimeType = "video/webm"; // the only one.
const frameMimeType = 'image/webp'; // can be no other
const S = String.fromCharCode;
const dataTypes = {
object: function(data) {
return toBlob(data);
},
number: function(data) {
return stream.num(data);
},
string: function(data) {
return stream.str(data);
},
array: function(data) {
return data;
},
double2Str: function(num) {
var c = new Uint8Array((new Float64Array([num])).buffer);
return S(c[7]) + S(c[6]) + S(c[5]) + S(c[4]) + S(c[3]) + S(c[2]) + S(c[1]) + S(c[0]);
}
};
const stream = {
num: function(num) { // writes int
var parts = [];
while (num > 0) {
parts.push(num & 0xff);
num = num >> 8;
}
return new Uint8Array(parts.reverse());
},
str: function(str) { // writes string
var i, len, arr;
len = str.length;
arr = new Uint8Array(len);
for (i = 0; i < len; i++) {
arr[i] = str.charCodeAt(i);
}
return arr;
},
compInt: function(num) { // could not find full details so bit of a guess
if (num < 128) { // number is prefixed with a bit (1000 is on byte 0100 two, 0010 three and so on)
num += 0x80;
return new Uint8Array([num]);
} else
if (num < 0x4000) {
num += 0x4000;
return new Uint8Array([num >> 8, num])
} else
if (num < 0x200000) {
num += 0x200000;
return new Uint8Array([num >> 16, num >> 8, num])
} else
if (num < 0x10000000) {
num += 0x10000000;
return new Uint8Array([num >> 24, num >> 16, num >> 8, num])
}
}
}
const ids = { // header names and values
videoData: 0x1a45dfa3,
Version: 0x4286,
ReadVersion: 0x42f7,
MaxIDLength: 0x42f2,
MaxSizeLength: 0x42f3,
DocType: 0x4282,
DocTypeVersion: 0x4287,
DocTypeReadVersion: 0x4285,
Segment: 0x18538067,
Info: 0x1549a966,
TimecodeScale: 0x2ad7b1,
MuxingApp: 0x4d80,
WritingApp: 0x5741,
Duration: 0x4489,
Tracks: 0x1654ae6b,
TrackEntry: 0xae,
TrackNumber: 0xd7,
TrackUID: 0x63c5,
FlagLacing: 0x9c,
Language: 0x22b59c,
CodecID: 0x86,
CodecName: 0x258688,
TrackType: 0x83,
Video: 0xe0,
PixelWidth: 0xb0,
PixelHeight: 0xba,
Cluster: 0x1f43b675,
Timecode: 0xe7,
Frame: 0xa3,
Keyframe: 0x9d012a,
FrameBlock: 0x81,
};
const keyframeD64Header = '\x9d\x01\x2a'; //VP8 keyframe header 0x9d012a
const videoDataPos = 1; // data pos of frame data header
const defaultDelay = dataTypes.double2Str(1000 / 25);
const header = [ // structure of webM header/chunks what ever they are called.
ids.videoData, [
ids.Version, 1,
ids.ReadVersion, 1,
ids.MaxIDLength, 4,
ids.MaxSizeLength, 8,
ids.DocType, 'webm',
ids.DocTypeVersion, 2,
ids.DocTypeReadVersion, 2
],
ids.Segment, [
ids.Info, [
ids.TimecodeScale, 1000000,
ids.MuxingApp, 'Groover',
ids.WritingApp, 'Groover',
ids.Duration, 0
],
ids.Tracks, [
ids.TrackEntry, [
ids.TrackNumber, 1,
ids.TrackUID, 1,
ids.FlagLacing, 0, // always o
ids.Language, 'und', // undefined I think this means
ids.CodecID, 'V_VP8', // These I think must not change
ids.CodecName, 'VP8', // These I think must not change
ids.TrackType, 1,
ids.Video, [
ids.PixelWidth, 0,
ids.PixelHeight, 0
]
]
]
]
];
function getHeader() {
header[3][2][3] = name;
header[3][2][5] = name;
header[3][2][7] = dataTypes.double2Str(frameDelay);
header[3][3][1][15][1] = width;
header[3][3][1][15][3] = height;
function create(dat) {
var i, kv, data;
data = [];
for (i = 0; i < dat.length; i += 2) {
kv = {
i: dat[i]
};
if (Array.isArray(dat[i + 1])) {
kv.d = create(dat[i + 1]);
} else {
kv.d = dat[i + 1];
}
data.push(kv);
}
return data;
}
return create(header);
}
function addCluster() {
webmData[videoDataPos].d.push({
i: ids.Cluster,
d: [{
i: ids.Timecode,
d: Math.round(clusterTimecode)
}]
}); // Fixed bug with Round
clusterCounter = 0;
}
function addFrame(frame) {
var VP8, kfS, riff;
riff = getWebPChunks(atob(frame.toDataURL(frameMimeType, quality).slice(23)));
VP8 = riff.RIFF[0].WEBP[0];
kfS = VP8.indexOf(keyframeD64Header) + 3;
frame = {
width: ((VP8.charCodeAt(kfS + 1) << 8) | VP8.charCodeAt(kfS)) & 0x3FFF,
height: ((VP8.charCodeAt(kfS + 3) << 8) | VP8.charCodeAt(kfS + 2)) & 0x3FFF,
data: VP8,
riff: riff
};
if (clusterCounter > CLUSTER_MAX_DURATION) {
addCluster();
}
webmData[videoDataPos].d[webmData[videoDataPos].d.length - 1].d.push({
i: ids.Frame,
d: S(ids.FrameBlock) + S(Math.round(clusterCounter) >> 8) + S(Math.round(clusterCounter) & 0xff) + S(128) + frame.data.slice(4),
});
clusterCounter += frameDelay;
clusterTimecode += frameDelay;
webmData[videoDataPos].d[0].d[3].d = dataTypes.double2Str(clusterTimecode);
}
function startEncoding() {
frameNumber = clusterCounter = clusterTimecode = 0;
webmData = getHeader();
addCluster();
}
function toBlob(vidData) {
var data, i, vData, len;
vData = [];
for (i = 0; i < vidData.length; i++) {
data = dataTypes[typeof vidData[i].d](vidData[i].d);
len = data.size || data.byteLength || data.length;
vData.push(stream.num(vidData[i].i));
vData.push(stream.compInt(len));
vData.push(data)
}
return new Blob(vData, {
type: videoMimeType
});
}
function getWebPChunks(str) {
var offset, chunks, id, len, data;
offset = 0;
chunks = {};
while (offset < str.length) {
id = str.substr(offset, 4);
// value will have top bit on (bit 32) so not simply a bitwise operation
// Warning little endian (Will not work on big endian systems)
len = new Uint32Array(
new Uint8Array([
str.charCodeAt(offset + 7),
str.charCodeAt(offset + 6),
str.charCodeAt(offset + 5),
str.charCodeAt(offset + 4)
]).buffer)[0];
id = str.substr(offset, 4);
chunks[id] = chunks[id] === undefined ? [] : chunks[id];
if (id === 'RIFF' || id === 'LIST') {
chunks[id].push(getWebPChunks(str.substr(offset + 8, len)));
offset += 8 + len;
} else if (id === 'WEBP') {
chunks[id].push(str.substr(offset + 8));
break;
} else {
chunks[id].push(str.substr(offset + 4));
break;
}
}
return chunks;
}
function Encoder(fps, _quality = 0.8, _name = "Groover") {
this.fps = fps;
this.quality = quality = _quality;
this.frameDelay = frameDelay = 1000 / fps;
this.frame = 0;
this.width = width = null;
this.timecode = 0;
this.name = name = _name;
}
Encoder.prototype = {
addFrame: function(frame) {
if ('canvas' in frame) {
frame = frame.canvas;
}
if (width === null) {
this.width = width = frame.width,
this.height = height = frame.height
startEncoding();
} else
if (width !== frame.width || height !== frame.height) {
throw RangeError("Frame size error. Frames must be the same size.");
}
addFrame(frame);
this.frame += 1;
this.timecode = clusterTimecode;
},
toBlob: function() {
return toBlob(webmData);
}
}
return {
Video: Encoder,
}
})()
</script>
</body>
</html>