forked from m1ga/ti.ffmpeg
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request m1ga#1 from narbs/main
Implement compatible iOS version of ti.ffmpeg using same interface as Android version
- Loading branch information
Showing
706 changed files
with
130,140 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
.DS_Store | ||
Build | ||
DerivedData | ||
dist | ||
xcuserdata |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: ¶graphStart end: ¶graphEnd | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
|
Oops, something went wrong.