Comments (21)
The scons config file for Android does need to be updated a bit, you're right. It seems to still be working fine with NDK 10, but it could use an update.
(you need to set the env variable ANDROID_NDK_ROOT to point to the location where you downloaded the NDK. once that's done, just using:
scons -u target=arm-android-linux
should work (works on my system, MacOSX with NDK r10c and r10d)
The error you're seeing here are only in two command line example apps, AvcInfo and HevcInfo, which you don't really need to build (command lines apps on Android are not often useful!). So you can either just remove them from the build by editing 'Build.scons', or you can pull the latest commit from the 'develop' branch here where I have committed a fix that allows this two apps to build on Android.
from bento4.
Thanks! develop branch successfully built!
Next question is about Mp4Edit app. Specifically about command --insert. What exact I have put to that command to append one mp4 file (for example file_to_append.mp4) to another file (original_file.mp4)?
from bento4.
mp4edit allows you to insert/change/remove atoms (boxes) from an MP4 file, but not concatenating two files. None of the current command line apps actually implement concatenation at this point. It would be fairly easy to write a concatenator based on the Bento4 class library API (just like all the other command line apps are built on top of the core library).
from bento4.
Oooh, I see...Sad to read this...ok, will look how to implement such functionality with Bento4.
As I understand, first I have to open one file, parse it to atoms and put that atoms to second file? Is that right?
from bento4.
It's simpler than this.
What you need to do is to create the output file, then open the first input file, read its samples one by one and add them to the output, then open the second file, read its samples one by one and add them to the output. You'll also need to copy the sample descriptions from the input(s) to the output. If you know for sue that both inputs have the same sample descriptions, you can simply copy the info from the first file. If they may be different, you would add to the output both input sample descriptions (for each track), and when you add the samples from the second input you specify that the sample description index is 1 (instead of 0).
You can take a look at the mp4mux app for an example of how to create an output file and adding samples (and sample descriptions to it).
from bento4.
Thanks for tips! They partially helps ) But anyway I still stuck in merging of two mp4 files. I've looked in code of Mp4Mux app into method AddMp4Tracks. It looks quite simple: get input Tracks and add them to output Movie and nothing more. I've done the same but result video has wrong orientation and it plays frames only from first file. Can you look at my code? What I'm doing wrong? What about SampleDescription? I didn't found in method AddMp4Track some usage of Samples.
// create the first input stream
AP4_Result result;
AP4_ByteStream* input = NULL;
result = AP4_FileByteStream::Create(input_filename, AP4_FileByteStream::STREAM_MODE_READ, input);
if (AP4_FAILED(result))
{
sprintf (status, "ERROR: cannot open input file (%s)\n", input_filename);
return env->NewStringUTF(status);
}
//Get Movie from first file
AP4_File first_file(*input);
input->Release();
AP4_Movie* input_movie = first_file.GetMovie();
if (input_movie == NULL)
{
sprintf (status, "ERROR: input file (%s) doesn't have Movie object\n", input_filename);
return env->NewStringUTF(status);
}
__android_log_print(ANDROID_LOG_ERROR, "Mp4Editor", "input file created");
// create the second input stream
AP4_ByteStream* input2 = NULL;
result = AP4_FileByteStream::Create(appended_filename, AP4_FileByteStream::STREAM_MODE_READ, input2);
if (AP4_FAILED(result))
{
input->Release();
sprintf (status, "ERROR: cannot open output file (%s)\n", appended_filename);
return env->NewStringUTF(status);
}
//Get Movie from second(appended) file
AP4_File second_file(*input2);
input2->Release();
AP4_Movie* input_movie2 = second_file.GetMovie();
if (input_movie2 == NULL)
{
sprintf (status, "ERROR: input file (%s) doesn't have Movie object\n", appended_filename);
return env->NewStringUTF(status);
}
__android_log_print(ANDROID_LOG_ERROR, "Mp4Editor", "input2 file created");
// create the output stream
AP4_ByteStream* output = NULL;
result = AP4_FileByteStream::Create(output_filename, AP4_FileByteStream::STREAM_MODE_WRITE, output);
if (AP4_FAILED(result))
{
sprintf (status, "ERROR: cannot open output file (%s)\n", output_filename);
return env->NewStringUTF(status);
}
//Create output Movie
AP4_Movie* output_movie = new AP4_Movie();
if (output_movie == NULL)
{
sprintf (status, "ERROR: output file (%s) doesn't have Movie object\n", output_filename);
return env->NewStringUTF(status);
}
__android_log_print(ANDROID_LOG_ERROR, "Mp4Editor", "output file created");
//ADD TRACKS FROM TWO INPUT FILES TO OUTPUT FILE
//Put tracks from first input file to output Movie
AP4_List<AP4_Track>::Item* track_item = input_movie->GetTracks().FirstItem();
AP4_List<AP4_Track>::Item* track_item2 = input_movie2->GetTracks().FirstItem();
while (track_item)
{
AP4_Track* track = track_item->GetData();
track = track->Clone();
// reset the track ID so that it can be re-assigned
track->SetId(0);
output_movie->AddTrack(track);
track_item = track_item->GetNext();
}
//Put tracks from second input file to output Movie
while (track_item2)
{
AP4_Track* track = track_item2->GetData();
track = track->Clone();
// reset the track ID so that it can be re-assigned
track->SetId(0);
output_movie->AddTrack(track);
track_item2 = track_item2->GetNext();
}
// create a multimedia file
AP4_File file(output_movie);
// setup the brands
AP4_Array<AP4_UI32> brands;
brands.Append(AP4_FILE_BRAND_ISOM);
brands.Append(AP4_FILE_BRAND_MP42);
// set the file type
file.SetFileType(AP4_FILE_BRAND_MP42, 1);
// write the file to the output
AP4_FileWriter::Write(file, *output);
// cleanup
output->Release();
from bento4.
Hello!
I used mp4info app and found that my input is coded in H.264:
File:
major brand: isom
minor version: 0
compatible brand: isom
compatible brand: iso2
compatible brand: avc1
Movie:
duration: 9152 ms
time scale: 6000
fragments: no
Found 2 Tracks
Track 1:
flags: 15 ENABLED IN-MOVIE IN-PREVIEW
id: 1
type: Video
duration: 8879 ms
language: ```
media:
sample count: 264
timescale: 90000
duration: 799151 (media timescale units)
duration: 8879 (ms)
bitrate (computed): 41888.176 Kbps
display width: 3840.000000
display height: 2160.000000
frame rate (computed): 29.732
Sample Description 0
Coding: avc1 (H.264)
Width: 3840
Height: 2160
Depth: 24
AVC Profile: 66 (Baseline)
AVC Profile Compat: 80
AVC Level: 51
AVC NALU Length Size: 4
AVC SPS: [67428033da00f0010fa01b4284d4]
AVC PPS: [68ce06e2]
Codecs String: avc1.428033
Track 2:
flags: 15 ENABLED IN-MOVIE IN-PREVIEW
id: 2
type: Audio
duration: 9152 ms
language: ```
media:
sample count: 429
timescale: 48000
duration: 439296 (media timescale units)
duration: 9152 (ms)
bitrate (computed): 96.247 Kbps
Sample Description 0
Coding: mp4a (MPEG-4 Audio)
Stream Type: Audio
Object Type: MPEG-4 Audio
Max Bitrate: 96000
Avg Bitrate: 96000
Buffer Size: 768
MPEG-4 Audio Object Type: 2 (AAC Low Complexity)
MPEG-4 Audio Decoder Config:
Sampling Frequency: 48000
Channels: 1
Sample Rate: 48000
Sample Size: 16
Channels: 1
Is that right? Then I tried to use code of method AddH264Track from Mp4Mux.cpp just to put one input file to new output file, but get failed again. Still don't see clear how to manage my input files ((((
from bento4.
I tried another aproach to get correct output file. I decide to construct new AP4_Track with AP4_SampleTable in which I added all AP4_Samples from 2 input files. On that stage I've ignored AUDIO tracks (is that ok to do this?). So, the code is looks like this:
AP4_Track* track_video = input_movie->GetTrack(AP4_Track::TYPE_VIDEO);
track_video = track_video->Clone();
AP4_Track* track_video2 = input_movie2->GetTrack(AP4_Track::TYPE_VIDEO);
track_video2 = track_video2->Clone();
AP4_Track* track_audio = input_movie->GetTrack(AP4_Track::TYPE_AUDIO);
AP4_Track* track_audio2 = input_movie2->GetTrack(AP4_Track::TYPE_AUDIO);
// create a sample table
AP4_SyntheticSampleTable* sample_table = new AP4_SyntheticSampleTable();
AP4_SyntheticSampleTable* sample_video = (AP4_SyntheticSampleTable*)track_video->GetSampleTable();
AP4_SyntheticSampleTable* sample_video2 = (AP4_SyntheticSampleTable*)track_video2->GetSampleTable();
AP4_SyntheticSampleTable* sample_audio = (AP4_SyntheticSampleTable*)track_audio->GetSampleTable();
AP4_SyntheticSampleTable* sample_audio2 = (AP4_SyntheticSampleTable*)track_audio2->GetSampleTable();
AP4_SampleDescription* description_video = track_video->GetSampleDescription(0);
AP4_SampleDescription* description_video2 = track_video2->GetSampleDescription(0);
AP4_SampleDescription* description_audio = track_audio->GetSampleDescription(0);
unsigned int video_width = track_video->GetWidth();
unsigned int video_height = track_video->GetHeight();
for(int i = 0; i < sample_video->GetSampleCount(); i++)
{
AP4_Sample sample;
sample_video->GetSample(i, sample);
sample_table->AddSample(sample);
}
for(int i = 0; i < sample_video2->GetSampleCount(); i++)
{
AP4_Sample sample;
sample_video2->GetSample(i, sample);
sample_table->AddSample(sample);
}
sample_table->AddSampleDescription(description_video);
AP4_UI32 movie_timescale = track_video->GetMovieTimeScale();
AP4_UI32 media_timescale = track_video->GetMediaTimeScale();
AP4_UI32 duration_movie1 = input_movie->GetDurationMs();
AP4_UI32 duration_movie2 = input_movie2->GetDurationMs();
AP4_UI32 duration_media1 = track_video->GetMediaDuration();
AP4_UI32 duration_media2 = track_video2->GetMediaDuration();
AP4_UI64 video_track_duration = duration_movie1 + duration_movie2;
AP4_UI64 video_media_duration = duration_media1 + duration_media2;
// create a video track
AP4_Track* track = new AP4_Track(AP4_Track::TYPE_VIDEO,
sample_table,
0, // auto-select track id
movie_timescale, // movie time scale
video_track_duration, // track duration
media_timescale, // media time scale
video_media_duration, // media duration
"```", // language
video_width<<16, // width
video_height<<16 // height
);
output_movie->AddTrack(track);
// create a multimedia file
AP4_File file(output_movie);
// setup the brands
AP4_Array<AP4_UI32> brands;
brands.Append(AP4_FILE_BRAND_ISOM);
brands.Append(AP4_FILE_BRAND_MP42);
brands.Append(AP4_FILE_BRAND_AVC1);
// set the file type
file.SetFileType(AP4_FILE_BRAND_MP42, 0);
// write the file to the output
AP4_FileWriter::Write(file, *output);
// cleanup
output->Release();
But still no luck! Result file isn't played by videoplayer.
mp4Info app gets this:
File:
major brand: mp42
minor version: 0
Movie:
duration: 6784 ms
time scale: 90000
fragments: no
Found 1 Tracks
Track 1:
flags: 7 ENABLED IN-MOVIE IN-PREVIEW
id: 1
type: Video
duration: 6784 ms
language: ```
media:
sample count: 197
timescale: 90000
duration: 593966 (media timescale units)
duration: 6600 (ms)
bitrate (computed): 11886.091 Kbps
frame rate (computed): 29.850
Sample Description 0
Coding: avc1 (H.264)
Width: 1280
Height: 720
Depth: 24
AVC Profile: 66 (Baseline)
AVC Profile Compat: 80
AVC Level: 31
AVC NALU Length Size: 4
AVC SPS: [6742801fda014016e806d0a135]
AVC PPS: [68ce06e2]
Codecs String: avc1.42801F
from bento4.
Ok then ) Now I got video which is partially played. Video player shows 6 sec. duration but plays only 2 sec. + orientation of video is wrong.
Code is (I looked to AP4_Track.Clone() code to create own new version):
// create a sample table
AP4_SyntheticSampleTable* sample_table = new AP4_SyntheticSampleTable();
AP4_SyntheticSampleTable* sample_video = (AP4_SyntheticSampleTable*)track_video->GetSampleTable();
AP4_SyntheticSampleTable* sample_video2 = (AP4_SyntheticSampleTable*)track_video2->GetSampleTable();
AP4_SampleDescription* description_video = track_video->GetSampleDescription(0);
// add clones of the sample descriptions to the new sample table
for (unsigned int i=0; ;i++) {
AP4_SampleDescription* sample_description = sample_video->GetSampleDescription(i);
if (sample_description == NULL) break;
sample_table->AddSampleDescription(sample_description->Clone());
}
AP4_Sample sample;
AP4_Ordinal index = 0;
while (AP4_SUCCEEDED(sample_video->GetSample(index, sample)))
{
AP4_ByteStream* data_stream;
data_stream = sample.GetDataStream();
sample_table->AddSample(*data_stream,
sample.GetOffset(),
sample.GetSize(),
sample.GetDuration(),
sample.GetDescriptionIndex(),
sample.GetDts(),
sample.GetCtsDelta(),
sample.IsSync());
AP4_RELEASE(data_stream); // release our ref, the table has kept its own ref.
index++;
}
index = 0;
while (AP4_SUCCEEDED(sample_video2->GetSample(index, sample)))
{
AP4_ByteStream* data_stream;
data_stream = sample.GetDataStream();
sample_table->AddSample(*data_stream,
sample.GetOffset(),
sample.GetSize(),
sample.GetDuration(),
sample.GetDescriptionIndex(),
sample.GetDts(),
sample.GetCtsDelta(),
sample.IsSync());
AP4_RELEASE(data_stream); // release our ref, the table has kept its own ref.
index++;
}
AP4_UI32 duration_movie1 = track_video->GetDuration();
AP4_UI32 duration_movie2 = track_video2->GetDuration();
AP4_UI32 duration_media1 = track_video->GetMediaDuration();
AP4_UI32 duration_media2 = track_video2->GetMediaDuration();
// create the cloned track
AP4_Track* clone = new AP4_Track(track_video->GetType(),
sample_table,
0,
track_video->GetMovieTimeScale(),
duration_movie1 + duration_movie2,
track_video->GetMediaTimeScale(),
duration_media1 + duration_media2,
track_video->GetTrackLanguage(),
track_video->GetWidth(),
track_video->GetHeight());
output_movie->AddTrack(clone);
// create a multimedia file
AP4_File file(output_movie);
// setup the brands
AP4_Array<AP4_UI32> brands;
brands.Append(AP4_FILE_BRAND_ISOM);
brands.Append(AP4_FILE_BRAND_MP42);
brands.Append(AP4_FILE_BRAND_AVC1);
// set the file type
file.SetFileType(AP4_FILE_BRAND_MP42, 0);
// write the file to the output
AP4_FileWriter::Write(file, *output);
// cleanup
output->Release();
mp4info app result:
File:
major brand: mp42
minor version: 0
Movie:
duration: 6164 ms
time scale: 1000
fragments: no
Found 1 Tracks
Track 1:
flags: 7 ENABLED IN-MOVIE IN-PREVIEW
id: 1
type: Video
duration: 6164 ms
language: ```
media:
sample count: 71
timescale: 90000
duration: 554767 (media timescale units)
duration: 6164 (ms)
bitrate (computed): 4649.342 Kbps
display width: 1280.000000
display height: 720.000000
frame rate (computed): 11.518
Sample Description 0
Coding: avc1 (H.264)
Width: 1280
Height: 720
Depth: 24
AVC Profile: 66 (Baseline)
AVC Profile Compat: 80
AVC Level: 31
AVC NALU Length Size: 4
AVC SPS: [6742801fda014016e806d0a135]
AVC PPS: [68ce06e2]
Codecs String: avc1.42801F
I feel that the Truth is near ))) Please help to stop my pains! )
from bento4.
I think you're almost there. I fear that there may be a small issue with calling sample_table->AddSample directly, but since you're now calling the more detailed AddSample() method, that should avoid that issue. The last remaining problem is probably because when you add the samples of the second track, the sample timestamps won't work, as they need to be shifted by the amount of the first track. What you should do it call sample.SetDts(0) before adding the samples (don't change the CtsDelta or the Duration). This way the DTS will be recomputed for you automatically. I'll try to find some time today and double check that this is the only missing part here.
from bento4.
Great! That works! Also I successfully added audio samples and now merged video is played well! BUT! There is one more problem: video orientation. I shot videos in portrait orientation while device's sensor is in landscape. And result video is rotated by -90 degrees (
from bento4.
The 90 degree rotation is probably related to some missing signaling in the sample description.
The video itself doesn't change, but there is sometimes as parameter in the track headers that provide a hint to the player that the video should be rotated when displayed.
Can you share an example of a video that exhibits the problem? What player are you using to display the video (I assume the player correctly displays the original videos that you are concatenating)
from bento4.
I share 2 input videos and result video in dropbox:
https://www.dropbox.com/s/9o370kl3dc6oou4/20150521_result_video.mp4?dl=0 - RESULT
https://www.dropbox.com/s/45d8prrx9hsvofa/20150521_video_01.mp4?dl=0 - INPUT 01
https://www.dropbox.com/s/zbnlhtt1zbzs3k5/20150521_video_02.mp4?dl=0 - INPUT 02
from bento4.
The reason the output video doesn't have the proper orientation is because the AP4_Track object was created without the same transform matrix as the original (not your fault, the sample code does the same).
I've added a new AP4_Track constructor that allows you to pass an existing AP4_Track object as a prototype to "inherit" the settings from, including the transform matrix (note that the notion of a non-default transform matrix is something that's specific to Quicktime, not all MP4 players will support it).
So at the point where you call new AP4_Track(), call this version of the constructor:
AP4_Track(AP4_SampleTable* sample_table, // ownership is transfered to the AP4_Track object
AP4_UI32 track_id,
AP4_UI32 movie_time_scale, // 0 = use default
AP4_UI64 track_duration, // in the movie timescale
AP4_UI32 media_time_scale,
AP4_UI64 media_duration, // in the media timescale
const AP4_Track* track_prototype); // prototype for the type and other track fields
(pass the original track pointer instead of language, width and height).
This is part of the latest commit in the master branch (not yet in the pre-compiled binaries)
from bento4.
Awesome! Seems result video now is absolutely correct! Thank you so much for you time!
from bento4.
Aaaaaand one more issue was founded ))) Any video player shows not correct full time of result video (about 2 times less) but still plays entire file.
I looked at the code of AP4_Track creation and thought that parameter track_duration is bad but seems it's fine
AP4_UI32 duration_movie = 0;
AP4_UI32 duration_media = 0;
AP4_UI32 movie_time_scale = 0;
AP4_UI32 media_time_scale = 0;
for (int i = 0; i < inputFileCount; i++)
{
....
AP4_Track* track_video = input_movie->GetTrack(AP4_Track::TYPE_VIDEO);
...
duration_movie =+ track_video->GetDuration();
duration_media =+ track_video->GetMediaDuration();
...
if(i == 0)
{
movie_time_scale = track_video->GetMovieTimeScale();
media_time_scale = track_video->GetMediaTimeScale();
}
...
}
// create the output video track
AP4_Track* output_video_track = new AP4_Track(sample_video_table,
0,
movie_time_scale,
duration_movie,
media_time_scale,
duration_media,
prototype_track_video);
from bento4.
I resolved this issue. Problem was in duration_media parameter of audio track.
from bento4.
@barbibulle
You mentioned the following in your first comment:
you need to set the env variable ANDROID_NDK_ROOT to point to the location where you downloaded the NDK. once that's done, just using:
scons -u target=arm-android-linux
should work (works on my system, MacOSX with NDK r10c and r10d)
I have set the path to my ndk inside Config.scons
, but I can't get it working.
Below I have added all the ways I've tried to set the ndk path:
First:
ANDROID_NDK_ROOT=os.getenv(/Users/Shared/Library/Android/sdk/ndk-bundle)
Then I get:
scons: Reading SConscript files ...
********** Configuring Build Target = arm-android-linux / Release ********
File "/Users/Shared/Desktop/Bento4-master/Build/Targets/arm-android-linux/Config.scons", line 13
ANDROID_NDK_ROOT=os.getenv(/Users/Shared/Library/Android/sdk/ndk-bundle)
^
SyntaxError: invalid syntax
Second:
ANDROID_NDK_ROOT=os.getenv('/Users/Shared/Library/Android/sdk/ndk-bundle')
Then I get:
scons: Reading SConscript files ...
********** Configuring Build Target = arm-android-linux / Release ********
Exception: ANDROID_NDK_ROOT environment variable not set:
File "/Users/Shared/Desktop/Bento4-master/SConstruct", line 1:
SConscript('Build/Boot.scons')
File "/usr/local/Cellar/scons/3.1.2/libexec/scons-local/SCons/Script/SConscript.py", line 660:
return method(*args, **kw)
File "/usr/local/Cellar/scons/3.1.2/libexec/scons-local/SCons/Script/SConscript.py", line 597:
return _SConscript(self.fs, *files, **subst_kw)
File "/usr/local/Cellar/scons/3.1.2/libexec/scons-local/SCons/Script/SConscript.py", line 286:
exec(compile(scriptdata, scriptname, 'exec'), call_stack[-1].globals)
File "/Users/Shared/Desktop/Bento4-master/Build/Boot.scons", line 67:
SConscript('Build.scons', variant_dir='Targets/'+env['target']+'/'+build_config, exports='env', duplicate=0)
File "/usr/local/Cellar/scons/3.1.2/libexec/scons-local/SCons/Script/SConscript.py", line 660:
return method(*args, **kw)
File "/usr/local/Cellar/scons/3.1.2/libexec/scons-local/SCons/Script/SConscript.py", line 597:
return _SConscript(self.fs, *files, **subst_kw)
File "/usr/local/Cellar/scons/3.1.2/libexec/scons-local/SCons/Script/SConscript.py", line 286:
exec(compile(scriptdata, scriptname, 'exec'), call_stack[-1].globals)
File "/Users/Shared/Desktop/Bento4-master/Build/Build.scons", line 149:
execfile(target_config_file)
File "/Users/Shared/Desktop/Bento4-master/Build/Targets/arm-android-linux/Config.scons", line 15:
raise Exception('ANDROID_NDK_ROOT environment variable not set')
Can you please tell me how I should set the ndk path?
from bento4.
You need to set ANDROID_NDK_ROOT
as an environment variable, not variable in scons: you don't need to edit any file.
On linux and macos systems, you would do that using a shell command like:
export ANDROID_NDK_ROOT=/Users/Shared/Library/Android/sdk/ndk-bundle
.
Please note that we haven't tested recent versions of the NDK, so the NDK build may not entirely work with the latest NDK versions. I'll check on that.
from bento4.
Following up on this topic: I have pushed a small update to master that documents how to build with recent versions of the Android NDK, using CMake. See the top-level README.md for details.
from bento4.
@barbibulle
Awesome, I was able to build it successfully. Do you have any example on how to use the library? I'm only interested in using mp4info
to get info from a video file. Unfortunately, I do not have any experience in C++. Any help would be appreciated.
from bento4.
Related Issues (20)
- AddressSanitizer: heap-buffer-overflow in mp42aac
- Update dashif:laurl to dashif:Laurl per latest specification
- C++ 20 support ? HOT 3
- HLS with multiple audio channels: mp4hls creates additional "random" audio channels. Why is that? HOT 2
- HDR10Plus signaling issue HOT 1
- Packaging DASH stream with time based index without splitting mp4 file HOT 3
- AddressSanitizer: heap-buffer-overflow in mp42aac
- mp4dump decodes `senc` box incorrectly for non-constant IV
- DRM: Apply different Content Key to different tracks and renditions HOT 5
- mp4decrypt can decrypt aac sample-aes? HOT 1
- mp4mux ERROR: unsupported input type 'C' HOT 2
- Multiple vulnerabilities exist in mp4edit
- SEGV ASAN in mp4decrypt
- AddressSanitizer: FPE in mp42aac
- Memory leaks in Ap4DataBuffer.cpp:210:28 (v1.6.641)
- [REQ] add a (GH-compliant) license file
- mp42ts : ac-4 "Error: audio codec not supported" HOT 2
- How to watch the contents of NowTV using Linux
- new release with C++20 compatibility?
- [REGRESSION] master branch have broken DolbyDigital streams HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from bento4.