p.sh
| @@ -12,7 +12,7 @@ EXEC_PATH="$APP_DIR/realesrgan-ncnn-vulkan" | |||
| 12 | 12 | # 模型可选: realesrgan-x4plus (适合真实影像), realesrgan-x4plus-anime (适合动漫) | |
| 13 | 13 | MODEL_NAME="realesrgan-x4plus" | |
| 14 | 14 | SCALE=4 | |
| 15 | - | # 显卡ID,自动探测一般填0,如果多卡可修改 | |
| 15 | + | # 显卡ID,自动探测一般填0 | |
| 16 | 16 | GPU_ID=0 | |
| 17 | 17 | ||
| 18 | 18 | # 下载链接 | |
| @@ -85,6 +85,10 @@ fi | |||
| 85 | 85 | ||
| 86 | 86 | echo -e "${GREEN}找到 ${#video_files[@]} 个视频文件,开始处理...${NC}" | |
| 87 | 87 | ||
| 88 | + | # 进度条绘制用的全长字符串 | |
| 89 | + | FULL_BAR="##################################################" | |
| 90 | + | BAR_WIDTH=50 | |
| 91 | + | ||
| 88 | 92 | for INPUT_VIDEO in "${video_files[@]}"; do | |
| 89 | 93 | BASE_NAME=$(basename "$INPUT_VIDEO") | |
| 90 | 94 | FILENAME="${BASE_NAME%.*}" | |
| @@ -109,19 +113,56 @@ for INPUT_VIDEO in "${video_files[@]}"; do | |||
| 109 | 113 | continue | |
| 110 | 114 | fi | |
| 111 | 115 | ||
| 116 | + | # 统计总帧数 (ls -1U 速度较快,不排序) | |
| 117 | + | TOTAL_FRAMES=$(ls -1U "$TMP_FRAMES" | wc -l) | |
| 118 | + | echo "总帧数: $TOTAL_FRAMES" | |
| 119 | + | ||
| 112 | 120 | # Step 2: AI 超分 | |
| 113 | 121 | echo -e "${YELLOW}[2/3] 正在进行 AI 超分 (Real-ESRGAN GPU)...${NC}" | |
| 114 | 122 | echo "模型: $MODEL_NAME | 放大倍数: x$SCALE" | |
| 115 | 123 | ||
| 124 | + | # 1. 以后台方式启动 (&),并将标准输出和错误输出都扔进黑洞 | |
| 116 | 125 | "$EXEC_PATH" \ | |
| 117 | 126 | -i "$TMP_FRAMES" \ | |
| 118 | 127 | -o "$OUT_FRAMES" \ | |
| 119 | 128 | -n "$MODEL_NAME" \ | |
| 120 | 129 | -s "$SCALE" \ | |
| 121 | 130 | -g "$GPU_ID" \ | |
| 122 | - | -f png | |
| 131 | + | -f png > /dev/null 2>&1 & | |
| 132 | + | ||
| 133 | + | PID=$! # 获取进程ID | |
| 134 | + | ||
| 135 | + | # 2. 循环监控进度 | |
| 136 | + | while kill -0 $PID 2> /dev/null; do | |
| 137 | + | # 统计已生成的文件数 | |
| 138 | + | CURRENT_FRAMES=$(ls -1U "$OUT_FRAMES" | wc -l) | |
| 123 | 139 | ||
| 124 | - | if [ $? -ne 0 ]; then | |
| 140 | + | # 计算百分比 | |
| 141 | + | if [ "$TOTAL_FRAMES" -gt 0 ]; then | |
| 142 | + | PERCENT=$(( CURRENT_FRAMES * 100 / TOTAL_FRAMES )) | |
| 143 | + | else | |
| 144 | + | PERCENT=0 | |
| 145 | + | fi | |
| 146 | + | ||
| 147 | + | # 防止文件系统统计延迟导致超过 100% | |
| 148 | + | if [ "$PERCENT" -gt 100 ]; then PERCENT=100; fi | |
| 149 | + | ||
| 150 | + | # 计算进度条填充长度 | |
| 151 | + | FILLED_LEN=$(( PERCENT * BAR_WIDTH / 100 )) | |
| 152 | + | ||
| 153 | + | # 打印进度条 (\r 回车不换行) | |
| 154 | + | printf "\r[${GREEN}%-${BAR_WIDTH}s${NC}] %3d%% (%d/%d)" "${FULL_BAR:0:FILLED_LEN}" "$PERCENT" "$CURRENT_FRAMES" "$TOTAL_FRAMES" | |
| 155 | + | ||
| 156 | + | sleep 1 | |
| 157 | + | done | |
| 158 | + | ||
| 159 | + | # 等待进程彻底退出并获取退出码 | |
| 160 | + | wait $PID | |
| 161 | + | EXIT_CODE=$? | |
| 162 | + | ||
| 163 | + | echo "" # 进度条跑完换个行 | |
| 164 | + | ||
| 165 | + | if [ $EXIT_CODE -ne 0 ]; then | |
| 125 | 166 | echo -e "${RED}AI 超分失败,请检查显卡驱动或显存是否足够。${NC}" | |
| 126 | 167 | rm -rf "$TMP_FRAMES" "$OUT_FRAMES" | |
| 127 | 168 | continue | |
| @@ -133,10 +174,10 @@ for INPUT_VIDEO in "${video_files[@]}"; do | |||
| 133 | 174 | # 获取原视频帧率 | |
| 134 | 175 | FPS=$(ffprobe -v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate "$INPUT_VIDEO") | |
| 135 | 176 | ||
| 136 | - | # 极限画质合成: H.265 + CRF 18 + veryslow | |
| 177 | + | # 修复点:保持双引号防止 nullglob 错误 | |
| 137 | 178 | ffmpeg -v error -stats \ | |
| 138 | 179 | -framerate "$FPS" -i "$OUT_FRAMES/frame%08d.png" \ | |
| 139 | - | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 180 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map "1:a:0?" -c:a copy \ | |
| 140 | 181 | -c:v libx265 -preset veryslow -crf 18 -pix_fmt yuv420p -tag:v hvc1 \ | |
| 141 | 182 | "$OUTPUT_VIDEO" | |
| 142 | 183 | ||
| @@ -144,7 +185,7 @@ for INPUT_VIDEO in "${video_files[@]}"; do | |||
| 144 | 185 | echo -e "${YELLOW}H.265 编码失败,尝试回退到 H.264...${NC}" | |
| 145 | 186 | ffmpeg -v error -stats \ | |
| 146 | 187 | -framerate "$FPS" -i "$OUT_FRAMES/frame%08d.png" \ | |
| 147 | - | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 188 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map "1:a:0?" -c:a copy \ | |
| 148 | 189 | -c:v libx264 -preset veryslow -crf 18 -pix_fmt yuv420p \ | |
| 149 | 190 | "$OUTPUT_VIDEO" | |
| 150 | 191 | fi | |
p.sh
| @@ -85,10 +85,6 @@ fi | |||
| 85 | 85 | ||
| 86 | 86 | echo -e "${GREEN}找到 ${#video_files[@]} 个视频文件,开始处理...${NC}" | |
| 87 | 87 | ||
| 88 | - | # 进度条绘制用的全长字符串 | |
| 89 | - | FULL_BAR="##################################################" | |
| 90 | - | BAR_WIDTH=50 | |
| 91 | - | ||
| 92 | 88 | for INPUT_VIDEO in "${video_files[@]}"; do | |
| 93 | 89 | BASE_NAME=$(basename "$INPUT_VIDEO") | |
| 94 | 90 | FILENAME="${BASE_NAME%.*}" | |
| @@ -113,56 +109,19 @@ for INPUT_VIDEO in "${video_files[@]}"; do | |||
| 113 | 109 | continue | |
| 114 | 110 | fi | |
| 115 | 111 | ||
| 116 | - | # 统计总帧数 (ls -1U 速度较快,不排序) | |
| 117 | - | TOTAL_FRAMES=$(ls -1U "$TMP_FRAMES" | wc -l) | |
| 118 | - | echo "总帧数: $TOTAL_FRAMES" | |
| 119 | - | ||
| 120 | 112 | # Step 2: AI 超分 | |
| 121 | 113 | echo -e "${YELLOW}[2/3] 正在进行 AI 超分 (Real-ESRGAN GPU)...${NC}" | |
| 122 | 114 | echo "模型: $MODEL_NAME | 放大倍数: x$SCALE" | |
| 123 | 115 | ||
| 124 | - | # 1. 以后台方式启动 (&),并将标准输出和错误输出都扔进黑洞 | |
| 125 | 116 | "$EXEC_PATH" \ | |
| 126 | 117 | -i "$TMP_FRAMES" \ | |
| 127 | 118 | -o "$OUT_FRAMES" \ | |
| 128 | 119 | -n "$MODEL_NAME" \ | |
| 129 | 120 | -s "$SCALE" \ | |
| 130 | 121 | -g "$GPU_ID" \ | |
| 131 | - | -f png > /dev/null 2>&1 & | |
| 132 | - | ||
| 133 | - | PID=$! # 获取进程ID | |
| 134 | - | ||
| 135 | - | # 2. 循环监控进度 | |
| 136 | - | while kill -0 $PID 2> /dev/null; do | |
| 137 | - | # 统计已生成的文件数 | |
| 138 | - | CURRENT_FRAMES=$(ls -1U "$OUT_FRAMES" | wc -l) | |
| 139 | - | ||
| 140 | - | # 计算百分比 | |
| 141 | - | if [ "$TOTAL_FRAMES" -gt 0 ]; then | |
| 142 | - | PERCENT=$(( CURRENT_FRAMES * 100 / TOTAL_FRAMES )) | |
| 143 | - | else | |
| 144 | - | PERCENT=0 | |
| 145 | - | fi | |
| 146 | - | ||
| 147 | - | # 防止文件系统统计延迟导致超过 100% | |
| 148 | - | if [ "$PERCENT" -gt 100 ]; then PERCENT=100; fi | |
| 122 | + | -f png | |
| 149 | 123 | ||
| 150 | - | # 计算进度条填充长度 | |
| 151 | - | FILLED_LEN=$(( PERCENT * BAR_WIDTH / 100 )) | |
| 152 | - | ||
| 153 | - | # 打印进度条 (\r 回车不换行) | |
| 154 | - | printf "\r[${GREEN}%-${BAR_WIDTH}s${NC}] %3d%% (%d/%d)" "${FULL_BAR:0:FILLED_LEN}" "$PERCENT" "$CURRENT_FRAMES" "$TOTAL_FRAMES" | |
| 155 | - | ||
| 156 | - | sleep 1 | |
| 157 | - | done | |
| 158 | - | ||
| 159 | - | # 等待进程彻底退出并获取退出码 | |
| 160 | - | wait $PID | |
| 161 | - | EXIT_CODE=$? | |
| 162 | - | ||
| 163 | - | echo "" # 进度条跑完换个行 | |
| 164 | - | ||
| 165 | - | if [ $EXIT_CODE -ne 0 ]; then | |
| 124 | + | if [ $? -ne 0 ]; then | |
| 166 | 125 | echo -e "${RED}AI 超分失败,请检查显卡驱动或显存是否足够。${NC}" | |
| 167 | 126 | rm -rf "$TMP_FRAMES" "$OUT_FRAMES" | |
| 168 | 127 | continue | |
| @@ -174,19 +133,18 @@ for INPUT_VIDEO in "${video_files[@]}"; do | |||
| 174 | 133 | # 获取原视频帧率 | |
| 175 | 134 | FPS=$(ffprobe -v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate "$INPUT_VIDEO") | |
| 176 | 135 | ||
| 177 | - | # 修复点:给 map 参数加上双引号 "1:a:0?",防止 nullglob 导致参数丢失 | |
| 136 | + | # 极限画质合成: H.265 + CRF 18 + veryslow | |
| 178 | 137 | ffmpeg -v error -stats \ | |
| 179 | 138 | -framerate "$FPS" -i "$OUT_FRAMES/frame%08d.png" \ | |
| 180 | - | -i "$INPUT_VIDEO" -map 0:v:0 -map "1:a:0?" -c:a copy \ | |
| 139 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 181 | 140 | -c:v libx265 -preset veryslow -crf 18 -pix_fmt yuv420p -tag:v hvc1 \ | |
| 182 | 141 | "$OUTPUT_VIDEO" | |
| 183 | 142 | ||
| 184 | 143 | if [ $? -ne 0 ]; then | |
| 185 | 144 | echo -e "${YELLOW}H.265 编码失败,尝试回退到 H.264...${NC}" | |
| 186 | - | # 修复点:fallback 命令同样加上引号 | |
| 187 | 145 | ffmpeg -v error -stats \ | |
| 188 | 146 | -framerate "$FPS" -i "$OUT_FRAMES/frame%08d.png" \ | |
| 189 | - | -i "$INPUT_VIDEO" -map 0:v:0 -map "1:a:0?" -c:a copy \ | |
| 147 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 190 | 148 | -c:v libx264 -preset veryslow -crf 18 -pix_fmt yuv420p \ | |
| 191 | 149 | "$OUTPUT_VIDEO" | |
| 192 | 150 | fi | |
Automating AI Video Upscaling with Real ESRGAN and Docker.md (檔案已刪除)
| @@ -1,192 +0,0 @@ | |||
| 1 | - | ||
| 2 | - | If you have a library of old, low-resolution videos that you want to restore, manually processing them frame-by-frame or using GUI tools can be tedious. In this post, I'm sharing a "set it and forget it" solution using Docker and Real-ESRGAN. | |
| 3 | - | ||
| 4 | - | This setup uses an NVIDIA GPU to automate the entire pipeline: splitting the video into frames, upscaling them with AI, and merging them back into a high-quality video file. | |
| 5 | - | ||
| 6 | - | ### Prerequisites | |
| 7 | - | ||
| 8 | - | * A machine with an NVIDIA GPU. | |
| 9 | - | * Docker Desktop or Docker Engine installed. | |
| 10 | - | * **NVIDIA Container Toolkit** (to allow Docker to access your GPU). | |
| 11 | - | ||
| 12 | - | ----- | |
| 13 | - | ||
| 14 | - | ## 1\. The Environment: Dockerfile | |
| 15 | - | ||
| 16 | - | First, we need a consistent environment. This Dockerfile builds on top of an NVIDIA base image, installs `ffmpeg` and `vulkan` drivers, and pre-downloads the Real-ESRGAN executable so we don't have to download it every time we run the container. | |
| 17 | - | ||
| 18 | - | **Dockerfile** | |
| 19 | - | ||
| 20 | - | ```dockerfile | |
| 21 | - | # Use a base image with CUDA drivers and Ubuntu to support GPU | |
| 22 | - | FROM hub.aiursoft.com/aiursoft/internalimages/nvidia:latest | |
| 23 | - | ||
| 24 | - | # Set environment variables to avoid interactive prompts during the build | |
| 25 | - | ENV DEBIAN_FRONTEND=noninteractive | |
| 26 | - | ||
| 27 | - | # Update package lists and install required dependencies: wget, unzip, ffmpeg, and vulkan loader | |
| 28 | - | RUN apt-get update && apt-get install -y --no-install-recommends \ | |
| 29 | - | wget \ | |
| 30 | - | unzip \ | |
| 31 | - | ffmpeg \ | |
| 32 | - | libvulkan1 \ | |
| 33 | - | && rm -rf /var/lib/apt/lists/* | |
| 34 | - | ||
| 35 | - | # Set the working directory | |
| 36 | - | WORKDIR /app | |
| 37 | - | ||
| 38 | - | # Download, unzip, and setup Real-ESRGAN during the image build | |
| 39 | - | # This is more efficient than downloading every time the container starts | |
| 40 | - | RUN wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesrgan-ncnn-vulkan-20220424-ubuntu.zip && \ | |
| 41 | - | unzip realesrgan-ncnn-vulkan-20220424-ubuntu.zip && \ | |
| 42 | - | rm realesrgan-ncnn-vulkan-20220424-ubuntu.zip && \ | |
| 43 | - | chmod +x realesrgan-ncnn-vulkan | |
| 44 | - | ||
| 45 | - | # Copy the automation script to the container's /app/ directory | |
| 46 | - | COPY process_videos.sh . | |
| 47 | - | # Grant execution permissions to the script | |
| 48 | - | RUN chmod +x process_videos.sh | |
| 49 | - | ||
| 50 | - | # Define mount points for external directory mapping | |
| 51 | - | VOLUME /input | |
| 52 | - | VOLUME /output | |
| 53 | - | ||
| 54 | - | # Set our processing script as the container's entrypoint | |
| 55 | - | # This script will execute automatically when the container starts | |
| 56 | - | ENTRYPOINT ["/app/process_videos.sh"] | |
| 57 | - | ``` | |
| 58 | - | ||
| 59 | - | ----- | |
| 60 | - | ||
| 61 | - | ## 2\. The Logic: Processing Script | |
| 62 | - | ||
| 63 | - | This bash script handles the heavy lifting. It scans the `/input` folder for videos, and for every file it finds, it performs three steps: | |
| 64 | - | ||
| 65 | - | 1. **Extract:** Uses `ffmpeg` to dump all frames to a temp folder. | |
| 66 | - | 2. **Upscale:** Runs `realesrgan-ncnn-vulkan` on that folder to create 4x larger frames. | |
| 67 | - | 3. **Assemble:** Stitches the new frames back together using hardware acceleration (`h264_nvenc`) if available, or falls back to CPU encoding. | |
| 68 | - | ||
| 69 | - | **process\_videos.sh** | |
| 70 | - | ||
| 71 | - | ```bash | |
| 72 | - | #!/bin/bash | |
| 73 | - | ||
| 74 | - | # Set input and output directories | |
| 75 | - | INPUT_DIR="/input" | |
| 76 | - | OUTPUT_DIR="/output" | |
| 77 | - | ||
| 78 | - | # Ensure the output directory exists | |
| 79 | - | mkdir -p "$OUTPUT_DIR" | |
| 80 | - | ||
| 81 | - | echo "Starting video enhancement tasks..." | |
| 82 | - | # Enable nullglob to handle cases where no files match a pattern | |
| 83 | - | shopt -s nullglob | |
| 84 | - | ||
| 85 | - | # Find all supported video files | |
| 86 | - | video_files=("$INPUT_DIR"/*.mp4 "$INPUT_DIR"/*.mov "$INPUT_DIR"/*.avi) | |
| 87 | - | ||
| 88 | - | # Check if any video files were found | |
| 89 | - | if [ ${#video_files[@]} -eq 0 ]; then | |
| 90 | - | echo "No supported video files found in $INPUT_DIR (.mp4, .mov, .avi)." | |
| 91 | - | exit 0 | |
| 92 | - | fi | |
| 93 | - | ||
| 94 | - | echo "Found ${#video_files[@]} video files. Starting processing..." | |
| 95 | - | ||
| 96 | - | # Loop through the found video files | |
| 97 | - | for INPUT_VIDEO in "${video_files[@]}"; do | |
| 98 | - | # Check if the file exists and is a regular file | |
| 99 | - | if [ ! -f "$INPUT_VIDEO" ]; then | |
| 100 | - | continue | |
| 101 | - | fi | |
| 102 | - | ||
| 103 | - | echo "--- Processing: $INPUT_VIDEO ---" | |
| 104 | - | ||
| 105 | - | # --- Configuration --- | |
| 106 | - | SCALE=4 # Upscaling scale | |
| 107 | - | MODEL="realesrgan-x4plus" # Model to use | |
| 108 | - | GPU_ID=0 # GPU index to use | |
| 109 | - | REALESRGAN_EXEC="/app/realesrgan-ncnn-vulkan" # Path to Real-ESRGAN executable | |
| 110 | - | ||
| 111 | - | # Create a temporary working directory for frames | |
| 112 | - | WORK_DIR=$(mktemp -d) | |
| 113 | - | mkdir -p "$WORK_DIR/out" | |
| 114 | - | echo "Working directory: $WORK_DIR" | |
| 115 | - | ||
| 116 | - | # Define output video path | |
| 117 | - | BASE_NAME=$(basename "$INPUT_VIDEO") | |
| 118 | - | OUTPUT_VIDEO="$OUTPUT_DIR/${BASE_NAME%.*}_enhanced_x${SCALE}.mp4" | |
| 119 | - | ||
| 120 | - | # --- 1. Extract Frames --- | |
| 121 | - | echo "Step 1: Splitting video into frames..." | |
| 122 | - | ffmpeg -i "$INPUT_VIDEO" -qscale:v 1 -qmin 1 -vsync 0 "$WORK_DIR/in_%08d.png" | |
| 123 | - | if [ $? -ne 0 ]; then | |
| 124 | - | echo "Error: ffmpeg failed to split '$INPUT_VIDEO'." | |
| 125 | - | rm -rf "$WORK_DIR" # Clean up temp files | |
| 126 | - | continue # Continue to the next video | |
| 127 | - | fi | |
| 128 | - | ||
| 129 | - | # --- 2. Process Frames with AI Model --- | |
| 130 | - | echo "Step 2: Upscaling frames using Real-ESRGAN..." | |
| 131 | - | $REALESRGAN_EXEC \ | |
| 132 | - | -i "$WORK_DIR" \ | |
| 133 | - | -o "$WORK_DIR/out" \ | |
| 134 | - | -n "$MODEL" \ | |
| 135 | - | -s "$SCALE" \ | |
| 136 | - | -g "$GPU_ID" \ | |
| 137 | - | -f png | |
| 138 | - | if [ $? -ne 0 ]; then | |
| 139 | - | echo "Error: Real-ESRGAN failed to process frames for '$INPUT_VIDEO'." | |
| 140 | - | rm -rf "$WORK_DIR" | |
| 141 | - | continue | |
| 142 | - | fi | |
| 143 | - | ||
| 144 | - | # --- 3. Assemble Video --- | |
| 145 | - | echo "Step 3: Assembling processed frames into video..." | |
| 146 | - | FPS=$(ffprobe -v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate "$INPUT_VIDEO") | |
| 147 | - | # Attempt to use NVIDIA GPU hardware encoding | |
| 148 | - | ffmpeg -framerate "$FPS" -i "$WORK_DIR/out/in_%08d.png" \ | |
| 149 | - | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 150 | - | -c:v h264_nvenc -preset slow -cq 18 "$OUTPUT_VIDEO" | |
| 151 | - | if [ $? -ne 0 ]; then | |
| 152 | - | echo "Warning: NVIDIA hardware encoding (h264_nvenc) failed. Trying CPU encoding (libx264)..." | |
| 153 | - | # Fallback to CPU encoding if GPU encoding fails | |
| 154 | - | ffmpeg -framerate "$FPS" -i "$WORK_DIR/out/in_%08d.png" \ | |
| 155 | - | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 156 | - | -c:v libx264 -preset medium -crf 22 "$OUTPUT_VIDEO" | |
| 157 | - | fi | |
| 158 | - | ||
| 159 | - | # --- 4. Cleanup --- | |
| 160 | - | echo "Step 4: Cleaning up temporary files..." | |
| 161 | - | rm -rf "$WORK_DIR" | |
| 162 | - | ||
| 163 | - | echo "--- Finished processing: $INPUT_VIDEO. Output file: $OUTPUT_VIDEO ---" | |
| 164 | - | done | |
| 165 | - | ||
| 166 | - | echo "All videos processed." | |
| 167 | - | ``` | |
| 168 | - | ||
| 169 | - | ----- | |
| 170 | - | ||
| 171 | - | ## How to Run It | |
| 172 | - | ||
| 173 | - | ### 1\. Build the Image | |
| 174 | - | ||
| 175 | - | In the directory containing the `Dockerfile` and the `process_videos.sh` script, run: | |
| 176 | - | ||
| 177 | - | ```bash | |
| 178 | - | docker build -t video-enhancer . | |
| 179 | - | ``` | |
| 180 | - | ||
| 181 | - | ### 2\. Run the Container | |
| 182 | - | ||
| 183 | - | Prepare a folder named `videos_to_process` with your source files and an empty `enhanced_videos` folder. Then run: | |
| 184 | - | ||
| 185 | - | ```bash | |
| 186 | - | docker run --gpus all --rm \ | |
| 187 | - | -v $(pwd)/videos_to_process:/input \ | |
| 188 | - | -v $(pwd)/enhanced_videos:/output \ | |
| 189 | - | video-enhancer | |
| 190 | - | ``` | |
| 191 | - | ||
| 192 | - | The container will spin up, process every video in the input folder, save the 4x upscaled versions to the output folder, and then shut down cleanly. Happy upscaling\! | |
p.sh(檔案已創建)
| @@ -0,0 +1,201 @@ | |||
| 1 | + | #!/bin/bash | |
| 2 | + | ||
| 3 | + | # =================配置区域================= | |
| 4 | + | # 基础路径配置 | |
| 5 | + | BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| 6 | + | INPUT_DIR="$BASE_DIR/videos_to_process" | |
| 7 | + | OUTPUT_DIR="$BASE_DIR/enhanced_videos" | |
| 8 | + | APP_DIR="$BASE_DIR/app" | |
| 9 | + | EXEC_PATH="$APP_DIR/realesrgan-ncnn-vulkan" | |
| 10 | + | ||
| 11 | + | # Real-ESRGAN 配置 | |
| 12 | + | # 模型可选: realesrgan-x4plus (适合真实影像), realesrgan-x4plus-anime (适合动漫) | |
| 13 | + | MODEL_NAME="realesrgan-x4plus" | |
| 14 | + | SCALE=4 | |
| 15 | + | # 显卡ID,自动探测一般填0,如果多卡可修改 | |
| 16 | + | GPU_ID=0 | |
| 17 | + | ||
| 18 | + | # 下载链接 | |
| 19 | + | DOWNLOAD_URL="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesrgan-ncnn-vulkan-20220424-ubuntu.zip" | |
| 20 | + | ZIP_FILE="realesrgan-ncnn-vulkan-20220424-ubuntu.zip" | |
| 21 | + | ||
| 22 | + | # ========================================= | |
| 23 | + | ||
| 24 | + | # 颜色定义 | |
| 25 | + | GREEN='\033[0;32m' | |
| 26 | + | YELLOW='\033[1;33m' | |
| 27 | + | RED='\033[0;31m' | |
| 28 | + | NC='\033[0m' # No Color | |
| 29 | + | ||
| 30 | + | # 0. 检查依赖 (ffmpeg) | |
| 31 | + | if ! command -v ffmpeg &> /dev/null; then | |
| 32 | + | echo -e "${RED}Error: ffmpeg 未安装。${NC}" | |
| 33 | + | echo "请运行: sudo apt update && sudo apt install ffmpeg" | |
| 34 | + | exit 1 | |
| 35 | + | fi | |
| 36 | + | ||
| 37 | + | # 1. 准备目录和环境 | |
| 38 | + | mkdir -p "$OUTPUT_DIR" | |
| 39 | + | mkdir -p "$APP_DIR" | |
| 40 | + | ||
| 41 | + | # 检查并下载 Real-ESRGAN | |
| 42 | + | if [ ! -f "$EXEC_PATH" ]; then | |
| 43 | + | echo -e "${YELLOW}Real-ESRGAN 可执行文件未找到,准备下载...${NC}" | |
| 44 | + | ||
| 45 | + | # 进入 app 目录下载 | |
| 46 | + | cd "$APP_DIR" || exit | |
| 47 | + | ||
| 48 | + | if [ -f "$ZIP_FILE" ]; then | |
| 49 | + | rm "$ZIP_FILE" | |
| 50 | + | fi | |
| 51 | + | ||
| 52 | + | echo "正在下载: $DOWNLOAD_URL" | |
| 53 | + | wget -q --show-progress "$DOWNLOAD_URL" | |
| 54 | + | ||
| 55 | + | if [ $? -ne 0 ]; then | |
| 56 | + | echo -e "${RED}下载失败,请检查网络连接。${NC}" | |
| 57 | + | exit 1 | |
| 58 | + | fi | |
| 59 | + | ||
| 60 | + | echo "正在解压..." | |
| 61 | + | unzip -q "$ZIP_FILE" | |
| 62 | + | ||
| 63 | + | # 赋予执行权限 | |
| 64 | + | chmod +x realesrgan-ncnn-vulkan | |
| 65 | + | ||
| 66 | + | # 清理压缩包 | |
| 67 | + | rm "$ZIP_FILE" | |
| 68 | + | ||
| 69 | + | echo -e "${GREEN}Real-ESRGAN 安装完成!${NC}" | |
| 70 | + | ||
| 71 | + | # 回到基础目录 | |
| 72 | + | cd "$BASE_DIR" || exit | |
| 73 | + | else | |
| 74 | + | echo -e "${GREEN}Real-ESRGAN 已安装,跳过下载。${NC}" | |
| 75 | + | fi | |
| 76 | + | ||
| 77 | + | # 2. 开始扫描视频并处理 | |
| 78 | + | shopt -s nullglob | |
| 79 | + | video_files=("$INPUT_DIR"/*.mp4 "$INPUT_DIR"/*.mov "$INPUT_DIR"/*.avi "$INPUT_DIR"/*.mkv) | |
| 80 | + | ||
| 81 | + | if [ ${#video_files[@]} -eq 0 ]; then | |
| 82 | + | echo -e "${YELLOW}在 $INPUT_DIR 中没有找到支持的视频文件。${NC}" | |
| 83 | + | exit 0 | |
| 84 | + | fi | |
| 85 | + | ||
| 86 | + | echo -e "${GREEN}找到 ${#video_files[@]} 个视频文件,开始处理...${NC}" | |
| 87 | + | ||
| 88 | + | # 进度条绘制用的全长字符串 | |
| 89 | + | FULL_BAR="##################################################" | |
| 90 | + | BAR_WIDTH=50 | |
| 91 | + | ||
| 92 | + | for INPUT_VIDEO in "${video_files[@]}"; do | |
| 93 | + | BASE_NAME=$(basename "$INPUT_VIDEO") | |
| 94 | + | FILENAME="${BASE_NAME%.*}" | |
| 95 | + | OUTPUT_VIDEO="$OUTPUT_DIR/${FILENAME}_x${SCALE}.mp4" | |
| 96 | + | ||
| 97 | + | echo "----------------------------------------------------------------" | |
| 98 | + | echo -e "正在处理: ${YELLOW}$BASE_NAME${NC}" | |
| 99 | + | echo "----------------------------------------------------------------" | |
| 100 | + | ||
| 101 | + | # 创建临时目录 | |
| 102 | + | TMP_FRAMES=$(mktemp -d -p "$BASE_DIR" "tmp_frames_XXXXXX") | |
| 103 | + | OUT_FRAMES=$(mktemp -d -p "$BASE_DIR" "out_frames_XXXXXX") | |
| 104 | + | ||
| 105 | + | # Step 1: 拆帧 | |
| 106 | + | echo -e "${YELLOW}[1/3] 正在提取帧 (使用 ffmpeg)...${NC}" | |
| 107 | + | # 使用 png 格式以保证无损画质传入 AI | |
| 108 | + | ffmpeg -v error -stats -i "$INPUT_VIDEO" -qscale:v 1 -qmin 1 -fps_mode passthrough "$TMP_FRAMES/frame%08d.png" | |
| 109 | + | ||
| 110 | + | if [ $? -ne 0 ]; then | |
| 111 | + | echo -e "${RED}拆帧失败,跳过此视频。${NC}" | |
| 112 | + | rm -rf "$TMP_FRAMES" "$OUT_FRAMES" | |
| 113 | + | continue | |
| 114 | + | fi | |
| 115 | + | ||
| 116 | + | # 统计总帧数 (ls -1U 速度较快,不排序) | |
| 117 | + | TOTAL_FRAMES=$(ls -1U "$TMP_FRAMES" | wc -l) | |
| 118 | + | echo "总帧数: $TOTAL_FRAMES" | |
| 119 | + | ||
| 120 | + | # Step 2: AI 超分 | |
| 121 | + | echo -e "${YELLOW}[2/3] 正在进行 AI 超分 (Real-ESRGAN GPU)...${NC}" | |
| 122 | + | echo "模型: $MODEL_NAME | 放大倍数: x$SCALE" | |
| 123 | + | ||
| 124 | + | # 1. 以后台方式启动 (&),并将标准输出和错误输出都扔进黑洞 | |
| 125 | + | "$EXEC_PATH" \ | |
| 126 | + | -i "$TMP_FRAMES" \ | |
| 127 | + | -o "$OUT_FRAMES" \ | |
| 128 | + | -n "$MODEL_NAME" \ | |
| 129 | + | -s "$SCALE" \ | |
| 130 | + | -g "$GPU_ID" \ | |
| 131 | + | -f png > /dev/null 2>&1 & | |
| 132 | + | ||
| 133 | + | PID=$! # 获取进程ID | |
| 134 | + | ||
| 135 | + | # 2. 循环监控进度 | |
| 136 | + | while kill -0 $PID 2> /dev/null; do | |
| 137 | + | # 统计已生成的文件数 | |
| 138 | + | CURRENT_FRAMES=$(ls -1U "$OUT_FRAMES" | wc -l) | |
| 139 | + | ||
| 140 | + | # 计算百分比 | |
| 141 | + | if [ "$TOTAL_FRAMES" -gt 0 ]; then | |
| 142 | + | PERCENT=$(( CURRENT_FRAMES * 100 / TOTAL_FRAMES )) | |
| 143 | + | else | |
| 144 | + | PERCENT=0 | |
| 145 | + | fi | |
| 146 | + | ||
| 147 | + | # 防止文件系统统计延迟导致超过 100% | |
| 148 | + | if [ "$PERCENT" -gt 100 ]; then PERCENT=100; fi | |
| 149 | + | ||
| 150 | + | # 计算进度条填充长度 | |
| 151 | + | FILLED_LEN=$(( PERCENT * BAR_WIDTH / 100 )) | |
| 152 | + | ||
| 153 | + | # 打印进度条 (\r 回车不换行) | |
| 154 | + | printf "\r[${GREEN}%-${BAR_WIDTH}s${NC}] %3d%% (%d/%d)" "${FULL_BAR:0:FILLED_LEN}" "$PERCENT" "$CURRENT_FRAMES" "$TOTAL_FRAMES" | |
| 155 | + | ||
| 156 | + | sleep 1 | |
| 157 | + | done | |
| 158 | + | ||
| 159 | + | # 等待进程彻底退出并获取退出码 | |
| 160 | + | wait $PID | |
| 161 | + | EXIT_CODE=$? | |
| 162 | + | ||
| 163 | + | echo "" # 进度条跑完换个行 | |
| 164 | + | ||
| 165 | + | if [ $EXIT_CODE -ne 0 ]; then | |
| 166 | + | echo -e "${RED}AI 超分失败,请检查显卡驱动或显存是否足够。${NC}" | |
| 167 | + | rm -rf "$TMP_FRAMES" "$OUT_FRAMES" | |
| 168 | + | continue | |
| 169 | + | fi | |
| 170 | + | ||
| 171 | + | # Step 3: 合成视频 | |
| 172 | + | echo -e "${YELLOW}[3/3] 正在合成视频 (High Quality H.265)...${NC}" | |
| 173 | + | ||
| 174 | + | # 获取原视频帧率 | |
| 175 | + | FPS=$(ffprobe -v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate "$INPUT_VIDEO") | |
| 176 | + | ||
| 177 | + | # 修复点:给 map 参数加上双引号 "1:a:0?",防止 nullglob 导致参数丢失 | |
| 178 | + | ffmpeg -v error -stats \ | |
| 179 | + | -framerate "$FPS" -i "$OUT_FRAMES/frame%08d.png" \ | |
| 180 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map "1:a:0?" -c:a copy \ | |
| 181 | + | -c:v libx265 -preset veryslow -crf 18 -pix_fmt yuv420p -tag:v hvc1 \ | |
| 182 | + | "$OUTPUT_VIDEO" | |
| 183 | + | ||
| 184 | + | if [ $? -ne 0 ]; then | |
| 185 | + | echo -e "${YELLOW}H.265 编码失败,尝试回退到 H.264...${NC}" | |
| 186 | + | # 修复点:fallback 命令同样加上引号 | |
| 187 | + | ffmpeg -v error -stats \ | |
| 188 | + | -framerate "$FPS" -i "$OUT_FRAMES/frame%08d.png" \ | |
| 189 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map "1:a:0?" -c:a copy \ | |
| 190 | + | -c:v libx264 -preset veryslow -crf 18 -pix_fmt yuv420p \ | |
| 191 | + | "$OUTPUT_VIDEO" | |
| 192 | + | fi | |
| 193 | + | ||
| 194 | + | # 清理临时文件 | |
| 195 | + | echo -e "${GREEN}清理临时文件...${NC}" | |
| 196 | + | rm -rf "$TMP_FRAMES" "$OUT_FRAMES" | |
| 197 | + | ||
| 198 | + | echo -e "${GREEN}完成! 输出文件: $OUTPUT_VIDEO${NC}" | |
| 199 | + | done | |
| 200 | + | ||
| 201 | + | echo -e "${GREEN}所有任务已完成。${NC}" | |
Automating AI Video Upscaling with Real ESRGAN and Docker.md (檔案已創建)
| @@ -0,0 +1,192 @@ | |||
| 1 | + | ||
| 2 | + | If you have a library of old, low-resolution videos that you want to restore, manually processing them frame-by-frame or using GUI tools can be tedious. In this post, I'm sharing a "set it and forget it" solution using Docker and Real-ESRGAN. | |
| 3 | + | ||
| 4 | + | This setup uses an NVIDIA GPU to automate the entire pipeline: splitting the video into frames, upscaling them with AI, and merging them back into a high-quality video file. | |
| 5 | + | ||
| 6 | + | ### Prerequisites | |
| 7 | + | ||
| 8 | + | * A machine with an NVIDIA GPU. | |
| 9 | + | * Docker Desktop or Docker Engine installed. | |
| 10 | + | * **NVIDIA Container Toolkit** (to allow Docker to access your GPU). | |
| 11 | + | ||
| 12 | + | ----- | |
| 13 | + | ||
| 14 | + | ## 1\. The Environment: Dockerfile | |
| 15 | + | ||
| 16 | + | First, we need a consistent environment. This Dockerfile builds on top of an NVIDIA base image, installs `ffmpeg` and `vulkan` drivers, and pre-downloads the Real-ESRGAN executable so we don't have to download it every time we run the container. | |
| 17 | + | ||
| 18 | + | **Dockerfile** | |
| 19 | + | ||
| 20 | + | ```dockerfile | |
| 21 | + | # Use a base image with CUDA drivers and Ubuntu to support GPU | |
| 22 | + | FROM hub.aiursoft.com/aiursoft/internalimages/nvidia:latest | |
| 23 | + | ||
| 24 | + | # Set environment variables to avoid interactive prompts during the build | |
| 25 | + | ENV DEBIAN_FRONTEND=noninteractive | |
| 26 | + | ||
| 27 | + | # Update package lists and install required dependencies: wget, unzip, ffmpeg, and vulkan loader | |
| 28 | + | RUN apt-get update && apt-get install -y --no-install-recommends \ | |
| 29 | + | wget \ | |
| 30 | + | unzip \ | |
| 31 | + | ffmpeg \ | |
| 32 | + | libvulkan1 \ | |
| 33 | + | && rm -rf /var/lib/apt/lists/* | |
| 34 | + | ||
| 35 | + | # Set the working directory | |
| 36 | + | WORKDIR /app | |
| 37 | + | ||
| 38 | + | # Download, unzip, and setup Real-ESRGAN during the image build | |
| 39 | + | # This is more efficient than downloading every time the container starts | |
| 40 | + | RUN wget https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesrgan-ncnn-vulkan-20220424-ubuntu.zip && \ | |
| 41 | + | unzip realesrgan-ncnn-vulkan-20220424-ubuntu.zip && \ | |
| 42 | + | rm realesrgan-ncnn-vulkan-20220424-ubuntu.zip && \ | |
| 43 | + | chmod +x realesrgan-ncnn-vulkan | |
| 44 | + | ||
| 45 | + | # Copy the automation script to the container's /app/ directory | |
| 46 | + | COPY process_videos.sh . | |
| 47 | + | # Grant execution permissions to the script | |
| 48 | + | RUN chmod +x process_videos.sh | |
| 49 | + | ||
| 50 | + | # Define mount points for external directory mapping | |
| 51 | + | VOLUME /input | |
| 52 | + | VOLUME /output | |
| 53 | + | ||
| 54 | + | # Set our processing script as the container's entrypoint | |
| 55 | + | # This script will execute automatically when the container starts | |
| 56 | + | ENTRYPOINT ["/app/process_videos.sh"] | |
| 57 | + | ``` | |
| 58 | + | ||
| 59 | + | ----- | |
| 60 | + | ||
| 61 | + | ## 2\. The Logic: Processing Script | |
| 62 | + | ||
| 63 | + | This bash script handles the heavy lifting. It scans the `/input` folder for videos, and for every file it finds, it performs three steps: | |
| 64 | + | ||
| 65 | + | 1. **Extract:** Uses `ffmpeg` to dump all frames to a temp folder. | |
| 66 | + | 2. **Upscale:** Runs `realesrgan-ncnn-vulkan` on that folder to create 4x larger frames. | |
| 67 | + | 3. **Assemble:** Stitches the new frames back together using hardware acceleration (`h264_nvenc`) if available, or falls back to CPU encoding. | |
| 68 | + | ||
| 69 | + | **process\_videos.sh** | |
| 70 | + | ||
| 71 | + | ```bash | |
| 72 | + | #!/bin/bash | |
| 73 | + | ||
| 74 | + | # Set input and output directories | |
| 75 | + | INPUT_DIR="/input" | |
| 76 | + | OUTPUT_DIR="/output" | |
| 77 | + | ||
| 78 | + | # Ensure the output directory exists | |
| 79 | + | mkdir -p "$OUTPUT_DIR" | |
| 80 | + | ||
| 81 | + | echo "Starting video enhancement tasks..." | |
| 82 | + | # Enable nullglob to handle cases where no files match a pattern | |
| 83 | + | shopt -s nullglob | |
| 84 | + | ||
| 85 | + | # Find all supported video files | |
| 86 | + | video_files=("$INPUT_DIR"/*.mp4 "$INPUT_DIR"/*.mov "$INPUT_DIR"/*.avi) | |
| 87 | + | ||
| 88 | + | # Check if any video files were found | |
| 89 | + | if [ ${#video_files[@]} -eq 0 ]; then | |
| 90 | + | echo "No supported video files found in $INPUT_DIR (.mp4, .mov, .avi)." | |
| 91 | + | exit 0 | |
| 92 | + | fi | |
| 93 | + | ||
| 94 | + | echo "Found ${#video_files[@]} video files. Starting processing..." | |
| 95 | + | ||
| 96 | + | # Loop through the found video files | |
| 97 | + | for INPUT_VIDEO in "${video_files[@]}"; do | |
| 98 | + | # Check if the file exists and is a regular file | |
| 99 | + | if [ ! -f "$INPUT_VIDEO" ]; then | |
| 100 | + | continue | |
| 101 | + | fi | |
| 102 | + | ||
| 103 | + | echo "--- Processing: $INPUT_VIDEO ---" | |
| 104 | + | ||
| 105 | + | # --- Configuration --- | |
| 106 | + | SCALE=4 # Upscaling scale | |
| 107 | + | MODEL="realesrgan-x4plus" # Model to use | |
| 108 | + | GPU_ID=0 # GPU index to use | |
| 109 | + | REALESRGAN_EXEC="/app/realesrgan-ncnn-vulkan" # Path to Real-ESRGAN executable | |
| 110 | + | ||
| 111 | + | # Create a temporary working directory for frames | |
| 112 | + | WORK_DIR=$(mktemp -d) | |
| 113 | + | mkdir -p "$WORK_DIR/out" | |
| 114 | + | echo "Working directory: $WORK_DIR" | |
| 115 | + | ||
| 116 | + | # Define output video path | |
| 117 | + | BASE_NAME=$(basename "$INPUT_VIDEO") | |
| 118 | + | OUTPUT_VIDEO="$OUTPUT_DIR/${BASE_NAME%.*}_enhanced_x${SCALE}.mp4" | |
| 119 | + | ||
| 120 | + | # --- 1. Extract Frames --- | |
| 121 | + | echo "Step 1: Splitting video into frames..." | |
| 122 | + | ffmpeg -i "$INPUT_VIDEO" -qscale:v 1 -qmin 1 -vsync 0 "$WORK_DIR/in_%08d.png" | |
| 123 | + | if [ $? -ne 0 ]; then | |
| 124 | + | echo "Error: ffmpeg failed to split '$INPUT_VIDEO'." | |
| 125 | + | rm -rf "$WORK_DIR" # Clean up temp files | |
| 126 | + | continue # Continue to the next video | |
| 127 | + | fi | |
| 128 | + | ||
| 129 | + | # --- 2. Process Frames with AI Model --- | |
| 130 | + | echo "Step 2: Upscaling frames using Real-ESRGAN..." | |
| 131 | + | $REALESRGAN_EXEC \ | |
| 132 | + | -i "$WORK_DIR" \ | |
| 133 | + | -o "$WORK_DIR/out" \ | |
| 134 | + | -n "$MODEL" \ | |
| 135 | + | -s "$SCALE" \ | |
| 136 | + | -g "$GPU_ID" \ | |
| 137 | + | -f png | |
| 138 | + | if [ $? -ne 0 ]; then | |
| 139 | + | echo "Error: Real-ESRGAN failed to process frames for '$INPUT_VIDEO'." | |
| 140 | + | rm -rf "$WORK_DIR" | |
| 141 | + | continue | |
| 142 | + | fi | |
| 143 | + | ||
| 144 | + | # --- 3. Assemble Video --- | |
| 145 | + | echo "Step 3: Assembling processed frames into video..." | |
| 146 | + | FPS=$(ffprobe -v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate "$INPUT_VIDEO") | |
| 147 | + | # Attempt to use NVIDIA GPU hardware encoding | |
| 148 | + | ffmpeg -framerate "$FPS" -i "$WORK_DIR/out/in_%08d.png" \ | |
| 149 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 150 | + | -c:v h264_nvenc -preset slow -cq 18 "$OUTPUT_VIDEO" | |
| 151 | + | if [ $? -ne 0 ]; then | |
| 152 | + | echo "Warning: NVIDIA hardware encoding (h264_nvenc) failed. Trying CPU encoding (libx264)..." | |
| 153 | + | # Fallback to CPU encoding if GPU encoding fails | |
| 154 | + | ffmpeg -framerate "$FPS" -i "$WORK_DIR/out/in_%08d.png" \ | |
| 155 | + | -i "$INPUT_VIDEO" -map 0:v:0 -map 1:a:0? -c:a copy \ | |
| 156 | + | -c:v libx264 -preset medium -crf 22 "$OUTPUT_VIDEO" | |
| 157 | + | fi | |
| 158 | + | ||
| 159 | + | # --- 4. Cleanup --- | |
| 160 | + | echo "Step 4: Cleaning up temporary files..." | |
| 161 | + | rm -rf "$WORK_DIR" | |
| 162 | + | ||
| 163 | + | echo "--- Finished processing: $INPUT_VIDEO. Output file: $OUTPUT_VIDEO ---" | |
| 164 | + | done | |
| 165 | + | ||
| 166 | + | echo "All videos processed." | |
| 167 | + | ``` | |
| 168 | + | ||
| 169 | + | ----- | |
| 170 | + | ||
| 171 | + | ## How to Run It | |
| 172 | + | ||
| 173 | + | ### 1\. Build the Image | |
| 174 | + | ||
| 175 | + | In the directory containing the `Dockerfile` and the `process_videos.sh` script, run: | |
| 176 | + | ||
| 177 | + | ```bash | |
| 178 | + | docker build -t video-enhancer . | |
| 179 | + | ``` | |
| 180 | + | ||
| 181 | + | ### 2\. Run the Container | |
| 182 | + | ||
| 183 | + | Prepare a folder named `videos_to_process` with your source files and an empty `enhanced_videos` folder. Then run: | |
| 184 | + | ||
| 185 | + | ```bash | |
| 186 | + | docker run --gpus all --rm \ | |
| 187 | + | -v $(pwd)/videos_to_process:/input \ | |
| 188 | + | -v $(pwd)/enhanced_videos:/output \ | |
| 189 | + | video-enhancer | |
| 190 | + | ``` | |
| 191 | + | ||
| 192 | + | The container will spin up, process every video in the input folder, save the 4x upscaled versions to the output folder, and then shut down cleanly. Happy upscaling\! | |