Skip to content

Commit 470a0df

Browse files
committed
feat: add video repair functionality to RecordsAdapter
- Updated versionCode to 17 in build.gradle.kts. - Implemented a new feature in RecordsAdapter to repair video files, including handling for both file and content URIs. - Added a new menu item for video repair with an associated icon. - Introduced new string resources for video repair messages in multiple languages. - Created a new drawable resource for the repair icon.
1 parent b40f89a commit 470a0df

File tree

13 files changed

+262
-3
lines changed

13 files changed

+262
-3
lines changed

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ android {
1919
applicationId = "com.fadcam"
2020
minSdk = 28
2121
targetSdk = 34
22-
versionCode = 16
22+
versionCode = 17
2323
versionName = "2.0.0-beta"
2424

2525
// testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

app/src/main/java/com/fadcam/ui/RecordsAdapter.java

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import com.fadcam.Utils; // Import Utils for the new formatter
5656

5757
import java.io.File;
58+
import java.io.FileInputStream;
5859
import java.io.FileOutputStream;
5960
import java.io.IOException;
6061
import java.io.InputStream;
@@ -80,6 +81,8 @@
8081
import androidx.core.app.ShareCompat;
8182
import android.content.ContentResolver;
8283
import androidx.core.content.FileProvider;
84+
import com.arthenica.ffmpegkit.FFmpegKit;
85+
import com.arthenica.ffmpegkit.ReturnCode;
8386

8487
// Modify the class declaration to remove the ListPreloader implementation
8588
public class RecordsAdapter extends RecyclerView.Adapter<RecordsAdapter.RecordViewHolder> {
@@ -642,6 +645,9 @@ private PopupMenu setupPopupMenu(RecordViewHolder holder, VideoItem videoItem) {
642645
} else if (id == R.id.action_save) {
643646
saveVideoToGalleryInternal(videoItem);
644647
return true;
648+
} else if (id == R.id.action_fix_video) {
649+
fixVideoFile(videoItem);
650+
return true;
645651
} else if (id == R.id.action_rename) {
646652
showRenameDialog(videoItem);
647653
return true;
@@ -1724,4 +1730,129 @@ private void setSnowVeilButtonColors(androidx.appcompat.app.AlertDialog dialog)
17241730
}
17251731
}
17261732
}
1733+
1734+
private void fixVideoFile(VideoItem videoItem) {
1735+
if (videoItem == null || videoItem.uri == null) {
1736+
Toast.makeText(context, context.getString(R.string.fix_video_invalid), Toast.LENGTH_SHORT).show();
1737+
return;
1738+
}
1739+
String scheme = videoItem.uri.getScheme();
1740+
File inputFile = null;
1741+
File outputFile;
1742+
String outputName = "FIXED_" + videoItem.displayName;
1743+
boolean isSAF;
1744+
File safTempInput = null;
1745+
File safTempOutput;
1746+
if ("file".equals(scheme)) {
1747+
safTempOutput = null;
1748+
isSAF = false;
1749+
inputFile = new File(videoItem.uri.getPath());
1750+
if (!inputFile.exists()) {
1751+
Toast.makeText(context, context.getString(R.string.fix_video_file_not_exist), Toast.LENGTH_SHORT).show();
1752+
return;
1753+
}
1754+
outputFile = new File(inputFile.getParent(), outputName);
1755+
if (outputFile.exists()) {
1756+
Toast.makeText(context, context.getString(R.string.fix_video_fixed_exists), Toast.LENGTH_SHORT).show();
1757+
return;
1758+
}
1759+
} else if ("content".equals(scheme)) {
1760+
isSAF = true;
1761+
try {
1762+
safTempInput = File.createTempFile("saf_repair_", ".mp4", context.getCacheDir());
1763+
inputFile = safTempInput;
1764+
try (InputStream in = context.getContentResolver().openInputStream(videoItem.uri);
1765+
OutputStream out = new FileOutputStream(inputFile)) {
1766+
byte[] buf = new byte[8192];
1767+
int len;
1768+
while ((len = in.read(buf)) > 0) {
1769+
out.write(buf, 0, len);
1770+
}
1771+
}
1772+
} catch (Exception e) {
1773+
Log.e(TAG, "Failed to copy SAF video for repair", e);
1774+
Toast.makeText(context, context.getString(R.string.fix_video_saf_copy_fail), Toast.LENGTH_LONG).show();
1775+
if (safTempInput != null && safTempInput.exists()) safTempInput.delete();
1776+
return;
1777+
}
1778+
safTempOutput = new File(context.getCacheDir(), outputName);
1779+
outputFile = safTempOutput;
1780+
} else {
1781+
safTempOutput = null;
1782+
outputFile = null;
1783+
isSAF = false;
1784+
Toast.makeText(context, context.getString(R.string.fix_video_internal_only), Toast.LENGTH_LONG).show();
1785+
return;
1786+
}
1787+
String inputPath = inputFile.getAbsolutePath();
1788+
String outputPath = outputFile.getAbsolutePath();
1789+
String ffmpegCmd = String.format("-y -i %s -c copy %s", inputPath, outputPath);
1790+
Toast.makeText(context, context.getString(R.string.fix_video_repairing), Toast.LENGTH_SHORT).show();
1791+
File finalSafTempInput = safTempInput;
1792+
FFmpegKit.executeAsync(ffmpegCmd, session -> {
1793+
if (ReturnCode.isSuccess(session.getReturnCode())) {
1794+
if (isSAF) {
1795+
boolean wroteToSAF = false;
1796+
try {
1797+
// Use the SAF folder URI from preferences, just like RecordingService
1798+
String safFolderUriString = sharedPreferencesManager.getCustomStorageUri();
1799+
if (safFolderUriString != null) {
1800+
Uri safFolderUri = Uri.parse(safFolderUriString);
1801+
DocumentFile safFolder = DocumentFile.fromTreeUri(context, safFolderUri);
1802+
if (safFolder != null && safFolder.canWrite()) {
1803+
DocumentFile fixedDoc = safFolder.createFile("video/mp4", outputName);
1804+
if (fixedDoc != null) {
1805+
try (OutputStream out = context.getContentResolver().openOutputStream(fixedDoc.getUri());
1806+
InputStream in = new FileInputStream(outputFile)) {
1807+
byte[] buf = new byte[8192];
1808+
int len;
1809+
while ((len = in.read(buf)) > 0) {
1810+
out.write(buf, 0, len);
1811+
}
1812+
}
1813+
wroteToSAF = true;
1814+
new Handler(Looper.getMainLooper()).post(() -> {
1815+
Toast.makeText(context, context.getString(R.string.fix_video_saf_success, outputName), Toast.LENGTH_LONG).show();
1816+
});
1817+
}
1818+
}
1819+
}
1820+
if (!wroteToSAF) {
1821+
new Handler(Looper.getMainLooper()).post(() -> {
1822+
Toast.makeText(context, context.getString(R.string.fix_video_saf_write_fail), Toast.LENGTH_LONG).show();
1823+
Toast.makeText(context, "Cannot write to this folder. This may be due to SD card, USB, or cloud storage permissions. The repaired file is saved in app storage.", Toast.LENGTH_LONG).show();
1824+
Toast.makeText(context, context.getString(R.string.fix_video_saf_export), Toast.LENGTH_LONG).show();
1825+
// TODO: Offer share/export dialog for the fixed file in app storage
1826+
});
1827+
}
1828+
} catch (Exception e) {
1829+
Log.e(TAG, "Failed to write repaired file to SAF folder", e);
1830+
new Handler(Looper.getMainLooper()).post(() -> {
1831+
Toast.makeText(context, context.getString(R.string.fix_video_saf_write_fail), Toast.LENGTH_LONG).show();
1832+
Toast.makeText(context, "Cannot write to this folder. This may be due to SD card, USB, or cloud storage permissions. The repaired file is saved in app storage.", Toast.LENGTH_LONG).show();
1833+
Toast.makeText(context, context.getString(R.string.fix_video_saf_export), Toast.LENGTH_LONG).show();
1834+
// TODO: Offer share/export dialog for the fixed file in app storage
1835+
});
1836+
} finally {
1837+
// Clean up temp files
1838+
if (finalSafTempInput != null && finalSafTempInput.exists()) finalSafTempInput.delete();
1839+
if (safTempOutput != null && safTempOutput.exists()) safTempOutput.delete();
1840+
}
1841+
} else {
1842+
new Handler(Looper.getMainLooper()).post(() -> {
1843+
Toast.makeText(context, context.getString(R.string.fix_video_success, outputFile.getName()), Toast.LENGTH_LONG).show();
1844+
});
1845+
}
1846+
} else {
1847+
new Handler(Looper.getMainLooper()).post(() -> {
1848+
Toast.makeText(context, context.getString(R.string.fix_video_fail), Toast.LENGTH_LONG).show();
1849+
});
1850+
// Clean up temp files on failure as well
1851+
if (isSAF) {
1852+
if (finalSafTempInput != null && finalSafTempInput.exists()) finalSafTempInput.delete();
1853+
if (safTempOutput != null && safTempOutput.exists()) safTempOutput.delete();
1854+
}
1855+
}
1856+
});
1857+
}
17271858
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#FFFFFF" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
2+
3+
<path android:fillColor="@android:color/white" android:pathData="M7.5,5.6L10,7 8.6,4.5 10,2 7.5,3.4 5,2l1.4,2.5L5,7zM19.5,15.4L17,14l1.4,2.5L17,19l2.5,-1.4L22,19l-1.4,-2.5L22,14zM22,2l-2.5,1.4L17,2l1.4,2.5L17,7l2.5,-1.4L22,7l-1.4,-2.5zM14.37,7.29c-0.39,-0.39 -1.02,-0.39 -1.41,0L1.29,18.96c-0.39,0.39 -0.39,1.02 0,1.41l2.34,2.34c0.39,0.39 1.02,0.39 1.41,0L16.7,11.05c0.39,-0.39 0.39,-1.02 0,-1.41l-2.33,-2.35zM13.34,12.78l-2.12,-2.12 2.44,-2.44 2.12,2.12 -2.44,2.44z"/>
4+
5+
</vector>

app/src/main/res/menu/video_item_menu.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
android:icon="@drawable/ic_save"
77
android:title="Save to Gallery"/>
88
<item
9+
android:id="@+id/action_fix_video"
10+
android:icon="@drawable/ic_repair"
11+
android:title="@string/fix_video_menu_title"/>
12+
<item
913
android:id="@+id/action_rename"
1014
android:icon="@drawable/ic_rename"
1115
android:title="Rename"/>

app/src/main/res/values-ar/strings.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,4 +505,18 @@
505505
<string name="toast_no_drive_app">لم يتم العثور على تطبيق Google Drive</string>
506506

507507
<string name="dialog_audio_noise_suppression_label">تثبيط الضوضاء</string>
508+
509+
<string name="fix_video_menu_title">إصلاح الفيديو (إصلاح التشغيل)</string>
510+
<string name="fix_video_repairing">جاري الإصلاح… قد يستغرق ذلك بضع ثوانٍ.</string>
511+
<string name="fix_video_success">تم إصلاح الفيديو! تم الحفظ باسم: %1$s</string>
512+
<string name="fix_video_fail">فشل الإصلاح. راجع السجلات لمزيد من التفاصيل.</string>
513+
<string name="fix_video_invalid">ملف فيديو غير صالح.</string>
514+
<string name="fix_video_file_not_exist">الملف غير موجود.</string>
515+
<string name="fix_video_fixed_exists">ملف FIXED_ موجود بالفعل.</string>
516+
<string name="fix_video_internal_only">الإصلاح مدعوم فقط للملفات الداخلية أو القابلة للوصول.</string>
517+
<string name="fix_video_saf_copy_fail">فشل نسخ فيديو SAF للإصلاح.</string>
518+
<string name="fix_video_saf_write_fail">لا يمكن حفظ الملف المُصلح في مجلد SAF. تم الحفظ في تخزين التطبيق.</string>
519+
<string name="fix_video_saf_success">تم إصلاح الفيديو! تم الحفظ باسم: %1$s.</string>
520+
<string name="fix_video_saf_export">يمكنك تصدير أو مشاركة الملف المُصلح من تخزين التطبيق إذا لزم الأمر.</string>
521+
508522
</resources>

app/src/main/res/values-el/strings.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,4 +603,18 @@
603603
<string name="toast_no_youtube_app">Η εφαρμογή YouTube δεν βρέθηκε</string>
604604
<string name="toast_no_drive_app">Η εφαρμογή Google Drive δεν βρέθηκε</string>
605605
<string name="dialog_audio_noise_suppression_label">Καταστολή Θορύβου</string>
606+
607+
<string name="fix_video_menu_title">Επιδιόρθωση βίντεο (Επιδιόρθωση αναπαραγωγής)</string>
608+
<string name="fix_video_repairing">Επιδιόρθωση… Αυτό μπορεί να διαρκέσει μερικά δευτερόλεπτα.</string>
609+
<string name="fix_video_success">Το βίντεο επιδιορθώθηκε! Αποθηκεύτηκε ως: %1$s</string>
610+
<string name="fix_video_fail">Η επιδιόρθωση απέτυχε. Δείτε τα αρχεία καταγραφής για λεπτομέρειες.</string>
611+
<string name="fix_video_invalid">Μη έγκυρο αρχείο βίντεο.</string>
612+
<string name="fix_video_file_not_exist">Το αρχείο δεν υπάρχει.</string>
613+
<string name="fix_video_fixed_exists">Το αρχείο FIXED_ υπάρχει ήδη.</string>
614+
<string name="fix_video_internal_only">Η επιδιόρθωση υποστηρίζεται μόνο για εσωτερικά ή προσβάσιμα αρχεία.</string>
615+
<string name="fix_video_saf_copy_fail">Αποτυχία αντιγραφής βίντεο SAF για επιδιόρθωση.</string>
616+
<string name="fix_video_saf_write_fail">Δεν είναι δυνατή η αποθήκευση του επιδιορθωμένου αρχείου στο φάκελο SAF. Αποθηκεύτηκε στην αποθήκευση της εφαρμογής.</string>
617+
<string name="fix_video_saf_success">Το βίντεο επιδιορθώθηκε! Αποθηκεύτηκε ως: %1$s.</string>
618+
<string name="fix_video_saf_export">Εξάγετε ή μοιραστείτε το επιδιορθωμένο αρχείο από την αποθήκευση της εφαρμογής αν χρειάζεται.</string>
619+
606620
</resources>

app/src/main/res/values-fr/strings.xml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Astuce : Avec H.265 (HEVC), vous pouvez utiliser environ la moitié du débit de
208208
<string name="records_options_title">Options d\'Enregistrements</string>
209209
<string name="setting_theme_title">Thème de l\'Application</string>
210210
<string name="settings_option_theme">Choisir le Thème</string>
211-
<string name="settings_theme_changed">Thème modifié. Application des changements...</string>
211+
<string name="settings_theme_changed">Thème modifié. Application des changements</string>
212212
<string name="note_theme">Choisissez votre thème d\'application préféré</string>
213213

214214
<!-- App Icon Strings -->
@@ -369,7 +369,7 @@ Recommandé : 192000 bps, 48000 Hz pour la plupart des utilisateurs.</string>
369369
<string name="disable_battery_optimization">Désactiver l\'Optimisation de la Batterie</string>
370370

371371
<string name="onboarding_human_title">Vérifier si vous êtes humain</string>
372-
<string name="onboarding_human_desc">Hmmmm... d\'accord. Ces règles sont non-négociables. Si vous ne pouvez pas accepter, veuillez désinstaller FadCam maintenant.</string>
372+
<string name="onboarding_human_desc">Hmmmm d\'accord. Ces règles sont non-négociables. Si vous ne pouvez pas accepter, veuillez désinstaller FadCam maintenant.</string>
373373
<string name="onboarding_human_checkbox1">Je n\'utiliserai pas FadCam pour enfreindre des lois, envahir la vie privée de quiconque, ou avoir un comportement déplacé. Le développeur n\'est pas responsable si je fais une erreur. (C\'est ma responsabilité.)</string>
374374
<string name="onboarding_human_checkbox2">J\'aime déjà FadCam avant même de l\'utiliser. (Non optionnel)</string>
375375
<string name="onboarding_human_button">Laissez-moi entrer !</string>
@@ -517,4 +517,18 @@ Recommandé : 192000 bps, 48000 Hz pour la plupart des utilisateurs.</string>
517517
<string name="toast_no_drive_app">Application Google Drive introuvable</string>
518518

519519
<string name="dialog_audio_noise_suppression_label">Suppression du bruit</string>
520+
521+
<string name="fix_video_menu_title">Réparer la vidéo (Lecture)</string>
522+
<string name="fix_video_repairing">Réparation… Cela peut prendre quelques secondes.</string>
523+
<string name="fix_video_success">Vidéo réparée ! Enregistrée sous : %1$s</string>
524+
<string name="fix_video_fail">Échec de la réparation. Voir les logs pour plus de détails.</string>
525+
<string name="fix_video_invalid">Fichier vidéo invalide.</string>
526+
<string name="fix_video_file_not_exist">Le fichier n\'existe pas.</string>
527+
<string name="fix_video_fixed_exists">Le fichier FIXED_ existe déjà.</string>
528+
<string name="fix_video_internal_only">La réparation n\'est prise en charge que pour les fichiers internes ou accessibles.</string>
529+
<string name="fix_video_saf_copy_fail">Échec de la copie du fichier SAF pour réparation.</string>
530+
<string name="fix_video_saf_write_fail">Impossible d\'écrire le fichier réparé dans le dossier SAF. Enregistré dans le stockage de l\'application.</string>
531+
<string name="fix_video_saf_success">Vidéo réparée ! Enregistrée sous : %1$s.</string>
532+
<string name="fix_video_saf_export">Exportez ou partagez le fichier réparé depuis le stockage de l\'application si besoin.</string>
533+
520534
</resources>

app/src/main/res/values-in/strings.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,4 +509,17 @@ Direkomendasikan: 192000 bps, 48000 Hz untuk kebanyakan pengguna.</string>
509509
<string name="toast_no_drive_app">Aplikasi Google Drive tidak ditemukan</string>
510510

511511
<string name="dialog_audio_noise_suppression_label">Penekanan Kebisingan</string>
512+
<string name="fix_video_menu_title">Perbaiki Video (Perbaiki Pemutaran)</string>
513+
<string name="fix_video_repairing">Memperbaiki… Ini mungkin memakan waktu beberapa detik.</string>
514+
<string name="fix_video_success">Video berhasil diperbaiki! Disimpan sebagai: %1$s</string>
515+
<string name="fix_video_fail">Perbaikan gagal. Lihat log untuk detailnya.</string>
516+
<string name="fix_video_invalid">File video tidak valid.</string>
517+
<string name="fix_video_file_not_exist">File tidak ditemukan.</string>
518+
<string name="fix_video_fixed_exists">File FIXED_ sudah ada.</string>
519+
<string name="fix_video_internal_only">Perbaikan hanya didukung untuk file internal atau yang dapat diakses.</string>
520+
<string name="fix_video_saf_copy_fail">Gagal menyalin video SAF untuk diperbaiki.</string>
521+
<string name="fix_video_saf_write_fail">Tidak dapat menulis file hasil perbaikan ke folder SAF. Disimpan di penyimpanan aplikasi.</string>
522+
<string name="fix_video_saf_success">Video berhasil diperbaiki! Disimpan sebagai: %1$s.</string>
523+
<string name="fix_video_saf_export">Ekspor atau bagikan file hasil perbaikan dari penyimpanan aplikasi jika diperlukan.</string>
524+
512525
</resources>

app/src/main/res/values-it/strings.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,4 +437,17 @@ Consigliato: 192000 bps, 48000 Hz per la maggior parte degli utenti.</string>
437437
<string name="toast_no_drive_app">App Google Drive non trovata</string>
438438

439439
<string name="dialog_audio_noise_suppression_label">Soppressione del rumore</string>
440+
<string name="fix_video_menu_title">Ripara Video (Ripristina Riproduzione)</string>
441+
<string name="fix_video_repairing">Riparazione in corso… Potrebbe richiedere alcuni secondi.</string>
442+
<string name="fix_video_success">Video riparato! Salvato come: %1$s</string>
443+
<string name="fix_video_fail">Riparazione fallita. Vedi i log per i dettagli.</string>
444+
<string name="fix_video_invalid">File video non valido.</string>
445+
<string name="fix_video_file_not_exist">Il file non esiste.</string>
446+
<string name="fix_video_fixed_exists">Il file FIXED_ esiste già.</string>
447+
<string name="fix_video_internal_only">La riparazione è supportata solo per file interni o accessibili.</string>
448+
<string name="fix_video_saf_copy_fail">Copia del video SAF per la riparazione non riuscita.</string>
449+
<string name="fix_video_saf_write_fail">Impossibile scrivere il file riparato nella cartella SAF. Salvato nella memoria dell\'app.</string>
450+
<string name="fix_video_saf_success">Video riparato! Salvato come: %1$s.</string>
451+
<string name="fix_video_saf_export">Esporta o condividi il file riparato dalla memoria dell\'app se necessario.</string>
452+
440453
</resources>

0 commit comments

Comments
 (0)