I have just recently found Objective Zip Ihave been reading through the instructions to get it set up in my project. However I am not really sure how to use it to decompress some NSData I have that I am wanting to decompress.
I have looked at the example solution and it seems to be performing the unzip on a zip file the code looks roughly like this
ZipFile *unzipFile= [[ZipFile alloc] initWithFileName:filePath mode:ZipFileModeUnzip];
[unzipFile goToFirstFileInZip];
ZipReadStream *read1= [unzipFile readCurrentFileInZip];
give or take some other instructions this is how they show you to use it, their sample code is here
I would like to know how to do the same thing but using NSData? or would I have to convert the NSData into a zipFile? if so how is that performed properly?
The NSData I am trying to unzip if zlib compressed... any example code would be helpful
here it is https://stackoverflow.com/a/6466832/751885
I use the following two methods process NSData
and call
saveToFile
method write on disk.
[[self compressData:uncompressedData] writeToFile:#"fileName.zip" atomically:YES];
Compress:
-(NSData*) compressData:(NSData* )uncompressedData {
if ([uncompressedData length] == 0) return uncompressedData;
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.total_out = 0;
strm.next_in=(Bytef *)[uncompressedData bytes];
strm.avail_in = (unsigned int)[uncompressedData length];
// Compresssion Levels:
// Z_NO_COMPRESSION
// Z_BEST_SPEED
// Z_BEST_COMPRESSION
// Z_DEFAULT_COMPRESSION
if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY) != Z_OK) return nil;
NSMutableData *compressed = [NSMutableData dataWithLength:16384]; // 16K chunks for expansion
do {
if (strm.total_out >= [compressed length])
[compressed increaseLengthBy: 16384];
strm.next_out = [compressed mutableBytes] + strm.total_out;
strm.avail_out = (unsigned int)([compressed length] - strm.total_out);
deflate(&strm, Z_FINISH);
} while (strm.avail_out == 0);
deflateEnd(&strm);
[compressed setLength: strm.total_out];
return [NSData dataWithData:compressed];
}
Uncompress:
-(NSData*) uncompressGZip:(NSData*) compressedData {
if ([compressedData length] == 0) return compressedData;
NSUInteger full_length = [compressedData length];
NSUInteger half_length = [compressedData length] / 2;
NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef *)[compressedData bytes];
strm.avail_in = (unsigned int)[compressedData length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit2(&strm, (15+32)) != Z_OK) return nil;
while (!done) {
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length]) {
[decompressed increaseLengthBy: half_length];
}
strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = (unsigned int)([decompressed length] - strm.total_out);
// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END) {
done = YES;
} else if (status != Z_OK) {
break;
}
}
if (inflateEnd (&strm) != Z_OK) return nil;
// Set real length.
if (done) {
[decompressed setLength: strm.total_out];
return [NSData dataWithData: decompressed];
} else {
return nil;
}
}
Related
I am unable to unzip a zip file saved in Sqlite As Blob , after i query the db it returns me a NSData . I tried the inflate method but it returns this error "Decompression of data failed with code -3". Any opinions??
Thanks !
This is the code
NSData *content = [[NSData alloc] initWithBytes:sqlite3_column_blob(statement, 3) length:sqlite3_column_bytes(statement, 3)];
content = [NSData gzipInflate:content];
/// Ns data category
#implementation NSData (Unzip)
+ (NSData *)gzipInflate:(NSData*)data
{
if ([data length] == 0) return data;
unsigned full_length = [data length];
unsigned half_length = [data length] / 2;
NSMutableData *decompressed = [NSMutableData dataWithLength: full_length + half_length];
BOOL done = NO;
int status;
z_stream strm;
strm.next_in = (Bytef *)[data bytes];
strm.avail_in = [data length];
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
if (inflateInit2(&strm, (15+32)) != Z_OK) return nil;
while (!done)
{
// Make sure we have enough room and reset the lengths.
if (strm.total_out >= [decompressed length])
[decompressed increaseLengthBy: half_length];
// strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = [decompressed length] - strm.total_out;
// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);
if (status == Z_STREAM_END) done = YES;
else if (status != Z_OK) break;
}
if (inflateEnd (&strm) != Z_OK) return nil;
// Set real length.
if (done)
{
[decompressed setLength: strm.total_out];
return [NSData dataWithData: decompressed];
}
else return nil;
}
#end
Can anyone provide me a tutorial / documentation on compressing and decompressing strings in memory in objective-c (for iPhone development).
I am looking at Objective-Zip, but it only seems to work by writing the compressed data to a file.
give you an example
#interface NSString (Gzip)
- (NSData *)compress;
#end
#implementation NSString (Gzip)
- (NSData *)compress
{
size_t len = [self length];
size_t bufLen = (len + 12) * 1.001;
u_char *buf = (u_char *)malloc(bufLen);
if (buf == NULL) {
NSLog(#"malloc error");
return nil;
}
int err = compress(buf, &bufLen, (u_char *)[[self dataUsingEncoding:NSUTF8StringEncoding] bytes], len);
if (err != Z_OK) {
NSLog(#"compress error");
free(buf);
return nil;
}
NSData *rtn = [[[NSData alloc] initWithBytes:buf length:bufLen] autorelease];
free(buf);
return rtn;
}
#end
I'm successfully sending int and CGPoint data in the struct of the GKTank example, BUT I tried desperately to send a NSString. I also tried it with char instead of NSString with no success...
Here's the code for
sending:
NSString *playerName = #"My Name";
NSData *dat = [playerName dataUsingEncoding:NSUTF8StringEncoding];
[BTINconnector sendNetworkPacketwithPacketID:NETWORK_PLAYERNAME withData:dat ofLength:sizeof(dat) reliable:YES];
the method (known from GKTank, modified)
- (void)sendNetworkPacketwithPacketID:(int)packetID withData:(void *)data ofLength:(int)length reliable:(BOOL)howtosend {
//NSLog(#"bmpc:ok 3 send packet to %i",gamePeerId);
// the packet we'll send is resued
static unsigned char networkPacket[kMaxPacketSize];
const unsigned int packetHeaderSize = 2 * sizeof(int); // we have two "ints" for our header
if(length < (kMaxPacketSize - packetHeaderSize)) { // our networkPacket buffer size minus the size of the header info
int *pIntData = (int *)&networkPacket[0];
// header info
pIntData[0] = gamePacketNumber++;
pIntData[1] = packetID;
// copy data in after the header
memcpy( &networkPacket[packetHeaderSize], data, length );
NSData *packet = [NSData dataWithBytes: networkPacket length: (length+8)];
if(howtosend == YES) {
[mySession sendData:packet toPeers:[NSArray arrayWithObject:gamePeerId] withDataMode:GKSendDataReliable error:nil];
} else {
[mySession sendData:packet toPeers:[NSArrayarrayWithObject:gamePeerId] withDataMode:GKSendDataUnreliable error:nil];
}
}
}
and the method for receiving (from GKTank modified by me)
- (void)receiveDataDG:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession *)session context:(void *)context {
static int lastPacketTime = -1;
unsigned char *incomingPacket = (unsigned char *)[data bytes];
int *pIntData = (int *)&incomingPacket[0];
NSData* nData = (NSData*)&incomingPacket[0];
//NSData* bData = nData[8];
//
// developer check the network time and make sure packers are in order
//
int packetTime = pIntData[0];
int packetID = pIntData[1];
if(packetTime < lastPacketTime && packetID != NETWORK_COINTOSS) {
NSLog(#"bmc: EXITED");
return;
}
lastPacketTime = packetTime;
switch( packetID ) {
case NETWORK_PLAYERNAME:
{
NSData *hjk = [NSData dataWithBytes:&pIntData[2] length:sizeof(int)];
NSString *gotitb = [[NSString alloc] initWithData:hjk encoding:NSUTF8StringEncoding];
NSLog(#"bmc:str %#,%#",gotitb,gotitb);
…
The gotitb returns null, but I don't know why. Please help.
You don't want sizeof(dat) you want [dat length].
I'm trying to cache some images in sqlite as nsdata and I'm having an issue when I attempt to insert the byte array using sqlite3_exec and a raw SQL string (as NSString)
NSData *imgData = UIImagePNGRepresentation(img);
NSString* sql = [NSString stringWithFormat:#"INSERT INTO persistedimg (imgx,idvalx) VALUES (%#,'%#')", imgData, idValue];
rc = sqlite3_exec(db, [sql UTF8String], callbackFunction, (void*)contextObject, &zErrMsg);
But the problem with the above is I'm adding NSData to the sql string directly instead of the bytes.
I wanted to do something like this
... [imgData bytes], [imgData length]
But because I'm not using the typical "_bind_blob" like approach I'm not sure how to do it w/ a raw string
Update
I'm using a wrapper that I'd like to stick w/ and simply write a new method to support image insert / query commands
the below is my entire wrapper class so far
**
#import "SQLiteAccess.h"
#import <sqlite3.h>
#implementation SQLiteAccess
+ (NSString *)pathToDB {
NSString *dbName = #"test123";
NSString *originalDBPath = [[NSBundle mainBundle] pathForResource:dbName ofType:#"db"];
NSString *path = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *appSupportDir = [paths objectAtIndex:0];
NSString *dbNameDir = [NSString stringWithFormat:#"%#/test123", appSupportDir];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL isDir = NO;
BOOL dirExists = [fileManager fileExistsAtPath:dbNameDir isDirectory:&isDir];
NSString *dbPath = [NSString stringWithFormat:#"%#/%#.db", dbNameDir, dbName];
if(dirExists && isDir) {
BOOL dbExists = [fileManager fileExistsAtPath:dbPath];
if(!dbExists) {
NSError *error = nil;
BOOL success = [fileManager copyItemAtPath:originalDBPath toPath:dbPath error:&error];
if(!success) {
NSLog(#"error = %#", error);
} else {
path = dbPath;
}
} else {
path = dbPath;
}
} else if(!dirExists) {
NSError *error = nil;
BOOL success =[fileManager createDirectoryAtPath:dbNameDir attributes:nil];
if(!success) {
NSLog(#"failed to create dir");
}
success = [fileManager copyItemAtPath:originalDBPath toPath:dbPath error:&error];
if(!success) {
NSLog(#"error = %#", error);
} else {
path = dbPath;
}
}
return path;
}
+ (NSNumber *)executeSQL:(NSString *)sql withCallback:(void *)callbackFunction context:(id)contextObject {
NSString *path = [self pathToDB];
sqlite3 *db = NULL;
int rc = SQLITE_OK;
NSInteger lastRowId = 0;
rc = sqlite3_open([path UTF8String], &db);
if(SQLITE_OK != rc) {
NSLog(#"Can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return nil;
} else {
char *zErrMsg = NULL;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
rc = sqlite3_exec(db, [sql UTF8String], callbackFunction, (void*)contextObject, &zErrMsg);
if(SQLITE_OK != rc) {
NSLog(#"Can't run query '%#' error message: %s\n", sql, sqlite3_errmsg(db));
sqlite3_free(zErrMsg);
}
lastRowId = sqlite3_last_insert_rowid(db);
sqlite3_close(db);
[pool release];
}
NSNumber *lastInsertRowId = nil;
if(0 != lastRowId) {
lastInsertRowId = [NSNumber numberWithInteger:lastRowId];
}
return lastInsertRowId;
}
static int singleRowCallback(void *queryValuesVP, int columnCount, char **values, char **columnNames) {
NSMutableDictionary *queryValues = (NSMutableDictionary *)queryValuesVP;
int i;
for(i=0; i<columnCount; i++) {
[queryValues setObject:values[i] ? [NSString stringWithFormat:#"%s",values[i]] : [NSNull null]
forKey:[NSString stringWithFormat:#"%s", columnNames[i]]];
}
return 0;
}
+ (NSString *)selectOneValueSQL:(NSString *)sql {
NSMutableDictionary *queryValues = [NSMutableDictionary dictionary];
[self executeSQL:sql withCallback:singleRowCallback context:queryValues];
NSString *value = nil;
if([queryValues count] == 1) {
value = [[queryValues objectEnumerator] nextObject];
}
return value;
}
+ (NSNumber *)insertWithSQL:(NSString *)sql {
sql = [NSString stringWithFormat:#"BEGIN TRANSACTION; %#; COMMIT TRANSACTION;", sql];
return [self executeSQL:sql withCallback:NULL context:NULL];
}
+ (void)updateWithSQL:(NSString *)sql {
sql = [NSString stringWithFormat:#"BEGIN TRANSACTION; %#; COMMIT TRANSACTION;", sql];
[self executeSQL:sql withCallback:NULL context:nil];
}
#end
**
Any help with this solution would be huge!
I think a large part of the issue you are running into here is that you are trying to simplify the SQLite3 APIs too much. The APIs are not just for executing textual SQL queries; prepared statements and bind parameters exist for a reason. You shouldn't be trying to insert binary data in a string. That's just asking for problems, especially if your binary data has nulls in it.
To insert blobs, you really do need to use sqlite3_bind_blob with sqlite3_prepare_v2. When you bind the blob, you will need to also use [imgData bytes] as the blob data.
Are you perhaps looking for help reconstructing your API to make this sort of thing easier for this particular image caching use case?
Edit
Here's a simple example using bind to insert binary data. Assume there is a table called my_table with 2 columns: name of type VARCHAR and data of type BLOB. Please note that I have not tested or even tried compiling this, so there may be typos or errors.
sqlite3 *database;
// Open a connection to the database given its file path.
if (sqlite3_open("/path/to/sqlite/database.sqlite3", &database) != SQLITE_OK) {
// error handling...
}
// Construct the query and empty prepared statement.
const char *sql = "INSERT INTO `my_table` (`name`, `data`) VALUES (?, ?)";
sqlite3_stmt *statement;
// Prepare the data to bind.
NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:#"something"]);
NSString *nameParam = #"Some name";
// Prepare the statement.
if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
// Bind the parameters (note that these use a 1-based index, not 0).
sqlite3_bind_text(statement, 1, nameParam);
sqlite3_bind_blob(statement, 2, [imageData bytes], [imageData length], SQLITE_STATIC);
// SQLITE_STATIC tells SQLite that it doesn't have to worry about freeing the binary data.
}
// Execute the statement.
if (sqlite3_step(statement) != SQLITE_DONE) {
// error handling...
}
// Clean up and delete the resources used by the prepared statement.
sqlite3_finalize(statement);
// Now let's try to query! Just select the data column.
const char *selectSql = "SELECT `data` FROM `my_table` WHERE `name` = ?";
sqlite3_stmt *selectStatement;
if (sqlite3_prepare_v2(database, selectSql, -1, &selectStatement, NULL) == SQLITE_OK) {
// Bind the name parameter.
sqlite3_bind_text(selectStatement, 1, nameParam);
}
// Execute the statement and iterate over all the resulting rows.
while (sqlite3_step(selectStatement) == SQLITE_ROW) {
// We got a row back. Let's extract that BLOB.
// Notice the columns have 0-based indices here.
const void *blobBytes = sqlite3_column_blob(selectStatement, 0);
int blobBytesLength = sqlite3_column_bytes(selectStatement, 0); // Count the number of bytes in the BLOB.
NSData *blobData = [NSData dataWithBytes:blobBytes length:blobBytesLength];
NSLog("Here's that data!\n%#", blobData);
}
// Clean up the select statement
sqlite3_finalize(selectStatement);
// Close the connection to the database.
sqlite3_close(database);
I want to implement a feature that lets the user trim an audio file (.caf) which he perviously recorded. The recording part already works, but how can i add a trimming feature similar to the one in the Voicememos app. Is there an api for the audio trimmer apple uses?
Any help would be great...
How about using the AVFoundation? Import the audio file into an AVAsset (composition etc), then you can export it - setting preferred time + duration - to a file.
I wrote a stock function awhile ago that exports an asset to a file, you can also specify an audiomix. As below it exports all of the file, but you could add a NSTimeRange to exporter.timeRange and there you go. I haven't tested that though, but should work(?). Another alternative could be to adjust time ranges when creating the AVAsset + tracks. Of course the exporter only handles m4a (AAC). Sorry if this wasn't what you wanted.
-(void)exportAsset:(AVAsset*)asset toFile:(NSString*)filename overwrite:(BOOL)overwrite withMix:(AVAudioMix*)mix {
//NSArray* availablePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:asset];
AVAssetExportSession* exporter = [AVAssetExportSession exportSessionWithAsset:asset presetName:AVAssetExportPresetAppleM4A];
if (exporter == nil) {
DLog(#"Failed creating exporter!");
return;
}
DLog(#"Created exporter! %#", exporter);
// Set output file type
DLog(#"Supported file types: %#", exporter.supportedFileTypes);
for (NSString* filetype in exporter.supportedFileTypes) {
if ([filetype isEqualToString:AVFileTypeAppleM4A]) {
exporter.outputFileType = AVFileTypeAppleM4A;
break;
}
}
if (exporter.outputFileType == nil) {
DLog(#"Needed output file type not found? (%#)", AVFileTypeAppleM4A);
return;
}
// Set outputURL
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString* parentDir = [NSString stringWithFormat:#"%#/", [paths objectAtIndex:0]];
NSString* outPath = [NSString stringWithFormat:#"%#%#", parentDir, filename];
NSFileManager* manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:outPath]) {
DLog(#"%# already exists!", outPath);
if (!overwrite) {
DLog(#"Not overwriting, uh oh!");
return;
}
else {
// Overwrite
DLog(#"Overwrite! (delete first)");
NSError* error = nil;
if (![manager removeItemAtPath:outPath error:&error]) {
DLog(#"Failed removing %#, error: %#", outPath, error.description);
return;
}
else {
DLog(#"Removed %#", outPath);
}
}
}
NSURL* const outUrl = [NSURL fileURLWithPath:outPath];
exporter.outputURL = outUrl;
// Specify a time range in case only part of file should be exported
//exporter.timeRange =
if (mix != nil)
exporter.audioMix = mix; // important
DLog(#"Starting export! (%#)", exporter.outputURL);
[exporter exportAsynchronouslyWithCompletionHandler:^(void) {
// Export ended for some reason. Check in status
NSString* message;
switch (exporter.status) {
case AVAssetExportSessionStatusFailed:
message = [NSString stringWithFormat:#"Export failed. Error: %#", exporter.error.description];
DLog(#"%#", message);
[self showAlert:message];
break;
case AVAssetExportSessionStatusCompleted: {
/*if (playfileWhenExportFinished) {
DLog(#"playfileWhenExportFinished!");
[self playfileAfterExport:exporter.outputURL];
playfileWhenExportFinished = NO;
}*/
message = [NSString stringWithFormat:#"Export completed: %#", filename];
DLog(#"%#", message);
[self showAlert:message];
break;
}
case AVAssetExportSessionStatusCancelled:
message = [NSString stringWithFormat:#"Export cancelled!"];
DLog(#"%#", message);
[self showAlert:message];
break;
default:
DLog(#"Export unhandled status: %d", exporter.status);
break;
}
}];
}
The above answer of #Jonny is correct. Here's I'm adding the use of AudioMixer to add the Fade-in effect while audio trimming.
Output: Audio asset trimmed to 20 seconds with a 10 second fade in.
The trim being set up in the code snippet takes place at the 30 second
mark of the asset and therefore the track duration should be at least
50 seconds.
- (BOOL)exportAssettoFilePath:(NSString *)filePath {
NSString *inputFilePath = <inputFilePath>;
NSURL *videoToTrimURL = [NSURL fileURLWithPath:inputFilePath];
AVAsset *avAsset = [AVAsset assetWithURL:videoToTrimURL];
// we need the audio asset to be at least 50 seconds long for this snippet
CMTime assetTime = [avAsset duration];
Float64 duration = CMTimeGetSeconds(assetTime);
if (duration < 50.0) return NO;
// get the first audio track
NSArray *tracks = [avAsset tracksWithMediaType:AVMediaTypeAudio];
if ([tracks count] == 0) return NO;
AVAssetTrack *track = [tracks objectAtIndex:0];
// create the export session
// no need for a retain here, the session will be retained by the
// completion handler since it is referenced there
AVAssetExportSession *exportSession = [AVAssetExportSession
exportSessionWithAsset:avAsset
presetName:AVAssetExportPresetAppleM4A];
if (nil == exportSession) return NO;
// create trim time range - 20 seconds starting from 30 seconds into the asset
CMTime startTime = CMTimeMake(30, 1);
CMTime stopTime = CMTimeMake(50, 1);
CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
// create fade in time range - 10 seconds starting at the beginning of trimmed asset
CMTime startFadeInTime = startTime;
CMTime endFadeInTime = CMTimeMake(40, 1);
CMTimeRange fadeInTimeRange = CMTimeRangeFromTimeToTime(startFadeInTime,
endFadeInTime);
// setup audio mix
AVMutableAudioMix *exportAudioMix = [AVMutableAudioMix audioMix];
AVMutableAudioMixInputParameters *exportAudioMixInputParameters =
[AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:track];
[exportAudioMixInputParameters setVolumeRampFromStartVolume:0.0 toEndVolume:1.0
timeRange:fadeInTimeRange];
exportAudioMix.inputParameters = [NSArray
arrayWithObject:exportAudioMixInputParameters];
// configure export session output with all our parameters
exportSession.outputURL = [NSURL fileURLWithPath:filePath]; // output path
exportSession.outputFileType = AVFileTypeAppleM4A; // output file type
exportSession.timeRange = exportTimeRange; // trim time range
//exportSession.audioMix = exportAudioMix; // fade in audio mix
// perform the export
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (AVAssetExportSessionStatusCompleted == exportSession.status) {
NSLog(#"AVAssetExportSessionStatusCompleted");
} else if (AVAssetExportSessionStatusFailed == exportSession.status) {
// a failure may happen because of an event out of your control
// for example, an interruption like a phone call comming in
// make sure and handle this case appropriately
NSLog(#"AVAssetExportSessionStatusFailed");
} else {
NSLog(#"Export Session Status: %ld", (long)exportSession.status);
}
}];
return YES;}
Thanks
For More Details :
https://developer.apple.com/library/ios/qa/qa1730/_index.html