Incorrect write EXIF metadata to image - ios

I try put few exif parameters to image, but only few of them are visible.
When check information about exifDictionary all parametras has a value
[1]: https://i.stack.imgur.com/4OirK.png
But when check this file in exiftool, BodySerialNumber are valid, FileSource is incorrect value, and DateTimeOriginal is not visible, really don't understand what's going on
[2]: https://i.stack.imgur.com/mf4l4.png
This is my code, where i try to save meta to file
static func encodeImage(at url: URL, file: File, completionHandler: ((URL?) -> Void)?) {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
let filePath = documentsDirectory.appendingPathComponent("\(UUID().uuidString)\(file.fileExtension ?? ".jpg")")
guard let data = NSData(contentsOf: url),
let src = CGImageSourceCreateWithData(data, nil),
let uti = CGImageSourceGetType(src),
let cfPath = CFURLCreateWithFileSystemPath(nil, filePath.path as CFString, CFURLPathStyle.cfurlposixPathStyle, false),
let dest = CGImageDestinationCreateWithURL(cfPath, uti, 1, nil)
else {
completionHandler?(nil)
return
}
let exifProperties = [
kCGImagePropertyExifBodySerialNumber as String: file.device ?? "0",
kCGImagePropertyExifFileSource as String: file.deviceUrl ?? file.path,
kCGImagePropertyExifDateTimeOriginal as String: "\(file.createdAt.milliseconds)"
] as CFDictionary
let exifDictionary = [
kCGImagePropertyExifDictionary as String: exifProperties
] as CFDictionary
CGImageDestinationAddImageFromSource(dest, src, .zero, exifDictionary)
if CGImageDestinationFinalize(dest) {
completionHandler?(filePath)
} else {
completionHandler?(nil)
}
}
How can I fix it ?

Community wiki
The main problem because in Java / Kotlin, they can programatically write what they want in meta keys, maybe they use another version of exif, I really don't know about it, but in Swift you can use special format to key. All keys described in https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
And when I try to write String in kCGImagePropertyExifFileSource field I have 0, because this field must outcome integer value 1 ... 3
The same problem with kCGImagePropertyExifDateTimeOriginal here not only a String must be written, but also a special format YYYY: MM: DD HH: MM: SS, other formats cannot be written into this field !!!
What I mean, read about field formats and types!)

Related

Get correct parameter value from URL in "/:id=" format using regex

What is the best way to get the id value from this url:
URL(string: "urlScheme://search/:id=0001")
I've been trying to route this URL using a deep link request. However, my url routing solution JLRoutes shows the parameters as key = id and value = :id=0001.
I instead need the parameters to be key = id and value = "0001".
In an ideal world I would just be using a URL string like "urlScheme://search/0001" and not have any problem but the ":id=" part has to be in there. George's comment about converting the parameter to a URL in of itself and using .pathComponents.last does work, but I think a regex solution is probably going to scale better going forward.
The answer from #George should work fine, but two things struck me: you decided you wanted a regex solution, and to make this generic seemed to be asking for a recursive solution.
The below approach uses regex to identify up to the last /: delimiter, then has to do a bit of inelegant string handling to split it into the base string and the final pair of (key: value) params. I'd hoped to be able to write a regex that just matches those final parameters as that would be a far cleaner range to work with, but haven't managed it yet!
func paramsFrom(_ str: String) -> [String: String] {
guard let baseRange = str.range(of:#"^.+\/:"#, options: .regularExpression ) else { return [:] }
let base = String(str[baseRange].dropLast(2))
let params = str.replacingCharacters(in: baseRange, with: "").components(separatedBy: "=")
return [params.first! : params.last!].merging(paramsFrom(base)){(current, _) in current}
}
using this on your example string returns:
["id": "0001", "title": "256", "count": "100"]
EDIT:
Managed to dig out the old regex brain cells and match just the final pair of parameters. You could adapt the above to use the regex
(?<=\/:)[a-zA-Z0-9=]+$
and the have slightly cleaner string handling as the shortened base string becomes
String(str.dropLast(str[paramsRange].count))
If your URL is in the form of an actual URL query, e.g. urlScheme://search?id=0001, there is a nice way to do this.
With thanks to vadian, this is really simple. You can just do the following:
let components = URLComponents(string: "urlScheme://search?id=0001&a=2")!
let dict = components.queryItems?.reduce(into: [:]) { partialResult, queryItem in
partialResult[queryItem.name] = queryItem.value
}
Or a slightly more compact version for dict:
let dict = components.queryItems?.reduce(into: [:], { $0[$1.name] = $1.value })
Result from given input:
["id": "0001", "a": "2"]
If you must use the current URL form
You can replace the URL string, such as:
let urlStr = "urlScheme://search/:id=0001/:a=2"
let comps = urlStr.components(separatedBy: "/:")
let newUrl: String
if comps.count > 1 {
newUrl = "\(comps.first!)?\(comps.dropFirst().joined(separator: "&"))"
} else {
newUrl = urlStr
}
print(newUrl)
Prints: urlScheme://search?id=0001&a=2
Original answer (slightly modified)
If you have a URL with queries separated by /: you can use the following:
// Example with multiple queries
let url = URL(string: "urlScheme://search/:id=0001/:a=2")!
let queries = url.lastPathComponent.dropFirst().split(separator: "/:")
var dict = [String: String]()
for query in queries {
let splitQuery = query.split(separator: "=")
guard splitQuery.count == 2 else { continue }
let key = String(splitQuery.first!)
let value = String(splitQuery[1])
dict[key] = value
}
print(dict)
Result is same as before.
You can use next regex approach to enumerate parameters in your url path:
let urlString = "urlScheme://search/:id=0001" as NSString
let regex = try! NSRegularExpression(pattern: "([^:\\/]+)=([0-9]+)")
if let match = regex.matches(in: urlString as String, options: [], range: NSMakeRange(0, urlString.length)).first, match.numberOfRanges == 3 {
let key = urlString.substring(with: match.range(at: 1))
let value = urlString.substring(with: match.range(at: 2))
print(key, ":", value)
}
// Prints
id : 0001

Swift 4 How do I write/read dictionaries to/from the document directory?

I have encryptions in the form of dictionaries that I want to save to the document directory. I also want to be able to retrieve these dictionaries from the document directory to decrypt within the app. How can I write/read dictionaries to/from the document directory?
Dictionary has its own write method which writes a property list representation of the contents of the dictionary to a given URL. You can do it using below code:
Write
// Get application document directory path array
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
let fileName = "users"
if let documentPath = paths.first {
let filePath = NSMutableString(string: documentPath).appendingPathComponent(fileName)
let URL = NSURL.fileURL(withPath: filePath)
let dictionary = NSMutableDictionary(capacity: 0)
dictionary.setValue("valu1", forKey: "key1")
dictionary.setValue("valu2", forKey: "key2")
let success = dictionary.write(to: URL, atomically: true)
print("write: ", success)
}
Read
if let dictionary = NSMutableDictionary(contentsOf: URL){
print(dictionary)
}
Hope, it will work.
Steps include
Get Document URL
Write Date to File
Saving to Disk
Make use of syntax from here:
https://stackoverflow.com/a/26557965/6342609

Put a string before a certain string inside a text in Swift 3

I'm dealing with an issue here, I have a URL of an image which is like this
http://example.com/image/test.jpg
Which is a string.
And I would like to insert before .jpg a certain text like -40x40
Is there any way to analyze the URL string and somehow to add this text so the final string should be
http://example.com/image/text-40x40.jpg
What i've tried till now is this
var finalImage = "http://example.com/image/test.jpg"
finalImage.insert("-40x40" as Character, at: finalImage.endIndex - 4)
but i get 2 errors.
1) i cant add more than 1 character and 2) i cant do the math ad endIndex.
But i can't add more than one character there.
Thanks a lot!
Try this. It uses NSURL, which exists so that path manipulations are easy and legal! The documentation is really quite good.
let s1 = "http://example.com/image/test.jpg"
let u = URL(fileURLWithPath: s1)
let exExt = u.deletingPathExtension()
let s2 = exExt.absoluteString + "-40x40.jpg"
Another ways.
The code shown in the question, fixed:
var finalImage = "http://example.com/image/test.jpg"
let extIndex = finalImage.index(finalImage.endIndex, offsetBy: -4)
finalImage.insert(contentsOf: "-40x40".characters, at: extIndex)
Using NSRegularExpression:
let origImage = "http://example.com/image/test.jpg"
let regex = try! NSRegularExpression(pattern: "(\\.jpg)$", options: .caseInsensitive)
let finalImage = regex.stringByReplacingMatches(in: origImage, range: NSRange(0..<origImage.utf16.count), withTemplate: "-40x40$0")
As a complement to the neat accepted answer by #Grimxn: Foundation's URL has various more methods that allows for more separation "of concerns" in case you'd like to apply some more complex modification of the image (file) name, while not really worrying about the image (file) name extension.
let s1 = "http://example.com/something.cgi/image/test.jpg"
let u = URL(fileURLWithPath: s1)
// separate into (String) components of interest
let prefixUrl = u.deletingLastPathComponent().absoluteString
// "http://example.com/something.cgi/image/"
let fileName = u.deletingPathExtension().lastPathComponent
// "test"
let fileExtension = "." + u.pathExtension
// ".jpg"
// ... some methods that implements your possibly more
// complex filename modification
func modify(fileName fName: String) -> String {
// ...
return fName + "-40x40"
}
// reconstruct url with modified filename
let s2 = prefixUrl + modify(fileName: fileName) + fileExtension
print(s2) // http:/example.com/something.cgi/image/test-40x40.jpg
Grimxn's solution is probably the best fit for this problem, but for more complex manipulation of URLs, take a look at the NSURLComponents class. You can convert an NSURL to NSURLComponents, then use the various methods of NSURLComponents to manipulate your URL, and then finally convert the NSURLComponents back to an NSURL
As noted in a comment by #dfri, Swift 3 (and later) includes a native URLComponents class, which follows Swift naming and calling conventions. Going forward you should use that instead of the Objective-C/Swift 2 NSURLComponents class.
if let url = NSURL(string: "http://example.com/image/test.jpg"),withoutExt = url.URLByDeletingPathExtension
{
let finalstring : NSString = withoutExt.absoluteString + "-40x40.jpg"
print(finalstring)
}
Very simply with one line...
let newURL = oldURL.stringByReplacingOccurrencesOfString(".jpg", withString: "-40x40.jpg", options: NSStringCompareOptions.LiteralSearch, range: nil)

read file content and get the values back

I have an function to write some numbers to a file
fun writeNumToFile -> Void {
//get Documents’ path
let docPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last as? String
let filePath = docPath.stringByAppendingPathComponent(“myFlie.txt”)
//the count is NOT the count of elements in the array below.
//think it as an independent constant.
let count = 10
//Write count to file
String(count).writeToFile(filePath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
//Write an array of numbers to file
for idx in [1,2,3] {
String(idx as! String).writeToFile(filePath, atomically: false, encoding: NSUTF8StringEncoding, error: nil);
}
}
Now I want to read the numbers back from file, I know I can read the content of file by:
let fileContent = String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: nil)
but how can I get count & [1,2,3] array back once I get the content?
You are writing code as if you are using low-level file i/o. You're not. The writeToFile:atomically: methods that you are using overwrite a file with new contents, not append data to an existing file. your second write deletes the contents of your first write.
NSArray supports the writeToFile:atomically: method, and a [String] array should be inter-operable with NSArray.
You should be able to simply say:
let array = [1, 2, 3]
let ok = array .writeToFile(filePath, atomically: false)
Then later,
let array = NSArray.contentsOfFile(filePath)
I say "should be able to" because I am still learning the subtleties of interaction between Swift and the Foundation classes.
EDIT:
If you need to save multiple discrete things into a file, create a dictionary:
let someValue = 42
let anArray = [1, 2, 3]
let aDictionary = [
"someValue": someValue,
"array": anArray]
let ok = aDictionary.writeToFile(filePath, atomically: false)
and to read it:
let aDictionary = NSDictionary(contentsOfFile: filePath)
let someValue = aDictionary["someValue"] as! Int
let anArray = aDictionary["array"] as! [Int]
There is no need to save the number of items in the array separately. The array is able to reconstitute itself from the file contents, including the correct count of elements.
EDIT #2:
Note that iOS includes the C file i/o library, which should be callable from Swift. If you are glutton for punishment you could do what you are trying to do using fopen(), fseek(), fwrite(), etc. (But don't. It's much more work, much more error-prone, and a non-standard way of doing it in iOS or Mac OS.)

How to get the filename from the filepath in swift

How to get the filename from the given file path string?
For example if I have a filepath string as
file:///Users/DeveloperTeam/Library/Developer/CoreSimulator/Devices/F33222DF-D8F0-448B-A127-C5B03C64D0DC/data/Containers/Data/Application/5D0A6264-6007-4E69-A63B-D77868EA1807/tmp/trim.D152E6EA-D19D-4E3F-8110-6EACB2833CE3.MOV
and I would like to get the return result as
trim.D152E6EA-D19D-4E3F-8110-6EACB2833CE3.MOV
Objective C
NSString* theFileName = [string lastPathComponent]
Swift
let theFileName = (string as NSString).lastPathComponent
SWIFT 3.x or SWIFT 4:
Shortest and cleanest way of doing this is below. In this example url variable is type of URL in this way we can have a human readable String result of the full file name with extension like My file name.txt and Not like My%20file%20name.txt
// Result like: My file name.txt
let fileName = url.lastPathComponent
If you want to get the current file name such as for logging purposes, I use this.
Swift 4
URL(fileURLWithPath: #file).lastPathComponent
Swift 2:
var file_name = NSURL(fileURLWithPath: path_to_file).lastPathComponent!
let theURL = URL(string: "yourURL/somePDF.pdf") //use your URL
let fileNameWithExt = theURL?.lastPathComponent //somePDF.pdf
let fileNameLessExt = theURL?.deletingPathExtension().lastPathComponent //somePDF
In order for this to work your url must be of type URL not a string so don't convert it to a string before hand.
You can copy and paste this code directly into playground to see how it works.
Try this
let filename: String = "your file name"
let pathExtention = filename.pathExtension
let pathPrefix = filename.stringByDeletingPathExtension
Updated :
extension String {
var fileURL: URL {
return URL(fileURLWithPath: self)
}
var pathExtension: String {
return fileURL.pathExtension
}
var lastPathComponent: String {
return fileURL.lastPathComponent
}
}
Hope it helps.
Below code is working for me in Swift 4.X
let filename = (self.pdfURL as NSString).lastPathComponent // pdfURL is your file url
let fileExtention = (filename as NSString).pathExtension // get your file extension
let pathPrefix = (filename as NSString).deletingPathExtension // File name without extension
self.lblFileName.text = pathPrefix // Print name on Label
You can pass the url in fileUrl, like I did below:-
let fileUrl: String = "https://www.himgs.com/imagenes/hello/social/hello-fb-logo.png" // Pass the URL
let lastPathComponent = URL.init(string: fileUrl)?.lastPathComponent ?? "" // With this you will get last path component
let fileNameWithExtension = lastPathComponent
//This last path component will provide you file Name with extension.
I've done some performance tests (iOS 14, real device, release configuration):
(#file as NSString).lastPathComponent // The fastest option.
URL(string: #file)!.lastPathComponent // 2.5 times slower than NSString.
#file.components(separatedBy: "/").last! // 7 times slower than NSString.
Bonus:
URL(fileURLWithPath: #file, isDirectory: false).lastPathComponent // About the same as URL(string:).
URL(fileURLWithPath: #file).lastPathComponent // 2.5 times slower than with explicit isDirectory.
Swift 5. This one works faster than both URL and NSString options:
path.components(separatedBy: "/").last
To retrieve filename without its extension from a URL in Swift >= 4.2:
let urlWithoutFileExtension: URL = originalFileUrl.deletingPathExtension()
let fileNameWithoutExtension: String = urlWithoutFileExtension.lastPathComponent
Creates unique "file name" form url including two previous folders
func createFileNameFromURL (colorUrl: URL) -> String {
var arrayFolders = colorUrl.pathComponents
// -3 because last element from url is "file name" and 2 previous are folders on server
let indx = arrayFolders.count - 3
var fileName = ""
switch indx{
case 0...:
fileName = arrayFolders[indx] + arrayFolders[indx+1] + arrayFolders[indx+2]
case -1:
fileName = arrayFolders[indx+1] + arrayFolders[indx+2]
case -2:
fileName = arrayFolders[indx+2]
default:
break
}
return fileName
}

Resources