I am working on an Android app that requires OCR. I have decided to use Tesseract as API but I keep on getting this error:
E/Tesseract(native): Could not initialize Tesseract API with language=eng!
I have already copied file "eng.traineddata" to the location.
I am using Android Studio 2.1.2 (SDK 23)
Testing on device with API 22 Android Lollipop 5.1.1 (Read about Permission issue on Marshmallow)
Here is the code I am using:
public void reads(View view) {
TextView textView = (TextView) findViewById(R.id.textView);
int rotation = 0;
try {
ExifInterface exifInterface = new ExifInterface(mCurrentPhotoPath);
int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL);
switch (orientation){
case ExifInterface.ORIENTATION_ROTATE_90: rotation = 90; break;
case ExifInterface.ORIENTATION_ROTATE_180: rotation = 180; break;
case ExifInterface.ORIENTATION_ROTATE_270: rotation = 270; break;
}
} catch(Exception e) {
}
int w = imageBitmap.getWidth();
int h = imageBitmap.getHeight();
if (rotation != 0) {
Matrix matrix = new Matrix();
matrix.preRotate(rotation);
imageBitmap = Bitmap.createBitmap(imageBitmap,0,0,w,h,matrix,false);
} else {
imageBitmap = Bitmap.createBitmap(imageBitmap,0,0,w,h);
}
imageBitmap = imageBitmap.copy(Bitmap.Config.ARGB_8888,true);
TessBaseAPI ReadIt = new TessBaseAPI();
ReadIt.init("/storage/emulated/0/","eng");
ReadIt.setImage(imageBitmap);
String Text = ReadIt.getUTF8Text();
if (Text!=null) textView.setText(Text);
}
I have used this line in my build.gradle dependency:
compile 'com.rmtheis:tess-two:6.0.2'
also, I have copied the"eng.traineddata in the folder named tessdata directly by downloading in the particular stated directory.
Tesseract-two isn't using the newest version of the OCR engine, it uses 3.05, so we are forced to use data from here. It seems the new data uses a different model, neural networks. The previous models before 4.0 worked differently.
I have tried using the data from here
and here. These data sets are only compatible with the newest version of tesseract, 4.0 (source), so it won't work if you are using an older version of tesseract.
Are you using tess-two?. In your code:
TessBaseAPI ReadIt = new TessBaseAPI();
ReadIt.init("/storage/emulated/0/","eng");
"/storage/emulated/0/" path should be pointing to your data files. You must have a subdirectory
named "tessdata". See
https://github.com/rmtheis/tess-two/blob/d7a45fd2e08b7ec315cd1e29d1a7e0c72fb24a66/tess-two/src/com/googlecode/tesseract/android/TessBaseAPI.java#L176
Read more at:
Could not initialize Tesseract API with language=eng!
Release permissions of manifest in Activity:
In manifest:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
In onCreate:
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
1);
}
}
If you dont use Marshmallow and still have problem try clean and rebuild project.
I had this same issue and the problem was that Marshmallow specifically requires a new way for your app to get read/write permission to storage. This blog post solved my problem.
In my Main Activity I have the following:
#Override
protected void onCreate(Bundle savedInstanceState) {
...
...
getStorageAccessPermissions(); // Request storage read/write permissions from the user
}
#TargetApi(23)
private void getStorageAccessPermissions() {
int hasWriteStoragePermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
if (hasWriteStoragePermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE_WRITE_EXTERNAL_PERMISSIONS);
}
}
Where REQUEST_CODE_WRITE_EXTERNAL_PERMISSIONS is an integer constant declared globally.
In a class that I have extending TessBaseAPI I added the following just for logging purposes to make sure that I actually can access the storage.
/* Checks if external storage is available to at least write to and returns the path name */
private static String isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
String retval = "External storage is not writable";
if (Environment.MEDIA_MOUNTED.equals(state)) {
retval = Environment.getExternalStorageDirectory().toString();
}
return retval;
}
/* Checks if external storage is available to at least read from and returns the path name */
private static String isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
String retval = "External storage is not readable";
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
retval = Environment.getExternalStorageDirectory().toString();
}
return retval;
}
use absolute path to tessdata directory from external storage (not assets)
for example if your models are in
/storage/emulated/0/Android/data/com.xxx.yyy/files/tessmodels/tessdata/
use this path
/storage/emulated/0/Android/data/com.xxx.yyy/files/tessmodels/
make sure you have write/read external storage permissions
use this model, tested with tess-two:9.0.0. I got it from tess-two sample app
Newer versions of tess-two check to make sure that the training data files can be found on the device. If those training data files are not found, a more informative message than the error message you're seeing will be shown.
So when you see this error message on newer versions of tess-two, it means that the training data files were found in the expected location, but they are the wrong version or are otherwise unreadable. Check to make sure you're using the right version of the training data files.
Related
I'm making a new cross-platform MAUI App,and I tried to simply create a json file.the source code looks like this:
private async void WriteSomething()
{
#if ANDROID
FileStream fs = new(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) + "/test.json", FileMode.Create);
StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8);
await sw.WriteAsync(str);
sw.Close();
fs.Close();
#elif WINDOWS
string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\test.json";
FileStream fs = new(path, FileMode.Create);
StreamWriter sw = new StreamWriter(fs, System.Text.Encoding.UTF8);
await sw.WriteAsync(str);
sw.Close();
fs.Close();
#endif
}
The program runs well on windows machine,but the program throws System.UnauthorizedAccessException on android emulator.
I've searched this problem on StackOverflow,but most of the questions about this is on Xamarin platform.(Xamarin.Android or Xamarin.Forms)
According to the answers,I should request the storage permission like this:
First,add the following code to my AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Second,check if the target android version is Android M or above.If the answer is true,then invoke RequestPermissions method:
#if ANDROID
private void CheckAppPermissions()
{
if ((int)Build.VERSION.SdkInt < 23)
{
return;
}
else
{
if (PackageManager.CheckPermission(Manifest.Permission.ReadExternalStorage, PackageName) != Permission.Granted
&& PackageManager.CheckPermission(Manifest.Permission.WriteExternalStorage, PackageName) != Permission.Granted)
{
var permissions = new string[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
RequestPermissions(permissions, 1);
}
}
}
#endif
However,I found there is no such method in Android namespace.
CS0103 the name "RequestPermissions" does not exist in the current
context.
I guess it exists only in Xamarin platform.Or it acctually exists in another namespace.And that means I can't get the read and write permission I need.
A brand-new solution of the permission problem is needed.
**Note:**My English is poor.And I have little programming experience.Please forgive me.
Thanks in advance.
Actually, if you just operate the file in the System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal) path, it shouldn't need any storage permissions. Because it's the app's own private folder. And I have tested your code in a new project, the josn file was created successfully.
In addition, if you want to request the permission in the maui, you need to use the api in the maui. You can use the following code instead of the yours.
PermissionStatus statusread = await Permissions.RequestAsync<Permissions.StorageRead>();
PermissionStatus statuswrite = await Permissions.RequestAsync<Permissions.StorageWrite>();
Finally, you can also use the permission Mahesh mentioned, but it should be used if necessary. And if you still want to it, you can try to add the following code in the MainActivity to grant the permission by the user.
protected override void OnCreate(Bundle savedInstanceState)
{
if (!Android.OS.Environment.IsExternalStorageManager)
{
Intent intent = new Intent();
intent.SetAction(Android.Provider.Settings.ActionManageAppAllFilesAccessPermission);
Android.Net.Uri uri = Android.Net.Uri.FromParts("package", this.PackageName, null);
intent.SetData(uri);
StartActivity(intent);
}
base.OnCreate(savedInstanceState);
}
use this permission, hope it's helps, check once with give manually permission.
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
android.os.FileObserver requires a java.io.File to function.
But with Android 10 Google restricted access to everything but your app's private directory due to the famous "Storage Access Framework". Thus, accessing anything via java.io.File breaks and renders FileObserver useless unless you intend to use it in your app's private directory. However, I want to be notified when something is changed in a certain directory on external storage. I would also like to avoid periodically checking for changes.
I tried using ContentResolver.registerContentObserver(uri,notifyForDescendants,observer) and ran into some problems with that method:
Every Uri I have plugged in so far was accepted
It neither fails nor notifies if the Uri doesn't work
I cannot find any documentation telling me which Uris actually work
The only thing I got working to some extent is the following approach:
// works, but returns all changes to the external storage
contentResolver.registerContentObserver(MediaStore.Files.getContentUri("external"), true, contentObserver)
Unfortunately this includes all of the external storage and only returns Media Uris when changes happen - for example content://media/external/file/67226.
Is there a way to find out whether or not that Uri points to my directory?
Or is there a way to make registerContentObserver() work with a Uri in such a way that I get a notification whenever something in the folder has changed?
I also had no success trying various Uris related to DocumentsFile and external storage Uris.
I kept getting errors when even trying to use the base constructor such as the following -
No direct method <init>(Ljava/util/List;I)V in class Landroid/os/FileObserver; or its super classes (declaration of 'android.os.FileObserver' appears in /system/framework/framework.jar!classes2.dex)
From a comment on Detect file change using FileObserver on Android:
I saw that message (or something like that) when i was trying to use constructor FileObserver(File). Use of deprecated FileObserver(String) solved my problem.... Original FileObserver has bugs.
Full disclosure, I was using the Xamarin.Android API; however, the gist and the commenter I quoted were both working with Java. At any rate, indeed - tried again using the counterpart String constructor and I was finally able to make and use the observer. Grinds my gears to use a deprecated API, but apparently they're hanging onto it at least up to and including Android 12.0.0_r3... still, would much prefer the supported constructors actually work. Maybe there's some warrant here for filing an issue.
I found a way to implement FileObserver on Android 10 with ContentObserver, but it might only work with media files since it works with media content uris.
The uri for ContentResolver.registerContentObserver() should be the file's corresponding media uri (e.g. content://media/external/file/49) which is queried by file path.
fun getMediaUri(context: Context, file: File): Uri? {
val externalUri = MediaStore.Files.getContentUri("external")
context.contentResolver.query(
externalUri,
null,
"${MediaStore.Files.FileColumns.DATA} = ?",
arrayOf(file.path),
null
)?.use { cursor ->
if (cursor.moveToFirst()) {
val idIndex = cursor.getColumnIndex("_id")
val id = cursor.getLong(idIndex)
return Uri.withAppendedPath(externalUri, "$id")
}
}
return null
}
Then ContentObserver.onChange() will be triggered for every file change with uri: content://media/external/file/{id}; uri in ContentObserver.onChange(boolean selfChange, Uri uri) will always be content://media/external/file; only registered file will be with id (e.g. content://media/external/file/49?deletedata=false).
Does what FileObserver used to do when input uri's path matches registered uri.
I have a temporary solution for this issue, so let's see if this can help.
I start an infinite while loop watching for file created and file deleted (if you want file modified or file renamed you have to implement more) using DocumentFile. Below is my sample:
private static int currentFileIndirectory = 0;
private static final int FILE_CREATED = 0;
private static final int FILE_DELETED = 1;
private static DocumentFile[] onDirectoryChanged(DocumentFile[] documentFiles, int event) {
Log.d("FileUtil", "onDirectoryChanged: " + event);
if (event == FILE_CREATED) {
} else {
}
return documentFiles;
}
private static boolean didStartWatching = false;
private static void startWatchingDirectory(final DocumentFile directory) {
if (!didStartWatching) {
didStartWatching = true;
DocumentFile[] documentFiles = directory.listFiles();
if (null != documentFiles && documentFiles.length > 0) {
currentFileIndirectory = documentFiles.length;
}
new Thread(new Runnable() {
public void run() {
while (true) {
DocumentFile[] documentFiles = directory.listFiles();
if (null != documentFiles && documentFiles.length > 0) {
if (documentFiles.length != currentFileIndirectory) {
if (documentFiles.length > currentFileIndirectory) {//file created
DocumentFile[] newFiles = new DocumentFile[documentFiles.length - currentFileIndirectory];
onDirectoryChanged(newFiles, FILE_CREATED);
} else {//file Deleted
onDirectoryChanged(null, FILE_DELETED);
}
currentFileIndirectory = documentFiles.length;
}
}
}
}
}).start();
}
}
We are currently obtaining the path of album art using: MediaStore.Audio.AlbumColumns.ALBUM_ART, and is successfully obtaining the path, except on pixel 3a (Android 10). After some research, the ALBUM_ART became deprecated API 29 and over as shown: Here
In this link it says: "Apps may not have file system permissions to directly access this path. Instead of trying to open this path directly, apps should use ContentResolver#loadThumbnail to gain access."
My questions are:
1) I'm already stating on the application manifest the permissions for external storage access (READ_EXTERNAL_STORAGE) and is requesting permission while navigating in-app. Which permissions do i have to provide to allow access to album art in order to obtain the path?
2) I can't seem to find any content on loadThumbnail online (and not even on ContentResolver class through code, while i am using target and compile SDK 29), if 1) can't be done, then how do i use loadThumbnail and why it's not showing on code?
Thanks in advance.
In order to use the method of ContentResolver, make sure you have the latest SDK and relevant tools installed, and in your code first instantiate a ContentResolver object and then use it accordingly:
public class MainActivity extends AppCompatActivity {
public ContentResolver resolver;
Bitmap albumArt;
Size size;
Uri uriOfItem;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resolver = new ContentResolver(this) {
#NonNull
#Override
public Bitmap loadThumbnail(#NonNull Uri uri, #NonNull Size size, #Nullable CancellationSignal signal) throws IOException {
return super.loadThumbnail(uri, size, signal);
}
};
//uriOfItem = uri of your file
size = new Size(100, 100);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
albumArt = resolver.loadThumbnail(uriOfItem, size, null);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
EDIT: when it comes to your first question if #Rj_Innocent_Coder doesn't mind me including his comment here:
As part of the scoped-storage feature on Android Q, Google announced that SAF (storage access framework) will replace the normal storage permissions. This means that even if you will try to use storage permissions, it will only grant to access to specific types of files for File and file-path to be used
EDIT 2: after #hetoan2 's comment I check the documentation again and I noticed that ContentResolver is abstract hence not being able to use ContentResolver.loadThumbnail() as a method call. That means that within an activity you could simply use the following as well:
Bitmap albumArt = getContentResolver().loadThumbnail(uriOfFile, sizeOfAreaThatDisplaysThumbnail, cancellationSignalOrNull);
For someone else who is having issues here, this is the solution that worked for me:
if(android.os.Build.VERSION.SDK_INT >= 29)
{
val album = "Name Of Album"
val artist = "Name of Artist"
// Determine album ID first
val cursor = context.contentResolver.query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
MediaStore.Audio.Albums.ALBUM_ID,
"${MediaStore.Audio.Albums.ALBUM} = '$album' AND
${MediaStore.Audio.Albums.ARTIST} = '$artist'"
,null,null)
val uri = if(cursor != null && cursor.count > 0)
{
cursor.moveToFirst()
ContentUris.withAppendedId(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, cursor.getString(0).toLong())
}
else
{
// Dummy URI that will not return an image
// If you end up here, the album is not in the DataStore
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI
}
val bm = try
{
// Set size based on size of bitmap you want returned
context.contentResolver.loadThumbnail(uri, Size(50,50), null)
}
catch(e: java.lang.Exception)
{
// Return default image indicating no image available from DataStore
BitmapFactory.decodeResource(context.resources, R.drawable.no_image)
}
}
try this it will work and load with glide imageView
int thumbColumn = audioCursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID);
int _thumpId = audioCursor.getInt(thumbColumn);
imgFilePath = "content://media/external/audio/albumart/"+_thumpId;
audioCursor.moveToPosition(i);
Glide.with(getContext()).load(imgFilePath).placeholder(R.drawable.missed).into(tracksAlbumArt);
Update Andriod Studio latest 4.2.X and targetSdkVersion to 30
When I plug my device into big computer I see the following picture
How to find these (two) directories programmatically from withing Android application?
UPDATE
I wrote utility class to deduce roots. Unfortunately, it works for minSdkVersion=19
public class RootsUtil {
private final static String seed = Environment.DIRECTORY_PICTURES;
public final static File[] getRoots(Context context) {
File[] paths = context.getExternalFilesDirs(seed);
if( paths.length <= 1 ) {
return new File[] { Environment.getExternalStorageDirectory() };
}
else {
while(true) {
int count = 1;
for (int i = 1; i < paths.length; ++i) {
if (paths[0].getName().equals(paths[i].getName())) {
count++;
}
}
if( count==paths.length ) {
for (int i = 0; i < paths.length; ++i) {
paths[i] = paths[i].getParentFile();
}
}
else {
break;
}
}
return paths;
}
}
}
The question persists: are there any solutions for at least SDK=15?
P.S.
People downvoting this (absolutely normal) question: you are just declaring yourselves a clowns.
How to find these (two) directories programmatically from withing Android application?
You don't.
The one labeled "Phone" presumably is what the Android SDK refers to as external storage. I say "presumably" because device manufacturers seem to change this label — I usually see it called "Internal" or "Internal storage". To get the root of external storage, use Environment.getExternalStorageDirectory(). Note that this requires that you hold the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permissions, which includes asking for those permissions at runtime.
The one labeled "Card" presumably is referring to some removable media. You cannot work with the root directory of removable storage.
Maybe this will help you:
https://stackoverflow.com/a/40123073/5002496
thanks to that method you can list all mounted external storages paths. I am using it in my project to store data in sd-card and tested it on more than 20 devices.
MtpDevice.importFile(int objectHandle, String destPath) fails on 4.4 and above. Is there a way to import from an MtpDevice with the SD card write lock?
For the time being this is what I'm doing, though I'd love to avoid the double transfer:
// KitKat and higher require the extra step of importing to the cache then moving
if (Util.hasKitkat())
{
File tmp = new File(getExternalCacheDir(), name);
mMtpDevice.importFile(objectHandle, tmp.getPath());
success = FileUtil.moveFile(CameraImportActivity.this, tmp, endFile);
}
else
{
success = mMtpDevice.importFile(objectHandle, endFile.getPath());
}