1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 02:10:24 +00:00

add video details and extract button (#6908)

* add video details and extract button

* update

* update

* update

---------

Co-authored-by: zheng.shen <zheng.shen@seafile.com>
This commit is contained in:
shenzheng-1
2024-10-18 13:25:10 +08:00
committed by GitHub
parent 058b95362e
commit 9fbc7a8d34
9 changed files with 151 additions and 6 deletions

View File

@@ -124,7 +124,7 @@ const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail,
);
let component = dom;
if (Utils.imageCheck(dirent.name)) {
if (Utils.imageCheck(dirent.name) || Utils.videoCheck(dirent.name)) {
const fileDetails = getCellValueByColumn(record, { key: PRIVATE_COLUMN_KEY.FILE_DETAILS });
const fileDetailsJson = JSON.parse(fileDetails?.slice(9, -7) || '{}');
const fileLocation = getCellValueByColumn(record, { key: PRIVATE_COLUMN_KEY.LOCATION });

View File

@@ -222,6 +222,14 @@ class MetadataManagerAPI {
return this.req.post(url, params);
};
extractFileDetails = (repoID, objIds) => {
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/extract-file-details/';
const params = {
obj_ids: objIds,
};
return this.req.post(url, params);
};
zipDownload(repoID, parent_dir, dirents) {
const url = this.server + '/api/v2.1/repos/' + repoID + '/zip-task/';
const form = new FormData();

View File

@@ -218,6 +218,11 @@ class Context {
const lang = this.settings['lang'];
return this.metadataAPI.imageCaption(repoID, filePath, lang);
};
extractFileDetails = (objIds) => {
const repoID = this.settings['repoID'];
return this.metadataAPI.extractFileDetails(repoID, objIds);
};
}
export default Context;

View File

@@ -31,3 +31,11 @@ export const getParentDirFromRecord = (record) => {
export const getFileNameFromRecord = (record) => {
return record ? record[PRIVATE_COLUMN_KEY.FILE_NAME] : '';
};
export const geRecordIdFromRecord = record => {
return record ? record[PRIVATE_COLUMN_KEY.ID] : '';
};
export const getFileObjIdFromRecord = record => {
return record ? record[PRIVATE_COLUMN_KEY.OBJ_ID] : '';
};

View File

@@ -3,6 +3,8 @@ export {
getCellValueByColumn,
getParentDirFromRecord,
getFileNameFromRecord,
geRecordIdFromRecord,
getFileObjIdFromRecord,
} from './core';
export {

View File

@@ -7,7 +7,9 @@ import { useMetadataView } from '../../../hooks/metadata-view';
import { getColumnByKey, isNameColumn } from '../../../utils/column';
import { checkIsDir } from '../../../utils/row';
import { EVENT_BUS_TYPE, EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../../constants';
import { getFileNameFromRecord, getParentDirFromRecord } from '../../../utils/cell';
import { getFileNameFromRecord, getParentDirFromRecord, getFileObjIdFromRecord,
geRecordIdFromRecord,
} from '../../../utils/cell';
import './index.css';
@@ -21,6 +23,8 @@ const OPERATION = {
DELETE_RECORD: 'delete-record',
DELETE_RECORDS: 'delete-records',
RENAME_FILE: 'rename-file',
FILE_DETAIL: 'file-detail',
FILE_DETAILS: 'file-details',
};
const ContextMenu = (props) => {
@@ -74,6 +78,13 @@ const ContextMenu = (props) => {
list.push({ value: OPERATION.DELETE_RECORDS, label: gettext('Delete selected'), records: ableDeleteRecords });
}
const imageOrVideoRecords = records.filter(record => {
const fileName = getFileNameFromRecord(record);
return Utils.imageCheck(fileName) || Utils.videoCheck(fileName);
});
if (imageOrVideoRecords.length > 0) {
list.push({ value: OPERATION.FILE_DETAILS, label: gettext('Extract file details'), records: imageOrVideoRecords });
}
return list;
}
@@ -92,6 +103,13 @@ const ContextMenu = (props) => {
if (ableDeleteRecords.length > 0) {
list.push({ value: OPERATION.DELETE_RECORDS, label: gettext('Delete'), records: ableDeleteRecords });
}
const imageOrVideoRecords = records.filter(record => {
const fileName = getFileNameFromRecord(record);
return Utils.imageCheck(fileName) || Utils.videoCheck(fileName);
});
if (imageOrVideoRecords.length > 0) {
list.push({ value: OPERATION.FILE_DETAILS, label: gettext('Extract file details'), records: imageOrVideoRecords });
}
return list;
}
@@ -107,15 +125,21 @@ const ContextMenu = (props) => {
const isFolder = checkIsDir(record);
list.push({ value: OPERATION.OPEN_IN_NEW_TAB, label: isFolder ? gettext('Open folder in new tab') : gettext('Open file in new tab'), record });
list.push({ value: OPERATION.OPEN_PARENT_FOLDER, label: gettext('Open parent folder'), record });
const fileName = getFileNameFromRecord(record);
if (descriptionColumn) {
if (checkIsDescribableDoc(record)) {
list.push({ value: OPERATION.GENERATE_DESCRIPTION, label: gettext('Generate description'), record });
} else if (canModifyRow && Utils.imageCheck(getFileNameFromRecord(record))) {
} else if (canModifyRow && Utils.imageCheck(fileName)) {
list.push({ value: OPERATION.IMAGE_CAPTION, label: gettext('Generate image description'), record });
}
}
if (canModifyRow && (Utils.imageCheck(fileName) || Utils.videoCheck(fileName))) {
list.push({ value: OPERATION.FILE_DETAIL, label: gettext('Extract file detail'), record: record });
}
// handle delete folder/file
if (canDeleteRow) {
list.push({ value: OPERATION.DELETE_RECORD, label: isFolder ? gettext('Delete folder') : gettext('Delete file'), record });
}
@@ -220,6 +244,40 @@ const ContextMenu = (props) => {
});
}, [updateRecords]);
const updateFileDetails = useCallback((records) => {
const recordObjIds = records.map(record => getFileObjIdFromRecord(record));
if (recordObjIds.length > 50) {
toaster.danger(gettext('Select up to 50 files'));
return;
}
const recordIds = records.map(record => geRecordIdFromRecord(record));
window.sfMetadataContext.extractFileDetails(recordObjIds).then(res => {
const captureColumn = getColumnByKey(metadata.columns, PRIVATE_COLUMN_KEY.CAPTURE_TIME);
if (captureColumn) {
let idOldRecordData = {};
let idOriginalOldRecordData = {};
const captureColumnKey = PRIVATE_COLUMN_KEY.CAPTURE_TIME;
records.forEach(record => {
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [captureColumnKey]: record[captureColumnKey] };
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [captureColumnKey]: record[captureColumnKey] };
});
let idRecordUpdates = {};
let idOriginalRecordUpdates = {};
res.data.details.forEach(detail => {
const updateRecordId = detail[PRIVATE_COLUMN_KEY.ID];
idRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
idOriginalRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
});
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
}
}).catch(error => {
const errorMessage = gettext('Failed to extract file details');
toaster.danger(errorMessage);
});
}, [metadata, updateRecords]);
const handleOptionClick = useCallback((event, option) => {
event.stopPropagation();
switch (option.value) {
@@ -283,12 +341,22 @@ const ContextMenu = (props) => {
window.sfMetadataContext.eventBus.dispatch(METADATA_EVENT_BUS_TYPE.OPEN_EDITOR);
break;
}
case OPERATION.FILE_DETAILS: {
const { records } = option;
updateFileDetails(records);
break;
}
case OPERATION.FILE_DETAIL: {
const { record } = option;
updateFileDetails([record]);
break;
}
default: {
break;
}
}
setVisible(false);
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateDescription, imageCaption, selectNone, deleteRecords, toggleDeleteFolderDialog]);
}, [onOpenFileInNewTab, onOpenParentFolder, onCopySelected, onClearSelected, generateDescription, imageCaption, selectNone, deleteRecords, toggleDeleteFolderDialog, updateFileDetails]);
const getMenuPosition = useCallback((x = 0, y = 0) => {
let menuStyles = {

View File

@@ -13,7 +13,7 @@ from seahub.api2.authentication import TokenAuthentication
from seahub.repo_metadata.models import RepoMetadata, RepoMetadataViews
from seahub.views import check_folder_permission
from seahub.repo_metadata.utils import add_init_metadata_task, gen_unique_id, init_metadata, \
get_unmodifiable_columns, can_read_metadata, init_faces, add_init_face_recognition_task, get_metadata_by_faces
get_unmodifiable_columns, can_read_metadata, init_faces, add_init_face_recognition_task, get_metadata_by_faces, extract_file_details
from seahub.repo_metadata.metadata_server_api import MetadataServerAPI, list_metadata_view_records
from seahub.utils.timeutils import datetime_to_isoformat_timestr
from seahub.utils.repo import is_repo_admin
@@ -260,6 +260,8 @@ class MetadataRecords(APIView):
parameters = []
for record_data in records_data:
record = record_data.get('record', {})
if not record:
continue
record_id = record_data.get('record_id', '')
if not record_id:
error_msg = 'record_id invalid.'
@@ -272,6 +274,9 @@ class MetadataRecords(APIView):
sql = sql.rstrip('OR ')
sql += ';'
if not parameters:
return Response({'success': True})
try:
query_result = metadata_server_api.query_rows(sql, parameters)
except Exception as e:
@@ -1061,3 +1066,42 @@ class FaceRecognitionManage(APIView):
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
return Response({'task_id': task_id})
class MetadataExtractFileDetails(APIView):
authentication_classes = (TokenAuthentication, SessionAuthentication)
permission_classes = (IsAuthenticated,)
throttle_classes = (UserRateThrottle,)
def post(self, request, repo_id):
obj_ids = request.data.get('obj_ids')
if not obj_ids or not isinstance(obj_ids, list) or len(obj_ids) > 50:
error_msg = 'obj_ids is invalid.'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
record = RepoMetadata.objects.filter(repo_id=repo_id).first()
if not record or not record.enabled:
error_msg = f'The metadata module is disabled for repo {repo_id}.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
repo = seafile_api.get_repo(repo_id)
if not repo:
error_msg = 'Library %s not found.' % repo_id
return api_error(status.HTTP_404_NOT_FOUND, error_msg)
permission = check_folder_permission(request, repo_id, '/')
if permission != 'rw':
error_msg = 'Permission denied.'
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
params = {
'obj_ids': obj_ids,
'repo_id': repo_id
}
try:
resp = extract_file_details(params=params)
except Exception as e:
logger.exception(e)
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')
return Response({'details': resp})

View File

@@ -56,6 +56,15 @@ def get_metadata_by_faces(faces, metadata_server_api):
return query_result
def extract_file_details(params):
payload = {'exp': int(time.time()) + 300, }
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
headers = {"Authorization": "Token %s" % token}
url = urljoin(SEAFEVENTS_SERVER_URL, '/extract-file-details')
resp = requests.post(url, json=params, headers=headers, timeout=30)
return json.loads(resp.content)['details']
def generator_base64_code(length=4):
possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0123456789'
ids = random.sample(possible, length)

View File

@@ -212,7 +212,7 @@ from seahub.api2.endpoints.wiki2 import Wikis2View, Wiki2View, Wiki2ConfigView,
from seahub.api2.endpoints.subscription import SubscriptionView, SubscriptionPlansView, SubscriptionLogsView
from seahub.api2.endpoints.metadata_manage import MetadataRecords, MetadataManage, MetadataColumns, MetadataRecordInfo, \
MetadataViews, MetadataViewsMoveView, MetadataViewsDetailView, MetadataViewsDuplicateView, FacesRecords, \
FaceRecognitionManage, FacesRecord
FaceRecognitionManage, FacesRecord, MetadataExtractFileDetails
from seahub.api2.endpoints.user_list import UserListView
from seahub.api2.endpoints.seahub_io import SeahubIOStatus
@@ -1056,6 +1056,7 @@ if settings.ENABLE_METADATA_MANAGEMENT:
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/face-record/$', FacesRecord.as_view(), name='api-v2.1-metadata-face-record'),
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/face-records/$', FacesRecords.as_view(), name='api-v2.1-metadata-face-records'),
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/face-recognition/$', FaceRecognitionManage.as_view(), name='api-v2.1-metadata-face-recognition'),
re_path(r'^api/v2.1/repos/(?P<repo_id>[-0-9a-f]{36})/metadata/extract-file-details/$', MetadataExtractFileDetails.as_view(), name='api-v2.1-metadata-extract-file-details'),
]
# ai API