Open davidburson opened 4 months ago
Guten Tag, Hans here.
[!NOTE] New features, bugfixes, updates and other improvements are all handled mostly by
@mrousavy
in his free time. To support@mrousavy
, please consider π sponsoring him on GitHub π. Sponsored issues will be prioritized.
Add a file:// prefix
Thanks, @mrousavy, but the file:// prefix is already included in res.path
returned from const res = await camera.current.takePhoto();
As I tried to explain in my original post, the folder the file was saved in doesn't exist when program control returns from takePhoto()
. For example, if res.path returned from takePhoto()
is "file:///private/var/mobile/Containers/Data/Application/629FC6E1-1F8F-46B9-8D08-1CAF06EC450A/tmp/29A98AB6-0B9C-4F34-9846-23D036EAD751.jpeg", then when takePhoto()
returns, the folder "/private/var/mobile/Containers/Data/Application/" exists, but the folder "/private/var/mobile/Containers/Data/Application/629FC6E1-1F8F-46B9-8D08-1CAF06EC450A/" does NOT exist.
I edited PhotoCaptureDelegate.swift to log the path and whether the file exists. It DOES exist immediately after taking the picture, but if I add code to check if that same file still exists, pod install, and re-run the app, the first file NO LONGER exists.
So, it seems to me the photo file along with its containing folder [uuid]/tmp is being deleted before control returns from takePhoto()
.
Here's the photoOutput
with my edits:
func photoOutput(_: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
defer {
removeGlobal()
}
if let error = error as NSError? {
promise.reject(error: .capture(.unknown(message: error.description)), cause: error)
return
}
do {
let path = try FileUtils.writePhotoToTempFile(photo: photo, metadataProvider: metadataProvider)
// Does the path of a previous photo still exist?
if FileManager.default.fileExists(atPath: "/private/var/mobile/Containers/Data/Application/629FC6E1-1F8F-46B9-8D08-1CAF06EC450A/tmp/29A98AB6-0B9C-4F34-9846-23D036EAD751.jpeg") {
print("Old path exists: /private/var/mobile/Containers/Data/Application/629FC6E1-1F8F-46B9-8D08-1CAF06EC450A/tmp/29A98AB6-0B9C-4F34-9846-23D036EAD751.jpeg")
} else {
print("Old path does not exist: /private/var/mobile/Containers/Data/Application/629FC6E1-1F8F-46B9-8D08-1CAF06EC450A/tmp/29A98AB6-0B9C-4F34-9846-23D036EAD751.jpeg")
}
// path of the photo we just took
print("Photo saved to path: \(path.absoluteString)")
let exif = photo.metadata["{Exif}"] as? [String: Any]
let width = exif?["PixelXDimension"]
let height = exif?["PixelYDimension"]
let exifOrientation = photo.metadata[String(kCGImagePropertyOrientation)] as? UInt32 ?? CGImagePropertyOrientation.up.rawValue
let cgOrientation = CGImagePropertyOrientation(rawValue: exifOrientation) ?? CGImagePropertyOrientation.up
let orientation = getOrientation(forExifOrientation: cgOrientation)
let isMirrored = getIsMirrored(forExifOrientation: cgOrientation)
// Verify the file exists for the photo we just took
if FileManager.default.fileExists(atPath: path.path) {
print("File exists at path: \(path.path)")
} else {
print("File does not exist at path: \(path.path)")
}
promise.resolve([
"path": path.absoluteString,
"width": width as Any,
"height": height as Any,
"orientation": orientation,
"isMirrored": isMirrored,
"isRawPhoto": photo.isRawPhoto,
"metadata": photo.metadata,
"thumbnail": photo.embeddedThumbnailPhotoFormat as Any,
])
} catch let error as CameraError {
promise.reject(error: error)
} catch {
promise.reject(error: .capture(.unknown(message: "An unknown error occured while capturing the photo!")), cause: error as NSError)
}
}
I hope this clarification is helpful! I very much appreciate help in resolving this issue. It seems so strange that takePhoto()
would create a file in a [uuid]/tmp folder, then delete that whole folder before returning. I'm guessing the delete is happening in the Swift code, or due to some Apple "feature". Or possibly, there is also a permissions issue such that the [uuid]/tmp folder used in RNVC's Swift code cannot be seen in an app that uses RNVC.
In case it matters: I'm running Xcode 15.1 on an M1 mac Mini, running macOS Sonoma 14.5.
I have a workaround!
const destPath = [where you want to store the photo]
const res = await camera.current.takePhoto();
// THE WORKAROUND:
const sourcePath = Platform.OS === 'ios' ? res.path.replace('file:///private', '') : res.path;
await RNFetchBlob.fs.mv(sourcePath, destPath);
So I was barking up a forest of wrong trees because I thought I had already checked that workaround. I'm a dummy, but at least I learned a little bit this week. (The fact that I'm a dummy I already knew.)
I'll leave this issue open since it seems to me that this is a bug: on iOS, res.path in my opinion should return a useable path like it does on Android.
@mrousavy thanks again for an outstandingly helpful component!
Hm this is very weird. Why would I be able to write to that path if it doesn't exist....
iOS paths do seem weird to me. For example, in your Swift code when a photo is taken, it writes to a path that starts with /private in writeDataToTempFile (in ios/Core/Utils/FileUtils.swift). Immediately after that data.write
, I added a test in the Swift code to log whether the file existed in the supplied path (beginning with /private), and the identical path without /private). The file existed in BOTH.
Additionally, every time I run my app (which I do from Xcode, with Metro running), the path to where pics are taken has a different uuid.
For example, I reported above RNVC returned path: 'file:///private/var/mobile/Containers/Data/Application/5CF1ADC7-3D83-4415-BB1C-EB3ADCE8DC77/tmp/5CCB9739-3EFD-4FBB-95C3-C361B8262B1B.jpeg'.
Every time I run, I get a different uuid in place of the folder 5CF1ADC7-3D83-4415-BB1C-EB3ADCE8DC77. Yet, the old photos that I didn't move from each time I ran my app previously and took photos, they are all there in the path with the new uuid folder.
All that to say, it seems to me iOS is doing some strange things with folders, maybe creating a new symlink every time I run my app and pointing them all to the same place, and always having the /private and NOT /private paths point to the same place, but /private is not allowed at the main app level, only in the Swift code?
but /private is not allowed at the main app level, only in the Swift code?
This can't be, maybe I return a different path. Either way, I am using react-native-vision-camera 4.3.2 in ShadowLens in production and it works perfectly fine, so I don't think this is a bug in VisionCamera?
My best guess at this point is that I'm passing different parameters to RNVC than you do in ShadowLens, and that somehow the combination of parameters, maybe in conjunction with something else in my environment, causes this issue.
You've probably heard the old software developer's adage that "Nothing can be foolproof because fools are so ingenious". Maybe I've been the fool in that proverb in this case and, while trying to use RNVC as intended, I've somehow landed on a combination that causes this issue.
In any case, it's great that in ShadowLens you have a way to use RNVC that works, and for now my workaround works for me. If it becomes a problem again I'll revisit this issue. I'm currently wrestling with some orientation issues with RNVC on iOS. If I can't resolve them I'll open a separate issue, if my problems aren't already covered by what other people have found - I notice a few currently open issues dealing with orientation.
Thanks again, I really appreciate RNVC and all your work on it!
I dont think this changes anything.
After I take a photo, what do you do with the path?
This is the function where I take a photo, from the reproducible code I included with my original post:
const onTakePhotoPress = async () => {
if (camera && camera.current) {
const res = await camera.current.takePhoto();
console.log('res', res);
const resPathExists = await RNFetchBlob.fs.exists(res.path);
console.log('res.path exists?', resPathExists); // no errors, but this is always false
}
};
After taking a photo, I log the response, then I check to see if the res.path
exists. On iOS, it never does.
But I found that on iOS, if I strip off the "file:///private" that is always at the beginning of res.path
, then that new path DOES exist and I can do whatever I want with the file. For convenience, here's my one-line workaround that I now add immediately after taking a photo:
const sourcePath = Platform.OS === 'ios' ? res.path.replace('file:///private', '') : res.path;
In my real app, after taking the photo (and now utilizing my workaround to get a path that works) I move the file to where my app persists its data on the user's phone, using
await RNFetchBlob.fs.mv(oldPath, newPath)
i can confirm the same problem. i also tested whether the file with the private prefix exists and as david mentioned, it doesn't until you cut that part away. it is not bullet proof though, sometimes it fails to get the fail anyway
running latest version, 4.5.2
What's happening?
I recently upgraded my project to React Native 0.74 and RNVC 4.3.2. Works great on Android!
But on iOS, when I take a photo or snapshot there is no error, but the path in the response does not exist. Where did the file go?
In the response path, this part of the path exists on the device:
/private/var/mobile/Containers/Data/Application/
, but the uuid subdirectory that comes next in the response path does not exist. Similarly,/var/mobile/Containers/Data/Application/
(without/private
) exists, but not the uuid subdirectory.I tried to get the example project to run, but I failed.
yarn bootstrap
failed with "Could not find 'bundler' (2.3.22) required by your [path]/Gemfile.lock." I don't know how to fix this, and all my rather blind attempts to follow suggestions I found online failed. I also tried to get the example project running withyarn install
from the "example" directory, andpod install
from "example/ios". Still getting incomprehensible errors in Xcode.So I created a minimal example to demonstrate the RNVC issue and pasted it into the Reproduceable Code section below. Tap to take a picture, and the log shows the response from
takePhoto()
. It also shows whether the path returned bytakePhoto()
exists. And that's the issue: at least for me, it never exists on iOS.EDIT: video works fine on iOS. It's just taking photos and snapshots that has the bad path issue.
Reproduceable Code
Relevant log output
Camera Device
Device
iPhone X (iOS 16.7.8)
VisionCamera Version
4.3.2
Can you reproduce this issue in the VisionCamera Example app?
No, I cannot reproduce the issue in the Example app
Additional information