/** * 当主页中的“选择视频”按钮被点击后, 执行此函数 * * 1) 弹出一个文件选择框, 让用户选定一些本地文件 */ function onSelectVideoButtonClicked() { // 当用户选定了一些文件后, 函数 onUploadVideoChanged 将被执行. $('#upload-video').click(); } /** * 当主页中的“选择视频”按钮被点击后, 且用户选定了一些文件时, 此函数将被调用. * * 1) 检查这些文件的格式. 目前可以处理的视频文件格式是*.mp4 * 2) 将按钮'select-video'隐藏 * 3) 将表格'status-table'显示 * * 对于每个已选定的文件, 均执行以下异步流程: * * 1) 调用服务器的接口, 将文件上传到服务器 * 2) 将标题'caption'修改为 '正在处理{files.length}个文件, 请稍候查看结果' * 3) 为表格'status-table'追加一行, 用于展示处理当前文件的状态与进度, 并提供用户可执行的操作 * * @param {file} files 用户选择的文件列表. */ function onUploadVideoChanged(files) { for (var index = 0; index < files.length; index++) { var file = files[index]; if (file.type != 'video/mp4') { errorModal('错误', `文件${file.name}的格式错误`); return; } } $('#select-video').hide(); $('#status-table').show(); $('#caption').html('正在处理' + files.length + '个文件'); $.each(files, function(index, file) { uploadFile(index, file); appendRowToStatusTable(index, file); }); } /** * 当文件完成部分上传时, 此函数将被回调, 用于更新部分页面组件. * * 1) 将表格 status-table 中进度栏的进度信息修改为 '${percent}%' * * @param {index} index 文件的索引 * @param {number} percent 文件的上传进度 */ function onFileUploadStepped(index, percent) { prog = $(`#stat-prog-${index}`); prog.css({'width': `${percent}%`}); prog.html(`${percent}%`); } /** * 当文件上传成功后, 此函数将被回调, 用于开始进行特征提取, 同时更新部分界面组件. * * 1) 将表格 status-table 中状态栏的信息改为 '正在提取' * 2) 将表格 status-table 中进度栏的进度信息重置为 '0%' * 3) 调用服务器接口, 开始执行提取 * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {object} data 服务器响应数据 */ function onFileUploadFinished(index, file, data) { $(`#stat-label-${index}`).html('正在提取'); $(`#stat-prog-${index}`).css({'width': '0%'}); $(`#stat-prog-${index}`).html('0%'); extractFile(index, file, data.id); } /** * 当文件上传失败后, 此函数将被回调, 用于更新部分界面组件. * * 1) 将表格 status-table 中的状态栏信息改为 '上传失败'. * 2) 将表格 status-table 中进度栏的进度信息重置为 '0%' * 3) 将表格 status-table 中操作栏的按钮修改为 danger, 并激活以查看失败原因. * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {error} err 失败原因 */ function onFileUploadFailed(index, file, err) { $(`#stat-label-${index}`).html('
上传失败
'); $(`#stat-prog-${index}`).css({'width': '0%'}); $(`#stat-prog-${index}`).html('0%'); $(`#stat-button-${index}`).removeClass('btn-dark disabled'); $(`#stat-button-${index}`).addClass('btn-danger'); $(`#stat-button-${index}`).find('span').remove(); $(`#stat-button-${index}`).text('查看'); $(`#stat-button-${index}`).on('click', function() { errorModal('错误', `文件${file.name}上传失败: ${err}`); }); } /** * 当文件提取成功后, 此函数将被回调. 此时, 服务器已经保存了视频文件的特征信息到h5文件中. * * 1) 将表格 status-table 中进度栏的进度信息更新为50% * 2) 调用服务器接口, 开始执行提取关键帧 * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {object} data 服务器响应数据 */ function onExtractFileFinished(index, file, data) { $(`#stat-label-${index}`).html('正在分析'); $(`#stat-prog-${index}`).css({'width': '50%'}); $(`#stat-prog-${index}`).html('50%'); analyseFile(index, file, data.id); } /** * 当文件提取失败后, 此函数将被回调, 用于更新部分界面组件. * * 1) 将表格 status-table 中的状态栏信息改为 '提取失败'. * 2) 将表格 status-table 中进度栏的进度信息重置为 '0%' * 3) 将表格 status-table 中操作栏的按钮修改为 danger, 并激活以查看失败原因. * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {error} err 失败原因 */ function onExtractFileFailed(index, file, err) { $(`#stat-label-${index}`).html('
提取失败
'); $(`#stat-prog-${index}`).css({'width': '0%'}); $(`#stat-prog-${index}`).html('0%'); $(`#stat-button-${index}`).removeClass('btn-dark disabled'); $(`#stat-button-${index}`).addClass('btn-danger'); $(`#stat-button-${index}`).find('span').remove(); $(`#stat-button-${index}`).text('查看'); $(`#stat-button-${index}`).on('click', function() { errorModal('错误', `文件${file.name}提取失败: ${err}`); }); } /** * 当文件分析成功后, 此函数将被回调. 此时, 服务器已经保存了视频文件的特征信息和分析结果到h5文件中. * * 1) 将表格 status-table 中进度栏的进度信息更新为75% * 2) 调用服务器接口, 生成摘要视频 * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {object} data 服务器响应数据 */ function onAnalyseFileFinished(index, file, data) { $(`#stat-label-${index}`).html('正在导出'); $(`#stat-prog-${index}`).css({'width': '50%'}); $(`#stat-prog-${index}`).html('50%'); generateFile(index, file, data.id); } /** * 当文件分析失败后, 此函数将被回调, 用于更新部分界面组件. * * 1) 将表格 status-table 中的状态栏信息改为 '分析失败'. * 2) 将表格 status-table 中进度栏的进度信息重置为 '0%' * 3) 将表格 status-table 中操作栏的按钮修改为 danger, 并激活以查看失败原因. * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {error} err 失败原因 */ function onAnalyseFileFailed(index, file, err) { $(`#stat-label-${index}`).html('
分析失败
'); $(`#stat-prog-${index}`).css({'width': '0%'}); $(`#stat-prog-${index}`).html('0%'); $(`#stat-button-${index}`).removeClass('btn-dark disabled'); $(`#stat-button-${index}`).addClass('btn-danger'); $(`#stat-button-${index}`).find('span').remove(); $(`#stat-button-${index}`).text('查看'); $(`#stat-button-${index}`).on('click', function() { errorModal('错误', `文件${file.name}分析失败: ${err}`); }); } /** * 当文件生成成功后, 此函数将被回调. 此时, 服务器已经保存了视频文件的摘要. * * 1) 将表格 status-table 中进度栏的进度信息更新为100% * 2) 调用服务器接口, 生成摘要视频 * 3) * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {object} data 服务器响应数据 */ function onGenerateFileFinished(index, file, data) { $(`#stat-label-${index}`).html('
操作完成
'); $(`#stat-prog-${index}`).css({'width': '100%'}); $(`#stat-prog-${index}`).html('100%'); $(`#stat-button-${index}`).removeClass('btn-dark disabled'); $(`#stat-button-${index}`).addClass('btn-success'); $(`#stat-button-${index}`).find('span').remove(); $(`#stat-button-${index}`).text('查看'); $(`#stat-button-${index}`).on('click', function() { playFile(index, file, data.id); }); } /** * 当文件生成失败后, 此函数将被回调, 用于更新部分界面组件. * * 1) 将表格 status-table 中的状态栏信息改为 '生成失败'. * 2) 将表格 status-table 中进度栏的进度信息重置为 '0%' * 3) 将表格 status-table 中操作栏的按钮修改为 danger, 并激活以查看失败原因. * * @param {number} index 文件的索引 * @param {File} file 文件对象 * @param {error} err 失败原因 */ function onGenerateFileFailed(index, file, err) { $(`#stat-label-${index}`).html('
生成失败
'); $(`#stat-prog-${index}`).css({'width': '0%'}); $(`#stat-prog-${index}`).html('0%'); $(`#stat-button-${index}`).removeClass('btn-dark disabled'); $(`#stat-button-${index}`).addClass('btn-danger'); $(`#stat-button-${index}`).find('span').remove(); $(`#stat-button-${index}`).text('查看'); $(`#stat-button-${index}`).on('click', function() { errorModal('错误', `文件${file.name}生成失败: ${err}`); }); } /** * 上传指定的文件到服务器的接口. * * 此函数会回调 onFileUploadStepped 函数, 告知文件的上传进度. * * @param {index} index 文件的索引 * @param {file} file 文件对象 */ function uploadFile(index, file) { var formData = new FormData(); formData.append('file', file); $.ajax({ url: 'http://127.0.0.1:8000/upload', type: 'POST', data: formData, processData: false, // 不处理发送的数据 contentType: false, // 不设置内容类型 success: function(data) { if (data.status != 200) { //TODO: 添加错误处理 } else { onFileUploadFinished(index, file, data); } }, error: function(xhr, status, error) { if (xhr.status == 0) { error = '连接到服务器失败!'; } else { var errorAjax = `AJAX error: ${status} : ${error}`; var errorResponseText = `ResponseText: ${xhr.responseText}`; var errorStatusText = `StatusText: ${xhr.statusText}`; error = `${errorAjax} + \n\n + ${errorResponseText} + \n\n + ${errorStatusText}`; } onFileUploadFailed(index, file, error); }, xhr: function() { var xhr = new window.XMLHttpRequest(); xhr.upload.addEventListener('progress', function(event) { if (event.lengthComputable) { var percent = event.loaded / event.total; percent = parseInt(percent * 100); onFileUploadStepped(index, percent); } }, false); return xhr; }, }); } /** * 在结果窗口中播放服务器中编号为id的视频. * * @param {index} index 文件的索引 * @param {file} file 文件对象 * @param {string} id 服务器视频id */ function playFile(index, file, id) { $('#video-modal').find('.modal-title').text(`预览${file ? file.name : '此视频'}的关键镜头`); $('#video-modal').find('video').attr('src', `http://127.0.0.1:8000/fetch/${id}`); var player = new Plyr('video'); $('#video-modal').on('hidden.bs.modal', function(event) { player.pause(); }); $('#video-modal').modal(); } /** * 让服务器提取目标文件的特征. * * @param {index} index 文件的索引 * @param {file} file 文件对象 * @param {string} id 服务器视频id */ function extractFile(index, file, id) { var formData = new FormData(); formData.append('id', id); $.ajax({ url: 'http://127.0.0.1:8000/extract', type: 'POST', data: formData, processData: false, // 不处理发送的数据 contentType: false, // 不设置内容类型 success: function(data) { if (data.status != 200) { //TODO: 添加错误处理 } onExtractFileFinished(index, file, data); }, error: function(xhr, status, error) { onExtractFileFailed(index, file, error); }, }); } /** * 让服务器分析目标文件的关键帧. * * @param {index} index 文件的索引 * @param {file} file 文件对象 * @param {string} id 服务器视频id */ function analyseFile(index, file, id) { var formData = new FormData(); formData.append('id', id); $.ajax({ url: 'http://127.0.0.1:8000/analyse', type: 'POST', data: formData, processData: false, // 不处理发送的数据 contentType: false, // 不设置内容类型 success: function(data) { if (data.status != 200) { //TODO: 添加错误处理 } onAnalyseFileFinished(index, file, data); }, error: function(xhr, status, error) { onAnalyseFileFailed(index, file, error); }, }); } /** * 让服务器生成目标文件的摘要视频. * * @param {index} index 文件的索引 * @param {file} file 文件对象 * @param {string} id 服务器视频id */ function generateFile(index, file, id) { var formData = new FormData(); formData.append('id', id); $.ajax({ url: 'http://127.0.0.1:8000/generate', type: 'POST', data: formData, processData: false, // 不处理发送的数据 contentType: false, // 不设置内容类型 success: function(data) { if (data.status != 200) { //TODO: 添加错误处理 } onGenerateFileFinished(index, file, data); }, error: function(xhr, status, error) { onGenerateFileFailed(index, file, error); }, }); } /** * 向表格'status-table'中增加一行, 向用户展示上传文件的信息与当前状态, 并提供必要的操作. * * @param {number} index 正在处理的文件编号. * @param {file} file 正在处理的文件对象. * @note 在执行此函数时, 该文件对象可能正在异步上传. */ function appendRowToStatusTable(index, file) { var fileNameHtml = `${file.name}`; var fileTypeHtml = `${file.type}`; var fileSizeHtml = `${getReadableSize(file.size)}`; var fileStatHtml = `正在上传`; var fileProgHtml = `
0%
`; var fileOptrHtml = ``; $('#status-table').append( '' + fileNameHtml + fileTypeHtml + fileSizeHtml + fileStatHtml + fileProgHtml + fileOptrHtml + '' ); } // 注册按钮点击事件处理函数 $(document).ready(function() { $('#select-video').click(onSelectVideoButtonClicked); }); // 注册文件选择事件处理函数 $(document).ready(function() { $('#upload-video').change(function() { var files = $(this).prop('files'); onUploadVideoChanged(files); }); });