Skip to content

Commit 0642544

Browse files
kesha-antonovShahen Hovhannisyan
authored andcommitted
Integrate ffmpeg builds (#87)
* image instead of base64 * crop * merge. update gitignore * fix crop * handle invalid crop * use ffmpeg build * fix check * clean
1 parent bf0f784 commit 0642544

4 files changed

Lines changed: 167 additions & 109 deletions

File tree

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,6 @@ project(':react-native-video-processing').projectDir = new File(rootProject.proj
3030
compile project(':react-native-video-processing')
3131
```
3232

33-
6. Add this add the end of the file `android/app/build.gradle` (need it to download and compile ffmpeg lib):
34-
```
35-
allprojects {
36-
repositories {
37-
maven { url "https://jitpack.io" }
38-
}
39-
}
40-
```
41-
4233
#### [iOS]
4334

4435
1. In Xcode, click the "Add Files to <your-project-name>".
@@ -174,6 +165,16 @@ export class App extends Component {
174165
### How to setup Library
175166
[![Setup](https://img.youtube.com/vi/HRjgeT6NQJM/0.jpg)](https://youtu.be/HRjgeT6NQJM)
176167

168+
For Android:
169+
170+
add these lines
171+
```
172+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
173+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
174+
```
175+
176+
to your AndroidManifest.xml
177+
177178
## Contributing
178179

179180
1. Please follow the eslint style guide.

android/build.gradle

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,11 @@ dependencies {
2828
compile 'com.yqritc:android-scalablevideoview:1.0.4'
2929
compile 'com.googlecode.mp4parser:isoparser:1.1.20'
3030
compile 'com.github.wseemann:FFmpegMediaMetadataRetriever:1.0.14'
31-
compile 'com.github.kesha-antonov:ffmpeg-android-java:70e2c41aa13099ab274115c1991887d0aa394feb'
3231
}
3332

3433
allprojects {
3534
repositories {
3635
mavenLocal()
3736
jcenter()
38-
maven { url "https://jitpack.io" }
3937
}
4038
}
19.4 MB
Binary file not shown.

android/src/main/java/com/shahenlibrary/Trimmer/Trimmer.java

Lines changed: 157 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,26 @@
5757
import java.util.Arrays;
5858
import java.util.ArrayList;
5959

60-
import com.github.hiteshsondhi88.libffmpeg.FFmpeg;
61-
import com.github.hiteshsondhi88.libffmpeg.FFmpegExecuteResponseHandler;
62-
import com.github.hiteshsondhi88.libffmpeg.FFmpegLoadBinaryResponseHandler;
60+
import java.io.BufferedReader;
61+
import java.io.InputStreamReader;
62+
import java.io.InputStream;
63+
import java.io.BufferedInputStream;
64+
import java.io.FileInputStream;
6365

66+
import java.security.NoSuchAlgorithmException;
67+
import java.security.MessageDigest;
68+
import java.util.Formatter;
6469

6570
public class Trimmer {
6671

6772
private static final String LOG_TAG = "RNTrimmerManager";
73+
private static final String FFMPEG_FILE_NAME = "ffmpeg";
74+
private static final String FFMPEG_SHA1 = "f51256ddb13c2a4d2bb9e22812775751c32cfdf4";
75+
76+
private static boolean ffmpegLoaded = false;
77+
private static final int DEFAULT_BUFFER_SIZE = 4096;
78+
private static final int END_OF_FILE = -1;
6879

69-
private static boolean ffmpegLoaded;
7080

7181
public static void getPreviewImages(String path, Promise promise, ReactApplicationContext ctx) {
7282
FFmpegMediaMetadataRetriever retriever = new FFmpegMediaMetadataRetriever();
@@ -261,53 +271,23 @@ public static void compress(String source, ReadableMap options, final Promise pr
261271
}
262272
cmd.add(tempFile.getPath());
263273

264-
final String[] cmdToExec = cmd.toArray( new String[0] );
265-
266-
Log.d(LOG_TAG, Arrays.toString(cmdToExec));
267-
268-
try {
269-
FFmpeg.getInstance(ctx).execute(cmdToExec, new FFmpegExecuteResponseHandler() {
270-
271-
@Override
272-
public void onStart() {
273-
Log.d(LOG_TAG, "Compress: Start");
274-
}
275-
276-
@Override
277-
public void onProgress(String message) {
278-
}
279-
280-
@Override
281-
public void onFailure(String message) {
282-
if (cb != null) {
283-
cb.onError("compress error: failed. " + message);
284-
} else if (promise != null) {
285-
promise.reject("compress error: failed.", message);
286-
}
287-
}
288-
289-
@Override
290-
public void onSuccess(String message) {
291-
if (cb != null) {
292-
cb.onSuccess("file://" + tempFile.getPath());
293-
} else if (promise != null) {
294-
WritableMap event = Arguments.createMap();
295-
event.putString("source", "file://" + tempFile.getPath());
296-
promise.resolve(event);
297-
}
298-
}
299-
300-
@Override
301-
public void onFinish() {
302-
Log.d(LOG_TAG, "Compress: Finished");
303-
}
304-
});
305-
} catch (Exception e) {
274+
String error = executeFfmpegCommand(cmd, rctx);
275+
if ( error != null ) {
306276
if (cb != null) {
307-
cb.onError("compress error. Command already running" + e.toString());
277+
cb.onError("compress error: failed. " + error);
308278
} else if (promise != null) {
309-
promise.reject("compress error. Command already running", e.toString());
279+
promise.reject("compress error: failed.", error);
310280
}
281+
return;
282+
}
283+
284+
if (cb != null) {
285+
cb.onSuccess("file://" + tempFile.getPath());
286+
} else if (promise != null) {
287+
WritableMap event = Arguments.createMap();
288+
event.putString("source", "file://" + tempFile.getPath());
289+
promise.resolve(event);
290+
return;
311291
}
312292
}
313293

@@ -386,6 +366,14 @@ static void getPreviewImageAtPosition(String source, double sec, String format,
386366
promise.resolve(event);
387367
}
388368

369+
private static BufferedReader getOutputFromProcess(Process p) {
370+
return new BufferedReader(new InputStreamReader(p.getInputStream()));
371+
}
372+
373+
private static BufferedReader getErrorFromProcess(Process p) {
374+
return new BufferedReader(new InputStreamReader(p.getErrorStream()));
375+
}
376+
389377
static void crop(String source, ReadableMap options, final Promise promise, ReactApplicationContext ctx) {
390378
int cropWidth = (int)( options.getDouble("cropWidth") );
391379
int cropHeight = (int)( options.getDouble("cropHeight") );
@@ -456,77 +444,148 @@ static void crop(String source, ReadableMap options, final Promise promise, Reac
456444
// NOTE: OUTPUT FILE
457445
cmd.add(tempFile.getPath());
458446

459-
final String[] cmdToExec = cmd.toArray( new String[0] );
447+
String error = executeFfmpegCommand(cmd, ctx);
448+
if ( error != null ) {
449+
promise.reject("Crop error", error);
450+
return;
451+
}
460452

461-
Log.d(LOG_TAG, Arrays.toString(cmdToExec));
453+
WritableMap event = Arguments.createMap();
454+
event.putString("source", "file://" + tempFile.getPath());
455+
promise.resolve(event);
456+
return;
457+
}
462458

459+
static private String executeFfmpegCommand(ArrayList<String> cmd, ReactApplicationContext ctx) {
463460
try {
464-
FFmpeg.getInstance(ctx).execute(cmdToExec, new FFmpegExecuteResponseHandler() {
461+
// NOTE: 3. EXECUTE "ffmpeg" COMMAND
462+
String ffmpegInDir = getFfmpegAbsolutePath(ctx);
463+
cmd.add(0, ffmpegInDir);
464+
Process p = new ProcessBuilder(cmd).start();
465465

466-
@Override
467-
public void onStart() {
468-
Log.d(LOG_TAG, "crop: onStart");
469-
}
466+
BufferedReader input = getOutputFromProcess(p);
467+
String line = null;
470468

471-
@Override
472-
public void onProgress(String message) {
473-
Log.d(LOG_TAG, "crop: onProgress");
474-
}
469+
StringBuilder sInput = new StringBuilder();
475470

476-
@Override
477-
public void onFailure(String message) {
478-
Log.d(LOG_TAG, "crop: onFailure");
479-
promise.reject("Crop error: failed.", message);
480-
}
471+
while((line=input.readLine()) != null) {
472+
Log.d(LOG_TAG, "processing ffmpeg");
473+
System.out.println(sInput);
474+
sInput.append(line);
475+
}
476+
input.close();
481477

482-
@Override
483-
public void onSuccess(String message) {
484-
Log.d(LOG_TAG, "crop: onSuccess");
485-
Log.d(LOG_TAG, message);
478+
// TODO: DO IT ASYNC
479+
int errorCode = p.waitFor();
480+
Log.d(LOG_TAG, "ffmpeg processing completed");
486481

487-
WritableMap event = Arguments.createMap();
488-
event.putString("source", "file://" + tempFile.getPath());
489-
promise.resolve(event);
490-
}
482+
if ( errorCode != 0 ) {
483+
BufferedReader error = getErrorFromProcess(p);
484+
StringBuilder sError = new StringBuilder();
491485

492-
@Override
493-
public void onFinish() {
494-
Log.d(LOG_TAG, "crop: onFinish");
486+
Log.d(LOG_TAG, "ffmpeg error code: " + errorCode);
487+
while((line=error.readLine()) != null) {
488+
System.out.println(sError);
489+
sError.append(line);
495490
}
496-
});
491+
error.close();
492+
493+
return sError.toString();
494+
}
495+
496+
return null;
497497
} catch (Exception e) {
498-
promise.reject("Crop error. Command already running", e.toString());
498+
return e.toString();
499499
}
500500
}
501501

502-
public static void loadFfmpeg(ReactApplicationContext ctx){
502+
private static String getFilesDirAbsolutePath(ReactApplicationContext ctx) {
503+
return ctx.getFilesDir().getAbsolutePath();
504+
}
505+
506+
private static String getFfmpegAbsolutePath(ReactApplicationContext ctx) {
507+
return getFilesDirAbsolutePath(ctx) + File.separator + FFMPEG_FILE_NAME;
508+
}
509+
510+
public static String getSha1FromFile(final File file) {
511+
MessageDigest messageDigest = null;
503512
try {
504-
FFmpeg.getInstance(ctx).loadBinary(new FFmpegLoadBinaryResponseHandler() {
505-
@Override
506-
public void onStart() {
507-
Log.d(LOG_TAG, "load FFMPEG: onStart");
508-
}
513+
messageDigest = MessageDigest.getInstance("SHA1");
514+
} catch (NoSuchAlgorithmException e) {
515+
Log.d(LOG_TAG, "Failed to load SHA1 Algorithm. " + e.toString());
516+
return "";
517+
}
509518

510-
@Override
511-
public void onSuccess() {
512-
Log.d(LOG_TAG, "load FFMPEG: onSuccess");
513-
ffmpegLoaded = true;
519+
try {
520+
try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
521+
final byte[] buffer = new byte[1024];
522+
for (int read = 0; (read = is.read(buffer)) != -1;) {
523+
messageDigest.update(buffer, 0, read);
514524
}
525+
is.close();
526+
}
527+
} catch (IOException e) {
528+
Log.d(LOG_TAG, "Failed to load SHA1 Algorithm. IOException. " + e.toString());
529+
return "";
530+
}
515531

516-
@Override
517-
public void onFailure() {
518-
ffmpegLoaded = false;
519-
Log.d(LOG_TAG, "load FFMPEG: Failed to load ffmpeg");
520-
}
532+
try (Formatter f = new Formatter()) {
533+
for (final byte b : messageDigest.digest()) {
534+
f.format("%02x", b);
535+
}
536+
return f.toString();
537+
}
538+
}
539+
540+
public static void loadFfmpeg(ReactApplicationContext ctx) {
541+
// NOTE: 1. COPY "ffmpeg" FROM ASSETS TO /data/data/com.myapp...
542+
String filesDir = getFilesDirAbsolutePath(ctx);
521543

522-
@Override
523-
public void onFinish() {
524-
Log.d(LOG_TAG, "load FFMPEG: onFinish");
544+
try {
545+
File ffmpegFile = new File(filesDir, FFMPEG_FILE_NAME);
546+
if ( !(ffmpegFile.exists() && getSha1FromFile(ffmpegFile).equalsIgnoreCase(FFMPEG_SHA1)) ) {
547+
final FileOutputStream ffmpegStreamToDataDir = new FileOutputStream(ffmpegFile);
548+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
549+
550+
int n;
551+
InputStream ffmpegInAssets = ctx.getAssets().open("armeabi-v7a" + File.separator + FFMPEG_FILE_NAME);
552+
while(END_OF_FILE != (n = ffmpegInAssets.read(buffer))) {
553+
ffmpegStreamToDataDir.write(buffer, 0, n);
525554
}
526-
});
527-
} catch (Exception e){
555+
556+
ffmpegStreamToDataDir.flush();
557+
ffmpegStreamToDataDir.close();
558+
559+
ffmpegInAssets.close();
560+
}
561+
} catch (IOException e) {
562+
Log.d(LOG_TAG, "Failed to copy ffmpeg" + e.toString());
528563
ffmpegLoaded = false;
529-
Log.d("Failed to load ffmpeg", e.toString());
564+
return;
530565
}
566+
567+
String ffmpegInDir = getFfmpegAbsolutePath(ctx);
568+
569+
// NOTE: 2. MAKE "ffmpeg" EXECUTABLE
570+
String[] cmdlineChmod = { "chmod", "700", ffmpegInDir };
571+
// TODO: 1. CHECK PERMISSIONS. 2. DO IT ASYNC
572+
Process pChmod = null;
573+
try {
574+
pChmod = Runtime.getRuntime().exec(cmdlineChmod);
575+
} catch (IOException e) {
576+
Log.d(LOG_TAG, "Failed to make ffmpeg executable. Error in execution cmd. " + e.toString());
577+
ffmpegLoaded = false;
578+
return;
579+
}
580+
581+
try {
582+
pChmod.waitFor();
583+
} catch (InterruptedException e) {
584+
Log.d(LOG_TAG, "Failed to make ffmpeg executable. Error in wait cmd. " + e.toString());
585+
ffmpegLoaded = false;
586+
return;
587+
}
588+
589+
ffmpegLoaded = true;
531590
}
532591
}

0 commit comments

Comments
 (0)