Skip to content

Commit

Permalink
Merge pull request m1ga#1 from narbs/main
Browse files Browse the repository at this point in the history
Implement compatible iOS version of ti.ffmpeg using same interface as Android version
  • Loading branch information
m1ga authored Oct 2, 2023
2 parents 0fa64c6 + 2edbeac commit 59448d7
Show file tree
Hide file tree
Showing 706 changed files with 130,140 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.DS_Store
Build
DerivedData
dist
xcuserdata
17 changes: 17 additions & 0 deletions ios/Classes/TiFfmpegModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* ti.ffmpeg
*
* Created by Christian Clare, Tambit Software, 2023
*/

#import "TiModule.h"
#include <ffmpegkit/FFmpegKit.h>

@interface TiFfmpegModule : TiModule {
KrollCallback *_successCallback;
KrollCallback *_errorCallback;
KrollCallback *_logCallback;
KrollCallback *_progressCallback;
}

@end
229 changes: 229 additions & 0 deletions ios/Classes/TiFfmpegModule.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
/**
* ti.ffmpeg
*
* Created by Christian Clare, Tambit Software, 2023
*
*/

#import "TiFfmpegModule.h"
#import "TiBase.h"
#import "TiHost.h"
#import "TiUtils.h"

@implementation TiFfmpegModule

#pragma mark Internal

- (id)moduleGUID
{
return @"eb17ed6a-9cbe-433f-bc93-16cc91810514";
}

- (NSString *)moduleId
{
return @"ti.ffmpeg";
}

#pragma mark Lifecycle

- (void)startup
{
// This method is called when the module is first loaded
// You *must* call the superclass
[super startup];
DebugLog(@"[DEBUG] %@ loaded", self);
}

#pragma Public APIs

- (void)run:(id)args
{
ENSURE_SINGLE_ARG(args, NSDictionary);

NSString *input = [args objectForKey:@"input"];
// Inputs is an array of NSDictionary
NSArray *inputs = [args objectForKey:@"inputs"];
NSString *output = [args objectForKey:@"output"];
NSString *watermark = [args objectForKey:@"watermark"];
NSString *options = [args objectForKey:@"options"];

NSLog(@"watermark: %@", watermark);

_successCallback = [args objectForKey:@"success"];
_errorCallback = [args objectForKey:@"error"];
_logCallback = [args objectForKey: @"log"];
_progressCallback = [args objectForKey: @"progress"];

if (watermark != nil && ![[NSString stringWithFormat:@"%@", watermark] isEqualToString:@""]) {
watermark = [NSString stringWithFormat:@"-i %@", watermark];
} else {
watermark = @"";
}

NSLog(@"input: %@", input);
NSLog(@"output: %@", output);
NSLog(@"watermark: %@", watermark);
NSLog(@"options: %@", options);
NSLog(@"inputs: %@", inputs);

NSString *inputCommandLine = @"";

if (input != nil) {
NSLog(@"input processing...");
inputCommandLine = [NSString stringWithFormat: @"-i %@", input];
} else if (inputs != nil) {
NSLog(@"inputs processing...");
// Loop through files and options adding
for (int i = 0; i < [inputs count]; i++) {
NSDictionary *singleInput = inputs[i];
NSString *option = [singleInput valueForKey:@"option"];
inputCommandLine = [inputCommandLine stringByAppendingFormat:@"%@ -i %@ ", (option != nil ? option : @""), [singleInput valueForKey:@"input"]];
}
}

NSLog(@"inputCommandLine: %@", inputCommandLine);

NSString *commandLine = [NSString stringWithFormat: @"%@ %@ %@ %@", inputCommandLine, watermark, options, output];

NSLog(@"commandLine: %@", commandLine);

FFmpegSession* session = [FFmpegKit executeAsync:commandLine withCompleteCallback:^(FFmpegSession* session){

SessionState state = [session getState];
ReturnCode *returnCode = [session getReturnCode];

NSLog(@"FFmpeg process exited with state %@ and rc %@.%@", [FFmpegKitConfig sessionStateToString:state], returnCode, [session getFailStackTrace]);

if (returnCode != nil && [returnCode.description isEqualToString:@"0"]) {
NSLog(@"FFmpeg returned success...")
NSLog(@"returnCode.description: %@", returnCode.description);
NSDictionary *event = @{ @"success" : @(YES) };
[self fireSuccessCallbackWithDict: event];
} else {
NSLog(@"FFmpeg returned failure...")
// Get last line of output as that is normally where the error is reported
NSString *output = [session getOutput];
NSString *error = [self getLastLine:output];
NSLog(@"Error: %@", error);
NSDictionary *event = @{
@"success" : @(NO),
@"error" : error != nil ? error : @""
};
[self fireErrorCallbackWithDict: event];
}

} withLogCallback:^(Log *log) {

if (log != nil) {
NSDictionary *event = @{ @"message" : [log getMessage] };

[self fireLogCallbackWithDict: event];
// NSLog(@"FFmpeg process logged %@", [log getMessage]);
}

} withStatisticsCallback:^(Statistics *statistics) {

if (statistics != nil) {
// This is causing a crash - probably because one of the get calls is failing
// NSLog(@"FFmpeg process statistics: frame: %@, time: %@, bitrate: %@, speed: %@", [statistics getVideoFrameNumber], [statistics getTime], [statistics getBitrate], [statistics getSpeed]);
NSLog(@"FFmpeg process statistics: frame: %@, size: %@, currentTime: %@", [@([statistics getVideoFrameNumber]) stringValue], [@([statistics getSize]) stringValue], [@([statistics getTime]) stringValue]);
NSDictionary *event = @{
@"frame" : [@([statistics getVideoFrameNumber]) stringValue],
@"size" : [@([statistics getSize]) stringValue],
@"currentTime" : [@([statistics getTime]) stringValue],

};
[self fireProgressCallbackWithDict: event];
}

}];

}

- (void)info:(id)args
{
ENSURE_SINGLE_ARG(args, NSDictionary);

NSString *options = [args objectForKey:@"options"];

if (options == nil || [options isEqualToString:@""]) {
NSLog(@"Info call failed: options not available");
return;
}

// NSLog(@"options: %@", options);

NSString *commandLine = [NSString stringWithFormat: @"%@", options];

// NSLog(@"commandLine: %@", commandLine);

FFmpegSession* session = [FFmpegKit executeAsync:commandLine withCompleteCallback:^(FFmpegSession* session){

NSString *output = [session getOutput];
NSLog(@"output: %@", output);

} withLogCallback:^(Log *log) {

} withStatisticsCallback:^(Statistics *statistics) {

}];

}

- (void)fireSuccessCallbackWithDict:(NSDictionary *)dict
{
if (_successCallback) {
[self _fireEventToListener:@"success" withObject:dict listener:_successCallback thisObject:nil];
}
}

- (void)fireErrorCallbackWithDict:(NSDictionary *)dict
{
if (_errorCallback) {
[self _fireEventToListener:@"error" withObject:dict listener:_errorCallback thisObject:nil];
}
}

- (void)fireLogCallbackWithDict:(NSDictionary *)dict
{
if (_logCallback) {
[self _fireEventToListener:@"log" withObject:dict listener:_logCallback thisObject:nil];
}
}

- (void)fireProgressCallbackWithDict:(NSDictionary *)dict
{
if (_progressCallback) {
[self _fireEventToListener:@"progress" withObject:dict listener:_progressCallback thisObject:nil];
}
}

- (NSString*)getLastLine:(NSString*)textString
{
if (textString == nil) {
return @"";
}
NSUInteger stringLength = [textString length];
NSUInteger paragraphStart = 0;
NSUInteger paragraphEnd = 0;
NSUInteger bodyEnd = 0;
NSMutableArray *lineArray = [NSMutableArray array];
NSRange range;
while (paragraphEnd < stringLength)
{
[textString getParagraphStart: &paragraphStart end: &paragraphEnd
contentsEnd: &bodyEnd forRange:NSMakeRange(paragraphEnd, 0)];
range = NSMakeRange(paragraphStart, bodyEnd - paragraphStart);
[lineArray addObject:[textString substringWithRange:range]];
}

if ([lineArray count] == 0) {
return @"";
} else {
return [lineArray lastObject];
}

}


@end
12 changes: 12 additions & 0 deletions ios/Classes/TiFfmpegModuleAssets.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* This is a generated file. Do not edit or your changes will be lost
*/

@interface TiFfmpegModuleAssets : NSObject {

}

- (NSData *)moduleAsset;
- (NSData *)resolveModuleAsset:(NSString*)path;

@end
24 changes: 24 additions & 0 deletions ios/Classes/TiFfmpegModuleAssets.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* This is a generated file. Do not edit or your changes will be lost
*/
#import "TiFfmpegModuleAssets.h"

extern NSData* filterDataInRange(NSData* thedata, NSRange range);

@implementation TiFfmpegModuleAssets

- (NSData *)moduleAsset
{


return nil;
}

- (NSData *)resolveModuleAsset:(NSString *)path
{


return nil;
}

@end
98 changes: 98 additions & 0 deletions ios/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Titanium ffmpeg module for iOS

# Github project

[ti.ffmpeg on Github](https://github.com/m1ga/ti.ffmpeg)

This is the iOS counterpart of the ti.ffmpeg module. The iOS version was written by narbs (Christian Clare, Tambit Software).

The Android version was written by m1ga.

# Version of ffmpeg

The ffmpeg xcframework libraries in the platform folder are from the [ffmpeg-kit releases](https://github.com/arthenica/ffmpeg-kit/releases) project.

The current version used in v1.0 is:

ffmpeg-kit-min-6.0-ios-xcframework

This is the min version. Using the min version decreased the module file size to 39.1mb from 62.6mb (full version).

# Example:

The ti.ffmpeg iOS module can be installed by adding the ti.ffmpeg module to tiapp.xml:

``` xml
<module platform="iphone" version="1.0.0">ti.ffmpeg</module>
```

Then require in code. Sample usage is below:

```js
const ffmpeg = require("ti.ffmpeg");

// To output information to the console, use the info method:
ffmpeg.info({
options: "-encoders"
});

// Specify either a single file name:

let file = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, "video.mp4");

// Or you can specify multiple files, each with their own options:

let inputs = [
{input: "cover_start.png", option: "-framerate 30 -t 3 -loop 1"},
{input: "video1.mp4", option: ""},
{input: "BannerBar.png", option: ""},
];

// If using multiple input files, look up the file path for each file:

var files_in = [];

for (var i = 0; i < inputs.length; i++) {
var input = inputs[i];
let file = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, input.input);
if (file != null && file.exists()) {
files_in.push({input: file.nativePath, option: input.option});
} else {
// Might not be a file but an input audio directive
files_in.push({input: input.input, option: input.option});
}
}

let file_out = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, "video_out.mp4");
let file_watermark = Ti.Filesystem.getFile(Ti.Filesystem.applicationDataDirectory, "watermark.png");

ffmpeg.run({
// Specify EITHER input OR inputs
input: file,
// inputs: files_in,
output: file_out,
watermark: file_watermark,
options: "-filter_complex \"overlay=5:5\" -c:v mpeg4",
success: function(e) {
// if (e.success) {
// ...
// }
},
error: function(e) {
Ti.API.info("error, success: " + e.success + ', error: ' + e.error);
},
log: function(e) {
Ti.API.info("log: " + e.message);
},
progress: function(e) {
Ti.API.info("progress, frame: " + e.frame + ", size: " + e.size + ", currentTime: " + e.currentTime);
}
})

```


## Author

- Christian Clare ([@narbs](https://mastodon.social/@narbs) / [@narbs](https://twitter.com/narbs) / [Web](https://www.tambitsoftware.com))

Loading

0 comments on commit 59448d7

Please sign in to comment.