Swift, how to get an image "where from" metadata field? - swift

I'm reading a file from the file system on macOS, I tried to extract the metadata with the following code:
let imagedata = try? Data(contentsOf: fileUrl)
if imagedata == nil {
print("Could not read contents of image")
return
}
var source: CGImageSource = CGImageSourceCreateWithData((imagedata as! CFMutableData), nil)!
var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [AnyHashable: Any]
print("image metadata", metadata)
And this is the output that I get:
image metadata Optional([AnyHashable("ColorModel"): RGB, AnyHashable("DPIHeight"): 72, AnyHashable("PixelHeight"): 840, AnyHashable("ProfileName"): sRGB IEC61966-2.1, AnyHashable("PixelWidth"): 840, AnyHashable("{PNG}"): {
Chromaticities = (
"0.3127",
"0.329",
"0.64",
"0.33",
"0.3",
"0.6000000000000001",
"0.15",
"0.06"
);
Gamma = "0.45455";
InterlaceType = 0;
XPixelsPerMeter = 2834;
YPixelsPerMeter = 2834;
sRGBIntent = 0;
}, AnyHashable("DPIWidth"): 72, AnyHashable("Depth"): 8])
And that extracts the usual metadata, but I actually would like to know where the image was downloaded from, when I drag n' drop an image into the desktop and then right click and then get info, I see the field:
Is there any way to retrieve this field?
Edit: I tried this:
if let attr = try? FileManager.default.attributesOfItem(atPath: fileUrl.path) {
print("attributes", attr)
}
and now I can see the kMDItemWhereFroms field, but I don't know how to reach it or parse. Maybe someone has an idea here?

I think it's always nice to show how tinker, how to find the data, so I'll show how I got it, while I didn't know at all about it.
First thought, it might not be in image Metadata, but in "File System" ones, so let's look at FileManager. It's in FileManager.attributesOfItem(atPath:)
Step 1:
let attributes = try fileManager.attributesOfItem(atPath: filePath)
This gives me:
[__C.NSFileAttributeKey(_rawValue: NSFileGroupOwnerAccountName): staff,
__C.NSFileAttributeKey(_rawValue: NSFileReferenceCount): 1,
__C.NSFileAttributeKey(_rawValue: NSFileModificationDate): 2020-05-13 15:16:20 +0000,
__C.NSFileAttributeKey(_rawValue: NSFileSystemFileNumber): 8635383780,
__C.NSFileAttributeKey(_rawValue: NSFilePosixPermissions): 420,
__C.NSFileAttributeKey(_rawValue: NSFileType): NSFileTypeRegular,
__C.NSFileAttributeKey(_rawValue: NSFileOwnerAccountID): 501,
__C.NSFileAttributeKey(_rawValue: NSFileGroupOwnerAccountID): 20,
__C.NSFileAttributeKey(_rawValue: NSFileExtensionHidden): 0,
__C.NSFileAttributeKey(_rawValue: NSFileHFSTypeCode): 0,
__C.NSFileAttributeKey(_rawValue: NSFileCreationDate): 2020-05-13 15:16:20 +0000,
__C.NSFileAttributeKey(_rawValue: NSFileOwnerAccountName): Larme,
__C.NSFileAttributeKey(_rawValue: NSFileSize): 1258299,
__C.NSFileAttributeKey(_rawValue: NSFileSystemNumber): 16777220,
__C.NSFileAttributeKey(_rawValue: NSFileExtendedAttributes): {
"com.apple.lastuseddate#PS" = {length = 16, bytes = 0x440fbc5e00000000503f091700000000};
"com.apple.macl" = {length = 72, bytes = 0x02009685 15175d5d 47089537 71b6a786 ... 00000000 00000000 };
"com.apple.metadata:kMDItemDownloadedDate" = {length = 53, bytes = 0x62706c69 73743030 a1013341 c2362362 ... 00000000 00000013 };
"com.apple.metadata:kMDItemWhereFroms" = {length = 414, bytes = 0x62706c69 73743030 a201025f 11013b68 ... 00000000 00000178 };
"com.apple.quarantine" = {length = 57, bytes = 0x30303833 3b356562 63306634 343b5361 ... 44344445 45343845 };
}, __C.NSFileAttributeKey(_rawValue: NSFileHFSCreatorCode): 0]
What to look in this info:
[...
__C.NSFileAttributeKey(_rawValue: NSFileExtendedAttributes): {
...
"com.apple.metadata:kMDItemWhereFroms" = {length = 414, bytes = 0x62706c69 73743030 a201025f 11013b68 ... 00000000 00000178 };
...
]
Ok, so let's continue to step 2, let's dig deeper:
let extendedAttributes = attributes[FileAttributeKey(rawValue: "NSFileExtendedAttributes")] as! [String: Any]
let data = extendedAttributes["com.apple.metadata:kMDItemWhereFroms"] as! Data //that's the new output for `Data` in new systems (iOS13+, etc.)
As always, I tend to do just in case to get more info: let str = String(data: data, encoding: .utf8) which returns nil. Let's remember that not all data is convertible into utf8 string like that. For instance, an image won't make it.
But I know a few tricks, let's do instead: let str = String(data: data, encoding: .ascii), this gives me:
"bplist00¢\u{01}\u{02}_\u{11}\u{01};https://doc-04-6k-docs.googleusercontent.com/docs/securesc/teo5l5s1g996hre86q7qv786r7me56c3/3nneqai4sh4lvk3bdlnpokt8793t4t5t/1589382975000/11847256646728493158/11847256646728493158/0B1U9OypmOvxpODVFb2VGZEdpckk?e=download&authuser=0&nonce=faav7fnl402re&user=11847256646728493158&hash=vl51p8m313rnnao3dleqsl348rp2vo82_\u{10}+https://drive.google.com/drive/u/0/my-drive\0\u{08}\0\u{0B}\u{01}J\0\0\0\0\0\0\u{02}\u{01}\0\0\0\0\0\0\0\u{03}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{01}x"
I downloaded my sample from my Google Drive, info seems making sense. What do see there?: bplist, that's the most important one here.
A PropertyList? Let's decode it then:
let urls = try PropertyListDecoder().decode([String].self, from: data)
It made sense for me to be a [String], it would have been different, that would had be harder. But it's a simple one.
Final code:
static func downloadedFromURLs(for filePath: String) -> [String]? {
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: filePath) else { print("File doesn't exists"); return nil }
do {
let attributes = try fileManager.attributesOfItem(atPath: filePath)
guard let extendedAttributes = attributes[FileAttributeKey(rawValue: "NSFileExtendedAttributes")] as? [String: Any] else {
print("Didn't find NSFileExtendedAttributes value")
return nil
}
guard let data = extendedAttributes["com.apple.metadata:kMDItemWhereFroms"] as? Data else {
print("Didn't find com.apple.metadata:kMDItemWhereFroms value")
return nil
}
let urls = try PropertyListDecoder().decode([String].self, from: data)
return urls
} catch {
print("Error: \(error)")
return nil
}
}
Side note:
There might be some constant instead of writing hard coded strings for "com.apple.metadata:kMDItemWhereFroms"
and NSFileExtendedAttributes, but I didn't see it here.
Some related interested discussion.
https://apple.stackexchange.com/questions/110239/where-is-the-where-from-meta-data-stored-when-downloaded-via-chrome

Related

How to pass list of images as MLMultiArray in coreML Swift?

I have a image classification model, whose input is:
MultiArray (Float32 1 × 224 × 224 × 3)
and output:
MultiArray (Float32)
And I am not able to figure out how to pass the input in the prediction function.
I have a list of images, which i got through this code
let fileList = FileManager.default.enumerator(at: url, includingPropertiesForKeys: keys)
let ar = try? MLMultiArray(shape: [1, 224, 224, 3], dataType: MLMultiArrayDataType.float32)
let model = try Classification(configuration: MLModelConfiguration())
for case let file as URL in fileList {
guard url.startAccessingSecurityScopedResource() else {
continue
}
let image = UIImage(contentsOfFile: file.path)!
// ar?[i] = getArrayOfBytesFromImage(imageData: image.jpegData(compressionQuality: 1)! as NSData)
url.stopAccessingSecurityScopedResource()
}
How can I pass list of images into MLMultiArray?

Swift: NSMutableAttributedString generated from PDF. Can it be parsed into a Dictionary?

I have several large PDF docs (70-200, pages each). The PDFs themselves are generated from HTML pages (I can't get the source code of the HTML pages which is why I am working with the PDFs). Anyway, what I want to do is parse the PDF into separate pages based on the converted H1 tag attribute. When I print out the PDF I get this:
Seller Tag (AST)
{
NSBaselineOffset = 0;
NSColor = "Device RGB colorspace 0.94118 0.32549 0.29804 1";
NSFont = "\"Helvetica 8.00 pt. P [] (0x7ff0f262e590) fobj=0x7ff0f4339680, spc=2.22\"";
}Table of Contents
{
NSBaselineOffset = 0;
NSColor = "Device RGB colorspace 0.94118 0.32549 0.29804 1";
NSFont = "\"Helvetica 34.00 pt. P [] (0x7ff0f262e590) fobj=0x7ff0f432f940, spc=9.45\"";
}...
which looks like a bunch of attributes contained in a Dictionary. But when I run this code:
let strContent = myAppManager.pdfToText(fromPDF:pdfDirPath.absoluteString + "/" + thisFile)
let strPDF:NSAttributedString = strContent
let strNSPDF = strPDF.string as NSString
let rangeOfString = NSMakeRange(0, strNSPDF.length)
let arrAttributes = strPDF.attributes(at: 0, longestEffectiveRange: nil, in: rangeOfString)
print(arrAttributes)
I get this output
[__C.NSAttributedStringKey(_rawValue: NSColor): Device RGB colorspace 0.94118 0.32549 0.29804 1, __C.NSAttributedStringKey(_rawValue: NSBaselineOffset): 0, __C.NSAttributedStringKey(_rawValue: NSFont): "Helvetica 8.00 pt. P [] (0x7ff0f441d490) fobj=0x7ff0f4339680, spc=2.22"]
I was kind of expecting a high number, like 1000 or more entries, not 1.
So snooping around, I know the H1 HTML tag gets converted to this:
Table of Contents
{
NSBaselineOffset = 0;
NSColor = "Device RGB colorspace 0.94118 0.32549 0.29804 1";
NSFont = "\"Helvetica 34.00 pt. P [] (0x7ff0f262e590) fobj=0x7ff0f432f940, spc=9.45\"";
}
So what I am looking to do is delimit the converted H1s so I can get the content between as a page and do stuff with it. Any ideas or suggestions would be appreciated.
Quickly done, assuming you have:
someText[HEADER1]someText1[HEADER2]someText2[HEADER3]someText3...
Where [HEADERN] have the same attributes (and you know them) but not the same as someTextN.
We want in the end, and array of:
struct Page: CustomStringConvertible {
let title: NSAttributedString? //Tha's be the h1 tag content
let content: NSAttributedString?
var description: String {
return "Title: \(title?.string ?? "") - content: \(content?.string ?? "")"
}
}
Initial sample:
let htmlString = "<b>Title 1</b> Text for part one.\n <b>Title 2</b> Text for part two<b>Title 3</b>Text for part three"
let attributedString = try! NSAttributedString(data: Data(htmlString.utf8),
options: [.documentType : NSAttributedString.DocumentType.html],
documentAttributes: nil)
With:
let headerAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.boldSystemFont(ofSize: 12)]
print("headerAttributes: \(headerAttributes)")
func headerOneAttributes(_ headerAttributes: [NSAttributedString.Key: Any], matches attributes: [NSAttributedString.Key: Any]?) -> Bool {
guard let attributes = attributes else { return false }
guard let attributesFont = attributes[.font] as? NSFont, let headerFont = headerAttributes[.font] as? NSFont else {
return false
}
return attributesFont.fontDescriptor.symbolicTraits == NSFontDescriptor.SymbolicTraits(rawValue: 268435458) //Here fonts arent' equal equal, some work here plus checking on other attributes too and font size?
// Do you own check
// return false
}
We can iterates the attributes to get all the headers ranges:
var headerRanges: [NSRange] = []
attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: []) { attributes, range, stop in
if headerOneAttributes(headerAttributes, matches: attributes) {
headerRanges.append(range)
}
}
With an iteration on the ranges:
var pages: [Page] = []
guard !headerRanges.isEmpty else { return }
//In case the first title doesn't "start at the beginning", we have a "content" with no title at start
if let first = headerRanges.first, first.location > 0 {
pages.append(Page(title: nil, content: attributedString.attributedSubstring(from: first)))
}
// Then we iterate
for (anIndex, aRange) in headerRanges.enumerated() {
print(pages)
let title = attributedString.attributedSubstring(from: aRange)
let subtext: NSAttributedString?
// If there is a "nextRange", then we get the end of subtext from it
if anIndex + 1 <= headerRanges.count - 1 {
let next = headerRanges[anIndex + 1]
let location = aRange.location + aRange.length
let length = next.location - location
subtext = attributedString.attributedSubstring(from: NSRange(location: location, length: length))
} else {
//There is no next => Until the end
let location = aRange.location + aRange.length
let length = attributedString.length - location
subtext = attributedString.attributedSubstring(from: NSRange(location: location, length: length))
}
pages.append(Page(title:title, content: subtext))
}
print(pages)
PS: UIFont/NSFont: ~the same, I tested on a macOS app, not iOS, that's why.
Okay, so #Larme put me on the right track for what I was looking for. Posting the code in hopes it helps someone else. I've tested this on a 77 page document and it worked. I should have noted in the question that I am working on MacOS.
func parsePDF(_ strPDFContent:NSMutableAttributedString) -> Array<Dictionary<String, Any>> {
//some initial setup
let strNSPDF = strPDFContent.string as NSString
var arrDocSet:Array<Dictionary<String, Any>> = []
//get all the page headers
var arrRanges = [NSRange]()
strPDFContent.enumerateAttribute(NSAttributedString.Key.font, in: NSRange(0..<strPDFContent.length), options: .longestEffectiveRangeNotRequired) {
value, range, stop in
if let thisFont = value as? NSFont {
if thisFont.pointSize == 34 {
arrRanges.append(range)
}
}
}
//get the content and store data
for (idx, range) in arrRanges.enumerated() {
//get title
let strTitle = String(strNSPDF.substring(with: range))
var textRange = NSRange(location:0, length:0)
//skip opening junk
if !strTitle.contains("Table of Contents\n") {
if idx < arrRanges.count-1 {
textRange = NSRange(location: range.upperBound, length: arrRanges[idx+1].lowerBound - range.upperBound)
} else if idx == arrRanges.count-1 {
textRange = NSRange(location: range.upperBound, length: strNSPDF.length - range.upperBound)
}
let strContent = String(strNSPDF.substring(with: textRange))
arrDocSet.append(["title":strTitle, "content":strContent, "contentRange":textRange, "titleRange":range])
}
}
print(arrDocSet)
return arrDocSet
}
This will output:
["titleRange": {10001, 27}, "title": "Set up Placements with AST\n", "content": "This page contains a sample web page showing how Xandr\'s seller tag (AST) functions can be implemented in the header and body of a sample client page.\nSee AST API Reference for more details on using ...
...
ready.\nExample\n$sf.ext.status();\n", "title": " SafeFrame API Reference\n", "contentRange": {16930, 9841}
Let me know if there's places I could be more efficient.

Can't decompress H.264 frames with VideoToolbox when hardcoding PPS and SPS parameters

I wrote a H.264 decoder that works great. However for performance reasons, I'm trying to hard code in the PPS and SPS parameters (they never change).
For reference this is what they look like as a base 10 [UInt8]:
SPS [103, 66, 0, 40, 244, 5, 1, 236, 128]
PPS [104, 206, 9, 136]
What's bizarre is that when I decode the PPS and SPS parameters from frames that I receive and use those to create the format description and decompression session, it works great; but when I hard code them in, it doesn't work and I get VT error -12909 (bad data) when I try to decompress frames. As far as I can tell, the byte arrays are the exact same.
To hard code them in, I just wrote my createFormatDescription like this:
func createFormatDescription(sps: [UInt8]?, pps: [UInt8]?) -> Bool {
self.formatDesc = nil
let sps = sps ?? [103, 66, 0, 40, 244, 5, 1, 236, 128] as [UInt8]
let pps = pps ?? [104, 206, 9, 136] as [UInt8]
let pointerSPS = UnsafePointer<UInt8>(sps)
let pointerPPS = UnsafePointer<UInt8>(pps)
let dataParamArray = [pointerSPS, pointerPPS]
let parameterSetPointers = UnsafePointer<UnsafePointer<UInt8>>(dataParamArray)
let sizeParamArray = [sps.count, pps.count]
let parameterSetSizes = UnsafePointer<Int>(sizeParamArray)
let status = CMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault, 2, parameterSetPointers, parameterSetSizes, 4, &formatDesc)
if status == noErr {
print("===== Format description created")
return true
} else {
return false
}
}
And call this in my init() method. Other than that, I made no changes; frames are being interpreted in the exact same way.
For reference, here is what my VT callback looks like:
private var callback: VTDecompressionOutputCallback = {(
decompressionOutputRefCon: UnsafeMutableRawPointer?,
sourceFrameRefCon: UnsafeMutableRawPointer?,
status: OSStatus,
infoFlags: VTDecodeInfoFlags,
imageBuffer: CVPixelBuffer?,
presentationTimeStamp: CMTime,
duration: CMTime) in
let decoder: VideoFrameDecoder = Unmanaged<VideoFrameDecoder>.fromOpaque(decompressionOutputRefCon!).takeUnretainedValue()
if imageBuffer != nil && status == noErr {
decoder.imageDecompressed(image: imageBuffer!)
} else {
decoder.delegate?.frameDecodingFailed()
print("***** FAILED TO DECOMPRESS. VT ERROR \(status)")
}
}
Pretty basic. And just to reiterate, I'm always getting "VT ERROR -12909"

Swift set some string value to file's attribute

According to Apple's document, we can set lots of attributes for a file
A dictionary containing as keys the attributes to set for path and as values the corresponding value for the attribute. You can set the following attributes: busy, creationDate, extensionHidden, groupOwnerAccountID, groupOwnerAccountName, hfsCreatorCode, hfsTypeCode, immutable, modificationDate, ownerAccountID, ownerAccountName, posixPermissions. You can change single attributes or any combination of attributes; you need not specify keys for all attributes.
I want to set an extra parameter for a file. The parameter is a String, I can't find any attribute that I can set String.
For example, I tried
try FileManager.default.setAttributes([FileAttributeKey.ownerAccountName: NSString(string: "0B2TwsHM7lBpSMU1tNXVfSEp0RGs"), FileAttributeKey.creationDate: date], ofItemAtPath: filePath.path)
But when I load keys
let keys = try FileManager.default.attributesOfItem(atPath: filePath.path)
print(keys)
I only get .creationDate changed
[__C.FileAttributeKey(_rawValue: NSFileType): NSFileTypeRegular,
__C.FileAttributeKey(_rawValue: NSFilePosixPermissions): 420,
__C.FileAttributeKey(_rawValue: NSFileSystemNumber): 16777220,
__C.FileAttributeKey(_rawValue: NSFileReferenceCount): 1,
__C.FileAttributeKey(_rawValue: NSFileGroupOwnerAccountName): staff,
__C.FileAttributeKey(_rawValue: NSFileSystemFileNumber): 8423614,
__C.FileAttributeKey(_rawValue: NSFileGroupOwnerAccountID): 20,
__C.FileAttributeKey(_rawValue: NSFileModificationDate): 2017-08-16 06:03:57 +0000,
__C.FileAttributeKey(_rawValue: NSFileCreationDate): 1970-01-01 00:33:20 +0000,
__C.FileAttributeKey(_rawValue: NSFileSize): 9795,
__C.FileAttributeKey(_rawValue: NSFileExtensionHidden): 0,
__C.FileAttributeKey(_rawValue: NSFileOwnerAccountID): 501]
Is there any way that I can set string value to FileAttribute?
The documentation says "You can set the following attributes". That means you can only set those specific attributes via those API's.
What you are really looking for are Extended Attributes, and these use a separate set of (C-style) API's.
Something like:
let directory = NSTemporaryDirectory()
let someExampleText = "some sample text goes here"
do {
try someExampleText.write(toFile: "\(directory)/test.txt", atomically: true, encoding: .utf8)
} catch let error {
print("error while writing is \(error.localizedDescription)")
}
let valueString = "setting some value here"
let result = setxattr("\(directory)/test.txt", "com.stackoverflow.test", valueString, valueString.characters.count, 0, 0)
print("result is \(result) ; errno is \(errno)")
if errno != 0
{
perror("could not save")
}
let sizeOfRetrievedValue = getxattr("\(directory)/test.txt", "com.stackoverflow.test", nil, 0, 0, 0)
var data = Data(count: sizeOfRetrievedValue)
let newResult = data.withUnsafeMutableBytes({
getxattr("\(directory)/test.txt", "com.stackoverflow.test", $0, data.count, 0, 0)
})
if let resultString = String(data: data, encoding: .utf8)
{
print("retrieved string is \(resultString)")
}

How to read from file and put in an Array

This is an application of Sieve of Eratosthenes written in Swift.
I'm able to write the output on a file, but I'm only able to start by manually passing [primesList].
Instead, I would like to read the file primesList.txt and put it in [primesList].
import Foundation
// set a file called test.txt on Desktop to be used by this program
let dir = "~/Desktop"
let file = "primesList.txt"
let path = dir.stringByExpandingTildeInPath
let filePath: NSString = path.stringByAppendingPathComponent(file)
// set some needed variables and constants
var highestNumberEvaluated = 100
var primesList = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
let readFromFile = String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: nil)
println(readFromFile)
let inputNumber = 100
let range2Evaluate = 100
let last2Evaluate = inputNumber + range2Evaluate
println("Find primes between \(inputNumber) and \(last2Evaluate):\n")
// create a dictionary of numbers to be evaluated true or false
var numbersList = [Int: Bool]()
for i in inputNumber...last2Evaluate {
numbersList[i] = true
}
// mark as not prime (false) all not primes in numbersList
for i in primesList {
if i < Int(sqrt(Double(last2Evaluate))) {
let myMultiplier = inputNumber / i
var bottomValue = i * myMultiplier
for var j = bottomValue; j < (bottomValue + range2Evaluate + 2); j += i {
numbersList[j] = false
}
} else {break}
}
// create an array made by all primes found true, then sort
var primesFoundList = [Int]()
for (myKey, myValue) in numbersList {
if myValue == true {
primesFoundList.append(myKey)
}
}
primesFoundList.sort { $0 < $1 }
// add the primes found to the original primes list and write on the file test.txt
primesList += primesFoundList
let toBeWritten = toString(primesList)
toBeWritten.writeToFile(filePath, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
on line 13-14 I can get the content of the file, how can I write this content in the array [primesList]?
The toString() function is not very well suited to produce output that is to be
read again. You could create a simple comma-separated list with
let toBeWritten = ", ".join(primesList.map { String($0)})
toBeWritten.writeToFile(filePath, atomically: true, encoding: NSUTF8StringEncoding, error: nil)
which can be read again with
if let readFromFile = String(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: nil) {
primesList = readFromFile.componentsSeparatedByString(",").map() { ($0 as NSString).integerValue }
}
(Note: This assumes that the file has the correct format, otherwise it may crash.)
Alternatively, use the writeToFile() method from NSArray which writes the
array as a property list (XML) file:
(primesList as NSArray).writeToFile(filePath, atomically: false)
which can be read back with
if let storedList = NSArray(contentsOfFile: filePath) as? [Int] {
primesList = storedList
}