极简短视频查看、删除应用

本地短视频服务器

背景:我的NAS中存放了很多短视频,多到很多没看过,于是写了这个程序来随机查看并删除短视频

运行:

安装依赖后运行main.py

直接使用docker:

docker pull realwang/short_video

docker run -d -p 3000:3000 -v /path/to/your/video:/app/video realwang/short_video

功能

  • 扫描本地视频和图片,并在网页上显示
  • 在网页上 点赞和删除文件
  • 上下滑动来切换文件
  • 媒体文件放在video目录下
  • 本地数据库

代码由4个文件组成

1.数据库操作
复制代码
# database.py

import sqlite3

def init_db():
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS media
                 (id INTEGER PRIMARY KEY, name TEXT, path TEXT, views INTEGER DEFAULT 0, likes INTEGER DEFAULT 0)''')
    conn.commit()
    conn.close()

def add_media(name, path):
    try:
        conn = sqlite3.connect('media.db')
        c = conn.cursor()
        c.execute("INSERT INTO media (name, path) VALUES (?, ?)", (name, path))
        conn.commit()
        conn.close()
        return True
    except sqlite3.IntegrityError:
        # 处理重复键错误等数据库约束错误
        return False
    except Exception as e:
        print(f"Error inserting {name} into database: {str(e)}")
        return False

def update_views(media_id):
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("UPDATE media SET views = views + 1 WHERE id = ?", (media_id,))
    conn.commit()
    conn.close()

def update_likes(media_id):
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("UPDATE media SET likes = likes + 1 WHERE id = ?", (media_id,))
    conn.commit()
    conn.close()

def delete_media(media_id):
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("DELETE FROM media WHERE id = ?", (media_id,))
    conn.commit()
    conn.close()

def get_random_media():
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("SELECT id, name, path FROM media ORDER BY RANDOM() LIMIT 1")
    media = c.fetchone()
    conn.close()
    return media
2.文件扫描
复制代码
#scanner.py
import os
import time
import sqlite3
from database import init_db, add_media

def scan_directory(directory='video'):
    init_db()
    print(f"扫描目录: {directory}")
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(('.mp4', '.jpg', '.png', '.gif')):
                path = os.path.join(root, file)
                print(f"发现文件: {file} 路径: {path}")
                try:
                    result = add_media(file, path)
                    #print(f"add_media 返回: {result}")  # Debug: Print the return value
                    if result:
                        print(f"插入 {file} 到数据库")
                    else:
                        print(f"插入 {file} 到数据库失败")
                except Exception as e:
                    print(f"由于异常无法插入 {file} 到数据库: {str(e)}")

def incremental_scan(directory='video'):
    scanned_files = set()
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("SELECT path FROM media")
    for row in c.fetchall():
        scanned_files.add(row[0])
    conn.close()

    print("开始增量扫描...")
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith(('.mp4', '.jpg', '.png', '.gif')):
                path = os.path.join(root, file)
                if path not in scanned_files:
                    print(f"发现新文件: {file} 路径: {path}")
                    try:
                        result = add_media(file, path)
                        print(f"add_media 返回: {result}")  # Debug: Print the return value
                        if result:
                            print(f"插入 {file} 到数据库")
                        else:
                            print(f"插入 {file} 到数据库失败")
                    except Exception as e:
                        print(f"由于异常无法插入 {file} 到数据库: {str(e)}")
                else:
                    print(f"跳过已存在文件: {file}")

if __name__ == '__main__':
    init_db()
    while True:
        incremental_scan()
        time.sleep(3600)  # 每小时扫描一次
3.web服务
复制代码
#app.py

from flask import Flask, render_template, request, send_file, jsonify, send_from_directory
import os
import sqlite3
import random  # 导入 random 模块
from database import init_db, update_views, update_likes, delete_media, get_random_media

app = Flask(__name__)

def is_video_file(path):
    return path.lower().endswith('.mp4')

def is_image_file(path):
    return path.lower().endswith('.jpg') or path.lower().endswith('.jpeg') or path.lower().endswith('.png')

def get_random_media():
    conn = sqlite3.connect('media.db')
    c = conn.cursor()

    rand_num = random.random()  # Generate a random number between 0 and 1
    if rand_num < 0.9:
        c.execute("SELECT id, name, path FROM media WHERE path LIKE '%.mp4' ORDER BY RANDOM() LIMIT 1;")
    else:
        c.execute("SELECT id, name, path FROM media WHERE path LIKE '%.jpg' OR path LIKE '%.jpeg' OR path LIKE '%.png' ORDER BY RANDOM() LIMIT 1;")

    media = c.fetchone()

    conn.close()
    return media

@app.route('/')
def index():
    media = get_random_media()
    if media:
        media_id, name, path = media
        update_views(media_id)
        conn = sqlite3.connect('media.db')
        c = conn.cursor()
        c.execute("SELECT likes FROM media WHERE id = ?", (media_id,))
        likes = c.fetchone()[0]
        conn.close()
        return render_template('index.html', media_id=media_id, name=name, path=path, likes=likes,
                               is_video=is_video_file(path), is_image=is_image_file(path))
    return "No media found"

@app.route('/media/<path:filename>')
def media(filename):
    return send_from_directory(directory=os.path.dirname(filename), path=os.path.basename(filename))

@app.route('/like/<int:media_id>', methods=['POST'])
def like(media_id):
    update_likes(media_id)
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("SELECT likes FROM media WHERE id = ?", (media_id,))
    likes = c.fetchone()[0]
    conn.close()
    return jsonify(success=True, likes=likes)

@app.route('/delete/<int:media_id>', methods=['POST'])
def delete(media_id):
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("SELECT path FROM media WHERE id = ?", (media_id,))
    path = c.fetchone()[0]
    conn.close()
    if os.path.exists(path):
        os.remove(path)
    delete_media(media_id)
    return jsonify(success=True)

@app.route('/download/<int:media_id>')
def download(media_id):
    conn = sqlite3.connect('media.db')
    c = conn.cursor()
    c.execute("SELECT path FROM media WHERE id = ?", (media_id,))
    path = c.fetchone()[0]
    conn.close()
    return send_file(path, as_attachment=True)

if __name__ == '__main__':
    init_db()  # 确保数据库和表被初始化
    app.run(host='0.0.0.0', port=3000, debug=True)
4.启动器
复制代码
import os
import threading
import time

# 定义运行scanner.py的函数
def run_scanner():
    # 清空并创建scanner.log文件
    with open('log/scanner.log', 'wb') as f:
        pass
    os.system('python scanner.py > log/scanner.log 2>&1')

# 定义运行app.py的函数
def run_app():
    # 清空并创建app.log文件
    with open('log/app.log', 'wb') as f:
        pass
    os.system('python app.py > log/app.log 2>&1')

if __name__ == '__main__':
    # 创建log子目录
    os.makedirs('log', exist_ok=True)

    # 创建并启动线程运行scanner.py
    scanner_thread = threading.Thread(target=run_scanner)
    scanner_thread.start()

    # 等待3秒钟
    time.sleep(3)

    # 创建并启动线程运行app.py
    app_thread = threading.Thread(target=run_app)
    app_thread.start()
相关推荐
benbenAItalk1 小时前
数字人口播视频的批量生产实践:素材规范、任务编排与质量验收
java·前端·音视频
AI创界者4 小时前
前沿多模态大模型 MiniMax-H3无审查 本地部署与深度实测指南
人工智能·aigc·音视频
世岩清上9 小时前
展厅数字内容同质化严重,怎样打造专属叙事风格?
大数据·前端·javascript·人工智能·html·音视频·展厅改造
LCG元10 小时前
STM32F407 I2S驱动WM8978音频播放实战:PLLI2S时钟整定、DMA双缓冲防爆音与WAV采样率实测
stm32·嵌入式硬件·音视频
jbk331111 小时前
PixiuCut「视频批量分割」支持智能镜头分割、按时长、按数量分割,可定义最小时长避免无效片段等多可选参数可设置
人工智能·音视频·剪辑软件·剪映自动化软件
投票竞赛11 小时前
作品投票版权注意事项,线上评选上传图片视频须知
python·音视频
李高钢14 小时前
Python Flask 框架入门:从零搭建你的第一个 Web 应用
前端·python·flask
byte轻骑兵14 小时前
【AVDTP】规范精讲[13]: 吃透内容保护机制,打通蓝牙音视频版权加密全流程
人工智能·音视频·avrcp·蓝牙耳机·蓝牙音频
世岩清上16 小时前
文旅历史展厅纪录片式视频,怎样弱化说教感提升自主观看欲?
人工智能·音视频·宣传片·展厅改造
言乐616 小时前
Python加速器2视频网页加速器
前端·javascript·css·python·音视频