// Request from sign language tool, issued when a complete recording has been captured. // It is passed as a binary blob that is the actual content that needs to be made into // an mp4 file. (At this point we don't try to handle recordings too big for this approach.) // We make a file (with an arbitrary guid name) and attempt to make it the recording for the // first page element with class bloom-videoContainer. private void HandleRecordedVideoRequest(ApiRequest request) { lock (request) { var videoFolder = BookStorage.GetVideoDirectoryAndEnsureExistence(CurrentBook.FolderPath); var fileName = GetNewVideoFileName(); var path = Path.Combine(videoFolder, fileName); using (var rawVideo = TempFile.CreateAndGetPathButDontMakeTheFile()) { using (var rawVideoOutput = new FileStream(rawVideo.Path, FileMode.Create)) { // Do NOT just get RawPostData and try to write it to a file; this // typically runs out of memory for anything more than about 2min of video. // (It write to a MemoryStream, and this seems to manage memory very badly. // 66MB should not be a huge problem, but somehow it is.) using (var rawVideoInput = request.RawPostStream) { rawVideoInput.CopyTo(rawVideoOutput); } } // The output stream should be closed before trying to access the newly written file. SaveVideoFile(path, rawVideo.Path); } var relativePath = BookStorage.GetVideoFolderName + Path.GetFileName(path); request.ReplyWithText(UrlPathString.CreateFromUnencodedString(relativePath).UrlEncodedForHttpPath); } }
// Request from sign language tool to import a video. private void HandleImportVideoRequest(ApiRequest request) { string path = null; View.Invoke((Action)(() => { var videoFiles = LocalizationManager.GetString("EditTab.Toolbox.SignLanguage.FileDialogVideoFiles", "Video files"); var dlg = new DialogAdapters.OpenFileDialogAdapter { Multiselect = false, CheckFileExists = true, Filter = $"{videoFiles} (*.mp4)|*.mp4" }; var result = dlg.ShowDialog(); if (result == DialogResult.OK) { path = dlg.FileName; } })); if (!string.IsNullOrEmpty(path)) { _importedVideoIntoBloom = true; var newVideoPath = Path.Combine(BookStorage.GetVideoDirectoryAndEnsureExistence(CurrentBook.FolderPath), GetNewVideoFileName()); // Use a new name to defeat caching. RobustFile.Copy(path, newVideoPath); var relativePath = BookStorage.GetVideoFolderName + Path.GetFileName(newVideoPath); request.ReplyWithText(UrlPathString.CreateFromUnencodedString(relativePath).UrlEncodedForHttpPath); } else { // If the user canceled, we didn't exactly succeed, but having the user cancel is such a normal // event that posting a failure, which is a nuisance to ignore, is not warranted. request.ReplyWithText(""); } }
/// <summary> /// In case the user has been editing video files outside of Bloom, for example, /// after using the Show In Folder command in the sign language toolbox, /// we want to update the pages that show those videos to use the new versions. /// Unfortunately something, probably the browser itself, seems to be very /// persistent about caching videos, even though our server tells it not to cache /// things in the book folder (and debug builds don't ever tell it to cache anything). /// We prevent cache hits on modified files by adding a fake param to the URI. /// (Also we will remove any trimming...see comment below.) /// This function is called whenever Bloom is activated in Edit mode. /// </summary> /// <param name="sender"></param> /// <param name="eventArgs"></param> public void CheckForChangedVideoOnActivate(object sender, EventArgs eventArgs) { // We're only going to check for video changes on the current book, if any. // It's not inconceivable that whatever caches videos will keep a cached one // for another book, but I think trying to modify books we don't even have open // is to dangerous, as well as quite difficult. if (CurrentBook == null) { return; } // On Linux, this method interferes with successfully referencing a newly imported video file. // See https://issues.bloomlibrary.org/youtrack/issue/BL-6723. Ignoring just the one call to // this method suffices for things to work. (On Windows, the sequence of events differs, but // this change is safe.) if (_importedVideoIntoBloom) { _importedVideoIntoBloom = false; return; } var videoFolderPath = BookStorage.GetVideoDirectoryAndEnsureExistence(CurrentBook.FolderPath); var filesModifiedSinceDeactivate = new DirectoryInfo(videoFolderPath) .GetFiles("*.mp4") .Where(f => GetRealLastModifiedTime(f) > DeactivateTime) .Select(f => f.FullName) .ToList(); if (!filesModifiedSinceDeactivate.Any()) { return; } // We might modify the current page, but the user may also have modified it // without doing anything to cause a Save before the deactivate. So save their // changes before we go to work on it. Model.SaveNow(); foreach (var videoPath in filesModifiedSinceDeactivate) { var expectedSrcAttr = UrlPathString.CreateFromUnencodedString(BookStorage.GetVideoFolderName + Path.GetFileName(videoPath)); var videoElts = CurrentBook.RawDom.SafeSelectNodes($"//video/source[contains(@src,'{expectedSrcAttr.UrlEncodedForHttpPath}')]"); if (videoElts.Count == 0) { continue; // not used in book, ignore } // OK, the user has modified the file outside of Bloom. Something is determined to cache video. // Defeat it by setting a fake param. // Note that doing this will discard any fragment in the existing URL, typically trimming. // I think this is good...if the user has edited the video, we should start over assuming he // wants all of it. var newSrcAttr = UrlPathString.CreateFromUnencodedString(BookStorage.GetVideoFolderName + Path.GetFileName(videoPath)); HtmlDom.SetSrcOfVideoElement(newSrcAttr, new ElementProxy((XmlElement)videoElts[0]), true, "?now=" + DateTime.Now.Ticks); } // We could try to figure out whether one of the modified videos is on the current page. // But that's the most likely video to be modified, and it doesn't take long to reload, // and this only happens in the very special case that the user has modified a video outside // of Bloom. View.UpdateSingleDisplayedPage(_pageSelection.CurrentSelection); // Likewise, this is probably overkill, but it's a probably-rare case. View.UpdateAllThumbnails(); }