add: PASSAGE V1 COM OFFICIELLE

préparation du template
intégration du fond animé
fusion du style background et style pcp
This commit is contained in:
2026-03-18 20:15:01 +01:00
parent ade729bc58
commit 13c91b464f
45 changed files with 10987 additions and 106 deletions
@@ -0,0 +1,199 @@
<!DOCTYPE html>
<style>
body {
font-family: sans-serif;
}
</style>
<input type="file">
<button>Construct</button>
<main></main>
<script>
let contents;
const fileInput = document.querySelector('input');
fileInput.addEventListener('change', async () => {
let file = fileInput.files[0];
let buffer = await file.arrayBuffer();
let totalBytes = new Uint8Array(buffer);
const isAlphanumeric = (charCode) => {
return charCode && (
(charCode >= 48 && charCode <= 57)
|| (charCode >= 65 && charCode <= 90)
|| (charCode >= 97 && charCode <= 122));
};
const parseContents = (bytes) => {
let totalContents = [];
let view = new DataView(bytes.buffer);
let lastIndex = 0;
for (let i = 0; i < bytes.byteLength; i++) {
cond:
if (
isAlphanumeric(bytes[i+4])
&& isAlphanumeric(bytes[i+5])
&& isAlphanumeric(bytes[i+6])
&& isAlphanumeric(bytes[i+7])
) {
let size = view.getUint32(i, false);
if (size < 8) break cond;
if (i + size > bytes.byteLength) break cond;
let tag = String.fromCharCode(bytes[i + 4])
+ String.fromCharCode(bytes[i + 5])
+ String.fromCharCode(bytes[i + 6])
+ String.fromCharCode(bytes[i + 7]);
if ((tag.toLowerCase() !== tag) && tag !== 'avcC' && tag !== 'avc1') break cond;
if (i - lastIndex > 0) {
totalContents.push(bytes.slice(lastIndex, i));
}
let contents = tag === 'mdat'
? [bytes.slice(i + 8, i + size)]
: parseContents(bytes.slice(i + 8, i + size));
totalContents.push({
tag,
contents
});
lastIndex = i + size;
i += size - 1;
}
}
if (bytes.byteLength - lastIndex > 1) {
totalContents.push(bytes.slice(lastIndex));
}
return totalContents;
};
contents = parseContents(totalBytes);
document.querySelector('main').append(...contents.map(dataToDiv));
});
let crossedOut = new Set();
let modified = new Map();
const dataToDiv = (data) => {
if (data instanceof Uint8Array) {
let div = document.createElement('div');
div.setAttribute('contenteditable', true);
div.textContent = [...data].map(x => x.toString(16).padStart(2, '0').toLowerCase()).join('');
div.style.whiteSpace = 'nowrap';
div.addEventListener('keydown', () => {
setTimeout(() => {
modified.set(data, hexStringToUint8Array(div.textContent));
if (div.textContent.length % 2) {
div.style.background = 'lime';
} else {
div.style.background = '';
}
});
});
return div;
}
let div = document.createElement('div');
let span = document.createElement('span');
span.style.background = 'lightgray';
span.textContent = data.tag;
let children = document.createElement('div');
children.style.paddingLeft = '10px';
div.append(span);
div.append(children);
children.append(...data.contents.map(dataToDiv));
span.addEventListener('click', (e) => {
if (crossedOut.has(data)) {
crossedOut.delete(data);
span.style.textDecoration = '';
children.style.opacity = 1;
} else {
crossedOut.add(data);
span.style.textDecoration = 'line-through';
children.style.opacity = 0.3;
}
e.stopPropagation();
});
return div;
};
const hexStringToUint8Array = (hexString) => {
if (hexString.length % 2 !== 0) {
hexString += '0';
}
const byteCount = hexString.length / 2;
const uint8Array = new Uint8Array(byteCount);
for (let i = 0; i < byteCount; i++) {
const hexByte = hexString.slice(i * 2, i * 2 + 2);
uint8Array[i] = parseInt(hexByte, 16);
}
return uint8Array;
};
document.querySelector('button').addEventListener('click', () => {
let constructed = construct(contents);
downloadBlob(new Blob([new Uint8Array(constructed)]), 'edited.mp4');
});
const u32 = (value) => {
let bytes = new Uint8Array(4);
let view = new DataView(bytes.buffer);
view.setUint32(0, value, false);
return [...bytes];
};
const ascii = (text, nullTerminated = false) => {
let bytes = Array(text.length).fill(null).map((_, i) => text.charCodeAt(i));
if (nullTerminated) bytes.push(0x00);
return bytes;
};
const construct = (contents) => {
if (contents instanceof Uint8Array) {
if (modified.has(contents)) return [...modified.get(contents)];
else return [...contents];
} else if (Array.isArray(contents)) {
return contents.flatMap(construct);
} else {
let constructedContents = construct(contents.contents);
let size = constructedContents.length + 8;
return [
...u32(size),
...ascii(crossedOut.has(contents) ? 'free' : contents.tag),
...constructedContents
];
}
};
const downloadBlob = (blob, filename) => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 0);
};
</script>
@@ -0,0 +1,121 @@
<script src="../build/mp4-muxer.js"></script>
<script type="module">
const width = 1280;
const height = 720;
const sampleRate = 44100;
const numberOfChannels = 1;
let fileHandle = await new Promise(resolve => {
window.addEventListener('click', async () => {
let fileHandle = await window.showSaveFilePicker({
startIn: 'videos',
suggestedName: `video.mp4`,
types: [{
description: 'Video File',
accept: {'video/mp4' :['.mp4']}
}],
});
resolve(fileHandle);
}, { once: true });
});
let fileWritableStream = await fileHandle.createWritable();
let buf = new Uint8Array(2**24);
let maxPos = 0;
let muxer = new Mp4Muxer.Muxer({
//target: new Mp4Muxer.FileSystemWritableFileStreamTarget(fileWritableStream),
target: new Mp4Muxer.ArrayBufferTarget(),
video: {
codec: 'avc',
width,
height
},
audio: {
codec: 'aac',
numberOfChannels,
sampleRate
},
fastStart: false
});
let canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
let ctx = canvas.getContext('2d');
let videoEncoder = new VideoEncoder({
output: (chunk, meta) => {
//console.log(chunk, meta);
muxer.addVideoChunk(chunk, meta);
},
error: (e) => console.error(e)
});
videoEncoder.configure({
codec: 'avc1.640028',
width: width,
height: height,
bitrate: 1e6,
framerate: 10
});
let audioEncoder = new AudioEncoder({
output: (chunk, meta) => {
//console.log(chunk, meta);
muxer.addAudioChunk(chunk, meta);
},
error: (e) => console.error(e)
});
audioEncoder.configure({
codec: 'mp4a.40.2',
sampleRate,
numberOfChannels,
bitrate: 128000,
});
for (let i = 0; i < 100; i++) {
ctx.fillStyle = ['red', 'lime', 'blue', 'yellow'][Math.floor(Math.random() * 4)];
ctx.fillRect(Math.random() * width, Math.random() * height, Math.random() * width, Math.random() * height);
let frame = new VideoFrame(canvas, { timestamp: 100000 * i });
videoEncoder.encode(frame);
}
let audioContext = new AudioContext();
let audioBuffer = await audioContext.decodeAudioData(await (await fetch('./CantinaBand60.wav')).arrayBuffer());
let length = 10;
let data = new Float32Array(length * numberOfChannels * sampleRate);
data.set(audioBuffer.getChannelData(0).subarray(0, data.length), 0);
//data.set(audioBuffer.getChannelData(0).subarray(0, data.length/2), data.length/2);
let audioData = new AudioData({
format: 'f32-planar',
sampleRate,
numberOfFrames: length * sampleRate,
numberOfChannels,
timestamp: 0,
data
});
audioEncoder.encode(audioData);
audioData.close();
await videoEncoder.flush();
await audioEncoder.flush();
muxer.finalize();
let buffer = muxer.target.buffer;
console.log(buffer);
await fileWritableStream.close();
function download(blob, filename) {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename;
a.click();
}
download(new Blob([buffer]), 't.mp4');
</script>