@@ -604,8 +653,7 @@ const VideoRecorder = ({
setSelectedCamera(device.deviceId);
setShowCameraMenu(false);
}}
- className={`w-full text-left px-3 py-2 rounded hover:bg-gray-100 text-sm ${selectedCamera === device.deviceId ? 'bg-gray-200 font-medium' : ''
- }`}
+ className={`w-full text-left px-3 py-2 rounded hover:bg-gray-100 text-sm ${selectedCamera === device.deviceId ? 'bg-gray-200 font-medium' : ''}`}
>
{device.label || `Camera ${device.deviceId.substring(0, 5)}`}
@@ -638,12 +686,31 @@ VideoRecorder.propTypes = {
setNotify: PropTypes.func.isRequired,
setSnackText: PropTypes.func.isRequired,
setOpenSnackBar: PropTypes.func.isRequired,
+ fileNameOverride: PropTypes.string,
+ titleOverride: PropTypes.string,
+ hideVerseNavigation: PropTypes.bool,
+ disableExistingVideoCheck: PropTypes.bool,
+ allowOverwriteExistingVideo: PropTypes.bool,
+ hideDeleteButton: PropTypes.bool,
+ commentCount: PropTypes.number,
+ onOpenComments: PropTypes.func,
+ showCommentsButton: PropTypes.bool,
};
VideoRecorder.defaultProps = {
onRecordingComplete: null,
onVerseChange: null,
mode: 'record',
+ fileNameOverride: null,
+ titleOverride: null,
+ hideVerseNavigation: false,
+ disableExistingVideoCheck: false,
+ allowOverwriteExistingVideo: false,
+ hideDeleteButton: false,
+ commentCount: 0,
+ onOpenComments: null,
+ showCommentsButton: false,
+
};
export default VideoRecorder;
diff --git a/renderer/src/components/Sync/Gitea/SyncFromGitea.js b/renderer/src/components/Sync/Gitea/SyncFromGitea.js
index ccea72bb2..5da162f49 100644
--- a/renderer/src/components/Sync/Gitea/SyncFromGitea.js
+++ b/renderer/src/components/Sync/Gitea/SyncFromGitea.js
@@ -41,8 +41,9 @@ export async function downloadFromGitea(repo, auth, setSyncProgress, notifyStatu
logger.debug('SyncFromGitea.js', 'in SyncFromGiea : fetch and parse metaData Success');
// Validate the burrito
const success = await validate('metadata', 'gitea/metadata.json', JSON.stringify(metaDataSB), metaDataSB.meta.version);
+ const flavorName = metaDataSB.type?.flavorType?.flavor?.name;
// if success proceed else raise error
- if (success) {
+ if (success || flavorName === 'videoTranslation') {
logger.debug('SyncFromGitea.js', 'in SyncFromGiea : metaData SB validated');
// setProjectData
setSyncProgress((prev) => ({
diff --git a/renderer/src/components/hooks/video/useVerseJoining.js b/renderer/src/components/hooks/video/useVerseJoining.js
index 9a8175ce1..9f2843179 100644
--- a/renderer/src/components/hooks/video/useVerseJoining.js
+++ b/renderer/src/components/hooks/video/useVerseJoining.js
@@ -60,6 +60,10 @@ export const useVerseJoining = ({
isPreCombined: verse.isPreCombined || false,
};
+ if (Array.isArray(verse.comments) && verse.comments.length > 0) {
+ verseData.comments = verse.comments;
+ }
+
if (verse.joinedVerses && verse.joinedVerses.length > 0) {
verseData.verseSegments = verse.joinedVerses.map((vNum) => {
const freshText = getOriginalVerseText(
@@ -237,6 +241,10 @@ export const useVerseJoining = ({
verseText: combinedText,
joinedVerses,
isPreCombined: false,
+ comments: [
+ ...(previousVerse.comments || []),
+ ...(currentVerse.comments || []),
+ ],
};
updatedContent.splice(currentVerseIndex, 1);
@@ -376,10 +384,19 @@ export const useVerseJoining = ({
logger.debug('No USFM available, skipping validation for remaining verses');
}
+ const firstVerseComments = (verse.comments || []).filter(
+ (comment) => String(comment.verseNumber) === String(firstVerseNum),
+ );
+
+ const remainingVerseComments = (verse.comments || []).filter(
+ (comment) => String(comment.verseNumber) !== String(firstVerseNum),
+ );
+
const firstVerseEntry = {
verseNumber: firstVerseNum.toString(),
verseText: firstVerseText || '',
isPreCombined: false,
+ comments: firstVerseComments,
};
let remainingVerseEntry;
@@ -392,6 +409,7 @@ export const useVerseJoining = ({
verseNumber: singleNum.toString(),
verseText: singleText,
isPreCombined: false,
+ comments: remainingVerseComments,
};
logger.debug('Split into two single verses:', {
@@ -445,6 +463,7 @@ export const useVerseJoining = ({
joinedVerses: remainingVerses,
verseSegments: segments,
isPreCombined: false,
+ comments: remainingVerseComments,
};
logger.debug('Split into single verse and range:', {
diff --git a/renderer/src/components/hooks/video/useVideoPlayback.js b/renderer/src/components/hooks/video/useVideoPlayback.js
index 6c9bf386b..eab9ccb63 100644
--- a/renderer/src/components/hooks/video/useVideoPlayback.js
+++ b/renderer/src/components/hooks/video/useVideoPlayback.js
@@ -11,6 +11,7 @@ export const useVideoPlayback = ({
projectPath,
videoPreviewRef,
onError,
+ fileNameOverride,
}) => {
const [isPlaying, setIsPlaying] = useState(false);
const [playbackTime, setPlaybackTime] = useState(0);
@@ -23,14 +24,16 @@ export const useVideoPlayback = ({
useEffect(() => {
if (currentMode === 'view' && hasVideo && videoPreviewRef.current) {
- const videoExt = ['webm', 'mp4'].find((ext) => fs.existsSync(path.join(projectPath, `${chapter}_${verse}.${ext}`)));
+ const videoExt = fileNameOverride
+ ? path.extname(fileNameOverride).replace('.', '')
+ : ['webm', 'mp4'].find((ext) => fs.existsSync(path.join(projectPath, `${chapter}_${verse}.${ext}`)));
if (!videoExt) {
onError('Video file not found');
return;
}
- const filename = `${chapter}_${verse}.${videoExt}`;
+ const filename = fileNameOverride || `${chapter}_${verse}.${videoExt}`;
const fullPath = path.join(projectPath, filename);
try {
@@ -108,7 +111,7 @@ export const useVideoPlayback = ({
};
}, 50);
}
- }, [currentMode, hasVideo, verse, chapter, projectPath]);
+ }, [currentMode, hasVideo, verse, chapter, projectPath, fileNameOverride]);
useEffect(() => {
if (currentMode === 'view' && videoPreviewRef.current) {
diff --git a/renderer/src/components/hooks/video/useVideoRecording.js b/renderer/src/components/hooks/video/useVideoRecording.js
index f8317b677..c3595c712 100644
--- a/renderer/src/components/hooks/video/useVideoRecording.js
+++ b/renderer/src/components/hooks/video/useVideoRecording.js
@@ -8,6 +8,7 @@ export const useVideoRecording = ({
streamRef,
onSaveComplete,
onError,
+ fileNameOverride,
}) => {
const [isRecording, setIsRecording] = useState(false);
const [isPaused, setIsPaused] = useState(false);
@@ -22,15 +23,38 @@ export const useVideoRecording = ({
const saveVideo = useCallback(async (blob) => {
setIsProcessing(true);
+ let tempPath = null;
+ let backupPath = null;
+ let filePath = null;
try {
const arrayBuffer = await blob.arrayBuffer();
const buffer = Buffer.from(arrayBuffer);
- const filename = `${chapter}_${verse}.webm`;
- const filePath = path.join(projectPath, filename);
-
- fs.writeFileSync(filePath, buffer);
+ const filename = fileNameOverride || `${chapter}_${verse}.webm`;
+ filePath = path.join(projectPath, filename);
+
+ if (fileNameOverride && fs.existsSync(filePath)) {
+ tempPath = path.join(
+ projectPath,
+ `.${filename}.${Date.now()}.tmp`,
+ );
+ backupPath = path.join(
+ projectPath,
+ `.${filename}.${Date.now()}.backup`,
+ );
+
+ fs.writeFileSync(tempPath, buffer);
+ fs.renameSync(filePath, backupPath);
+ fs.renameSync(tempPath, filePath);
+ try {
+ fs.unlinkSync(backupPath);
+ } catch (cleanupErr) {
+ logger.warn('Could not remove previous video backup:', cleanupErr);
+ }
+ } else {
+ fs.writeFileSync(filePath, buffer);
+ }
if (onSaveComplete) {
onSaveComplete({
@@ -45,12 +69,22 @@ export const useVideoRecording = ({
setIsProcessing(false);
return true;
} catch (err) {
+ try {
+ if (backupPath && filePath && fs.existsSync(backupPath) && !fs.existsSync(filePath)) {
+ fs.renameSync(backupPath, filePath);
+ }
+ if (tempPath && fs.existsSync(tempPath)) {
+ fs.unlinkSync(tempPath);
+ }
+ } catch (restoreErr) {
+ logger.error('Error restoring previous video after save failure:', restoreErr);
+ }
logger.error('Error saving video:', err);
onError(`Failed to save video: ${err.message}`);
setIsProcessing(false);
return false;
}
- }, [chapter, verse, projectPath, onSaveComplete, onError]);
+ }, [chapter, verse, projectPath, onSaveComplete, onError, fileNameOverride]);
const startRecording = useCallback(async () => {
if (!streamRef.current) {
@@ -59,10 +93,10 @@ export const useVideoRecording = ({
}
try {
- const filename = `${chapter}_${verse}.webm`;
+ const filename = fileNameOverride || `${chapter}_${verse}.webm`;
const filePath = path.join(projectPath, filename);
- if (fs.existsSync(filePath)) {
+ if (!fileNameOverride && fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
logger.debug('Deleted existing video for re-recording');
}
@@ -108,7 +142,7 @@ export const useVideoRecording = ({
} catch (err) {
onError('Failed to start recording. Please try again.');
}
- }, [chapter, verse, projectPath, streamRef, saveVideo, onError]);
+ }, [chapter, verse, projectPath, streamRef, saveVideo, onError, fileNameOverride]);
const pauseRecording = useCallback(() => {
if (mediaRecorderRef.current && isRecording && !isPaused) {
diff --git a/renderer/src/layouts/editor/ConfirmationModal.js b/renderer/src/layouts/editor/ConfirmationModal.js
index a9c7d4c72..f299315e2 100644
--- a/renderer/src/layouts/editor/ConfirmationModal.js
+++ b/renderer/src/layouts/editor/ConfirmationModal.js
@@ -28,7 +28,7 @@ export default function ConfirmationModal({
return (
-