]> id.pley.net Git - movie_parser.git/commitdiff
Upload Dropbox.com versions of everything master
authorJer Noble <jer.noble@apple.com>
Thu, 15 Dec 2022 19:09:31 +0000 (11:09 -0800)
committerJer Noble <jer.noble@apple.com>
Thu, 15 Dec 2022 19:09:31 +0000 (11:09 -0800)
Atom.js [changed mode: 0644->0755]
AtomTester.html [changed mode: 0644->0755]
GapFinder.html [new file with mode: 0755]
GenerateManifest.html [new file with mode: 0755]
SampleData.js [new file with mode: 0755]
SceneFinder.html [new file with mode: 0755]

diff --git a/Atom.js b/Atom.js
old mode 100644 (file)
new mode 100755 (executable)
index a3ff73b..0875055
--- a/Atom.js
+++ b/Atom.js
@@ -408,7 +408,7 @@ class EditListBox extends FullBox {
             var mediaRateFraction = view.getUint16(headerOffset);
             headerOffset += 2;
 
-            this.edits.push([segmentDuration, mediaTime, mediaRateFraction, mediaRateInteger]);
+            this.edits.push([segmentDuration, mediaTime, mediaRateInteger, mediaRateFraction]);
         }
 
         return headerOffset;
@@ -1831,3 +1831,36 @@ class TrackFragmentBaseMediaDecodeTimeBox extends FullBox {
 };
 
 Atom.constructorMap['tfdt'] = TrackFragmentBaseMediaDecodeTimeBox.bind(null);
+
+
+class ColorBox extends Atom {
+    constructor(parent) {
+        super(parent);
+        this.description = "Color";
+    };
+
+    parse(buffer, offset) {
+        var headerOffset = super.parse(buffer, offset);
+        var view = new DataView(buffer, offset);
+
+        var typeArrayView = new Uint8Array(buffer, offset + headerOffset, 4);
+        this.colorType = String.fromCharCode.apply(null, typeArrayView);
+        headerOffset += 4;
+
+        if (this.colorType == 'nclx') {
+            this.colorPrimaries = view.getUint16(headerOffset);
+            headerOffset += 2;
+
+            this.transferCharacteristics = view.getUint16(headerOffset);
+            headerOffset += 2;
+
+            this.matrixCoefficients = view.getUint16(headerOffset);
+            headerOffset += 2;
+
+            this.fullRangeFlag = view.getUint8(headerOffset) & 0xF
+            headerOffset++;
+        }
+    }
+}
+
+Atom.constructorMap['colr'] = ColorBox.bind(null);
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
index 5d1d4de..1ff5f4e
@@ -87,7 +87,7 @@
                     continue;
                 var div = document.createElement('div');
                 var dt = document.createElement('dt');
-                dt.appendChild(document.createTextNode(property));
+                dt.appendChild(document.createTextNode(property + ': '));
                 var dd = document.createElement('dd');
                 dd.appendChild(toDOMRepresentation(value));
                 div.appendChild(dt);
     <style>
         dl { border: 1px solid black; padding: 1px; }
         dt, dd { display: inline; }
-        dt:after { content:": " }
+        /*dt:after { content:": " }*/
         dd:after { content:"\A"; white-space:pre; }
         dd dl { margin: 0; vertical-align: top }
         dd span { display: inline-block; vertical-align: top; }
diff --git a/GapFinder.html b/GapFinder.html
new file mode 100755 (executable)
index 0000000..e3307bf
--- /dev/null
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<head>
+    <title>GapFinder</title>
+    <script src=Atom.js></script>
+    <script>
+        // Add Blob.prototype.slice if it does not already exist:
+        if (typeof(Blob.prototype.slice) == 'undefined' && typeof(Blob.prototype.webkitSlice) != 'undefined')
+            Blob.prototype.slice = Blob.prototype.webkitSlice;
+
+               function readAtoms(file, offset, length) {
+                       function tryToReadAtom(file, offset) {
+                               return new Promise((resolve, reject) => {
+                                       var reader = new FileReader();
+                                       reader.onload = (event) => {
+                                               var atom = new Atom();
+                        atom.parse(reader.result, 0);
+                        resolve(atom);
+                                       };
+                                       reader.onerror = (error) => {
+                                               reject(error);  
+                                       };
+                    var subset = file.slice(offset, offset + 36);
+                    reader.readAsArrayBuffer(subset);
+                               });
+                       }
+                       
+                       return new Promise((resolve, reject) => {
+                var atoms = [];
+                var offset = 0;
+                tryToReadAtom(file, offset).then((atom) => {
+                    var reader = new FileReader();
+                    reader.onload = function(e) {
+                        var atom = Atom.create(reader.result);
+                        resolve(atom);
+                    };
+                    reader.onerror = (error) => { 
+                        reject(error);
+                    };
+                    var subset = file.slice(offset, offset + length);
+                    reader.readAsArrayBuffer(subset);
+                }, (error) {
+                    reject(error);
+                })
+                       })
+               }
+
+
+               
+        function onFileSelect(e) {
+            var file = e.target.files[0];
+
+            
+            readAtom(file, 0, file.size).then((atom) => {
+                console.log(atom);
+            }, () => {
+                               // do some failed stuff
+                       });
+        }
+        
+        function setup() {
+            document.getElementById('file').addEventListener('change', onFileSelect, false);
+        }
+    </script>
+    <style>
+        dl { border: 1px solid black; padding: 1px; }
+        dt, dd { display: inline; }
+        dt:after { content:": " }
+        dd:after { content:"\A"; white-space:pre; }
+        dd dl { margin: 0; vertical-align: top }
+        dd span { display: inline-block; vertical-align: top; }
+
+        dt, dd { display: inline-block; min-width: 14em; }
+        dd { margin-left: 1em; }
+        dd th { font-weight: normal; text-align: right; }
+        .description { text-align: center; font-weight: bold; }
+        
+        /* make the output display as a table */
+        /*
+        dl { display: table; }
+        dl div { display: table-row; }
+        dt, dd { display: table-cell; }
+        */
+    </style>
+</head>
+<body id="atomtester" onload="setup()">
+    <input type="file" id="file" name="file" />
+    <div id=output>
+</body>
\ No newline at end of file
diff --git a/GenerateManifest.html b/GenerateManifest.html
new file mode 100755 (executable)
index 0000000..a6f1af1
--- /dev/null
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    <title>GenerateManifest</title>
+    <script src=Atom.js></script>
+    <script>
+        // Add Blob.prototype.slice if it does not already exist:
+        if (typeof(Blob.prototype.slice) == 'undefined' && typeof(Blob.prototype.webkitSlice) != 'undefined')
+            Blob.prototype.slice = Blob.prototype.webkitSlice;
+
+        function onFileSelect(e) {
+            atoms = [];
+            var file = e.target.files[0];
+            var reader = new FileReader();
+            reader.readAsArrayBuffer(file);
+            reader.addEventListener('load', (e) => { onload(e, file); });
+        }
+
+        function onload(e, file) {
+            var reader = e.target;
+            var offset = 0;
+            while (offset < reader.result.byteLength) {
+                var atom = Atom.create(reader.result, offset);
+                offset += atom.size;
+                atoms.push(atom);
+            }
+
+            var manifest = generateManifest(atoms, file);
+            document.body.appendChild(document.createTextNode(JSON.stringify(manifest)));
+        }
+
+        function generateManifest(atoms, file)
+        {
+            var manifest = new Object;
+            manifest.url = file.name;
+
+            manifest.type = file.type;
+            var codecs = [];
+
+            var moov = atoms.find((atom) => { return atom.type === 'moov'; });
+            if (!moov)
+                return;
+
+            var timeScale = moov.getAtomByType('mdhd').timeScale
+
+            manifest.init = { offset: 0, size: moov.offset + moov.size };
+
+            var stsds = moov.getAtomsByType('stsd');
+            stsds.forEach((stsd) => {
+                stsd.getAtomsByType('avc1').forEach((avc1) => {
+                                       avc1.getAtomsByType('avcC').forEach((avcC) => {
+                                               var profile = ('0' + avcC.AVCProfileIndication.toString(16)).substr(-2);
+                                               var compat = ('0' + avcC.profileCompatibility.toString(16)).substr(-2);
+                                               var level = ('0' + avcC.AVCLevelIndication.toString(16)).substr(-2);
+                                               codecs.push(`"avc1.${profile + compat + level}"`);
+                                       });
+                });
+                stsd.getAtomsByType('hvc1').forEach((hvc1) => {
+                    codecs.push(`"hvc1"`);
+                });
+            });
+
+            if (codecs.length)
+                manifest.type += `; codecs=${ codecs.join(',') }`;
+
+            manifest.media = [];
+            var trex = moov.getAtomsByType('trex')[0];
+
+            atoms.forEach((atom, index) => {
+                if (atom.type !== 'moof')
+                    return;
+
+                var offset = atom.offset;
+                var size = atom.size;
+                var mdat = atoms[index + 1];
+                var timestamp = 0;
+                if (mdat.type === 'mdat')
+                    size += mdat.size;
+                var tfdt = atom.getAtomByType('tfdt');
+                if (tfdt)
+                    timestamp = tfdt.baseMediaDecodeTime / timeScale;
+
+                var defaultSampleDuration = trex.default_sample_duration;
+
+                var tfhd = atom.getAtomByType('tfhd');
+                if (tfhd.defaultSampleDurationPresent)
+                    defaultSampleDuration = tfhd.defaultSampleDuration;
+
+                var totalDuration = 0;
+                var trun = atom.getAtomByType('trun');
+                trun.samples.forEach((sample) => {
+                    if (trun.sampleDurationPresent)
+                        totalDuration += sample.sampleDuration;
+                    else
+                        totalDuration += defaultSampleDuration;
+                });
+                var duration = totalDuration / timeScale;
+
+                manifest.media.push({ offset: offset, size: size, timestamp: timestamp, duration: duration });
+            });
+            return manifest;
+        }
+
+        function setup() {
+            document.getElementById('file').addEventListener('change', onFileSelect, false);
+        }
+    </script>
+</head>
+<body onload="setup()">
+    <input type="file" id="file" name="file" />
+    <div id=output></div>
+</body>
\ No newline at end of file
diff --git a/SampleData.js b/SampleData.js
new file mode 100755 (executable)
index 0000000..efda7ef
--- /dev/null
@@ -0,0 +1,172 @@
+var SampleData = function() {
+    "use strict";
+    this.maximumSampleSize = 0;
+    this.maxScenes = 0;
+    this.syncSampleSizes = [];
+    this.decisions = [];
+    this.thresholds = [];
+    this.syncSampleTimes = [];
+    this.sceneSamples = [];
+};
+
+SampleData.prototype.decision = function(data, k, n, count) {
+    "use strict";
+    var n_m_1 = (n - 1 >= 0 ? n - 1 : 0);
+    var n_m_2 = (n - 2 >= 0 ? n - 2 : 0);
+    var n_p_1 = (n + 1 < count ? n + 1 : count - 1);
+
+    if (
+        ((
+            (data[n_m_1] - data[n_m_2] > 0) && 
+            (data[n] - data[n_m_1] > 0) && 
+            (data[n_p_1] - data[n] > 0) && 
+            (data[n_p_1] - data[n] >= data[n] - data[n - 1])
+        ) 
+        || 
+        (
+            (data[n_m_1] - data[n_m_2] < 0) &&
+            (data[n] - data[n_m_1] < 0) && 
+            (data[n_p_1] - data[n] < 0) && 
+            (data[n_p_1] - data[n] <= data[n] - data[n - 1])
+        )))
+        return 0;
+
+    var directionSet = new Array(4);
+    directionSet[0] = Math.abs(data[n] - data[n_m_1]);
+    directionSet[1] = Math.abs(data[n] - data[n_m_2]);
+    directionSet[2] = Math.abs(data[n_p_1] - data[n_m_1]);
+    directionSet[3] = Math.abs(data[n_p_1] - data[n_m_2]);
+
+    return directionSet.sort()[0];
+};
+
+SampleData.prototype.threshold = function(data, distances, k, n, count) {
+    "use strict";
+    var mean = 0;
+    var S = 0;
+    var min, max, min_n1, max_n1;
+    var directionSet = new Array(4);
+
+    min = min_n1 = data[k];
+    max = max_n1 = data[k];
+
+    var n_m_1 = (n >= 1 ? n - 1 : 0);
+    var n_m_2 = (n >= 2 ? n - 2 : 0);
+    var n_p_1 = (n + 1 < count ? n + 1 : count - 1);
+    directionSet[0] = data[n] - data[n_m_1];
+    directionSet[1] = data[n] - data[n_m_2];
+    directionSet[2] = data[n_p_1] - data[n_m_1];
+    directionSet[3] = data[n_p_1] - data[n_m_2];
+
+    // Knuth Variance Algorithm with Min/Max
+    // http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Algorithm_III
+    for (var i = k, count = 1; i <= n; ++i, ++count) {
+        var x = data[i];
+        var delta = x - mean;
+        mean += delta / count;
+        S += delta * (x - mean);
+
+        // Caculate min/max and min/max excluding the last entry.
+        if (x > max) max = x;
+        if (x < min) min = x;
+        if (i != n) {
+            if (x > max_n1) max_n1 = x;
+            if (x < max_n1) min_n1 = x;
+        }
+    }
+
+    var distance = Math.sqrt(1 / Math.log(distances[n] - distances[k]));
+    var variance = S / ((n - k) - 1);
+    var stddev = Math.sqrt(variance);
+    var weightedDev = stddev / (max - min);
+    var eta = 0;
+
+    var k_m_1 = (k >= 1 ? k - 1 : 0);
+
+    if (directionSet.sort()[0] > 0) {
+        eta = Math.abs(data[k] - data[k_m_1]);
+        if (max_n1 > data[k]) eta += Math.abs(max_n1 - data[k]);
+    } else {
+        eta = Math.abs(data[k] - data[k_m_1]);
+        if (min_n1 < data[k]) eta += Math.abs(min_n1 - data[k]);
+    }
+
+    return distance * weightedDev * eta;
+};
+
+SampleData.prototype.load = function(file, completion) {
+    "use strict";
+    this.reader = new FileReader();
+    this.file = file;    
+    this.completion = completion;
+    this.checkForMoovAtom(0);
+};
+
+SampleData.prototype.checkForMoovAtom = function(offset) {
+    "use strict";
+    this.reader.onload = (function(e) {
+        var result = e.target.result;
+        var basicAtom = new Atom();
+               basicAtom.parse(result);
+
+               if (basicAtom.type == 'moov') 
+            this.readMoovAtom(offset, basicAtom.size);
+        else
+            this.checkForMoovAtom(offset + basicAtom.size);
+    }).bind(this);
+    var subset = this.file.slice(offset, offset + 16);
+    this.reader.readAsArrayBuffer(subset);
+};
+
+SampleData.prototype.readMoovAtom = function(offset, length) {
+    "use strict";
+    this.reader.onload = (function(e) { 
+        var moovAtom = Atom.create(e.target.result);
+               var mediaAtoms = moovAtom.getAtomsByType('mdia');
+               var mediaAtom = mediaAtoms.find(function(atom){
+                       return atom.getAtomByType('hdlr').handlerType == 'vide';
+               });
+        var syncSampleAtom = mediaAtom.getAtomByType('stss');
+        var sampleSizeAtom = syncSampleAtom.parent.getAtomByType('stsz');
+               var mediaHeaderAtom = syncSampleAtom.parent.parent.parent.getAtomByType('mdhd');
+               this.timeScale = mediaHeaderAtom.timeScale;
+        for (var i = 0; i < syncSampleAtom.syncSamples.length; ++i) {
+            var sampleNumber = syncSampleAtom.syncSamples[i];
+            var sampleSize = sampleSizeAtom.sampleSizes[sampleNumber];
+            this.syncSampleSizes.push(sampleSize);
+            if (this.maximumSampleSize < sampleSize)
+                this.maximumSampleSize = sampleSize;
+            
+            var sampleTimeAtom = syncSampleAtom.parent.getAtomByType('stts');
+            var sampleSum = 0;
+
+            
+            this.syncSampleTimes.push(timeSum);
+        }
+
+        this.sceneSamples.push([0, 0]);
+        for (var i = 1; i < this.syncSampleTimes.length; ++i) {
+            var k = this.sceneSamples[this.sceneSamples.length-1][1];
+            var n = i;
+            var T = this.threshold(this.syncSampleSizes, this.syncSampleTimes, k, n, this.syncSampleSizes.length);
+            var D = this.decision(this.syncSampleSizes, k, n, this.syncSampleSizes.length);
+            this.thresholds.push(isFinite(T) ? T : 0);
+            this.decisions.push(D);
+
+            if (D > T && k + 1 < n) {
+                this.sceneSamples.push([D - T, i]);
+            }
+        }
+
+        this.sceneSamples.sort(function(a, b) {
+            if (a[0] == b[0])
+                return a[1] - b[1]
+            return a[0] - b[0];
+        });
+        
+        this.completion();
+
+    }).bind(this);
+    var subset = this.file.slice(offset, offset + length);
+    this.reader.readAsArrayBuffer(subset);
+};
diff --git a/SceneFinder.html b/SceneFinder.html
new file mode 100755 (executable)
index 0000000..7ae6d54
--- /dev/null
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<head>
+    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+    <title>SceneFinder</title>
+    <script src=Atom.js></script>
+    <script src=SampleData.js></script>
+    <script>
+        // Add Blob.prototype.slice if it does not already exist:
+        if (typeof(Blob.prototype.slice) == 'undefined' && typeof(Blob.prototype.webkitSlice) != 'undefined')
+            Blob.prototype.slice = Blob.prototype.webkitSlice;
+
+        var sampleData = null;
+
+        function onFileSelect(e) {
+            var file = e.target.files[0];
+            output.innerHTML = '';
+            sampleData = new SampleData();
+            sampleData.load(file, function(){
+                var video = document.createElement('video');
+                               var url = window.URL.createObjectURL(file);
+                video.src = url;
+                video.controls = true;
+                output.appendChild(video);
+                
+                var ol = document.createElement('ol');
+                output.appendChild(ol);
+                for (var i = 0; i < sampleData.sceneSamples.length; ++i) {
+                    var li = document.createElement('li');
+                    ol.appendChild(li);
+                    
+                    var a = document.createElement('a');
+                    li.appendChild(a);
+                    a.href = '#';
+                    a.time = sampleData.sceneSamples[i][0];
+                    a.onclick = function(e) {
+                                               video.currentTime = e.target.time;
+                                       };
+                    a.innerText = 'Chapter ' + i;
+                }
+            });
+        }
+
+        function setup() {
+            document.getElementById('file').addEventListener('change', onFileSelect, false);
+        }
+    </script>
+</head>
+<body onload="setup()">
+    <input type="file" id="file" name="file" />
+    <div id=output></div>
+</body>
\ No newline at end of file