Coder Social home page Coder Social logo

juanirache / gopro-telemetry Goto Github PK

View Code? Open in Web Editor NEW
306.0 306.0 55.0 1.72 MB

Reads telemetry from the GPMF track in GoPro cameras (Hero5 and later) and converts it to multiple formats

Home Page: https://tailorandwayne.com/gopro-telemetry-extractor/

License: MIT License

JavaScript 99.70% HTML 0.30%

gopro-telemetry's People

Contributors

akxe avatar dependabot[bot] avatar g3ncl avatar itsonlyjames avatar juanirache avatar rtf6x avatar sarfata avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gopro-telemetry's Issues

CircleCI tests timeout

Currently, circleCI tests fail because of timeouts. Probably a misconfiguration issue, as the timeouts we set are already surrealistically high

Missing dependency in npm installer

When using command "npm i gopro-telemetry" it is failing because it cannot find the file ssh://[email protected]/jleppert/egm96.git

Please update the installer so that it either has that dependency in the git repository or edit the installer so it's not trying to find it.

[Website] GPS precision change yelds incoherent GPS points

Hello,

I recently tried your website to extract GPS coordinates from a Hero 7 Black camera.
I tried changing the precision parameter and visualized the resulting coordinates in Google Maps.
As you can see in the screenshot below, the results are quite weird.
The 3 colors indicates 3 precision values:

  • Red is 10k
  • Green is 625
  • Blue is 529

I would have guessed that the 3 data points would be overlapping, with some points filtered out for green and blue.
Any idea what is going wrong?

PS: Haven't tried using the code yet, just the website

image

Data mismatch while extracting telemetry

Hello,
I've developed a program which allows me to extract telemetry from multiple videos. I'm encountering a strange behaviour in my program and I hope you might help me out by identifying what went wrong.

I have a video filmed by GoPro Hero 10 Black and I tried to extract the telemetry from it by using the following function:

async function convertFileToJSON(
    file: Buffer,
    file_name: String
): Promise<Telemetry> {
    return new Promise((resolve, reject) => {
        try {
            const cancellationToken = { cancelled: false };
            gpmfExtract(file, { browserMode: false, progress, cancellationToken })
                .then(async (extracted) => {
                    if (!extracted) return;

                    cancellationToken.cancelled = true;

                    await goproTelemetry(
                        extracted,
                        {  stream: "GPS5" },
                        (telemetry: Telemetry) => { resolve(telemetry) }
                    );
                })
                .catch((err) => reject(err));
        } catch (e) {
            reject(e);
        }
    });
}

The JSON extracted shows that the samples are taken from 2015-10-18 (While the video was taken at 2022-09-11). I tried to use your tool in order to check whether the video is faulty but I got a valid JSON (same video):

faulty-json example:
{"1":{"streams":{"GPS5":{"samples":[{"value":[31.6771656,34.6042632,75.035,0,0],"cts":81610.70799999998,"date":"2015-10-18T00:02:24.099Z"},{"value":[31.6771656,34.6042632,75.035,0,0],"cts":81710.73119999998,"date":"2015-10-18T00:02:24.199Z"},{"value":[31.6771656,34.6042632,75.035,0,0],"cts":81810.75439999998,"date":"2015-10-18T00:02:24.299Z"},{"value":[31.6771656,34.6042632,75.035,0,0],"cts":81910.77759999997,"date":"2015-10-18T00:02:24.399Z"},
........

and the JSON extracted from your tool:
{"1":{"streams":{"GPS5":{"samples":[{"value":[32.1026345,34.8322371,66.825,3.817,5.25],"cts":384401.024,"date":"2022-11-09T11:08:56.399Z","sticky":{"fix":3,"precision":475,"altitude system":"MSLV"}},{"value":[32.1026385,34.8322336,66.452,3.854,5.96],"cts":384501.4496,"date":"2022-11-09T11:08:56.499Z"},{"value":[32.1026377,34.8322326,67.006,3.661,6],"cts":384601.8752,"date":"2022-11-09T11:08:56.599Z"}
.......

Do you have any idea what might cause that? Maybe some headers in goproTelemetry() are missing?
Thank you,
Tal

MP4 times wrong when first file in series missing

Recent GoPro models record the same creation time for every file in a series. We can generally know the MP4 time of the clips because they are loaded all together and time adds up, but if the first file is missing the resulting MP4 times are wrong.

I'm not sure where the information to detect this and apply an offset is. Needs more investigation.

Number of frames in video

I am using gopro-telemetry with the groupTimes: "frames" option. Now when I count the occurences of cts in the result JSON file for my MP4 video I get 32457 frames. However, when I open the same video in Python and read the number of frames via OpenCV vidcap.get(cv2.CAP_PROP_FRAME_COUNT) this gives me 32460 - thus a small difference of 3 fewer frames in the JSON telemetry file.

The cts value of the first sample in the telemetry file is 0. Can I thus safely assume that these missing 3 samples belong to the last 3 frames in the video, for which we simply have no GPS data?

Add JSDoc/.d.ts type hints

As the title says. It would come handy to have types with explanation in code rather than in the readme.

Don't understand WrongSpeed option

According to the documentation and my personal understanding of it, the value of WrongSpeed must be in [m/s].

So in theory all GPS points with speeds higher then what i assigned WrongSpeed should be gone.

If i set "WrongSpeed: 55" the max speed should be arround 200 km/h, so extremely generous becouse the speed physically cant exceed 130 km/h in this case, all points get cut, except the first one.

What is it, that i dont understand?

Dependency egm96 is not compatible with browser

The package egm96 used to correct the GPS altitudes has the following line in it:

var data = fs.readFileSync(path.join(__dirname,'/WW15MGH.DAC'));

This does not work in the browser because fs.readFileSync is only available in Node.

I can see a few solutions:

  • Remove this dependency since it seems it is only required for old cameras (but that would mean getting wrong data for old cameras and is obviously a regression)
  • Change egm96 to only load the data the first time it is used. This way if you disable GPS correction in the browser you can use the rest of the library. Altitude correction will not work in the browser.
  • Pass the correction data as an argument to the egm96 package so the caller is responsible for loading it (via readFileSync on node - or via fetch in the browser)

I can make a pull request to implement one of these solutions but it seems you have already implemented a solution for https://goprotelemetryextractor.com/. Can you share what solutions you have chosen?

Duplicate Date Times / Bad Sample Data

Got a weird one here and it's random in that it's happened only in 1 video stream after processing many others.

The CTS timestamps all move forward correctly, but the date timestamps are duplicate even though the actual lat/lng values are different.

I wouldn't be surprised if this is a GoPro issue and not relevant to your – absolutely awesome – module.

I am simply logging the current sample and next sample here (functionally in an attempt to increment laps based on crossing start/finish line) and you can see the CTS, lat/lng values update ok but the date timestamps are the same across 6 samples...

image



Also for visualisation sake, here's a graph based on speed. You will notice that it drops to 0km/h consistently, which made me think it was accuracy based initially however after much debugging I'm not certain...

image image

(A speed based graph that is correct)
image

Looking forward to hearing your thoughts @JuanIrache, and thanks for an awesome suite of work mate!

RangeError [ERR_FS_FILE_TOO_LARGE]

Trying to get the GoPro telemetry data for a 4GB video using the sample code:

const gpmfExtract = require('gpmf-extract');
const goproTelemetry = require(`gopro-telemetry`);
const fs = require('fs');

const file = fs.readFileSync('path_to_your_file.mp4');

gpmfExtract(file)
  .then(extracted => {
    let telemetry = goproTelemetry(extracted);
    fs.writeFileSync('output_path.json', JSON.stringify(telemetry));
    console.log('Telemetry saved as JSON');
  })
  .catch(error => console.log(error));

Getting RangeError [ERR_FS_FILE_TOO_LARGE]: File size (4001043636) is greater than possible Buffer: 2147483647 bytes. Is there a way to extract the gpmf data without sync. reading the entire file?

Exact timestamp of each image

@JuanIrache Is there a way to get the exact timestamp of each image?

It can be calculated approximately using the starting time and frame rate. Is there an exact stamp from telemetry?

Telemetry Extractor Lite VIRB time bug

I've been using the Telemetry Lite tool for a while now, to export GPX files suitable for VIRB. It works pretty well, but today I discovered a bug when processing some video files (using time gauges): a recording of 35 minutes or so has been split in 4 parts of 4/4/4/1 GB. I've exported VIRB gpx files for all of them and processed them in VIRB. I noticed, however, that they all start and end with the same time, instead of the time being sequential. See the pictures below for a comparison between the GPX and VIRB export, the former being correct:

image

image

[DOUBT] GO PRO Files missing track data after renaming

I have some GO PRO files that I just copied to the computer and renamed, and when I use gpmf-extract it says track not found. Is this actually a thing, rename could change anything in the metadata of the file. Or maybe just copying from the SD Card to the computer could anything be changed?

Implement this on PHP server

I'm running a WordPress website, I'd like to integrate this project on my website

As mentioned this project depend on Javascrit, but I got error while trying to implement this code via jQuery

require is not defined

Any help with this error and how can I implement this project through javascript or jQuery

const goproTelemetry = require('gopro-telemetry');
function callback(data) {
// Do sometging with the data
}
const telemetry = goproTelemetry(input, options, callback);

What I'm trying to do is to upload video and then download metadata as CSV

Documentation unclear

Hi, this part of the documentation is not clear to me since the telemetry assignment does not follow the two possible usages - as promise (with await) or with callback (with the callback argument).
Is there a working code example of how to reuse the parsedData?

const parsedData = goproTelemetry({ rawData, timing }, { raw: true });
const telemetry = goproTelemetry({ parsedData, timing });

Thanks

[BUG] gopro-telemetry function call is blocking every request on my express server

Describe the bug
Gopro-telemetry function call is blocking every request on my express server

To Reproduce
Juste create a very basic express server like this one:

const express = require('express');
const bodyParser = require('body-parser');
const metadata2json = require('./export')

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

const port = process.env.PORT || 5000;

app.listen(port, () => {
        console.log(`App is listening on port ${port}.`);
    }
);

app.get("/helloworld", async (req, res) => {
    console.log("helloworld")
    res.send("helloworld")
})

app.get("/extract", async (req, res) => {
    await metadata2json('./GH020021.MP4', "TOTO.json")
    res.send("finish")
})

As you can see I have tow route, one that will serve as test ('/helloworld') and the other one that will extract my data.

Here is my metadata2json function:

const goproTelemetry = require(`gopro-telemetry`);
const gpmfExtract = require('gpmf-extract');
const fs = require('fs');
process.env.FFPROBE_PATH = require('@ffprobe-installer/ffprobe').path;
const ffprobe = require('ffprobe-client');
const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(require('@ffmpeg-installer/ffmpeg').path);

const extractGPMF = async videoFile => {
    const ffData = await ffprobe(videoFile);

    for (let i = 0; i < ffData.streams.length; i++) {
        if (ffData.streams[i].codec_tag_string === 'gpmd') {
            return await extractGPMFAt(videoFile, i);
        }
    }
    return null;
};

const extractGPMFAt = async (videoFile, stream) => {
    let rawData = Buffer.alloc(0);
    await new Promise(resolve => {
        ffmpeg(videoFile)
            .outputOption('-y')
            .outputOptions('-codec copy')
            .outputOptions(`-map 0:${stream}`)
            .outputOption('-f rawvideo')
            .pipe()
            .on('data', chunk => {
                rawData = Buffer.concat([rawData, chunk]);
            }).on('end', resolve);
    });
    return rawData;
};

const metadata2json = async (file, dest) => {
    const gpmf = await extractGPMF(file);
    const element = {
        rawData: gpmf,
        timing: {
            frameDuration: 0,
            start: "",
            sample: []
        }
    };
    console.log(element);
    let telemetry = await goproTelemetry(element, {promisify: true});
    console.log("salut")
    fs.writeFile(dest, JSON.stringify(telemetry), err => {
        if (err) {
            return console.log(err);
        }
    });
    console.log('Telemetry saved as JSON');
    console.log(file);
};

module.exports = metadata2json;

When I run this and make a request on /extract and right after on /helloworld I have this output:


> [email protected] start /home/basile_lamarque/tmp/test-express
> node index.js

App is listening on port 5000.
{
  rawData: <Buffer 44 45 56 43 00 01 1c b8 44 56 49 44 4c 04 00 01 00 00 00 01 44 56 4e 4d 63 0b 00 01 48 45 52 4f 38 20 42 6c 61 63 6b 00 53 54 52 4d 00 01 05 54 53 54 ... 5196778 more bytes>,
  timing: { frameDuration: 0, start: '', sample: [] }
}
salut
Telemetry saved as JSON
./GH020021.MP4
helloworld

Expected behaviour
I no clue of what happen, maybe goproTelemetry is consuming all my thread, I don't know.

Additional context
I have try to add this but it gave me the same result

let telemetry = await goproTelemetry(element, {promisify: true});

gopro-telemetry - performance question

I am testing the conversion of a 2 Gb GoPro video using both gpmfExtract and gopro-telemetry in the browser.
The gpmfExtract part takes about 2.5 seconds (with the worker disabled since it's crashing the browser).
The gopro-telemetry needs about 30 seconds.
I have also analyzed the gpmfExtract file by saving it to the disk as Blob, it's 2.68 Mb, so not a large amount of data.
I am using the latest [v1.2.0] version of the code.

Is it normal for gopro-telemetry to be taking so long or am I doing something wrong?
I have also tested with different presets like "gpx" but I don't see any performance gain.

Here is the relevant part of the code:

const videoFileObj = addedVideosFileObjArray[i].file
const cancellationToken = {cancelled: false}
const progress = percent => console.log(`gpmfExtract: ${percent}% processed`)
gpmfExtract(videoFileObj, {browserMode: true, useWorker: false, progress, cancellationToken}).then(res => {
    if (!res) return   // Cancelled
    const progress = percent => console.log(`goproTelemetry: ${percent}% processed`)
    goproTelemetry(res, {preset: "geojson", stream : "GPS", progress}).then(telemetry => {
        const telemetryJson = JSON.stringify(telemetry)
        console.log(telemetryJson)
    })
})

Here is an extract of the browser's console log:

21:13:15.053 MediaAddModal.svelte:61 gpmfExtract: 1% processed
.....
21:13:17.650 MediaAddModal.svelte:61 gpmfExtract: 100% processed
21:13:17.662 MediaAddModal.svelte:68 goproTelemetry: 0.01% processed
21:14:41.446 MediaAddModal.svelte:68 goproTelemetry: 0.2% processed
21:14:48.379 MediaAddModal.svelte:68 goproTelemetry: 0.4% processed
21:14:48.390 MediaAddModal.svelte:68 goproTelemetry: 0.6% processed
21:14:48.395 MediaAddModal.svelte:68 goproTelemetry: 0.9% processed
21:14:48.412 MediaAddModal.svelte:71 {"type":"Feature","geometry":{"type":"LineString","coordinates":
[[14.5326717,50.0264218,285.335],[14.5326715,50.0264203,285.23],... (etc... truncated)

buffer.readDoubleBE is not a function at readInt64BEasFloat

I'm trying to get data from a GoPro 10 video in browser (latest Chrome).

I'm using gpmfExtractWrapper and passing the data directly to :

            goProTelemetry(rawData, {
              stream: ['GPS'],
              progress: console.log,
            }).then(console.log)

and immediately get the following dump:

Uncaught (in promise) TypeError: buffer.readDoubleBE is not a function
    at readInt64BEasFloat (webpack-internal:///(:4200/app-pages-browser)/../../node_modules/gopro-telemetry/code/utils/findFirstTimes.js:8:51)
    at module.exports (webpack-internal:///(:4200/app-pages-browser)/../../node_modules/gopro-telemetry/code/utils/findFirstTimes.js:61:14)
    at eval (webpack-internal:///(:4200/app-pages-browser)/../../node_modules/gopro-telemetry/index.js:145:37)
    at Array.map (<anonymous>)
    at process (webpack-internal:///(:4200/app-pages-browser)/../../node_modules/gopro-telemetry/index.js:145:28)
    at async GoProTelemetry (webpack-internal:///(:4200/app-pages-browser)/../../node_modules/gopro-telemetry/index.js:347:18)
readInt64BEasFloat @ findFirstTimes.js:7
module.exports @ findFirstTimes.js:60
eval @ index.js:145
process @ index.js:145
setTimeout (async)
eval @ breathe.js:7
module.exports @ breathe.js:7
process @ index.js:115
GoProTelemetry @ index.js:347
eval @ VideoUploadContext.tsx:71
Promise.then (async)
reader.onload @ VideoUploadContext.tsx:69
load (async)
addVideo @ VideoUploadContext.tsx:61
handleDrop @ DropWrapper.tsx:103
callCallback @ react-dom.development.js:19467
invokeGuardedCallbackImpl @ react-dom.development.js:19516
invokeGuardedCallback @ react-dom.development.js:19591
invokeGuardedCallbackAndCatchFirstError @ react-dom.development.js:19605
executeDispatch @ react-dom.development.js:30661
processDispatchQueueItemsInOrder @ react-dom.development.js:30693
processDispatchQueue @ react-dom.development.js:30706
dispatchEventsForPlugins @ react-dom.development.js:30717
eval @ react-dom.development.js:30907
batchedUpdates$1 @ react-dom.development.js:23771
batchedUpdates @ react-dom.development.js:27623
dispatchEventForPluginEventSystem @ react-dom.development.js:30906
dispatchEvent @ react-dom.development.js:28679
dispatchDiscreteEvent @ react-dom.development.js:28650

When I breakpoint the function exported in findFirstTimes.js, I see that the object passed in to the data param is a Uint8Array with 1645084 bytes and forceGPSSrc is null

Hope that helps,
thanks

groupTimes option seems to be broken

I have a GoPro video (from a HERO10 Black) with a duration of about 8:30 min:

$ ffprobe GX050150.mp4
...
Duration: 00:08:39.22, start: 0.000000, bitrate: 45017 kb/s
...

When I run gopro-telemetry (as suggested via gpmf-extract) with option groupTimes = "frames", then output seems correct - I get a result for every ~33 ms (as I can see in the JSON result cts values). However, if I want to use another grouping time, this does not work anymore. For example with groupTimes = 10 I get only four result samples with these cts values:

  • "cts": 0
  • "cts": 528.3055555555554
  • "cts": 51032.30840715651
  • "cts": 310120.20807889925

Is this a bug in gopro-telemetry, or am I using this option wrong?

KML & GPX presets- invalid characters

Hello,

Firstly- thank you for gopro-telemetry- I'm only getting started, but it seems very useful

When using the gpx & kml presets, the output contains invalid characters- for example in the KML file, \n and \ characters
image

I can fix this by post-processing the output files, but how can I prevent this from occurring ?

thanks

Andrew

Using gopro-telemetry in web browser

Hello,

First of all, thanks a lot for this repository! I have been using it in NodeJS for a while and I now want to use it as part of an Angular app.

I run into the issue that setImmediate() is receiving very little support and most web browsers do not implement it. Editing the breathe.js file to skip this function makes it work in the web browser. But I wonder if you encountered this challenge before, maybe I'm not using the library properly?

I also understand that skipping this function certainly freezes (part of) the web app while processing the file, but that already sounds like optimisation for what I do!

Error: Error, negative length

Hi

I used gopro-telemetry some time ago and it worked fine- but I can't quite remember what I did
I've just tried it again and I get various errors

If anyone can help- I'd be grateful- I know next to nothing about javascript

On a Windows machine:

PS C:\Apps\gopro-telemetry\samples> node example.js
Error: Error, negative length
    at parseKLV (C:\Apps\gopro-telemetry\code\parseKLV.js:216:22)
    at async parseOne (C:\Apps\gopro-telemetry\index.js:31:18)
    at async process (C:\Apps\gopro-telemetry\index.js:175:20)
    at async GoProTelemetry (C:\Apps\gopro-telemetry\index.js:347:18)
    at async toJSON (C:\Apps\gopro-telemetry\samples\example.js:12:20)

On a Linux machine:

ub@z4g4:~/gopro-telemetry/samples$ node example.js
Error: Error, negative length
    at parseKLV (/home/ub/gopro-telemetry/code/parseKLV.js:216:22)
    at async parseOne (/home/ub/gopro-telemetry/index.js:31:18)
    at async process (/home/ub/gopro-telemetry/index.js:175:20)
    at async GoProTelemetry (/home/ub/gopro-telemetry/index.js:347:18)
    at async toJSON (/home/ub/gopro-telemetry/samples/example.js:12:20)

On a Window machine, following https://www.trekview.org/blog/2022/gopro-telemetry-exporter-getting-started/

PS C:\Apps\gopro-telemetry\samples> node GS018422-full-telemetry.js
[0:00:00.701] [BoxParser] Box of type '☺' has a size 1024 greater than its container size 116
C:\Apps\gopro-telemetry\samples\GS018422-full-telemetry.js:10
fs.writeFileSync('GS018422-full-telemetry.json', JSON.stringify(telemetry));
TypeError: Do not know how to serialize a BigInt
    at JSON.stringify (<anonymous>)
    at C:\Apps\gopro-telemetry\samples\GS018422-full-telemetry.js:10:55
    at GoProTelemetry (C:\Apps\gopro-telemetry\node_modules\gopro-telemetry\index.js:349:3)

Andrew

Hero7 Silver missing streams?

Debug export: from gopro-telemetry:
device name:"HERO7 Silver"
ACCL:Object {samples: Array(278796), name: "Accelerometer (z,x,y)", units: "m/s2"}
GPS5:Object {samples: Array(25387), name: "GPS (Lat., Long., Alt., 2D speed, 3D speed)", units: Array(5)}
GYRO:Object {samples: Array(139344), name: "Gyroscope (z,x,y)", units: "rad/s"}
ISOE:Object {samples: Array(83943), name: "Sensor ISO"}
SHUT:Object {samples: Array(83943), name: "Exposure time (shutter speed)", units: "s"}
WBAL:Object {samples: Array(83943), name: "White Balance temperature (Kelvin)"}
WRGB:Object {samples: Array(83943), name: "White Balance RGB gains"}

Meanwhile Dashware csv export discovers other also like
TSMP,
TMPC,
SIUN,
MTRX
ORIN,
GPSF,
GPSU,
GPSP,

  • few more

Am I doing something wrong?
Also how do you export it to CSV with some template?

Remove egm96

When testing, I noticed that when bundled, the egm96 library is 2.5mb in size. There must be a better way to convert to the correct output without being the 99% of the library size...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.