From b7fa64055aeef61fe46dc46a97ede5679e63ca11 Mon Sep 17 00:00:00 2001 From: pajowu Date: Sun, 10 Dec 2023 00:46:02 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20Allow=20user=20to=20disable=20v?= =?UTF-8?q?ideo=20preview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/editor/player.tsx | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/frontend/src/editor/player.tsx b/frontend/src/editor/player.tsx index 1d7a6097..8fb4f3f0 100644 --- a/frontend/src/editor/player.tsx +++ b/frontend/src/editor/player.tsx @@ -16,6 +16,7 @@ import { sortMediaFiles, useAudio } from '../utils/use_audio'; import { minutesInMs } from '../utils/duration_in_ms'; import { formattedTime } from './transcription_editor'; import { IconType } from 'react-icons'; +import { BiVideo, BiVideoOff } from 'react-icons/bi'; const DOUBLE_TAP_THRESHOLD_MS = 250; const SKIP_BUTTON_SEC = 2; @@ -42,7 +43,7 @@ export function PlayerBar({ const [playbackRate, setPlaybackRate] = useLocalStorage('playbackRate', 1); - const { sources, hasVideo } = useMemo(() => { + const { videoSources, audioSources, hasVideo } = useMemo(() => { // do not play the original file, it may be large const relevantMediaFiles = data?.media_files.filter((media) => !media.tags.includes('original')) || []; @@ -50,30 +51,36 @@ export function PlayerBar({ const videoFiles = relevantMediaFiles.filter((media) => media.tags.includes('video')); const audioFiles = relevantMediaFiles.filter((media) => !media.tags.includes('video')); - const mappedFiles = [...videoFiles, ...audioFiles].map((media) => { + const mapFile = (media: (typeof relevantMediaFiles)[0]) => { return { src: media.url, type: media.content_type, }; - }); + }; + + const mappedVideoFiles = videoFiles.map(mapFile); + const mappedAudioFiles = audioFiles.map(mapFile); return { - sources: sortMediaFiles(mappedFiles), + videoSources: sortMediaFiles(mappedVideoFiles), + audioSources: sortMediaFiles(mappedAudioFiles), hasVideo: videoFiles.length > 0, }; }, [data?.media_files]); + const [showVideo, setShowVideo] = useState(hasVideo); + const audio = useAudio({ playbackRate, - sources, - videoPreview: hasVideo, + sources: showVideo ? videoSources : audioSources, + videoPreview: showVideo, }); useEffect(() => { if (onShowVideo) { - onShowVideo(hasVideo); + onShowVideo(showVideo); } - }, [hasVideo]); + }, [showVideo]); // calculate the start of the current element to color it const [currentElementStartTime, setCurrentElementStartTime] = useState(0.0); @@ -207,6 +214,13 @@ export function PlayerBar({ + {hasVideo && ( + setShowVideo(!showVideo)} + /> + )} ); From ed407ad0447ec44bc368c82e306cf8d7a45ddc05 Mon Sep 17 00:00:00 2001 From: pajowu Date: Sun, 10 Dec 2023 00:50:48 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20Play=20original=20file=20if=20i?= =?UTF-8?q?t's=20the=20only=20source?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/editor/player.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/editor/player.tsx b/frontend/src/editor/player.tsx index 8fb4f3f0..44413e53 100644 --- a/frontend/src/editor/player.tsx +++ b/frontend/src/editor/player.tsx @@ -45,9 +45,18 @@ export function PlayerBar({ const { videoSources, audioSources, hasVideo } = useMemo(() => { // do not play the original file, it may be large - const relevantMediaFiles = + let relevantMediaFiles = data?.media_files.filter((media) => !media.tags.includes('original')) || []; + // but if the original is all we have, better play this than nothing at all + if ( + relevantMediaFiles.length == 0 && + data?.media_files !== undefined && + data?.media_files.length > 0 + ) { + relevantMediaFiles = data.media_files; + } + const videoFiles = relevantMediaFiles.filter((media) => media.tags.includes('video')); const audioFiles = relevantMediaFiles.filter((media) => !media.tags.includes('video')); From a8f051dc07b6e7a46a3404c12a23d8cfed580ed4 Mon Sep 17 00:00:00 2001 From: pajowu Date: Sun, 10 Dec 2023 02:20:28 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Cleanup=20source=20sor?= =?UTF-8?q?ting=20&=20only=20load=20src=20after=20document=20loaded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/document.ts | 4 +- frontend/src/editor/export/index.tsx | 4 +- frontend/src/editor/export/transcribee.tsx | 16 ++--- frontend/src/editor/player.tsx | 66 +++++++++----------- frontend/src/editor/transcription_editor.tsx | 4 +- frontend/src/utils/use_audio.ts | 11 +++- 6 files changed, 51 insertions(+), 54 deletions(-) diff --git a/frontend/src/api/document.ts b/frontend/src/api/document.ts index ae86e5c8..5f59846d 100644 --- a/frontend/src/api/document.ts +++ b/frontend/src/api/document.ts @@ -1,4 +1,4 @@ -import { fetcher, makeSwrHook } from '../api'; +import { RequestDataType, fetcher, makeSwrHook } from '../api'; export const listDocuments = fetcher.path('/api/v1/documents/').method('get').create(); export const createDocument = fetcher @@ -19,7 +19,7 @@ export const useListDocuments = makeSwrHook('listDocuments', listDocuments); export const useGetDocument = makeSwrHook('getDocument', getDocument); export const useGetDocumentTasks = makeSwrHook('getDocumentTasks', getDocumentTasks); -export type ApiDocument = ReturnType['data']; +export type ApiDocument = RequestDataType; export const deleteDocument = fetcher .path('/api/v1/documents/{document_id}/') diff --git a/frontend/src/editor/export/index.tsx b/frontend/src/editor/export/index.tsx index b8241b84..c7e330fe 100644 --- a/frontend/src/editor/export/index.tsx +++ b/frontend/src/editor/export/index.tsx @@ -12,7 +12,7 @@ export type ExportProps = { outputNameBase: string; editor: Editor; onClose: () => void; - document: ApiDocument; + document?: ApiDocument; }; export type ExportType = { @@ -48,7 +48,7 @@ export function ExportModal({ }: { onClose: () => void; editor: Editor; - document: ApiDocument; + document?: ApiDocument; } & Omit, 'label'>) { const [exportType, setExportType] = useState(exportTypes[0]); const ExportBodyComponent = exportType.component; diff --git a/frontend/src/editor/export/transcribee.tsx b/frontend/src/editor/export/transcribee.tsx index bf9a6461..27b5de06 100644 --- a/frontend/src/editor/export/transcribee.tsx +++ b/frontend/src/editor/export/transcribee.tsx @@ -5,23 +5,19 @@ import { Checkbox } from '../../components/form'; import { downloadBinaryAsFile } from '../../utils/download_text_as_file'; import { ExportProps } from '.'; import { HttpReader, Uint8ArrayReader, ZipWriter, Uint8ArrayWriter } from '@zip.js/zip.js'; -import { sortMediaFiles } from '../../utils/use_audio'; import { LoadingSpinnerButton, SecondaryButton } from '../../components/button'; +import { splitAndSortMediaFiles } from '../player'; export function TranscribeeExportBody({ onClose, outputNameBase, editor, document }: ExportProps) { const [loading, setLoading] = useState(false); const [includeOriginalMediaFile, setIncludeOriginalMediaFile] = useState(false); const bestMediaUrl = useMemo(() => { - const mappedFiles = - document?.media_files.map((media) => { - return { - src: media.url, - type: media.content_type, - }; - }) || []; - - return sortMediaFiles(mappedFiles)[0].src; + const { videoSources, audioSources, hasVideo } = splitAndSortMediaFiles( + document?.media_files || [], + ); + const bestSource = hasVideo ? videoSources[0] : audioSources[0]; + return bestSource.src; }, [document?.media_files]); const originalMediaUrl = useMemo(() => { diff --git a/frontend/src/editor/player.tsx b/frontend/src/editor/player.tsx index 44413e53..8200122d 100644 --- a/frontend/src/editor/player.tsx +++ b/frontend/src/editor/player.tsx @@ -2,7 +2,7 @@ import clsx from 'clsx'; import { IconButton } from '../components/button'; import { ImBackward2, ImPause2, ImPlay3 } from 'react-icons/im'; import { useCallback, useMemo, useEffect, useState, useRef, useContext } from 'react'; -import { useGetDocument } from '../api/document'; +import { ApiDocument, useGetDocument } from '../api/document'; import { CssRule } from '../utils/cssdom'; import { SEEK_TO_EVENT, SeekToEvent } from './types'; import { useEvent } from '../utils/use_event'; @@ -24,6 +24,28 @@ const SKIP_SHORTCUT_SEC = 3; let lastTabPressTs = 0; +export function splitAndSortMediaFiles(mediaFiles: ApiDocument['media_files']) { + const videoFiles = mediaFiles.filter((media) => media.tags.includes('video')); + const audioFiles = mediaFiles.filter((media) => !media.tags.includes('video')); + + const mapFile = (media: (typeof mediaFiles)[0]) => { + return { + src: media.url, + type: media.content_type, + tags: media.tags, + }; + }; + + const mappedVideoFiles = videoFiles.map(mapFile); + const mappedAudioFiles = audioFiles.map(mapFile); + + return { + videoSources: sortMediaFiles(mappedVideoFiles), + audioSources: sortMediaFiles(mappedAudioFiles), + hasVideo: videoFiles.length > 0, + }; +} + export function PlayerBar({ documentId, editor, @@ -41,43 +63,15 @@ export function PlayerBar({ }, ); - const [playbackRate, setPlaybackRate] = useLocalStorage('playbackRate', 1); - - const { videoSources, audioSources, hasVideo } = useMemo(() => { - // do not play the original file, it may be large - let relevantMediaFiles = - data?.media_files.filter((media) => !media.tags.includes('original')) || []; - - // but if the original is all we have, better play this than nothing at all - if ( - relevantMediaFiles.length == 0 && - data?.media_files !== undefined && - data?.media_files.length > 0 - ) { - relevantMediaFiles = data.media_files; - } - - const videoFiles = relevantMediaFiles.filter((media) => media.tags.includes('video')); - const audioFiles = relevantMediaFiles.filter((media) => !media.tags.includes('video')); - - const mapFile = (media: (typeof relevantMediaFiles)[0]) => { - return { - src: media.url, - type: media.content_type, - }; - }; - - const mappedVideoFiles = videoFiles.map(mapFile); - const mappedAudioFiles = audioFiles.map(mapFile); + const { videoSources, audioSources, hasVideo } = useMemo( + () => splitAndSortMediaFiles(data?.media_files || []), + [data?.media_files], + ); - return { - videoSources: sortMediaFiles(mappedVideoFiles), - audioSources: sortMediaFiles(mappedAudioFiles), - hasVideo: videoFiles.length > 0, - }; - }, [data?.media_files]); + const [playbackRate, setPlaybackRate] = useLocalStorage('playbackRate', 1); - const [showVideo, setShowVideo] = useState(hasVideo); + const [_showVideo, setShowVideo] = useState(true); + const showVideo = _showVideo && hasVideo; const audio = useAudio({ playbackRate, diff --git a/frontend/src/editor/transcription_editor.tsx b/frontend/src/editor/transcription_editor.tsx index c5becb43..e1096e6a 100644 --- a/frontend/src/editor/transcription_editor.tsx +++ b/frontend/src/editor/transcription_editor.tsx @@ -314,7 +314,9 @@ export function TranscriptionEditor({ className={clsx('2xl:-ml-20')} /> - + {!loadingState[0] && ( + + )} diff --git a/frontend/src/utils/use_audio.ts b/frontend/src/utils/use_audio.ts index 67410447..0d7b026d 100644 --- a/frontend/src/utils/use_audio.ts +++ b/frontend/src/utils/use_audio.ts @@ -158,16 +158,21 @@ const MEDIA_PRIORITY = [ 'audio/mpeg', ]; -export function sortMediaFiles(mediaFiles: T[]) { +export function sortMediaFiles(mediaFiles: T[]) { const sorted = []; + const relevantMediaFiles = mediaFiles.filter((media) => !media.tags.includes('original')); + const originalMediaFiles = mediaFiles.filter((media) => media.tags.includes('original')); + for (const contentType of MEDIA_PRIORITY) { - const files = mediaFiles.filter((file) => file.type == contentType); + const files = relevantMediaFiles.filter((file) => file.type == contentType); sorted.push(...files); } - const rest = mediaFiles.filter((file) => !MEDIA_PRIORITY.includes(file.type)); + const rest = relevantMediaFiles.filter((file) => !MEDIA_PRIORITY.includes(file.type)); sorted.push(...rest); + sorted.push(...originalMediaFiles); + return sorted; }