Jelajahi Sumber

feat(video_play): 优化视频文件处理和加权随机算法

- 添加 MD5 缓存机制,提高文件扫描效率
- 重构文件扫描逻辑,支持更准确的文件识别和信息获取
- 优化加权随机算法,确保权重的有效应用
- 更新 API 接口设计,提高系统可用性和可维护性
tianyunperfect 3 bulan lalu
induk
melakukan
96fde135f0
3 mengubah file dengan 261 tambahan dan 39 penghapusan
  1. 1 0
      .gitignore
  2. 164 0
      video_play/index.html
  3. 96 39
      video_play/main.py

+ 1 - 0
.gitignore

@@ -6,3 +6,4 @@ __pycache__
 *.csv
 /vector_test/models--shibing624--text2vec-base-chinese/
 /copyD/nohup.out
+venv

+ 164 - 0
video_play/index.html

@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="UTF-8">
+    <title>视频随机播放器</title>
+    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
+</head>
+
+<body>
+    <div id="app">
+        <el-container style="height: 100vh;">
+            <!-- 视频播放区域 -->
+            <el-main>
+                <div style="margin-bottom: 20px; display: flex; align-items: center; gap: 20px; flex-wrap: nowrap;">
+                    <el-button @click="getRandomVideo" type="primary">随机播放</el-button>
+                    <el-switch v-model="loopMode" active-text="自动连播" inactive-text="单次播放"></el-switch>
+                    <el-switch v-model="infiniteLoop" active-text="无限循环" inactive-text="正常模式"
+                        @change="toggleInfiniteLoop" style="flex-shrink: 0;">
+                    </el-switch>
+                    <div v-if="currentFile" style="display: flex; align-items: center; flex-shrink: 0;">
+                        <span style="margin-right: 10px;">当前播放:</span>
+                        <el-tag type="info">{{ currentFile.filename }}</el-tag>
+                        <span style="margin-left: 15px; margin-right: 10px;">权重:</span>
+                        <el-input-number v-model="currentFile.weight" :min="0" :max="10" size="mini"
+                            @change="updateWeight(currentFile)" style="width: 100px;">
+                        </el-input-number>
+                    </div>
+                </div>
+                <video ref="videoPlayer" controls :src="currentVideo" style="width: 100%; max-height: 80vh;"
+                    @ended="handleVideoEnd">
+                    您的浏览器不支持视频播放
+                </video>
+            </el-main>
+
+            <!-- 视频列表 -->
+            <el-aside width="450px" style="background: #f5f5f5; height: 100vh;">
+                <h3 style="margin-bottom: 15px;">视频列表(双击播放)</h3>
+                <el-table :data="files" stripe height="calc(100vh - 60px)" @row-dblclick="playSelected">
+                    <el-table-column prop="filename" label="文件名"></el-table-column>
+                    <el-table-column label="权重" width="150">
+                        <template slot-scope="{row}">
+                            <el-input-number v-model="row.weight" :min="0" :max="10" @change="updateWeight(row)"
+                                size="mini">
+                            </el-input-number>
+                        </template>
+                    </el-table-column>
+                </el-table>
+            </el-aside>
+        </el-container>
+    </div>
+
+    <script src="https://unpkg.com/vue@2/dist/vue.js"></script>
+    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
+    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
+
+    <script>
+        new Vue({
+            el: '#app',
+            data() {
+                return {
+                    files: [],
+                    currentVideo: '',
+                    currentVideoMD5: '',
+                    loopMode: true,
+                    infiniteLoop: false
+                }
+            },
+            mounted() {
+                this.fetchFiles()
+            },
+            computed: {
+                currentFile() {
+                    return this.files.find(file => file.md5 === this.currentVideoMD5)
+                }
+            },
+            methods: {
+                async fetchFiles() {
+                    const res = await axios.get('/files')
+                    this.files = res.data
+                },
+
+                // 播放选中视频(新增)
+                playSelected(row) {
+                    this.currentVideoMD5 = row.md5;
+                    this.currentVideo = `/play/${row.md5}?t=${Date.now()}`;
+
+                    this.$nextTick(() => {
+                        const player = this.$refs.videoPlayer;
+                        player.pause();
+                        player.load(); // 重新加载新源
+
+                        player.onloadeddata = () => {
+                            player.play().catch(error => {
+                                this.$message.warning('需要手动点击播放(浏览器限制)');
+                            });
+                        };
+                    });
+                },
+
+                // 权重更新(修改后)
+                async updateWeight(row) {
+                    try {
+                        await axios.post(`/weight/${row.md5}`, {
+                            weight: row.weight
+                        })
+                        this.$message.success('权重保存成功')
+                    } catch (error) {
+                        this.$message.error('保存失败')
+                        console.error('保存权重失败:', error)
+                    }
+                },
+
+                async getRandomVideo(forceNew = true) {
+                    try {
+                        if (this.infiniteLoop && this.currentVideoMD5 && !forceNew) {
+                            return this.playCurrent()
+                        }
+
+                        const res = await axios.get('/random')
+                        this.currentVideoMD5 = res.data.md5
+                        this.currentVideo = `/play/${res.data.md5}`
+
+                        this.$nextTick(() => {
+                            const player = this.$refs.videoPlayer
+                            player.play().catch(error => {
+                                this.$message.warning('需要手动点击播放(浏览器限制)')
+                            })
+                        })
+                    } catch (error) {
+                        this.$message.error('获取视频失败')
+                    }
+                },
+
+                // 修改后的播放当前视频方法
+                playCurrent() {
+                    const player = this.$refs.videoPlayer;
+                    // 直接操作播放器状态而不是修改src
+                    player.currentTime = 0;
+                    player.play().catch(error => {
+                        this.$message.warning('需要手动点击播放(浏览器限制)');
+                    });
+                },
+
+                handleVideoEnd() {
+                    if (this.infiniteLoop) {
+                        this.playCurrent()
+                    } else if (this.loopMode) {
+                        this.getRandomVideo(false)
+                    }
+                },
+
+                toggleInfiniteLoop() {
+                    if (this.infiniteLoop && !this.currentVideoMD5) {
+                        this.$message.warning('请先选择要循环的视频')
+                        this.infiniteLoop = false
+                    }
+                }
+            }
+        })
+    </script>
+</body>
+
+</html>

+ 96 - 39
video_play/main.py

@@ -9,26 +9,74 @@ app = Flask(__name__)
 CORS(app)
 
 # 配置文件路径
-VIDEO_DIR = "videos"  # 视频存放目录
-WEIGHT_FILE = "weights.json"  # 权重存储文件
+VIDEO_DIR = "/177_data/media/MV"  # 视频存放目录
+WEIGHT_FILE = "weights.json"      # 权重存储文件
+MD5_CACHE_FILE = "md5_cache.json" # MD5缓存文件
 
-def get_file_md5(filepath):
+def load_md5_cache():
+    """加载MD5缓存文件"""
+    try:
+        with open(MD5_CACHE_FILE, 'r') as f:
+            cache = json.load(f)
+            return {k: (v[0], v[1], v[2]) for k, v in cache.items()}
+    except (FileNotFoundError, json.JSONDecodeError):
+        return {}
+
+def save_md5_cache(cache):
+    """保存MD5缓存文件"""
+    serializable = {k: [v[0], v[1], v[2]] for k, v in cache.items()}
+    with open(MD5_CACHE_FILE, 'w') as f:
+        json.dump(serializable, f)
+
+def compute_md5(filepath):
     """计算文件的MD5值"""
     hash_md5 = hashlib.md5()
     with open(filepath, "rb") as f:
-        for chunk in iter(lambda: f.read(4096), b""):
+        for chunk in iter(lambda: f.read(8192), b""):
             hash_md5.update(chunk)
     return hash_md5.hexdigest()
 
-def scan_videos():
-    """扫描视频目录并返回带权重的文件列表"""
-    videos = []
+def scan_files():
+    """扫描视频文件并维护MD5缓存"""
+    cache = load_md5_cache()
+    updated = False
+    files = []
+    
     for filename in os.listdir(VIDEO_DIR):
-        if filename.split('.')[-1].lower() in ['mp4', 'avi', 'mov', 'mkv']:
-            filepath = os.path.join(VIDEO_DIR, filename)
-            md5 = get_file_md5(filepath)
-            videos.append({'md5': md5, 'filename': filename})
-    return videos
+        # 过滤非视频文件
+        if filename.split('.')[-1].lower() not in ['mp4', 'avi', 'mov', 'mkv']:
+            continue
+        
+        filepath = os.path.join(VIDEO_DIR, filename)
+        if not os.path.isfile(filepath):
+            continue
+        
+        try:
+            stat = os.stat(filepath)
+            current_size = stat.st_size
+            current_mtime = stat.st_mtime
+            
+            # 检查缓存有效性
+            if filename in cache:
+                cached_size, cached_mtime, cached_md5 = cache[filename]
+                if cached_size == current_size and cached_mtime == current_mtime:
+                    files.append({'filename': filename, 'md5': cached_md5, 'path': filepath})
+                    continue
+            
+            # 需要更新缓存
+            md5 = compute_md5(filepath)
+            cache[filename] = (current_size, current_mtime, md5)
+            updated = True
+            files.append({'filename': filename, 'md5': md5, 'path': filepath})
+            
+        except Exception as e:
+            print(f"Error processing {filename}: {str(e)}")
+    
+    # 保存更新后的缓存
+    if updated:
+        save_md5_cache(cache)
+    
+    return files
 
 def load_weights():
     """加载权重数据"""
@@ -43,58 +91,67 @@ def save_weights(weights):
     with open(WEIGHT_FILE, 'w') as f:
         json.dump(weights, f)
 
-def get_weighted_list():
-    """获取带权重的视频列表"""
-    videos = scan_videos()
-    weights = load_weights()
-    weighted_list = []
-    for v in videos:
-        weight = weights.get(v['md5'], 5)
-        weighted_list.append({
-            **v,
-            'weight': max(0, min(10, int(weight))),  # 限制在0-10之间
-            'path': os.path.join(VIDEO_DIR, v['filename'])
-        })
-    return weighted_list
+@app.route('/')
+def index():
+    return send_file('index.html')
 
 @app.route('/files')
 def get_files():
-    """接口1:获取文件列表"""
-    return jsonify(get_weighted_list())
+    """文件列表接口"""
+    files = scan_files()
+    weights = load_weights()
+    
+    # 合并权重数据
+    for f in files:
+        f['weight'] = weights.get(f['md5'], 5)
+    
+    return jsonify(files)
 
 @app.route('/random')
 def get_random():
-    """接口2:加权随机获取视频"""
-    files = get_weighted_list()
-    total_weight = sum(f['weight'] for f in files)
-    rand = random.uniform(0, total_weight)
-    current = 0
+    """加权随机接口"""
+    files = scan_files()
+    weights = load_weights()
+    
+    # 构建权重列表
+    weighted = []
     for f in files:
-        current += f['weight']
+        weight = weights.get(f['md5'], 5)
+        weighted.append( (f, max(0, min(10, weight))) )
+    
+    # 加权随机选择
+    total = sum(w for _, w in weighted)
+    if total == 0:
+        return jsonify(random.choice(files))
+    
+    rand = random.uniform(0, total)
+    current = 0
+    for f, w in weighted:
+        current += w
         if rand <= current:
             return jsonify(f)
     return jsonify(files[0])
 
 @app.route('/play/<md5>')
 def play_video(md5):
-    """接口3:播放指定视频"""
-    for f in get_weighted_list():
+    """播放接口"""
+    for f in scan_files():
         if f['md5'] == md5:
             return send_file(f['path'])
-    return "File not found", 404
+    return "Not Found", 404
 
 @app.route('/weight/<md5>', methods=['POST'])
 def update_weight(md5):
-    """接口4:更新权重"""
+    """设置权重"""
     weights = load_weights()
     try:
         weight = int(request.json.get('weight', 5))
         weights[md5] = max(0, min(10, weight))
         save_weights(weights)
-        return jsonify({'status': 'success'})
+        return jsonify({'status': 'ok'})
     except:
         return jsonify({'status': 'error'}), 400
 
 if __name__ == '__main__':
     os.makedirs(VIDEO_DIR, exist_ok=True)
-    app.run(debug=True)
+    app.run(debug=True, host='0.0.0.0', port=5000)