|  | 
| 47 | 47 |  var Vue = require('./vue.min'); // 某些版本不兼容 require('vue'); | 
| 48 | 48 |  var StringUtil = require('../apijson/StringUtil'); | 
| 49 | 49 |  var CodeUtil = require('../apijson/CodeUtil'); | 
|  | 50 | + var FileUtil = require('../apijson/FileUtil'); | 
| 50 | 51 |  var JSONObject = require('../apijson/JSONObject'); | 
| 51 | 52 |  var JSONResponse = require('../apijson/JSONResponse'); | 
| 52 | 53 |  var JSONRequest = require('../apijson/JSONRequest'); | 
| @@ -6784,97 +6785,164 @@ https://github.com/Tencent/APIJSON/issues | 
| 6784 | 6785 |  const doc = (this.currentRemoteItem || {}).Document || {} | 
| 6785 | 6786 |  const random = cri.Random || {} | 
| 6786 | 6787 |  const ind = isSub && this.currentRandomSubIndex != null ? this.currentRandomSubIndex : this.currentRandomIndex; | 
|  | 6788 | + const index = ind != null && ind >= 0 ? ind : -1; // items.length; | 
|  | 6789 | + const server = this.server | 
| 6787 | 6790 | 
 | 
| 6788 | 6791 |  var selectedFiles = Array.from(event.target.files); | 
| 6789 | 6792 |  // const previewList = document.getElementById('previewList'); | 
| 6790 | 6793 |  // previewList.innerHTML = ''; | 
| 6791 | 6794 | 
 | 
| 6792 | 6795 |  selectedFiles.forEach((file, i) => { | 
| 6793 | 6796 |  const reader = new FileReader(); | 
| 6794 |  | - reader.onload = (e) => { | 
|  | 6797 | + reader.onload = (evt) => { | 
| 6795 | 6798 |  // const img = document.createElement('img'); | 
| 6796 |  | - // img.src = e.target.result; | 
|  | 6799 | + // img.src = evt.target.result; | 
| 6797 | 6800 |  // img.style.height = '100%'; | 
| 6798 | 6801 |  // img.style.margin = '1px'; | 
| 6799 | 6802 |  // previewList.appendChild(img); | 
| 6800 | 6803 | 
 | 
| 6801 |  | - const index = ind != null && ind >= 0 ? ind : -1; // items.length; | 
| 6802 |  | - var item = JSONResponse.deepMerge({ | 
| 6803 |  | - Random: { | 
| 6804 |  | - id: -(index || 0) - 1, //表示未上传 | 
| 6805 |  | - toId: random.id, | 
| 6806 |  | - userId: random.userId || doc.userId, | 
| 6807 |  | - documentId: random.documentId || doc.id, | 
| 6808 |  | - count: 1, | 
| 6809 |  | - name: '分析位于 ' + index + ' 的这张图片', | 
| 6810 |  | - img: e.target.result, | 
| 6811 |  | - config: '' | 
|  | 6804 | + function callback(file, img, name, size, width, height, index, rank) { | 
|  | 6805 | + | 
|  | 6806 | + var item = JSONResponse.deepMerge({ | 
|  | 6807 | + Random: { | 
|  | 6808 | + id: -(index || 0) - 1, //表示未上传 | 
|  | 6809 | + toId: random.id, | 
|  | 6810 | + userId: random.userId || doc.userId, | 
|  | 6811 | + documentId: random.documentId || doc.id, | 
|  | 6812 | + count: 1, | 
|  | 6813 | + name: '分析位于 ' + index + ' 的这张图片', | 
|  | 6814 | + img: img, | 
|  | 6815 | + config: '' | 
|  | 6816 | + } | 
|  | 6817 | + }, items[index] || {}); | 
|  | 6818 | + item.status = 'uploading'; | 
|  | 6819 | + | 
|  | 6820 | + const r = item.Random || {}; | 
|  | 6821 | + r.name = r.file = name; | 
|  | 6822 | + r.size = size; | 
|  | 6823 | + r.width = width; | 
|  | 6824 | + r.height = height; | 
|  | 6825 | + r.rank = rank; | 
|  | 6826 | + | 
|  | 6827 | + if (index < 0) { // || r.id == null || r.id <= 0) { | 
|  | 6828 | + items.unshift(item); | 
|  | 6829 | + } else { | 
|  | 6830 | + items[index] = item; | 
| 6812 | 6831 |  } | 
| 6813 |  | - }, items[index] || {}); | 
| 6814 |  | - item.status = 'uploading'; | 
| 6815 | 6832 | 
 | 
| 6816 |  | - const r = item.Random || {}; | 
| 6817 |  | - r.name = r.file = file.name; | 
| 6818 |  | - r.size = file.size; | 
| 6819 |  | - r.width = file.width; | 
| 6820 |  | - r.height = file.height; | 
| 6821 |  | - | 
| 6822 |  | - if (index < 0) { // || r.id == null || r.id <= 0) { | 
| 6823 |  | - items.unshift(item); | 
| 6824 |  | - } else { | 
| 6825 |  | - items[index] = item; | 
| 6826 |  | - } | 
|  | 6833 | + if (isSub) { | 
|  | 6834 | + App.randomSubs = items; | 
|  | 6835 | + } else { | 
|  | 6836 | + App.randoms = items; | 
|  | 6837 | + } | 
| 6827 | 6838 | 
 | 
| 6828 |  | - if (isSub) { | 
| 6829 |  | - this.randomSubs = items; | 
| 6830 |  | - } | 
| 6831 |  | - else { | 
| 6832 |  | - this.randoms = items; | 
| 6833 |  | - } | 
|  | 6839 | + try { | 
|  | 6840 | + Vue.set(items, index < 0 ? 0 : index, item); | 
|  | 6841 | + } catch (e) { | 
|  | 6842 | + console.error(e) | 
|  | 6843 | + } | 
| 6834 | 6844 | 
 | 
| 6835 |  | - try { | 
| 6836 |  | - Vue.set(items, index < 0 ? 0 : index, item); | 
| 6837 |  | - } catch (e) { | 
| 6838 |  | - console.error(e) | 
| 6839 |  | - } | 
|  | 6845 | + const formData = new FormData(); | 
|  | 6846 | + formData.append('file', file); | 
| 6840 | 6847 | 
 | 
| 6841 |  | - const formData = new FormData(); | 
| 6842 |  | - formData.append('file', file); | 
|  | 6848 | + fetch(server + '/upload', { | 
|  | 6849 | + method: 'POST', | 
|  | 6850 | + body: formData | 
|  | 6851 | + }) | 
|  | 6852 | + .then(response => response.json()) | 
|  | 6853 | + .then(data => { | 
|  | 6854 | + var path = data.path; | 
|  | 6855 | + if (StringUtil.isEmpty(path, true) || data.size == null) { | 
|  | 6856 | + throw new Error('上传失败!' + JSON.stringify(data || {})); | 
|  | 6857 | + } | 
| 6843 | 6858 | 
 | 
| 6844 |  | - fetch(this.server + '/upload', { | 
| 6845 |  | - method: 'POST', | 
| 6846 |  | - body: formData | 
| 6847 |  | - }) | 
| 6848 |  | - .then(response => response.json()) | 
| 6849 |  | - .then(data => { | 
| 6850 |  | - var path = data.path; | 
| 6851 |  | - if (StringUtil.isEmpty(path, true) || data.size == null) { | 
| 6852 |  | - throw new Error('上传失败!' + JSON.stringify(data || {})); | 
| 6853 |  | - } | 
|  | 6859 | + console.log('Upload successful:', data); | 
|  | 6860 | + item.status = 'done'; | 
|  | 6861 | + if (!(server.includes('localhost') || server.includes('127.0.0.1'))) { | 
|  | 6862 | + r.img = (path.startsWith('/') ? server + path : path) || r.img; | 
|  | 6863 | + } | 
| 6854 | 6864 | 
 | 
| 6855 |  | - console.log('Upload successful:', data); | 
| 6856 |  | - item.status = 'done'; | 
| 6857 |  | - if (! (App.server.includes('localhost') || App.server.includes('127.0.0.1'))) { | 
| 6858 |  | - r.img = (path.startsWith('/') ? App.server + path : path) || r.img; | 
| 6859 |  | - } | 
|  | 6865 | +  try { | 
|  | 6866 | +  Vue.set(items, index < 0 ? 0 : index, item); | 
|  | 6867 | +  } catch (e) { | 
|  | 6868 | +  console.error(e) | 
|  | 6869 | +  } | 
| 6860 | 6870 | 
 | 
| 6861 |  | - try { | 
| 6862 |  | - Vue.set(items, index < 0 ? 0 : index, item); | 
| 6863 |  | - } catch (e) { | 
| 6864 |  | - console.error(e) | 
| 6865 |  | - } | 
|  | 6871 | + App.updateRandom(r) | 
|  | 6872 | + }) | 
|  | 6873 | + .catch(error => { | 
|  | 6874 | + console.error('Upload failed:', error); | 
|  | 6875 | + item.status = 'failed'; | 
|  | 6876 | + }); | 
|  | 6877 | + } | 
|  | 6878 | + | 
|  | 6879 | + if (file.type && file.type.startsWith("video/")) { | 
|  | 6880 | + // === 视频处理 === | 
|  | 6881 | + this.extractFramesAndUpload(file, 5, async (frameBlob, rank, totalFrames) => { | 
|  | 6882 | + const reader2 = new FileReader(); | 
|  | 6883 | + reader2.onload = (evt) => { // TODO 传 toId,视频作为分组,次数作为抽取帧数 | 
|  | 6884 | + var fn = file.name + '-' + rank + '.jpg' | 
|  | 6885 | + callback(new File([frameBlob], fn), reader2.result, fn, frameBlob.size, frameBlob.width, frameBlob.height, -1, rank) | 
|  | 6886 | + } | 
|  | 6887 | + reader2.readAsDataURL(frameBlob); | 
|  | 6888 | + }); | 
|  | 6889 | + } else { | 
|  | 6890 | + callback(file, reader.result, file.name, file.size, file.width, file.height, index) | 
|  | 6891 | + } | 
| 6866 | 6892 | 
 | 
| 6867 |  | - App.updateRandom(r) | 
| 6868 |  | - }) | 
| 6869 |  | - .catch(error => { | 
| 6870 |  | - console.error('Upload failed:', error); | 
| 6871 |  | - item.status = 'failed'; | 
| 6872 |  | - }); | 
| 6873 | 6893 |  }; | 
| 6874 | 6894 |  reader.readAsDataURL(file); | 
| 6875 | 6895 |  }); | 
| 6876 | 6896 |  }, | 
| 6877 | 6897 | 
 | 
|  | 6898 | + /** | 
|  | 6899 | + * 从视频抽帧(倒序),支持并发上传 | 
|  | 6900 | + * @param {File} file - 视频文件 | 
|  | 6901 | + * @param {number} concurrency - 最大并发数 | 
|  | 6902 | + * @param {function} onFrameReady - 回调(frameBlob, rank, totalFrames) | 
|  | 6903 | + */ | 
|  | 6904 | + extractFramesAndUpload: function(file, concurrency, onFrameReady) { | 
|  | 6905 | + const url = URL.createObjectURL(file); | 
|  | 6906 | + const video = document.createElement("video"); | 
|  | 6907 | + video.src = url; | 
|  | 6908 | + video.preload = "metadata"; | 
|  | 6909 | + | 
|  | 6910 | + video.onloadedmetadata = async () => { | 
|  | 6911 | + const duration = video.duration; | 
|  | 6912 | + let timestamps = []; | 
|  | 6913 | + | 
|  | 6914 | + if (duration <= 100) { | 
|  | 6915 | + for (let t = Math.floor(duration); t >= 0; t--) timestamps.push(t); | 
|  | 6916 | + } else { | 
|  | 6917 | + const step = duration / 100; | 
|  | 6918 | + for (let i = 100; i >= 0; i--) timestamps.push(i * step); | 
|  | 6919 | + } | 
|  | 6920 | + | 
|  | 6921 | + const totalFrames = timestamps.length; | 
|  | 6922 | + let index = 0; | 
|  | 6923 | + | 
|  | 6924 | + async function worker() { | 
|  | 6925 | + while (index < totalFrames) { | 
|  | 6926 | + const i = index++; | 
|  | 6927 | + const time = timestamps[i]; | 
|  | 6928 | + const frameBlob = await FileUtil.captureFrame(video, time); | 
|  | 6929 | + // rank 按倒序:0 表示最新帧,totalFrames-1 表示最早帧 | 
|  | 6930 | + const rank = i; | 
|  | 6931 | + onFrameReady(frameBlob, rank, totalFrames); | 
|  | 6932 | + } | 
|  | 6933 | + } | 
|  | 6934 | + | 
|  | 6935 | + // 并发执行 | 
|  | 6936 | + const workers = []; | 
|  | 6937 | + for (let i = 0; i < concurrency; i++) { | 
|  | 6938 | + workers.push(worker()); | 
|  | 6939 | + } | 
|  | 6940 | + await Promise.all(workers); | 
|  | 6941 | + | 
|  | 6942 | + URL.revokeObjectURL(url); | 
|  | 6943 | + }; | 
|  | 6944 | + }, | 
|  | 6945 | + | 
| 6878 | 6946 |  uploadImage: function(randomIndex, randomSubIndex) { | 
| 6879 | 6947 |  const isSub = randomSubIndex != null; | 
| 6880 | 6948 |  const items = (isSub ? this.randomSubs : this.randoms) || []; | 
|  | 
0 commit comments