|
57 | 57 | import java.util.Arrays; |
58 | 58 | import java.util.ArrayList; |
59 | 59 |
|
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; |
63 | 65 |
|
| 66 | +import java.security.NoSuchAlgorithmException; |
| 67 | +import java.security.MessageDigest; |
| 68 | +import java.util.Formatter; |
64 | 69 |
|
65 | 70 | public class Trimmer { |
66 | 71 |
|
67 | 72 | 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; |
68 | 79 |
|
69 | | - private static boolean ffmpegLoaded; |
70 | 80 |
|
71 | 81 | public static void getPreviewImages(String path, Promise promise, ReactApplicationContext ctx) { |
72 | 82 | FFmpegMediaMetadataRetriever retriever = new FFmpegMediaMetadataRetriever(); |
@@ -261,53 +271,23 @@ public static void compress(String source, ReadableMap options, final Promise pr |
261 | 271 | } |
262 | 272 | cmd.add(tempFile.getPath()); |
263 | 273 |
|
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 ) { |
306 | 276 | if (cb != null) { |
307 | | - cb.onError("compress error. Command already running" + e.toString()); |
| 277 | + cb.onError("compress error: failed. " + error); |
308 | 278 | } else if (promise != null) { |
309 | | - promise.reject("compress error. Command already running", e.toString()); |
| 279 | + promise.reject("compress error: failed.", error); |
310 | 280 | } |
| 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; |
311 | 291 | } |
312 | 292 | } |
313 | 293 |
|
@@ -386,6 +366,14 @@ static void getPreviewImageAtPosition(String source, double sec, String format, |
386 | 366 | promise.resolve(event); |
387 | 367 | } |
388 | 368 |
|
| 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 | + |
389 | 377 | static void crop(String source, ReadableMap options, final Promise promise, ReactApplicationContext ctx) { |
390 | 378 | int cropWidth = (int)( options.getDouble("cropWidth") ); |
391 | 379 | int cropHeight = (int)( options.getDouble("cropHeight") ); |
@@ -456,77 +444,148 @@ static void crop(String source, ReadableMap options, final Promise promise, Reac |
456 | 444 | // NOTE: OUTPUT FILE |
457 | 445 | cmd.add(tempFile.getPath()); |
458 | 446 |
|
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 | + } |
460 | 452 |
|
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 | + } |
462 | 458 |
|
| 459 | + static private String executeFfmpegCommand(ArrayList<String> cmd, ReactApplicationContext ctx) { |
463 | 460 | 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(); |
465 | 465 |
|
466 | | - @Override |
467 | | - public void onStart() { |
468 | | - Log.d(LOG_TAG, "crop: onStart"); |
469 | | - } |
| 466 | + BufferedReader input = getOutputFromProcess(p); |
| 467 | + String line = null; |
470 | 468 |
|
471 | | - @Override |
472 | | - public void onProgress(String message) { |
473 | | - Log.d(LOG_TAG, "crop: onProgress"); |
474 | | - } |
| 469 | + StringBuilder sInput = new StringBuilder(); |
475 | 470 |
|
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(); |
481 | 477 |
|
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"); |
486 | 481 |
|
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(); |
491 | 485 |
|
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); |
495 | 490 | } |
496 | | - }); |
| 491 | + error.close(); |
| 492 | + |
| 493 | + return sError.toString(); |
| 494 | + } |
| 495 | + |
| 496 | + return null; |
497 | 497 | } catch (Exception e) { |
498 | | - promise.reject("Crop error. Command already running", e.toString()); |
| 498 | + return e.toString(); |
499 | 499 | } |
500 | 500 | } |
501 | 501 |
|
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; |
503 | 512 | 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 | + } |
509 | 518 |
|
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); |
514 | 524 | } |
| 525 | + is.close(); |
| 526 | + } |
| 527 | + } catch (IOException e) { |
| 528 | + Log.d(LOG_TAG, "Failed to load SHA1 Algorithm. IOException. " + e.toString()); |
| 529 | + return ""; |
| 530 | + } |
515 | 531 |
|
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); |
521 | 543 |
|
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); |
525 | 554 | } |
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()); |
528 | 563 | ffmpegLoaded = false; |
529 | | - Log.d("Failed to load ffmpeg", e.toString()); |
| 564 | + return; |
530 | 565 | } |
| 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; |
531 | 590 | } |
532 | 591 | } |
0 commit comments