Update model files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env.template +10 -0
- .gitattributes +7 -33
- .gitignore +33 -0
- .python-version +1 -0
- README.md +106 -0
- analyze_result.csv +113 -0
- docker/Dockerfile +11 -0
- docker/docker-compose-vali.yml +33 -0
- docs/images/banner.png +3 -0
- docs/images/graph1.png +3 -0
- docs/images/graph2.png +3 -0
- docs/images/graph3.png +3 -0
- docs/images/graph4.png +3 -0
- docs/images/graph5.png +3 -0
- docs/incentive_mechanism.md +734 -0
- docs/miner_setup.md +273 -0
- docs/validator_setup.md +241 -0
- iqf_scores.csv +53 -0
- iqf_scores_1.csv +75 -0
- iqf_scores_2.csv +31 -0
- iqf_scores_3.csv +8 -0
- iqf_scores_4.csv +14 -0
- iqf_scores_custom.csv +48 -0
- iqf_scores_video1.csv +29 -0
- neurons/miner.py +299 -0
- neurons/validator.py +1227 -0
- pretrained/AIM_Training_2branche_KAN-Head.pt +3 -0
- pretrained/AIM_Training_2branche_MLP-Head.pt +3 -0
- pretrained/single_branch_pretrained/Authentic.pth +3 -0
- pretrained/single_branch_pretrained/Synthetic.pth +3 -0
- run.sh +261 -0
- scripts/capture_video_frames.py +83 -0
- scripts/check_queues.py +68 -0
- scripts/clip_video.py +207 -0
- scripts/compress_miner_king1_0.sh +10 -0
- scripts/compress_miner_king85_1.sh +10 -0
- scripts/compress_miner_king85_2.sh +10 -0
- scripts/compress_miner_king85_3.sh +10 -0
- scripts/compress_server.sh +3 -0
- scripts/get_video_lengths.py +212 -0
- scripts/video_info_cap.py +68 -0
- services/compress/__init__.py +0 -0
- services/compress/encoder.py +893 -0
- services/compress/models/preprocessing_pipeline.pkl +3 -0
- services/compress/models/scene_classifier_model.pth +3 -0
- services/compress/scene_detector.py +614 -0
- services/compress/server.py +798 -0
- services/compress/utils/__init__.py +34 -0
- services/compress/utils/analyze_video_fast.py +568 -0
- services/compress/utils/calculate_vmaf_adv.py +734 -0
.env.template
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
BUCKET_TYPE="type of bucket. one of: backblaze, amazon_s3, cloudflare, or hippius"
|
| 2 |
+
BUCKET_NAME="buckent name"
|
| 3 |
+
BUCKET_COMPATIBLE_ENDPOINT="bucket endpoint"
|
| 4 |
+
BUCKET_COMPATIBLE_ACCESS_KEY="bucket personal access key"
|
| 5 |
+
BUCKET_COMPATIBLE_SECRET_KEY="bucket personal secret key"
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
#validators only
|
| 9 |
+
PEXELS_API_KEY="Your Pexels account api key"
|
| 10 |
+
WANDB_API_KEY="Your wandb api key"
|
.gitattributes
CHANGED
|
@@ -1,35 +1,9 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
docs/images/banner.png filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
docs/images/graph1.png filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
docs/images/graph2.png filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
docs/images/graph3.png filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
docs/images/graph4.png filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
docs/images/graph5.png filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
services/compress/models/preprocessing_pipeline.pkl filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
note.txt
|
| 3 |
+
*.log
|
| 4 |
+
*.db
|
| 5 |
+
*.mp4
|
| 6 |
+
*.lock
|
| 7 |
+
*.hevc
|
| 8 |
+
*.y4m
|
| 9 |
+
*.egg-info
|
| 10 |
+
*.pyc
|
| 11 |
+
*.mp3
|
| 12 |
+
*.js
|
| 13 |
+
*.xml
|
| 14 |
+
*.txt
|
| 15 |
+
.DS_Store
|
| 16 |
+
*.json
|
| 17 |
+
.env
|
| 18 |
+
venv/
|
| 19 |
+
.venv/
|
| 20 |
+
video2x/
|
| 21 |
+
logs/
|
| 22 |
+
videos/
|
| 23 |
+
wandb/
|
| 24 |
+
IQA-PyTorch/
|
| 25 |
+
MDTVSFA/
|
| 26 |
+
output/
|
| 27 |
+
MANIQA/
|
| 28 |
+
frames/
|
| 29 |
+
tmp/
|
| 30 |
+
vmaf/
|
| 31 |
+
services/scoring/vmaf
|
| 32 |
+
services/upscaling/models/
|
| 33 |
+
tmp_scene/
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.12
|
README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div align="center">
|
| 2 |
+
|
| 3 |
+
# **Vidaio Subnet**: Revolutionizing Video Processing with AI-Driven Decentralization <!-- omit in toc -->
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
Please check our [Tweet](https://x.com/vidaio_τ) to follow us.
|
| 7 |
+
[Website](https://vidaio.io)
|
| 8 |
+
[](https://vidaio.io)
|
| 9 |
+
|
| 10 |
+
[](https://opensource.org/licenses/MIT)
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
</div>
|
| 15 |
+
|
| 16 |
+
## **Table of Contents**
|
| 17 |
+
|
| 18 |
+
1. [Introduction](#1-introduction)
|
| 19 |
+
2. [Subnet Architecture](#2-subnet-architecture)
|
| 20 |
+
- [Overview](#21-overview)
|
| 21 |
+
- [Miners](#22-miners)
|
| 22 |
+
- [Validators](#23-validators)
|
| 23 |
+
- [Synapses](#24-synapses)
|
| 24 |
+
- [Synthetic Query](#241-synthetic-query)
|
| 25 |
+
- [Organic Query](#242-organic-query)
|
| 26 |
+
3. [Setup](#3-setup)
|
| 27 |
+
4. [Roadmap](#4-roadmap)
|
| 28 |
+
5. [Appendix](#5-appendix)
|
| 29 |
+
- [Technical Glossary](#a-technical-glossary)
|
| 30 |
+
- [References](#b-references)
|
| 31 |
+
- [Contact Information](#c-contact-information)
|
| 32 |
+
|
| 33 |
+
## **1. Introduction**
|
| 34 |
+
Vidaio's mission is to democratise video enhancement through decentralisation, artificial intelligence, and blockchain technology. Leveraging the Bittensor ecosystem, Vidaio provides creators, businesses, and developers with scalable, affordable, and high-quality video processing solutions including upscaling and compression, while ensuring full ownership and control over their content.
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
## 2. Subnet Architecture
|
| 38 |
+
|
| 39 |
+
### 2.1 Overview
|
| 40 |
+
- **Miners**: Handle video processing tasks including upscaling and compression, optimizing models to ensure high-quality outputs.
|
| 41 |
+
- **Validators**: Assess miner performance using predefined metrics to maintain network integrity across all video processing workflows.
|
| 42 |
+
|
| 43 |
+
### 2.2 Miners
|
| 44 |
+
Miners enhance video quality and optimize file sizes using AI-driven processing techniques. They can:
|
| 45 |
+
- Optimise open-source models or develop proprietary ones for superior upscaling and compression results.
|
| 46 |
+
- Handle video upscaling and compression requests from validators and end-users.
|
| 47 |
+
- Process both upscaling tasks (enhancing video quality) and compression tasks (reducing file size while maintaining quality).
|
| 48 |
+
|
| 49 |
+
### 2.3 Validators
|
| 50 |
+
Validators ensure miners deliver consistent, high-quality results by evaluating performance through synthetic and organic queries for both upscaling and compression workflows.
|
| 51 |
+
|
| 52 |
+
### 2.4 Synapses
|
| 53 |
+
#### 2.4.1 Synthetic Query
|
| 54 |
+
Validators benchmark miner performance using controlled datasets:
|
| 55 |
+
- **Upscaling**: Downscale a high-resolution video to low-resolution, then miners upscale it back to high resolution.
|
| 56 |
+
- **Compression**: Provide high-quality videos for miners to compress while maintaining optimal quality-to-size ratios.
|
| 57 |
+
|
| 58 |
+
- Validators assess the processed outputs using metrics VMAF and PieAPP for quality evaluation.
|
| 59 |
+
|
| 60 |
+
#### 2.4.2 Organic Query
|
| 61 |
+
Real-world video data uploaded by users is processed as follows:
|
| 62 |
+
- Videos are chunked and queued for miners.
|
| 63 |
+
- Miners process and apply upscaling or compression based on user requirements.
|
| 64 |
+
- Results are aggregated and delivered back to users.
|
| 65 |
+
|
| 66 |
+
### 2.5 Incentive mechanism
|
| 67 |
+
- [Incetive Mechanism Guide](docs/incentive_mechanism.md)
|
| 68 |
+
|
| 69 |
+
## 3. Setup
|
| 70 |
+
- [Validator Setup Guide](docs/validator_setup.md)
|
| 71 |
+
- [Miner Setup Guide](docs/miner_setup.md)
|
| 72 |
+
|
| 73 |
+
## 4. Roadmap
|
| 74 |
+
|
| 75 |
+
### Phase 1: Implementing the Video Processing Synapses
|
| 76 |
+
- Launch the subnet with AI-powered video upscaling and compression capabilities.
|
| 77 |
+
- Focus on real-time processing of videos for both quality enhancement and size optimization.
|
| 78 |
+
|
| 79 |
+
### Phase 2: Developing Advanced Video Processing Models
|
| 80 |
+
- Build AI models for adaptive bitrate streaming and intelligent compression.
|
| 81 |
+
- Optimize bandwidth usage while maintaining video quality across different use cases.
|
| 82 |
+
|
| 83 |
+
### Phase 3: Implementing the Transcode Optimization Synapse
|
| 84 |
+
- Introduce AI-driven transcoding for compatibility across devices.
|
| 85 |
+
- Evaluate miners on speed, quality, and efficiency for all processing workflows.
|
| 86 |
+
|
| 87 |
+
### Phase 4: On-Demand Streaming Architecture
|
| 88 |
+
- Enable decentralized on-demand video streaming with integrated storage.
|
| 89 |
+
- Utilize peer-to-peer (P2P) models for redundancy and high availability.
|
| 90 |
+
|
| 91 |
+
### Phase 5: Live Streaming Through the Subnet
|
| 92 |
+
- Introduce live streaming with real-time AI-powered upscaling, compression, and transcoding.
|
| 93 |
+
- Integrate adaptive bitrate streaming for smooth playback.
|
| 94 |
+
|
| 95 |
+
### Phase 6: Subnet API for Real-World Integration
|
| 96 |
+
- Develop a RESTful API for seamless integration with external platforms.
|
| 97 |
+
- Include features for uploading, processing, and retrieving videos with multiple processing options.
|
| 98 |
+
|
| 99 |
+
## 5. Appendix
|
| 100 |
+
|
| 101 |
+
### A. Technical Glossary
|
| 102 |
+
- **VMAF**: [Video Multimethod Assessment Fusion](https://github.com/vidaio-subnet/vmaf)
|
| 103 |
+
- **Video2x**: [Vidio upscaling model](https://github.com/vidaio-subnet/video2x)
|
| 104 |
+
- **TOPIQ**: [Top-down Image Quality Assessment](https://arxiv.org/pdf/2308.03060v1)
|
| 105 |
+
- **LPIPS**: [Learned Perceptual Image Patch Similarity](https://github.com/richzhang/PerceptualSimilarity)
|
| 106 |
+
- **Bittensor Subnet**: [Decentralized AI Framework](https://docs.bittensor.com)
|
analyze_result.csv
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,metrics_resolution_width,metrics_resolution_height,metrics_frame_rate,metrics_bit_depth,input_codec,input_bitrate_kbps,bits_per_pixel,metrics_avg_motion,metrics_avg_edge_density,metrics_avg_texture,metrics_avg_color_complexity,metrics_avg_motion_variance,metrics_avg_grain_noise,metrics_avg_spatial_information,metrics_avg_temporal_information,metrics_avg_psnr,quality_noise_level,quality_sharpness,quality_contrast,quality_brightness,quality_color_saturation,quality_motion_blur,quality_compression_artifacts,quality_text_content,quality_edge_density,quality_temporal_consistency,quality_psnr
|
| 2 |
+
691de276-7b47-4015-bc54-da4343889084.mp4,4208,2040,25.0,8,h264,17985.092,0.08380438753448147,0.04384737944001503,0.005426638335942742,6.8855719566345215,7.156955718994141,7.524673709889497e-05,1.1901419162750244,26.9073429107666,22.757010920983905,20.114583549810607,0.02833493985235691,0.015084956772625446,0.14730598471266507,0.46570451798664747,0.47825197173603406,0.007945261437908496,0.004781483206897974,0.5164016097567037,0.013443464362931484,0.9986614345096869,19.556071543951166
|
| 3 |
+
5533624b-8b2f-476e-93c3-26ecc8abbbfc.mp4,3816,2216,29.97,8,h264,40848.835,0.16118135782046936,0.05624176500156784,0.026415047037365004,6.792716026306152,7.042492866516113,9.488612387943008e-06,2.512382745742798,38.068580627441406,17.092482685057654,21.17046433272484,0.14134825766086578,0.05944013595581055,0.1886964347338915,0.40776378246190087,0.22834675684838993,0.0767561909194802,0.014388006490965685,0.6034597344262048,0.13276803587781638,0.9937148534950766,20.368462730755084
|
| 4 |
+
eeb92086-2c5a-47b1-911a-9f23c3c51a83.mp4,1776,1136,24.0,8,h264,2672.796,0.055199262863215325,0.03173686158973804,0.0013392573911940107,6.271199703216553,6.502370357513428,8.143292527477933e-06,0.9688636064529419,16.720378875732422,15.422695223979982,23.325329066984917,0.01851261407136917,0.008041225373744965,0.2702982078458706,0.47260835685519825,0.49105042089002837,0.002524366355792412,0.004669796365002791,0.6441920573742758,0.0038926195121177514,0.9936521870358345,22.560553496084836
|
| 5 |
+
fcd1e637-3ac0-49af-920e-6bc5ae2e32cf.mp4,3696,2256,25.0,8,h264,39087.017,0.18750871653464737,0.016150920847465354,0.01093951482914249,3.45924711227417,3.2307815551757812,2.0072982843731174e-07,2.5420711040496826,50.68205261230469,16.757298690354837,23.392093338873003,0.12434706836938858,0.057585906237363815,0.4032672850400358,0.07724540338647816,0.3632161264328427,0.012838099523604844,0.005424946701774995,0.6764255555811407,0.021723695925823586,0.9999686643274331,23.093613988739953
|
| 6 |
+
a5f32e7e-a5f3-4a7e-b7fd-dc0fb551e243.mp4,1896,1120,29.97,8,h264,23338.264,0.3667122768740209,0.061917931399880624,0.09050651748040987,7.628411769866943,7.722901821136475,4.143976236350943e-06,4.869390487670898,82.86348724365234,25.48501617108024,18.59554961507288,0.4847494661808014,0.2182989865541458,0.10021196512715723,0.4154083440689919,0.31292339984517015,0.13150884694595139,0.016520978262027104,0.2850421313039984,0.19162546149789028,0.9980037344579182,18.556904219657582
|
| 7 |
+
59bb4aa2-724b-490a-944c-eaae28496192.mp4,3632,2096,24.0,8,h264,69412.382,0.3799168785064342,0.05599570354663959,0.04387347832666375,6.056894779205322,6.095084190368652,5.721938383188476e-07,4.240983963012695,80.17750549316406,31.89957463627171,17.26237240761681,0.36625877022743225,0.16681808233261108,0.2566843861128125,0.2162887687259836,0.4824837299832892,0.05429285100772326,0.017838669940829277,0.5918705022362714,0.09003080652890338,0.9984225156964289,17.10664600652759
|
| 8 |
+
64e41231-4092-46c3-906d-5170de857162.mp4,2280,1264,23.976,8,h264,23777.783,0.34412221401855625,0.06413068881366671,0.06563721407950254,6.899465084075928,6.884190559387207,1.840766088924721e-05,3.9509806632995605,52.393699645996094,20.247946697586123,19.842513662545336,0.35146045684814453,0.1335129588842392,0.16689629522158192,0.26620844908768154,0.4625996798249243,0.13319604060256127,0.02110528325041135,0.575414307128581,0.20663741533422164,0.9915836759698328,18.958802985591056
|
| 9 |
+
ea63ccc6-22fa-4b9b-91eb-67da4241ed6d.mp4,4304,2328,23.976,8,h264,43452.978,0.18087876209446968,0.1394414925377527,0.020270283217721227,7.215000152587891,7.374978542327881,0.002333856738159803,2.0605454444885254,46.671119689941406,51.1518810685618,12.604765054991669,0.08702545613050461,0.04784707725048065,0.1353242805091566,0.4523556977557734,0.30124959418556235,0.030720643467596676,0.008035152995338043,0.14340332336897502,0.054066424264489835,0.9066783540598498,10.662733148504527
|
| 10 |
+
2b4b64a6-a07f-4bce-93eb-0b3ce53e0575.mp4,3784,2008,29.97,8,h264,38360.653,0.16845519635715442,0.05625774827874177,0.028772252427920454,6.830992221832275,7.0621795654296875,1.0617354107425008e-05,2.7690491676330566,41.869808197021484,17.13586170732713,21.158016507670354,0.16543090343475342,0.06936812400817871,0.1945907355813904,0.40093583679074135,0.19673515344582784,0.08179026318966559,0.01480244193226099,0.9143815857079082,0.14012449146332218,0.9930504353683388,20.31808044588177
|
| 11 |
+
0ea244ef-3544-428f-9fff-61478f587f35.mp4,3944,2208,25.0,8,h264,12865.465,0.059094832179498485,0.11012237717646367,0.0002517583120204604,6.680722713470459,6.933882713317871,0.00017665350417377763,0.6035935878753662,8.824653625488281,28.863994801312817,16.080064259744987,0.00619066646322608,0.0023995821829885244,0.2047775009718129,0.3565399725466467,0.43710182121189506,0.0004759415635319595,0.0027012270875275135,0.2223476956374754,0.0005275395390540024,0.9722050198948968,15.090727575904303
|
| 12 |
+
620bbe9f-f29d-49f4-a6bc-9f8131e754ff.mp4,2112,1200,25.0,8,h264,9701.962,0.15312440025252524,0.02665024749641018,0.011701467803030304,7.390368461608887,7.473071098327637,7.221126922539258e-07,1.7604080438613892,33.12004089355469,14.255431724624623,24.171731641019242,0.06621285527944565,0.030513504520058632,0.10253218572419898,0.4184056754225259,0.3524694452696904,0.02344039351851852,0.0073311009133855505,0.06284248737373738,0.0384597932449495,0.9987974335883343,23.30514680986035
|
| 13 |
+
99010e5d-5200-4ef1-b624-88c69622c407.mp4,3944,2208,25.0,8,h264,12865.465,0.059094832179498485,0.11012237717646367,0.0002517583120204604,6.680722713470459,6.933882713317871,0.00017665350417377763,0.6035935878753662,8.824653625488281,28.863994801312817,16.080064259744987,0.00619066646322608,0.0023995821829885244,0.2047775009718129,0.3565399725466467,0.43710182121189506,0.0004759415635319595,0.0027012270875275135,0.2223476956374754,0.0005275395390540024,0.9722050198948968,15.090727575904303
|
| 14 |
+
0e4ce3ab-c7c1-4dd6-9d9d-983eabf8acf3.mp4,2176,1312,29.97,8,h264,12034.455,0.1406523388637023,0.03684070183764189,0.004346403671987088,6.438337802886963,6.428807258605957,3.48921061212562e-05,1.674142837524414,29.440975189208984,15.898470576627409,22.83817060754813,0.04417116567492485,0.019086694344878197,0.27141317766616685,0.5323091666051284,0.1473271014321708,0.01067878799766858,0.0052575784890602035,0.5868236919386657,0.021302758193597563,0.9938409873174072,22.136717544720863
|
| 15 |
+
708b5e08-9d59-4b41-9dba-f7735682a9b4.mp4,4032,2320,29.97,8,h264,22724.011,0.08105685321222518,0.04181160450590676,0.0013245330459770114,6.826323509216309,6.934536933898926,0.00012277416719666518,0.9263733625411987,16.907251358032227,21.607056072394094,20.696646810375498,0.01667613349854946,0.007195150945335627,0.2228156889157377,0.39245652413663423,0.7564402887392186,0.002196544027093596,0.003596095290655891,0.5808692813811348,0.003489219861795293,0.9858591746085408,19.987420021489257
|
| 16 |
+
dd477309-fa8a-4317-94ff-d8b54bc3cad1.mp4,3944,2168,25.0,8,h264,18113.648,0.08473634574073935,0.13552349128483232,0.005636896252329663,7.531042575836182,7.696881294250488,0.0003072400958021315,1.3717228174209595,31.410131454467773,42.14882312955888,13.435164381319346,0.03994758427143097,0.022566335275769234,0.09825435672437731,0.4887902318051936,0.4495643316600509,0.009923211554630761,0.006664993241429329,0.16810516355670657,0.018782968477504245,0.964893394653431,12.38130718901219
|
| 17 |
+
8b6d7852-7f21-47df-aa03-fc8c0f29e6d3.mp4,4216,2472,25.0,8,h264,30733.787,0.1179578911896735,0.14168876190277946,0.004752718108853313,6.274033546447754,6.732527732849121,0.0004122308285955512,1.4978402853012085,33.24341583251953,54.00886169112629,11.911389710325768,0.03861694410443306,0.022224539890885353,0.15351031888166186,0.5920864945591565,0.2681580551402431,0.008266845468743922,0.005113481233517329,0.13570151413733886,0.015360606151323666,0.9985361279804811,11.7227002191702
|
| 18 |
+
ac12840e-397e-4d7e-ab6f-e43091e014fc.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 19 |
+
d2b97603-9f13-4042-a901-0ebe567b36d4.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 20 |
+
5e800829-24b2-4ca7-bac4-89a02199fbec.mp4,1928,1144,29.97,8,h264,7670.417,0.11603771761102329,0.05360339340557336,0.01584053912892087,7.499912261962891,7.669247627258301,3.0794927409911567e-06,1.919191598892212,37.07494354248047,23.266718409618136,19.513456136493758,0.07946397364139557,0.04009568318724632,0.11794912686657971,0.5208322161695057,0.2232629198900251,0.03020419241892271,0.012287287972867489,0.263849394036116,0.052798925659402834,0.9977240688080937,18.971216361292488
|
| 21 |
+
66a24ee2-bdd0-4c76-82c0-9bbad7d24301.mp4,3960,2120,25.0,8,h264,11641.273,0.05546632837812083,0.06259769366341925,0.004200971983990852,3.4773783683776855,3.5694053173065186,1.093904618691676e-05,1.755215048789978,40.48407745361328,46.313463127672705,14.334369893453339,0.050556451082229614,0.029183844104409218,0.32478808327863545,0.20282036653998362,0.11656953273018755,0.004839035004129344,0.002967640214289228,0.3960360682294645,0.008121783876500858,0.998263455401219,13.03948355272954
|
| 22 |
+
1fda2da4-b877-415b-a0b2-9643e8c19343.mp4,3784,2008,29.97,8,h264,35429.582,0.15558382680977573,0.054290184688844834,0.012602681241208526,6.694496154785156,6.919416904449463,6.802361220513092e-05,2.3386123180389404,34.62127685546875,17.733186253125023,21.161139783311604,0.11751548200845718,0.04617464542388916,0.16985528248036963,0.47733727290811884,0.17626277868806386,0.041349226069646715,0.011168087211747965,0.7232348793690635,0.07039515037103172,0.9971271971765552,19.74997773179496
|
| 23 |
+
61a7d453-d120-440f-abfe-fa05714179b4.mp4,3456,2104,24.0,8,h264,17260.798,0.0989077128038011,0.0727313055577991,0.002759624524714829,6.432045936584473,6.439814567565918,0.0001275077932272746,1.2637557983398438,24.746780395507812,31.690624780635517,16.873166012287463,0.028985248878598213,0.01408995222300291,0.2357989120564686,0.19393517288886405,0.6341521050899118,0.004715903423226776,0.005453494377434254,0.6904527457987139,0.008170132837804534,0.9847355475512977,16.241323427262568
|
| 24 |
+
f11482e6-3485-46a8-9ade-92b46a803067.mp4,2016,1184,30.0,8,h264,14361.645,0.20055832897629772,0.028791556212856766,0.022591480989918488,7.037728786468506,7.124327182769775,7.288890164255228e-07,2.6167094707489014,42.61678695678711,14.121280569674624,24.095855414292735,0.13105686008930206,0.05644969269633293,0.20156533900233317,0.5128782488502158,0.26388842777476357,0.05298239087301587,0.008641775387028853,0.5257810824217074,0.08510191273863149,0.9993833200979341,23.873213487190434
|
| 25 |
+
a201f3de-fd3d-40c5-98c8-106b4ebb66ab.mp4,4008,2224,29.97,8,h264,14194.301,0.05313305309909823,0.006167030205529131,0.0025307523442323985,6.775632381439209,7.032013893127441,1.0845960374904866e-06,0.975328266620636,19.8259220123291,5.08622773136614,33.988931964386175,0.017742745578289032,0.00878979917615652,0.24839167533013198,0.4332490194001896,0.6622677167940415,0.003750816711899941,0.0044474839232862,0.4396791698377825,0.005154259825672396,0.9987883521995367,32.68039384243157
|
| 26 |
+
424df265-1080-4edf-8cfc-b05f62acfffc.mp4,3744,2176,29.97,8,h264,78212.749,0.3203288662187934,0.044290915204812126,0.05802588062468578,7.5659027099609375,7.705513954162598,1.0996226667551192e-06,4.917513847351074,77.87395477294922,24.95601362652544,19.381304863013963,0.5003300309181213,0.20496885478496552,0.09013898324527037,0.40636702643876493,0.2718818422945892,0.08350865469718871,0.015887831648190815,0.2587865257614798,0.11370963148881347,0.9993580352362319,18.992862809167306
|
| 27 |
+
ed1f86d1-17f2-462c-b82f-2d216774d941.mp4,3944,2208,25.0,8,h264,12849.851,0.059023112524620044,0.09063094842518908,0.00021101581562160096,6.528778076171875,6.836049556732178,0.00026608133723672546,0.6988304257392883,9.258260726928711,24.62951147116612,17.672909244450967,0.010030598379671574,0.0036405890714377165,0.2560277151441734,0.33395559014754445,0.4541880383818087,0.0008360939015786224,0.003517815455173453,0.29770278769928754,0.0017992497317517713,0.9728774016790624,17.639947833500877
|
| 28 |
+
2efd6ba4-b102-4009-b197-da0cdaa634dc.mp4,4176,2264,25.0,8,h264,20832.096,0.08813655009950855,0.13059464685199518,0.0005394488783287979,7.103693962097168,7.16506814956665,0.001146094109949705,0.9840006828308105,15.54456615447998,39.08756451032873,14.196888635731524,0.01746043749153614,0.00722542405128479,0.17444827832782725,0.3013473596598065,0.2914109880184468,0.000796836993262301,0.004156547986591856,0.7164728393557441,0.0004970667824215101,0.9454958452824559,14.345898901049
|
| 29 |
+
12dd2d2b-24f3-4c31-b873-fa4e53803778.mp4,3720,2000,24.0,8,h264,11168.197,0.06254590613799284,0.07419144172991776,0.001096532258064516,7.326708793640137,7.03421688079834,0.001104908264344367,0.7990032434463501,13.855031967163086,29.186652483284004,17.709958092816727,0.012685537338256836,0.00547337532043457,0.14025580058292889,0.34718295628645723,0.7360188973223698,0.0018994175627240143,0.003929015016183257,0.03343413978494622,0.0028954301075268815,0.9861516260805397,16.066609960586902
|
| 30 |
+
12cb34d6-c1d8-4b85-8f0e-60afaf26ffc9.mp4,3992,2128,29.97,8,h264,82020.534,0.322161540486348,0.19442412024402633,0.031029399023611133,7.5067291259765625,7.659872531890869,0.0004892454112115741,2.5568833351135254,51.4112548828125,47.859217215903854,11.412570567182874,0.14925998449325562,0.07771322876214981,0.10661558367184167,0.4059681511346936,0.39410029608826963,0.05666945576616892,0.01621010371794303,0.1896969848217739,0.09928256418852743,0.9999994889713084,11.553677315658312
|
| 31 |
+
918a9d77-727b-4255-bf27-d67c73ab109e.mp4,3848,2352,23.976,8,h264,44989.268,0.20732888701303545,0.10167412079176785,0.02935350725529297,7.006922721862793,7.049823760986328,0.00012149487561601138,2.5566444396972656,58.17711639404297,41.720730871253046,14.345926883977612,0.13242904841899872,0.0732438713312149,0.15790827165681595,0.29049045727091943,0.4459472019345969,0.044000461411175695,0.009885285670558611,0.3789205217776646,0.07739791277737706,0.977604551435959,13.299115682322263
|
| 32 |
+
acfbb4ee-70e5-45bf-8aa8-349e388499ed.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 33 |
+
bdc6f64e-fe6c-4f98-b37d-a8405f0674a6.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 34 |
+
17434ca5-7913-4355-95b4-a002b59756b8.mp4,3696,1816,24.0,8,h264,30840.548,0.1914533799686608,0.09403842515430282,0.00045524867936762206,6.87848424911499,7.145921230316162,0.0005040077409196204,1.1077892780303955,15.351152420043945,25.374898278246885,17.40825033016248,0.023234063759446144,0.009360809810459614,0.2048374967811586,0.41752296928427396,0.2840800078207762,0.001776139303672343,0.008505044815440973,0.09504590031847744,0.0028014271888170566,0.9861948653276555,17.26781125231009
|
| 35 |
+
cee06fa7-1a4d-4fe2-bf89-72bb8aba00cb.mp4,2008,1112,25.0,8,h264,12181.487,0.2182186183324257,0.04550726201907,0.028817553526899596,7.058570861816406,7.43195104598999,7.610834804144257e-07,2.839144229888916,46.980247497558594,20.172486329717614,20.7952804170892,0.16278205811977386,0.06897910684347153,0.17154791186967153,0.4820452520102503,0.3098746465681919,0.05630953404606992,0.011344536518057188,0.1670079275225238,0.09294991795408294,0.9987712448976839,20.14113077880257
|
| 36 |
+
da4c28f0-dce4-49b6-b992-df633c640d41.mp4,3880,2200,29.97,8,h264,106895.356,0.41784738502951563,0.12358454021721155,0.14118765229615743,7.390982151031494,7.575097560882568,4.107447877914606e-06,5.464156150817871,84.04344177246094,32.18738204100126,15.05805100501491,0.6543295979499817,0.29104089736938477,0.13737582217231528,0.5046602271961581,0.38149802344242917,0.19782860824742268,0.03251042154928049,0.4851184786004373,0.29476563964386127,0.9992652571806605,14.78405775820291
|
| 37 |
+
40949702-a809-4464-9d14-481e76abd967.mp4,1992,1080,25.0,8,h264,9412.376,0.17500327234865387,0.032604545360089596,0.03276494868362338,7.598055362701416,7.5778489112854,1.475981234591597e-07,3.0299410820007324,64.2403335571289,25.126574892633183,19.677779473362378,0.17224721610546112,0.09297648817300797,0.1250032341526129,0.3511127182416424,0.38581022650271873,0.04277542267836779,0.014354931811491648,0.16483495711240023,0.0433976647330061,0.9995155513120019,19.673463950479913
|
| 38 |
+
0cb873d6-4c3a-4ced-84e9-87939b08565d.mp4,3928,2176,25.0,8,h264,16372.595,0.07662088081795855,0.0655690748284945,0.00997179469420151,7.153440952301025,7.235421657562256,0.00026610638080850317,1.4226818084716797,38.33115768432617,37.17439902946259,16.020326652970137,0.036770280450582504,0.025037065148353577,0.1093549868154569,0.49416280431220255,0.2712272513396657,0.013138959918234097,0.0052625991714497404,0.04287055167026075,0.024374108493320952,0.99460809731599,14.191413000600473
|
| 39 |
+
04d651c3-3b08-4de9-befe-af12b3b3555b.mp4,1928,952,25.0,8,h264,9760.685,0.21271411572927926,0.14567895058316224,0.027217868475190903,7.379861354827881,7.489706516265869,0.00013466543611205162,2.472189426422119,50.065956115722656,45.60677688392366,12.756305164144322,0.14299236238002777,0.07285106182098389,0.12647779103585507,0.3213473471114912,0.3275197079482676,0.05156266344712159,0.016033847195406754,0.13787563780699003,0.09244841608842708,0.989902800881153,12.372051285756838
|
| 40 |
+
249c9213-36ad-4280-a900-306b96685c0e.mp4,4352,2512,25.0,8,h264,47129.108,0.17244106231266393,0.023663026379807144,0.0075284772796459344,7.559027671813965,7.671954154968262,1.689348490249105e-08,1.852216124534607,30.534713745117188,10.00865205155331,26.776982325943163,0.07633254677057266,0.032172251492738724,0.11798659075028768,0.48566255651266843,0.1818267550762109,0.03302835116928937,0.00833408534526825,0.22812887844230054,0.05521717264483421,0.9998370150268331,26.456385705685307
|
| 41 |
+
9ebd5e08-2435-46d7-8e11-92081982e848.mp4,4424,2264,29.97,8,h264,33097.411,0.11025942904567794,0.08548973267964877,0.015146562438098646,7.627282619476318,7.660618782043457,0.00014808484929126182,2.1128036975860596,39.34710693359375,34.54669112188891,15.952876977581399,0.07846289873123169,0.03848755359649658,0.0661154613354953,0.5784150388398565,0.3487781411412055,0.02954914381774537,0.006871931720525026,0.07993548148337477,0.054171871705250514,0.9848161683267036,14.44176895583773
|
| 42 |
+
dcc6663d-5979-4b94-8780-8f08f1459367.mp4,3992,2272,29.97,8,h264,45433.897,0.16714538369717863,0.023237782027220693,0.07615909636173757,7.42056131362915,7.560868740081787,7.731473646370793e-06,4.520887851715088,91.29413604736328,11.91958356243395,25.684373716053614,0.37042751908302307,0.20396246016025543,0.13353506631010406,0.339642842742698,0.3444164673070249,0.10233517210477293,0.02325626400609811,0.3504134883616999,0.13692911791893647,0.9939171225978607,23.759774352151624
|
| 43 |
+
58628666-a50b-4b53-86f3-9bce954fd87a.mp4,3656,2064,25.0,8,h264,14674.839,0.07778886888707953,0.017275690657986773,0.002144610961274235,4.755399227142334,4.147421360015869,7.62657340612031e-08,1.6233251094818115,30.24703598022461,20.98089051055077,21.510398834978275,0.043102558702230453,0.019970102235674858,0.3801464858933288,0.13324564785684487,0.611579316008798,0.0024631645124081896,0.002573372796177864,0.6121459220339367,0.003477677662714366,0.9973595402976382,21.254861574329713
|
| 44 |
+
2884fdc7-6b49-4008-ae33-54956784cf22.mp4,4136,2528,29.97,8,h264,96794.101,0.3088904973261177,0.199906057559813,0.03463755264059937,7.677958011627197,7.735160827636719,0.00022584952147722116,2.6518115997314453,55.998802185058594,53.27854414451389,10.790513990351288,0.1351182460784912,0.07386935502290726,0.07412626065180299,0.43634340055791515,0.3657297264831183,0.04920470995641848,0.013960901958247026,0.09381975389499625,0.09218259363599637,0.9924948222041553,10.641904721237971
|
| 45 |
+
bb85c24e-dfb2-4e74-a538-7b88444ba7a2.mp4,2064,1136,29.97,8,h264,6202.448,0.08826496730016038,0.012213032049264733,0.0028290138115514797,5.526223659515381,5.956391334533691,4.203832669183327e-08,1.2413911819458008,19.35202407836914,5.566881250909669,32.03723612534702,0.03160261735320091,0.012331601232290268,0.36391833500018755,0.4339056100931475,0.6477346277180488,0.006277124959056666,0.005463352271666129,0.8203375209265932,0.010636523842668414,0.9998206337412681,31.55410760665986
|
| 46 |
+
f0bab96c-7918-40b7-8e75-470ee67f2ce4.mp4,3912,2280,25.0,8,h264,42667.338,0.19134708319879454,0.018852291477169207,0.01145851271840132,3.321286678314209,3.2959964275360107,5.20539861462992e-07,2.6433749198913574,53.428077697753906,17.742542955666103,22.845592024866512,0.13821403682231903,0.06435994058847427,0.4485925112259545,0.05237976378738391,0.36296210795276146,0.013614579222425526,0.006106317198524873,0.8541742905320562,0.023815273741613747,0.9990574532561204,22.65985438616542
|
| 47 |
+
d863f7e4-dceb-4f47-8a78-dd6d1e4e880d.mp4,2200,1240,59.94,8,h264,24399.684,0.14921864679929195,0.05437209332413317,0.026887829912023466,7.528630256652832,7.624697685241699,1.1223690996102738e-05,2.919034004211426,34.91337203979492,15.516372340225086,21.779762544291827,0.1871851682662964,0.06431771069765091,0.1103539881428786,0.3952507633258582,0.3379617215801276,0.06735056207233626,0.012368634343147278,0.28846700879765397,0.10835392228739002,0.9977341311597953,20.767000095983466
|
| 48 |
+
ce34e0c1-7378-4fc8-96c7-44ddcbbf1487.mp4,4360,2352,59.94,8,h264,126287.219,0.2054560121788351,0.09277246707169107,0.05140101338700618,7.024899959564209,7.290508270263672,0.0001558291963913309,3.5155029296875,59.61481857299805,30.760610209878767,16.37874440928109,0.30129268765449524,0.12735895812511444,0.19224018761074277,0.5702624508009116,0.17240642905990122,0.085398268634671,0.016718280191222828,0.30273506573883374,0.15168595534544094,0.9744751753912574,15.8732601360474
|
| 49 |
+
4df1a23d-73f2-4cab-b43c-5538b566a458.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 50 |
+
d69e637f-ced7-4a94-b11f-7e3a7fc584c5.mp4,3864,2416,30.0,8,h264,35921.109,0.12826094454842116,0.02301661213602439,0.009748609168689071,7.3477983474731445,7.533846855163574,1.3443314194611425e-06,1.5714272260665894,32.717960357666016,17.222614996056663,22.955994719305767,0.048455942422151566,0.02490154840052128,0.12732583616434356,0.43533010538412503,0.41831419187872226,0.017358825908710733,0.005998436671992143,0.2724476860040493,0.028833130664445452,0.99976134757579,21.705490783403683
|
| 51 |
+
e7f5193f-e47b-4376-977d-feb98314b110.mp4,1976,1112,29.97,8,h264,15888.659,0.24127302758345293,0.11828014829741351,0.04825368450179128,7.395143032073975,7.48813009262085,2.0638003672822308e-05,3.874429225921631,70.99330139160156,40.35615202717711,14.08389671485215,0.2872275412082672,0.13475967943668365,0.12244732204030821,0.450834061852685,0.40473016799706213,0.06874262735560539,0.01605181209743023,0.2834111253507316,0.11797573580811463,0.989530204248128,13.702267426394744
|
| 52 |
+
be4a76cd-c69a-433b-b55c-9dfd478c9594.mp4,4200,2128,59.94,8,h264,76809.881,0.14337698369392188,0.10147443451064636,0.04369175170068027,7.535388946533203,7.552706718444824,0.00010668694178254137,3.557075023651123,75.94049835205078,39.92224579659751,14.607808994386144,0.2657359540462494,0.13470318913459778,0.10662796044874044,0.4325665933039882,0.5377423675610595,0.05649175020885547,0.01610845203200976,0.19080513784461153,0.09768808181167205,0.9839293682385235,13.14202322363013
|
| 53 |
+
d31d550a-bebb-4124-bd43-8b60d0e18de8.mp4,2256,1208,23.976,8,h264,14792.29,0.22638760914445671,0.04817584689252386,0.0726737529942229,7.142242431640625,7.410560607910156,6.98028072093307e-06,4.173787593841553,85.20892333984375,21.70381119343126,20.200777240953116,0.32179033756256104,0.17825736105442047,0.1331180156782398,0.34578490073208995,0.3365648953207698,0.09813797985846916,0.02062916321059068,0.42634162713509616,0.15032264953501479,0.9993916711217388,18.366078180220182
|
| 54 |
+
8fb42072-b53c-4ee3-b54d-4c2e1cf6d1ea.mp4,2032,1048,59.94,8,h264,23215.269,0.18187457664883735,0.09169163344208574,0.06755152296087034,7.2440505027771,7.581101894378662,0.00015709599938584044,4.340128421783447,69.27568817138672,32.185418920775575,16.1816205304785,0.4326435327529907,0.18270862102508545,0.1589946302332441,0.48114556349026766,0.26464688149808624,0.1195052192903368,0.0232531875371933,0.48419154845625206,0.19298147577688285,0.9696954825913656,15.691813835264224
|
| 55 |
+
7fb26377-5336-45c3-8648-14c7df0d1822.mp4,2112,1080,25.0,8,h264,6974.183,0.12230259189113356,0.06908184513985167,0.015561517957351289,7.259411811828613,7.45736837387085,0.0040572175292709475,1.9812953472137451,46.00967025756836,30.231063424849967,18.777326329420777,0.07225863635540009,0.04395774379372597,0.12630594757500477,0.5686536038260235,0.377071315116488,0.02405580691170969,0.009015806329747042,0.5724709479049758,0.03894938973063973,0.9592674120012764,18.98384660344204
|
| 56 |
+
6084559b-4f79-480c-ae41-bdb3d0a6987c.mp4,3864,2176,24.0,8,h264,7392.491,0.036633933606277654,0.007060472180133776,0.0033822768237729874,6.447038173675537,6.402669429779053,4.679335826993654e-07,1.1656887531280518,21.539216995239258,7.748514322343443,30.126523878507157,0.022850245237350464,0.010489453561604023,0.20668072131675053,0.23095527002449887,0.2557928800116594,0.003746998119900134,0.002171670629953345,0.6112707197915398,0.005819234963006942,0.9988106814283685,28.143217609294314
|
| 57 |
+
39c98272-d26f-4139-afbf-d870b7553fab.mp4,1912,1152,29.97,8,h264,9800.944,0.1484707142443854,0.039509684100133544,0.005349891765457927,6.316155910491943,6.450854301452637,3.7880130015596525e-05,1.5432862043380737,27.982141494750977,16.21058546361702,22.550528458925847,0.037944767624139786,0.01664702780544758,0.29896926714339833,0.568221517441918,0.16585721322902708,0.014577158879590886,0.005653653138627608,0.511477219897722,0.030219638031729425,0.9934711756986034,21.820869112944806
|
| 58 |
+
c6b21693-9149-46a7-8062-b4e36d861374.mp4,1800,1040,25.0,8,h264,5696.982,0.12173038461538462,0.01285861980475951,0.02749284188034188,6.882208824157715,6.9071173667907715,2.307484042479586e-05,2.7927966117858887,39.489967346191406,3.6518314388026685,34.61616402154644,0.17552293837070465,0.06558234244585037,0.18862381126808184,0.37493412518853697,0.47208936651583705,0.06163301282051282,0.012805463746190071,0.32623967236467233,0.044903044871794875,0.9997276437070555,34.748478048384115
|
| 59 |
+
b2b4e988-b71d-4d6f-a5dd-8db222348faa.mp4,1992,1232,25.0,8,h264,6935.385,0.11303957713972775,0.050860870829255396,0.006131669535283993,7.336886405944824,7.4586381912231445,3.8484092229019065e-05,1.7237539291381836,31.403980255126953,23.852990816751063,19.455405851878844,0.05761508271098137,0.02627158723771572,0.09626810904856715,0.5841186732542597,0.19463759939597114,0.010529401154401154,0.006394967902451754,0.13623840600497225,0.01848343047514734,0.9897579143924821,18.269961000639935
|
| 60 |
+
b803a998-c442-4979-b250-eae2183f1ba1.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 61 |
+
10e3a5e7-dda1-496d-84bb-f826e6567d82.mp4,2024,1160,29.97,8,h264,10593.035,0.15054459472875265,0.04069250301319929,0.005925275998364453,6.370438575744629,6.699704170227051,2.612764599108834e-05,1.745764970779419,30.442712783813477,16.2956588347572,22.448618378947685,0.06587354838848114,0.027121203020215034,0.20759801889914262,0.5772056769075355,0.15383775358310148,0.016580488165008404,0.007409828715026379,0.25078142746808413,0.027405402412430148,0.9977600003474199,21.799876719192074
|
| 62 |
+
b82a043d-d96b-4c3c-bd88-36673c8734fe.mp4,4272,2384,29.97,8,h264,31535.814,0.10331890801013918,0.011293258112761733,0.009357463458009703,2.474804639816284,2.908219337463379,1.0601571477324355e-07,2.572373390197754,50.87281036376953,17.336855145358008,23.234466595816986,0.11729395389556885,0.05357654020190239,0.39416468191624854,0.12791683318994926,0.19959534213405927,0.010275209155501931,0.004424559107671182,0.6027152379785335,0.014619496314380514,0.9996167497583706,23.97460201814576
|
| 63 |
+
f67fdc5d-c16c-434f-88b6-2885b6e16244.mp4,1704,936,29.97,8,h264,4124.463,0.08628498537862125,0.14167440865586234,0.0021714868985995748,6.9421491622924805,7.07874059677124,9.026881105823313e-05,1.47042715549469,24.149051666259766,48.281956539549896,12.537837268101267,0.03727716580033302,0.015906894579529762,0.11302669985011486,0.5849823456829463,0.23623558451241874,0.0032554956997445255,0.005870988437285026,0.34440038856118665,0.0038838354199269695,0.9677222326473224,12.12852561266281
|
| 64 |
+
a8fb16e1-2992-4ab7-95b7-47d48938f465.mp4,4416,2512,25.0,8,h264,47626.46,0.17173530820179084,0.024266345674041102,0.008263884081048648,7.565223693847656,7.665775299072266,4.3365062148095007e-07,1.8574262857437134,31.593847274780273,10.497816387919835,26.415430650927703,0.07536498457193375,0.032022129744291306,0.10909541513361631,0.5430451944241572,0.15671558099694924,0.03231884298363027,0.007478163422395785,0.2131279520139081,0.055001346796247574,0.9987616523664529,25.996976327787028
|
| 65 |
+
cf64e339-3346-4871-9275-ae8d71e69299.mp4,2224,1224,30.0,8,h264,15362.231,0.1881121450878513,0.06601673786403403,0.028416090656886255,7.328147888183594,7.462838172912598,5.204401908026663e-05,2.646209478378296,54.8980827331543,28.458309405125572,17.763753460185995,0.13267822563648224,0.06757838279008865,0.1174268615084031,0.39672566301053735,0.268810764056959,0.03962761163618125,0.011698253452777863,0.1732410640115359,0.06870165632200123,0.9941397876508482,17.797069126857515
|
| 66 |
+
43a9cd6b-81bc-438d-819c-6352d7193145.mp4,3784,2008,29.97,8,h264,36206.249,0.1589944463315321,0.054590670708535086,0.02001489285985024,6.582436561584473,6.803387641906738,6.757863391028719e-05,2.5635905265808105,38.959495544433594,18.689710073875673,20.818489665846208,0.13612745702266693,0.0552828311920166,0.17039050492755023,0.45392823078893096,0.18310380040864058,0.05806890478958023,0.012364944132665793,0.731457977111989,0.10490970841791397,0.9815015737117856,19.175683075370202
|
| 67 |
+
b7340fda-e72e-4b12-9029-316dbd0c9ce8.mp4,3856,1936,23.976,8,h264,14123.499,0.07890839236174724,0.013572721019411503,0.011638537987380406,7.1876540184021,6.9774322509765625,0.0002915820148605531,1.9975744485855103,37.95682907104492,7.40499957796313,36.933488581518844,0.07592128217220306,0.03492182120680809,0.1509332493571397,0.3837449406136722,0.3531127043688376,0.016453866394042272,0.005977872293442488,0.0796282384863345,0.018695976111073008,0.9611574445177921,30.707378416224845
|
| 68 |
+
c7d92eab-29ca-490c-ab85-e18229efdf96.mp4,4344,2512,29.97,8,h264,81522.02,0.24927500735458025,0.1000569803131514,0.03622233903414623,7.442636013031006,7.571652412414551,9.676651968898144e-05,2.972356081008911,65.53794860839844,41.86743263941248,14.34625876647874,0.19248835742473602,0.10125032812356949,0.09619653533017665,0.48763471764711247,0.2784021018853362,0.04904026663421348,0.01487108568350474,0.13413356832568926,0.08725383353274449,0.9874909332758101,13.507726959867174
|
| 69 |
+
70b2de9e-3c32-4b01-8505-8a78d5bccd59.mp4,4128,2344,59.94,8,h264,31615.423,0.054511102054488995,0.014116425747134979,0.004135806909278514,7.5224289894104,7.6964850425720215,1.6491528859753852e-07,1.345127820968628,23.74616241455078,10.07343460190578,27.54507604897654,0.03602388873696327,0.015233230777084827,0.12069503858303476,0.511027495028049,0.29980149562955943,0.007821250143309434,0.0043184760337074595,0.282369536052244,0.013587026169404978,0.9988060480115135,26.282205403914354
|
| 70 |
+
a3fa72da-8a4e-466c-9a11-fc2a67863b9e.mp4,4064,2328,29.97,8,h264,55651.917,0.19627126001383585,0.1697815446090767,0.01374202620613145,7.539480686187744,7.7134599685668945,0.0003595651628055783,1.936773657798767,40.06705093383789,45.708446787003915,12.175698271848955,0.0872727632522583,0.04557924345135689,0.09998841819431721,0.42120852938015224,0.39739355828828454,0.026862722217712475,0.01140574086457491,0.0736418196597848,0.052145853204399704,0.9822594713112537,11.54028731617105
|
| 71 |
+
34e7d8b9-5fd5-44d6-ba9c-194d4e2078ae.mp4,1864,1112,25.0,8,h264,7053.062,0.1361090483836107,0.054158667984824466,0.016409361780961498,7.490847587585449,7.695387840270996,0.00029081383222468306,1.8844280242919922,49.36473083496094,27.493341599677112,18.712213241512394,0.07012951374053955,0.04689024016261101,0.11799043690058277,0.49315103837152446,0.3031222125304049,0.022825677226459177,0.009728438220918179,0.2783456711026029,0.04091823108037175,0.9759738649373356,17.535968238489765
|
| 72 |
+
9d860f37-78b5-4e91-bbff-91bbfaaaa32a.mp4,2176,1128,23.976,8,h264,20564.598,0.3494423229093194,0.06460805954402254,0.06740179782019191,6.886200904846191,6.995933532714844,1.7384923576114026e-05,3.990297794342041,52.721641540527344,20.567321337157345,19.73471317473215,0.3589254915714264,0.13616614043712616,0.16749243576081677,0.2623429547776905,0.49264839289256585,0.1339137300531915,0.021455286691586178,0.4039163537755528,0.20700700908687944,0.991671204099078,18.815549968769943
|
| 73 |
+
81e87893-da5e-4db2-afd1-b1bb11a03de7.mp4,1848,1080,29.97,8,h264,10031.907,0.16771466231342774,0.13550930690738697,0.010763087221420555,7.5518693923950195,7.611828804016113,0.0004041885873757899,2.7923357486724854,59.45957565307617,39.83156659420939,13.715216440202253,0.15859724581241608,0.08018888533115387,0.07077146702152066,0.4440622167310621,0.42761248822387166,0.013438953022286357,0.009651358239352703,0.2976868553257442,0.019547659130992462,0.9545209309874343,12.184221110468258
|
| 74 |
+
329f1c4c-f5df-453d-9cfc-6f2cbef5142b.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 75 |
+
0d0496e7-fe16-443b-9c16-369aa386bb4e.mp4,3824,2080,29.97,8,h264,76672.191,0.3216398955481069,0.042603158777894,0.053163999637914384,7.49969482421875,7.679389953613281,4.4176801376596195e-07,4.726040840148926,73.43344116210938,24.73622180177029,19.5001879954157,0.4522957503795624,0.18221168220043182,0.12557819123761416,0.39036642367224755,0.32774845807932124,0.07771807276579766,0.014697935121754805,0.1534577499731789,0.10635309382040553,0.9972791267899178,19.181592760557905
|
| 76 |
+
e199cb9b-752f-4aa1-a4b9-1885fa53a026.mp4,3704,2192,24.0,8,h264,56749.399,0.2912316005367381,0.06223628419301381,0.05104786598823918,7.1486663818359375,7.31646728515625,1.790032174237729e-06,4.499476909637451,75.83165740966797,23.24094136818146,19.14477134501647,0.3928792476654053,0.17057494819164276,0.14824753398203114,0.3366086798822518,0.15801827706509972,0.07917789934469477,0.016016355405251186,0.3772218204295481,0.1360922079700777,0.9976828925927231,18.97621105248905
|
| 77 |
+
4cf5400c-d443-4509-8128-dfdcd50a9fcf.mp4,4272,2384,29.97,8,h264,31535.814,0.10331890801013918,0.011293258112761733,0.009357463458009703,2.474804639816284,2.908219337463379,1.0601571477324355e-07,2.572373390197754,50.87281036376953,17.336855145358008,23.234466595816986,0.11729395389556885,0.05357654020190239,0.39416468191624854,0.12791683318994926,0.19959534213405927,0.010275209155501931,0.004424559107671182,0.6027152379785335,0.014619496314380514,0.9996167497583706,23.97460201814576
|
| 78 |
+
e4bb727c-b404-4141-bfbe-af71df54c3ff.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 79 |
+
abcae1d5-f557-46b6-8704-3b34ecdb3855.mp4,2128,1080,23.976,8,h264,9758.537,0.17709757077345611,0.06317856101992454,0.021328842940685044,7.17816162109375,7.319029808044434,6.02598328172642e-05,2.1986310482025146,35.48875045776367,21.518270390254973,19.59833060833165,0.11287132650613785,0.04843633249402046,0.16474877279461442,0.3239840455350431,0.46334531351526964,0.0428242191125963,0.013388105357686678,0.34700553467000833,0.08521303258145363,0.9898198109653218,18.315084527253653
|
| 80 |
+
93495912-fd6c-435a-9fcb-c9cc978c122e.mp4,2008,1248,29.545,8,h264,12599.66,0.17017530504579095,0.09179893751414653,0.026034882904280316,7.28030252456665,7.439886569976807,2.6556406274797802e-05,2.5419487953186035,45.587520599365234,28.28280527215062,16.845219875476445,0.1255028247833252,0.05773575231432915,0.14505126243363076,0.5325459562996547,0.2646494876660611,0.045352377881635854,0.007195856577406327,0.08977498127149526,0.07977624757380733,0.9939426353785461,15.28308285240923
|
| 81 |
+
e2dd775b-6dbe-49fd-876e-3e9b4a2e80cd.mp4,3912,2208,23.976,8,h264,44342.566,0.21411454779604497,0.09509652238055057,0.029812093410094543,6.707069396972656,7.182584285736084,0.00012590194600901514,2.597018003463745,58.731727600097656,40.9387849600393,14.626761505678672,0.1354481726884842,0.07431226223707199,0.1634576248055144,0.3116403942588515,0.4550657866970771,0.04392097151833082,0.00971723503122727,0.11038838752062285,0.07683003662087667,0.9770803141912644,13.593667907575028
|
| 82 |
+
a508a47e-295b-4be0-ab3f-4b6478dacc65.mp4,4360,2352,59.94,8,h264,126287.219,0.2054560121788351,0.09277246707169107,0.05140101338700618,7.024899959564209,7.290508270263672,0.0001558291963913309,3.5155029296875,59.61481857299805,30.760610209878767,16.37874440928109,0.30129268765449524,0.12735895812511444,0.19224018761074277,0.5702624508009116,0.17240642905990122,0.085398268634671,0.016718280191222828,0.30273506573883374,0.15168595534544094,0.9744751753912574,15.8732601360474
|
| 83 |
+
733704b4-12f7-4977-b920-6896bc9ca77c.mp4,3632,2112,25.0,8,h264,35757.395,0.18646018451308236,0.023405982271658993,0.0114293402082499,7.527238368988037,7.664534568786621,4.917982302283578e-07,1.9592946767807007,32.69782638549805,10.107730053748933,26.739501253563247,0.08349424600601196,0.03576952591538429,0.12351014467901057,0.5205489246068594,0.23741459897574257,0.04216461663718685,0.008015718931953112,0.1772868761958795,0.06795654785742891,0.9985238316412702,26.44615321681865
|
| 84 |
+
07484eba-7a60-4754-ba0b-f9c0744af11b.mp4,3744,2320,29.97,8,h264,42375.281,0.16278036697297554,0.04680557474334414,0.021254674145299142,7.1397528648376465,7.319797515869141,0.00018653402036211456,2.0145583152770996,38.731197357177734,20.055538777924156,20.979329955462454,0.09581049531698227,0.04451819881796837,0.14676288494422374,0.3145919473712218,0.5450120102012216,0.04606807673887415,0.010496880548695723,0.3294798113763631,0.07769488653109342,0.9906222568234485,20.052267598182596
|
| 85 |
+
026fe674-d8c4-40a4-96c5-0cb6f1fb7c06.mp4,4080,2152,29.97,8,h264,18497.378,0.07029444373790415,0.015048249600882703,0.006021302573073839,7.39658260345459,7.519859313964844,5.888028856840528e-08,1.1937366724014282,29.523151397705078,9.507835113867262,27.918117677847636,0.0281049907207489,0.016799675300717354,0.1342756832257432,0.3967183437811308,0.33484082860371595,0.010203382778142237,0.004288815738012393,0.2226463602789319,0.017361243986442162,0.9997948641607675,26.13181026051079
|
| 86 |
+
af18139c-acaf-4c43-977a-9a86fb3f3602.mp4,3920,2208,24.0,8,h264,12827.599,0.061751711271012026,0.08963311173654244,0.0007833296362023069,7.278562068939209,6.828987121582031,0.0003169389877970123,0.9030637741088867,14.510713577270508,32.08218969560803,16.33525454985052,0.015057296492159367,0.006208827253431082,0.14356219282733998,0.3124642683160221,0.8005967936691585,0.0014279398353544317,0.0033351377739260593,0.0207769520851819,0.0021434117125110914,0.988551496232551,14.702759137373825
|
| 87 |
+
fd4e4b88-600d-4a77-acdc-a1315ce301cc.mp4,4568,2672,24.0,8,h264,17424.241,0.05948124889122805,0.09230391342392479,0.0007026391612571703,7.3348565101623535,7.044018745422363,0.0003478555279513488,0.9210092425346375,14.63671588897705,32.29689578650071,16.21557571299345,0.01604432426393032,0.006564124953001738,0.13583912832934256,0.3137757971488773,0.7292011663634032,0.0011647567387117186,0.0032998408811787763,0.12096363315400722,0.0016134270425873297,0.9884601788362083,14.531413968873947
|
| 88 |
+
a75909eb-460b-4eb8-ab69-1f81413fde39.mp4,3504,2216,24.0,8,h264,12771.349,0.06853172723523125,0.07632779644608449,0.0006379764024199265,7.036263465881348,7.020784854888916,0.00013900804781572326,0.7060607075691223,11.723587036132812,27.328210487867413,17.65899654426508,0.009950431063771248,0.0039873127825558186,0.18371826557134377,0.32048653231465507,0.7251103568436426,0.000994694390869778,0.0028258475164572396,0.043288330613388736,0.0012447738943013038,0.9744050005001919,17.147321929463487
|
| 89 |
+
d4346f5c-0084-437f-a2ab-3eaedfc55c11.mp4,3616,2064,29.97,8,h264,18160.488,0.08119002157127286,0.01640264128993163,0.0017206311741098993,7.56039571762085,7.616456508636475,1.0569616206471612e-07,1.204588532447815,17.363248825073242,6.7892021762389385,30.0987914517863,0.03240012750029564,0.01197904348373413,0.08993872157304633,0.4335584335421187,0.3625504576623417,0.005593768579497382,0.005058024854709704,0.10961778400905531,0.010008743975955272,0.9993855382951184,29.301524936051145
|
| 90 |
+
a3a1e82b-d8da-433a-bffc-fe9aa6c0d6b8.mp4,1904,1080,24.0,8,h264,3517.439,0.07127293336316008,0.037792377435723744,0.0050058356676003736,4.719388008117676,4.7925543785095215,4.7425856449873786e-07,1.670793890953064,34.762107849121094,28.021119450221647,18.698712076808796,0.04882233217358589,0.025018975138664246,0.32413395355304236,0.16579031467343655,0.3141739777792017,0.006094382197323373,0.003943651216104627,0.4692836393816786,0.009816808667911608,0.997371655361491,17.778837999654417
|
| 91 |
+
5fe0ff99-37a3-4760-81a4-f441876cb867.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 92 |
+
221cab7a-eb1a-4b14-9568-9ac4a4ec9330.mp4,1672,1096,29.97,8,h264,25545.608,0.46513891276344854,0.1205145017267201,0.11582920057276569,7.7315778732299805,7.773962497711182,1.0196675601302348e-05,6.703019618988037,100.35294342041016,41.351868413868374,13.893107574016607,0.9161150455474854,0.37918129563331604,0.05437708776697476,0.5162277035227647,0.293133004869604,0.15879368502543684,0.02824004056553046,0.3147559197429539,0.24483168459469842,0.9938609909003939,12.963095414146789
|
| 93 |
+
3e9e0378-5364-4b63-89a4-c8be5c4b826e.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 94 |
+
494e83e7-548e-408f-af3d-d702cc6ae594.mp4,1752,944,23.976,8,h264,5610.42,0.14148570006040403,0.07415588328242063,0.010378574607228542,7.490828037261963,7.702061653137207,7.470712731646609e-05,1.761763334274292,34.92809295654297,32.01824093913225,16.73719571119993,0.06444501131772995,0.03184501454234123,0.12185876870481349,0.5155074374479302,0.34469482376058025,0.02067350791992364,0.008139917006095251,0.39447451500141884,0.038919201300208955,0.9925388100348878,15.28414787432056
|
| 95 |
+
814a4743-06ee-4a4b-9e77-e11bee77ceeb.mp4,1752,1064,23.976,8,h264,4392.675,0.09828259450871203,0.032987469576270786,0.0056733228619493935,7.031918525695801,6.870492458343506,1.6726312174518266e-05,1.5741097927093506,26.83880615234375,14.938224865451867,23.5008968614912,0.04748789593577385,0.02055317908525467,0.1985460133073029,0.22976535062474882,0.7212429583505275,0.010479251782424098,0.007446895043055217,0.7969974880122681,0.018379102722559826,0.9985523841836662,22.36412702479837
|
| 96 |
+
2b8983b8-2396-4020-a6db-35b19bb2d287.mp4,4008,2088,29.97,8,h264,40148.403,0.160074931521024,0.05797263642180743,0.02584557895702847,6.822464942932129,7.055733680725098,7.172180051108346e-06,2.4900307655334473,36.52580261230469,17.285564280269153,20.99822895336429,0.14220792055130005,0.058163028210401535,0.20122307188328248,0.3787347649628497,0.2031581901056525,0.07335659938105908,0.014992917577425638,0.7752869102153293,0.12570052662873488,0.9969557560560646,20.24408139521997
|
| 97 |
+
e0ece203-f76b-40b9-8285-cb9fd999c5c4.mp4,3912,2192,24.0,8,h264,40776.066,0.19813202848618514,0.09945711239804685,0.03496622315017987,7.536346435546875,7.638005256652832,1.6951137484897176e-05,2.46947979927063,53.10587692260742,32.36660032537817,15.852002036949575,0.12401280552148819,0.06782073527574539,0.10089832206280241,0.3571532536652205,0.37619714213386257,0.053949238788629665,0.012217029929161072,0.2409559114385085,0.09947692762676698,0.9933512871772088,15.267127113506389
|
| 98 |
+
b872dec9-a527-443e-807b-8a650fb44aa5.mp4,3672,2200,29.97,8,h264,26838.802,0.11085391359538055,0.043140750910683145,0.007107075658546247,7.114101409912109,7.103025913238525,1.4296802407989588e-05,1.8586738109588623,30.439306259155273,17.2225639439719,21.915730799124205,0.07348591089248657,0.0299688708037138,0.12240659163278807,0.34816310030977465,0.42257273296854225,0.020484997029114677,0.008512660240133604,0.083881626724764,0.03608437314319667,0.9853751111654803,20.685591731501987
|
| 99 |
+
8f0464f5-2e81-4d50-b4bb-0667fadd55b6.mp4,1944,1000,30.0,8,h264,28629.622,0.49090572702331964,0.092782616194626,0.10684485596707818,7.022085666656494,7.256712436676025,1.2697564529638103e-05,6.1641411781311035,106.89842224121094,36.91257181957464,15.29488864155018,0.7986264824867249,0.35498401522636414,0.15702301984755945,0.5378053457597031,0.22792991675408159,0.1251349451303155,0.03104855741063754,0.33499417009602195,0.18092052469135803,0.986462531267651,14.750361283262205
|
| 100 |
+
890c5911-8ee1-4faa-a801-c605f0dde8e0.mp4,3840,2160,29.97002997002997,8,h264,14730.141,0.059256330138406636,0.08866558526140009,0.005286048418209876,7.256598472595215,7.234145164489746,0.00016839457810677365,0.8481879234313965,21.269725799560547,27.460039181811666,17.16618146535135,0.013288619928061962,0.009028784930706024,0.1450372010687532,0.30018618010797427,0.5296609669395022,0.011812829539609054,0.003580046041558186,0.22758680555555555,0.021873613522376545,0.9926868678702191,14.919262283403194
|
| 101 |
+
ac024e5d-540e-4905-810d-f1b33b0aa5c9.mp4,4264,2488,24.0,8,h264,18365.504,0.07213134615887341,0.012953859447246502,0.0136907625646254,7.144860744476318,7.201314449310303,3.7654594462563302e-06,1.621660828590393,41.49378204345703,13.670468671451426,25.199696043170125,0.048048000782728195,0.03361654654145241,0.18680149284170744,0.24222968787254692,0.3719897684756554,0.023478016555765358,0.009723028168082237,0.4041484805616051,0.018766062088644633,0.9968599666509461,23.98313975966201
|
| 102 |
+
31f7bc2c-838f-43c2-9cf8-745cc9ce27ab.mp4,4072,1984,25.0,8,h264,19146.36,0.09479747607579694,0.04550693960812429,0.0067543045741174975,7.1837944984436035,7.262981414794922,3.760470042574669e-05,1.2780088186264038,28.892736434936523,25.865269563431205,19.12290828994152,0.03580651804804802,0.018519092351198196,0.14137635115849687,0.45802809033771,0.30936963102122994,0.012009179196505905,0.005717673959831397,0.43970724538944167,0.021449097693136444,0.9821693564696935,17.673464012040057
|
| 103 |
+
2b0347f7-dc82-40d1-a16f-1e835c93471d.mp4,4312,2080,24.0,8,h264,25544.905,0.11867273816213549,0.2003347633852492,0.0045769855144855145,7.405059814453125,7.60549259185791,0.00036262674436563104,1.1897826194763184,28.893930435180664,54.28900251775701,10.69149180162558,0.028370706364512444,0.016952944919466972,0.11795479119000803,0.48218439867205626,0.5729273339580588,0.006566275985918843,0.0061726671022673445,0.2979950109414395,0.012031774029541887,0.9680268523493314,11.099574128495348
|
| 104 |
+
5cbb381b-c803-4c00-9f7c-eb34a93f0f11.mp4,3784,2008,29.97,8,h264,36618.783,0.16080602904817673,0.06542768694838852,0.019816163464535096,6.789691925048828,7.012326717376709,2.6450260263097307e-05,2.542257785797119,37.257537841796875,20.466320563078575,19.71761871395306,0.15050595998764038,0.060365621000528336,0.1814356638339386,0.42071156347264976,0.1782449488412061,0.0653040586070096,0.013540097512304783,0.8411135233554857,0.11950388193526107,0.9788613655686915,17.629037236788477
|
| 105 |
+
6f22e710-af89-4704-8e14-022222e9748b.mp4,3720,2272,59.94,8,h264,22322.13,0.044062347122982445,0.015392131575294059,0.008948607640466455,7.460536956787109,7.5750732421875,4.295919040300286e-06,1.6437866687774658,32.09257507324219,11.993583547762306,26.12937973756229,0.04855892062187195,0.023132318630814552,0.11537393971455372,0.46319905696831204,0.3847542499002731,0.013721311966782774,0.0031315109226852655,0.18334130004038562,0.02164215129486597,0.9949059255782428,25.658407466420066
|
| 106 |
+
f4f67a32-a8f5-43ac-93fb-6a41a45696de.mp4,3920,2072,24.0,8,h264,41024.51,0.21045359203044153,0.021401841745385414,0.11354254491371837,7.544743537902832,7.687009334564209,7.526890999826134e-07,5.495125770568848,111.36131286621094,8.965509817871082,27.70741804902253,0.5899763107299805,0.32699307799339294,0.12487122837721032,0.4635716519555055,0.31847648325739564,0.1427197012975074,0.030318242808183033,0.24108341212933046,0.10722546982113308,0.9999854710223858,27.01278655262587
|
| 107 |
+
ec818c60-f6b6-4272-8688-db52a71f64c7.mp4,4016,2304,24.0,8,h264,25400.055,0.11437924787395556,0.20554661131905522,0.003539487881806109,7.454084873199463,7.498093605041504,0.0008068909499226956,1.0435192584991455,25.511754989624023,54.08541538015693,10.652868069190628,0.01988261751830578,0.01239417027682066,0.11179273230338858,0.4369010138639342,0.533342198844871,0.005289785591430574,0.005997409888853629,0.2147454741220304,0.009756330580455953,0.9224177695196881,10.56287084864626
|
| 108 |
+
9a049c4c-bb40-4692-8b95-cb2ca94d339f.mp4,2072,1168,29.97,8,h264,6455.45,0.08900352041009824,0.009832887361719956,0.005371522452001904,7.566030025482178,7.583876132965088,2.1056042965511564e-06,1.864336609840393,31.31435775756836,4.709945760801032,33.683942863294355,0.05904766917228699,0.02554207108914852,0.07794002734583987,0.45699018562132987,0.46636608009240965,0.009612979540205567,0.004641825954119365,0.04973108504786589,0.013096381300576505,0.9963151955987875,32.48977789954205
|
| 109 |
+
bd22f174-e591-4d7a-a5fa-40447e18191e.mp4,1840,1008,59.94,8,h264,12423.375,0.11174922010519837,0.05327088904704394,0.005766369047619048,7.4864821434021,7.577877044677734,7.612468171226111e-06,2.7005228996276855,32.72980499267578,16.431595009157522,21.566036022415087,0.172185018658638,0.058717746287584305,0.09891754670975317,0.413866736610103,0.3300058145238772,0.04619978577179664,0.011242882038156191,0.050635136300897145,0.07710570867839889,0.9926592185787358,19.394910594585504
|
| 110 |
+
223c0d7d-c60b-4ce0-b867-a6292c9aa465.mp4,3784,2008,29.97,8,h264,38253.751,0.16798575186148923,0.0678976499847726,0.0288653262215409,6.804425239562988,7.0386223793029785,1.0396655579733884e-05,2.766972064971924,40.70533752441406,21.01144195852966,19.44021486807754,0.1735588163137436,0.07103276997804642,0.1924423491662839,0.38597894122497145,0.1846702573626785,0.08510461325943582,0.01528609481950601,0.9235667442983527,0.1461225657623207,0.9917693076510441,18.361035670056648
|
| 111 |
+
992adda7-c2d9-4447-904a-e8d8157adde8.mp4,4112,2280,25.0,8,h264,23346.907,0.09960964485630419,0.05243066451869163,0.0065050302068400566,6.603093147277832,6.729841709136963,0.00026400312456757177,1.3120918273925781,33.72609329223633,31.345461426843613,17.721685776054223,0.030552295967936516,0.0189980436116457,0.22162180790489205,0.21826521987045897,0.41920875111598327,0.009328815106833232,0.005134458498408397,0.5314387216419778,0.016115487831933922,0.9950573907373722,17.40613349465314
|
| 112 |
+
4ed3e002-a64d-4ab2-ac0a-3c46c2fcee77.mp4,3640,1920,25.0,8,h264,18403.842,0.10533334478021979,0.0518306550985779,0.007591288919413919,6.731770992279053,6.858646392822266,0.0002865738990085321,1.6696780920028687,39.051395416259766,31.16924249517514,17.799405625266896,0.04685891792178154,0.02683914639055729,0.20264662050472868,0.283926594970253,0.4202275605487922,0.010581978785103785,0.005136539849142234,0.20384500915750914,0.018152472527472527,0.995746789821698,17.49948355615558
|
| 113 |
+
c9895c78-cafb-424c-88c4-3959e7f51de5.mp4,3640,1920,25.0,8,h264,16884.299,0.0966363266941392,0.04825374240240968,0.007400927197802198,6.651494026184082,6.790753364562988,8.290628547042521e-05,1.619092583656311,38.700714111328125,28.234399211131237,18.456720883807584,0.04381901025772095,0.025941243395209312,0.21331411486039786,0.2665905835892049,0.4055052751068376,0.008540426587301588,0.004679830279201269,0.23979681776556774,0.014388307005494507,0.9819162065467213,18.323213503240172
|
docker/Dockerfile
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.9-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY ../requirements/common_requirements.txt /app/
|
| 6 |
+
|
| 7 |
+
RUN pip install --no-cache-dir -r common_requirements.txt
|
| 8 |
+
|
| 9 |
+
COPY ../services/ /app/services/
|
| 10 |
+
|
| 11 |
+
CMD ["python", "--version"]
|
docker/docker-compose-vali.yml
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.9'
|
| 2 |
+
services:
|
| 3 |
+
redis:
|
| 4 |
+
image: redis:latest
|
| 5 |
+
container_name: redis
|
| 6 |
+
ports:
|
| 7 |
+
- "6379:6379"
|
| 8 |
+
scoring_server:
|
| 9 |
+
build:
|
| 10 |
+
context: ./
|
| 11 |
+
dockerfile: Docker/Dockerfile
|
| 12 |
+
container_name: vali_scoring_server
|
| 13 |
+
command: ["python", "services/scoring/server.py"]
|
| 14 |
+
volumes:
|
| 15 |
+
- ../services:/app/services
|
| 16 |
+
|
| 17 |
+
video_scheduler_worker:
|
| 18 |
+
build:
|
| 19 |
+
context: ./
|
| 20 |
+
dockerfile: Docker/Dockerfile
|
| 21 |
+
container_name: vali_video_scheduler_worker
|
| 22 |
+
command: ["python", "services/video_scheduler/worker.py"]
|
| 23 |
+
volumes:
|
| 24 |
+
- ../services:/app/services
|
| 25 |
+
|
| 26 |
+
video_scheduler_server:
|
| 27 |
+
build:
|
| 28 |
+
context: ./
|
| 29 |
+
dockerfile: Docker/Dockerfile
|
| 30 |
+
container_name: vali_video_scheduler_server
|
| 31 |
+
command: ["python", "services/video_scheduler/server.py"]
|
| 32 |
+
volumes:
|
| 33 |
+
- ../services:/app/services
|
docs/images/banner.png
ADDED
|
Git LFS Details
|
docs/images/graph1.png
ADDED
|
Git LFS Details
|
docs/images/graph2.png
ADDED
|
Git LFS Details
|
docs/images/graph3.png
ADDED
|
Git LFS Details
|
docs/images/graph4.png
ADDED
|
Git LFS Details
|
docs/images/graph5.png
ADDED
|
Git LFS Details
|
docs/incentive_mechanism.md
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# VIDAIO Subnet Validation & Incentive Mechanism
|
| 2 |
+
|
| 3 |
+
## Table of Contents
|
| 4 |
+
- [Overview](#overview)
|
| 5 |
+
- [Task Types](#task-types)
|
| 6 |
+
- [Upscaling Tasks](#upscaling-tasks)
|
| 7 |
+
- [Compression Tasks](#compression-tasks)
|
| 8 |
+
- [Quality Validation Metrics](#quality-validation-metrics)
|
| 9 |
+
- [VMAF (Video Multi-Method Assessment Fusion)](#vmaf-video-multi-method-assessment-fusion)
|
| 10 |
+
- [PIE-APP (Perceptual Image-Error Assessment)](#pie-app-perceptual-image-error-assessment-through-pairwise-preference)
|
| 11 |
+
- [Upscaling System](#upscaling-system)
|
| 12 |
+
- [Upscaling Scoring](#upscaling-scoring)
|
| 13 |
+
- [Content Length Scoring](#content-length-scoring)
|
| 14 |
+
- [Upscaling Final Score](#upscaling-final-score)
|
| 15 |
+
- [Upscaling Penalty & Bonus System](#upscaling-penalty--bonus-system)
|
| 16 |
+
- [Compression System](#compression-system)
|
| 17 |
+
- [Compression Scoring](#compression-scoring)
|
| 18 |
+
- [Compression Penalty & Bonus System](#compression-penalty--bonus-system)
|
| 19 |
+
- [Implementation Guidelines](#implementation-guidelines)
|
| 20 |
+
- [Technical Specifications](#technical-specifications)
|
| 21 |
+
- [Mathematical Properties](#mathematical-properties)
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## Overview
|
| 26 |
+
|
| 27 |
+
The VIDAIO subnet validation mechanism ensures quality and reliability of miners' contributions through comprehensive assessment systems for different video processing tasks. The mechanism evaluates video processing performance using industry-standard **VMAF** and **PIE-APP** metrics, combined with advanced scoring systems that reward consistency and penalize poor performance.
|
| 28 |
+
|
| 29 |
+
**Key Features:**
|
| 30 |
+
- 🎯 **Multi-task support** for upscaling and compression operations
|
| 31 |
+
- 📊 **Quality validation** using industry-standard VMAF and PIE-APP metrics
|
| 32 |
+
- 🏆 **Performance-based incentive systems** with exponential rewards
|
| 33 |
+
- 📈 **Historical performance tracking** with rolling 10-round windows
|
| 34 |
+
- ⚖️ **Balanced penalty/bonus multipliers** encouraging sustained excellence
|
| 35 |
+
- 📦 **Dynamic content processing** (5s to 320s capability)
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Task Types
|
| 40 |
+
|
| 41 |
+
### Upscaling Tasks
|
| 42 |
+
|
| 43 |
+
Upscaling tasks require miners to enhance video quality by increasing resolution while maintaining or improving visual fidelity. These tasks focus on **quality improvement** as the primary objective.
|
| 44 |
+
|
| 45 |
+
**Key Characteristics:**
|
| 46 |
+
- **Primary Goal**: Quality enhancement and resolution improvement
|
| 47 |
+
- **Quality Metrics**: PIE-APP for scoring, VMAF for threshold validation
|
| 48 |
+
- **Content Length**: Dynamic processing durations (5s to 320s)
|
| 49 |
+
- **Scoring Focus**: Quality improvement with content length consideration
|
| 50 |
+
|
| 51 |
+
### Compression Tasks
|
| 52 |
+
|
| 53 |
+
Compression tasks require miners to reduce video file sizes while maintaining quality above specified VMAF thresholds. These tasks focus on **efficiency optimization** balancing file size reduction with quality preservation.
|
| 54 |
+
|
| 55 |
+
**Key Characteristics:**
|
| 56 |
+
- **Primary Goal**: File size reduction with quality maintenance
|
| 57 |
+
- **Quality Metrics**: VMAF for both scoring and threshold validation
|
| 58 |
+
- **Compression Rate**: File size reduction efficiency measurement
|
| 59 |
+
- **Scoring Focus**: Compression efficiency with quality threshold compliance
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
## Quality Validation Metrics
|
| 64 |
+
|
| 65 |
+
### VMAF (Video Multi-Method Assessment Fusion)
|
| 66 |
+
|
| 67 |
+
VMAF serves as the foundational video quality assessment metric, comparing frame-by-frame quality between original and processed videos. This metric provides objective measurement of subjective video quality as perceived by humans.
|
| 68 |
+
|
| 69 |
+
#### Key Characteristics
|
| 70 |
+
- **Purpose**: Frame-by-frame quality comparison
|
| 71 |
+
- **Range**: 0-100 (higher values indicate better quality)
|
| 72 |
+
- **Usage**: Threshold validation and quality scoring
|
| 73 |
+
- **Industry Standard**: Widely adopted in professional video processing
|
| 74 |
+
|
| 75 |
+
#### Mathematical Implementation
|
| 76 |
+
|
| 77 |
+
**Harmonic Mean Calculation:**
|
| 78 |
+
```
|
| 79 |
+
H = n / (1/S_1 + 1/S_2 + ... + 1/S_n)
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
**Where:**
|
| 83 |
+
- `S_i`: VMAF score for frame `i` (i = 1, 2, ..., n)
|
| 84 |
+
- `n`: Total number of frames in the video
|
| 85 |
+
- `H`: Harmonic mean emphasizing poor-quality frame impact
|
| 86 |
+
|
| 87 |
+
#### Why Harmonic Mean?
|
| 88 |
+
|
| 89 |
+
The harmonic mean approach provides several critical advantages:
|
| 90 |
+
|
| 91 |
+
| Advantage | Description | Impact |
|
| 92 |
+
|-----------|-------------|---------|
|
| 93 |
+
| **Sensitivity to Low Values** | Heavily penalizes poor-quality frames | Ensures consistent quality |
|
| 94 |
+
| **Quality Consistency** | Prevents miners from neglecting frame quality | Maintains processing standards |
|
| 95 |
+
| **Threshold Function** | Validates authentic processing processes | Prevents gaming attempts |
|
| 96 |
+
|
| 97 |
+
> **Note**: VMAF scores are calculated using 5 random frames for both upscaling and compression tasks.
|
| 98 |
+
|
| 99 |
+
---
|
| 100 |
+
|
| 101 |
+
### PIE-APP (Perceptual Image-Error Assessment through Pairwise Preference)
|
| 102 |
+
|
| 103 |
+
PIE-APP provides deep learning-based perceptual similarity assessment between original and processed video frames, serving as the primary quality scoring mechanism for upscaling tasks.
|
| 104 |
+
|
| 105 |
+
#### Technical Specifications
|
| 106 |
+
|
| 107 |
+
| Parameter | Value | Description |
|
| 108 |
+
|-----------|-------|-------------|
|
| 109 |
+
| **Scale Range** | (-∞, ∞) | Theoretical range |
|
| 110 |
+
| **Practical Range** | 0 to 5+ | Positive values (lower = better) |
|
| 111 |
+
| **Processing Interval** | Every frame | Default frame sampling rate |
|
| 112 |
+
| **Implementation** | Deep learning-based | Advanced perceptual assessment |
|
| 113 |
+
|
| 114 |
+
#### Calculation Process
|
| 115 |
+
|
| 116 |
+
**Step 1: Raw PIE-APP Score**
|
| 117 |
+
```
|
| 118 |
+
PIE-APP_score = (Σ abs(d(F_i, F'_i))) / n
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
**Where:**
|
| 122 |
+
- `F_i`: Frame `i` from original video
|
| 123 |
+
- `F'_i`: Corresponding frame `i` from processed video
|
| 124 |
+
- `d(F_i, F'_i)`: Perceptual difference between frames
|
| 125 |
+
- `n`: Number of processed frames (4 random frames)
|
| 126 |
+
|
| 127 |
+
**Step 2: Score Normalization**
|
| 128 |
+
```
|
| 129 |
+
1. Cap values: max(Average_PIE-APP, 2.0)
|
| 130 |
+
2. Sigmoid normalization: normalized_score = 1/(1+exp(-Average_PIE-APP))
|
| 131 |
+
3. Final transformation: Convert "lower is better" to "higher is better" (0-1 range)
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
#### Visual Score Transformation
|
| 135 |
+
|
| 136 |
+
The PIE-APP scoring system uses sophisticated mathematical transformations:
|
| 137 |
+
|
| 138 |
+
- *Sigmoid normalization function for PIE-APP scores*
|
| 139 |
+
!<img src="./images/graph2.png" alt="Sigmoid Function" width="1200" height="800">
|
| 140 |
+
|
| 141 |
+
- *Final score transformation converting to 0-1 range*
|
| 142 |
+
!<img src="./images/graph1.png" alt="Final Score Function" width="1200" height="800">
|
| 143 |
+
|
| 144 |
+
---
|
| 145 |
+
|
| 146 |
+
## Upscaling System
|
| 147 |
+
|
| 148 |
+
### Upscaling Scoring
|
| 149 |
+
|
| 150 |
+
#### Quality Score Calculation
|
| 151 |
+
|
| 152 |
+
**VMAF Threshold Validation:**
|
| 153 |
+
```
|
| 154 |
+
If VMAF_score < VMAF_threshold:
|
| 155 |
+
S_Q = 0 (Zero score for quality violation)
|
| 156 |
+
Else:
|
| 157 |
+
S_Q = PIE-APP_Final_Score
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
**Where:**
|
| 161 |
+
- `S_Q`: Quality score for upscaling
|
| 162 |
+
- `VMAF_score`: Achieved VMAF quality score
|
| 163 |
+
- `VMAF_threshold`: Minimum required VMAF score
|
| 164 |
+
- `PIE-APP_Final_Score`: Normalized PIE-APP score (0-1 range)
|
| 165 |
+
|
| 166 |
+
#### Metric Integration
|
| 167 |
+
|
| 168 |
+
| Metric | Range | Primary Function | Usage |
|
| 169 |
+
|--------|-------|------------------|-------|
|
| 170 |
+
| **VMAF** | 0-100 | Threshold validation | Upscaling verification |
|
| 171 |
+
| **PIE-APP** | 0-1 (final) | Quality scoring | Performance evaluation |
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
### Content Length Scoring
|
| 176 |
+
|
| 177 |
+
#### Dynamic Content Length Requests
|
| 178 |
+
|
| 179 |
+
Miners actively request content processing durations within 35-second evaluation windows, enabling optimized resource allocation and performance assessment.
|
| 180 |
+
|
| 181 |
+
#### Available Processing Durations
|
| 182 |
+
|
| 183 |
+
| Duration | Status | Availability |
|
| 184 |
+
|----------|--------|--------------|
|
| 185 |
+
| 5s | ✅ Default | Currently Available |
|
| 186 |
+
| 10s | ✅ Available | Currently Available |
|
| 187 |
+
| 20s | 🔄 Coming Soon | Future Release |
|
| 188 |
+
| 40s | 🔄 Coming Soon | Future Release |
|
| 189 |
+
| 80s | 🔄 Coming Soon | Future Release |
|
| 190 |
+
| 160s | 🔄 Coming Soon | Future Release |
|
| 191 |
+
| 320s | 🔄 Coming Soon | Future Release |
|
| 192 |
+
|
| 193 |
+
> **Current Limitation**: Processing durations up to 10 seconds are currently supported.
|
| 194 |
+
|
| 195 |
+
#### Length Score Mathematical Model
|
| 196 |
+
|
| 197 |
+
**Formula:**
|
| 198 |
+
```
|
| 199 |
+
S_L = log(1 + content_length) / log(1 + 320)
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
**Parameters:**
|
| 203 |
+
- `content_length`: Processing duration in seconds
|
| 204 |
+
- `S_L`: Normalized length score (0 to 1)
|
| 205 |
+
|
| 206 |
+
#### Performance Analysis Table
|
| 207 |
+
|
| 208 |
+
| Duration (s) | S_L Score | Percentage | Improvement | Performance Tier |
|
| 209 |
+
|--------------|-----------|------------|-------------|------------------|
|
| 210 |
+
| 5 | 0.3105 | 31.05% | Baseline | Default |
|
| 211 |
+
| 10 | 0.4155 | 41.55% | +33% | Significant Gain |
|
| 212 |
+
| 20 | 0.5275 | 52.75% | +27% | Strong Performance |
|
| 213 |
+
| 40 | 0.6434 | 64.34% | +22% | High Capability |
|
| 214 |
+
| 80 | 0.7614 | 76.14% | +18% | Advanced Processing |
|
| 215 |
+
| 160 | 0.8804 | 88.04% | +16% | Expert Level |
|
| 216 |
+
| 320 | 1.0000 | 100.00% | +14% | Maximum Score |
|
| 217 |
+
|
| 218 |
+
#### Logarithmic Scaling Benefits
|
| 219 |
+
|
| 220 |
+
| Benefit | Description | Impact |
|
| 221 |
+
|---------|-------------|---------|
|
| 222 |
+
| **Fair Distribution** | Balanced scoring across duration ranges | Equitable competition |
|
| 223 |
+
| **Diminishing Returns** | Reduced gains for extreme durations | Prevents over-optimization |
|
| 224 |
+
| **Normalized Output** | Consistent 0-1 scoring range | Standardized evaluation |
|
| 225 |
+
| **Capacity Recognition** | Rewards longer processing capabilities | Incentivizes advancement |
|
| 226 |
+
|
| 227 |
+
**Strategic Insights:**
|
| 228 |
+
- **Optimal Entry Point**: 10s processing provides largest relative improvement (+33%)
|
| 229 |
+
- **Scaling Pattern**: Each duration doubling yields progressively smaller benefits
|
| 230 |
+
- **Maximum Achievement**: 320s processing represents theoretical performance ceiling
|
| 231 |
+
|
| 232 |
+
---
|
| 233 |
+
|
| 234 |
+
### Upscaling Final Score
|
| 235 |
+
|
| 236 |
+
#### Score Component Architecture
|
| 237 |
+
|
| 238 |
+
The comprehensive upscaling scoring system integrates two fundamental metrics:
|
| 239 |
+
|
| 240 |
+
| Component | Symbol | Description | Weight |
|
| 241 |
+
|-----------|--------|-------------|--------|
|
| 242 |
+
| **Quality Score** | S_Q | Processing accuracy and output quality | W1 = 0.5 |
|
| 243 |
+
| **Length Score** | S_L | Content processing capacity | W2 = 0.5 |
|
| 244 |
+
|
| 245 |
+
#### Preliminary Score Calculation
|
| 246 |
+
|
| 247 |
+
**Formula:**
|
| 248 |
+
```
|
| 249 |
+
S_pre = S_Q × W1 + S_L × W2
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
**Current Configuration:**
|
| 253 |
+
- `W1 = 0.5` (Quality weight)
|
| 254 |
+
- `W2 = 0.5` (Length weight)
|
| 255 |
+
|
| 256 |
+
> **Dynamic Adjustment**: Weights are continuously optimized based on real-world performance data and network requirements.
|
| 257 |
+
|
| 258 |
+
#### Final Score Transformation
|
| 259 |
+
|
| 260 |
+
The preliminary score undergoes exponential transformation for enhanced performance differentiation:
|
| 261 |
+
|
| 262 |
+
**Formula:**
|
| 263 |
+
```
|
| 264 |
+
S_F = 0.1 × e^(6.979 × (S_pre - 0.5))
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
**Parameters:**
|
| 268 |
+
- `S_F`: Final upscaling score
|
| 269 |
+
- `S_pre`: Preliminary combined score
|
| 270 |
+
- `e`: Euler's number (≈2.718)
|
| 271 |
+
|
| 272 |
+
#### Performance Tier Analysis
|
| 273 |
+
|
| 274 |
+
| S_pre | S_F Score | Multiplier | Performance Tier | Reward Category |
|
| 275 |
+
|-------|-----------|------------|------------------|-----------------|
|
| 276 |
+
| 0.30 | 0.0248 | 0.25× | Poor Performance | Significant Penalty |
|
| 277 |
+
| 0.36 | 0.0376 | 0.38× | Below Average | Moderate Penalty |
|
| 278 |
+
| 0.42 | 0.0572 | 0.57× | Low Average | Minor Penalty |
|
| 279 |
+
| 0.48 | 0.0870 | 0.87× | Near Average | Slight Penalty |
|
| 280 |
+
| 0.54 | 0.1322 | 1.32× | Above Average | Moderate Reward |
|
| 281 |
+
| 0.60 | 0.2010 | 2.01× | Good Performance | Strong Reward |
|
| 282 |
+
| 0.66 | 0.3055 | 3.05× | High Performance | Major Reward |
|
| 283 |
+
| 0.72 | 0.4643 | 4.64× | Very High Performance | Excellent Reward |
|
| 284 |
+
| 0.78 | 0.7058 | 7.06× | Excellent Performance | Outstanding Reward |
|
| 285 |
+
| 0.84 | 1.0728 | 10.73× | Outstanding Performance | Elite Reward |
|
| 286 |
+
| 0.90 | 1.6307 | 16.31× | Elite Performance | Maximum Reward |
|
| 287 |
+
|
| 288 |
+
#### Exponential Function Characteristics
|
| 289 |
+
|
| 290 |
+
**System Benefits:**
|
| 291 |
+
| Feature | Description | Strategic Impact |
|
| 292 |
+
|---------|-------------|------------------|
|
| 293 |
+
| **Enhanced Differentiation** | Clear performance tier separation | Competitive advantage clarity |
|
| 294 |
+
| **Reward Amplification** | 16× multiplier difference (top vs bottom) | Strong performance incentives |
|
| 295 |
+
| **Competitive Optimization** | Non-linear improvement rewards | Encourages continuous advancement |
|
| 296 |
+
| **Exponential Scaling** | Small S_pre gains yield large S_F improvements | High-performance focus |
|
| 297 |
+
|
| 298 |
+
**Strategic Performance Guidelines:**
|
| 299 |
+
- **Minimum Target**: Achieve S_pre > 0.6 for meaningful reward activation
|
| 300 |
+
- **Optimization Focus**: Exponential curve creates powerful excellence incentives
|
| 301 |
+
- **High-Performance Strategy**: Small quality improvements at elevated levels yield disproportionate benefits
|
| 302 |
+
|
| 303 |
+
#### Graph Analysis
|
| 304 |
+
|
| 305 |
+
!<img src="./images/graph3.png" alt="Length Score Analysis" width="1200" height="1000">
|
| 306 |
+
|
| 307 |
+
---
|
| 308 |
+
|
| 309 |
+
### Upscaling Penalty & Bonus System
|
| 310 |
+
|
| 311 |
+
#### Historical Performance Multiplier Architecture
|
| 312 |
+
|
| 313 |
+
The advanced upscaling scoring system incorporates a **rolling 10-round historical performance window** to evaluate consistency patterns and apply dynamic multipliers based on sustained performance trends.
|
| 314 |
+
|
| 315 |
+
#### System Formula
|
| 316 |
+
|
| 317 |
+
```
|
| 318 |
+
Final Adjusted Score = S_F × Performance Multiplier
|
| 319 |
+
Performance Multiplier = Bonus Multiplier × S_F Penalty × S_Q Penalty
|
| 320 |
+
```
|
| 321 |
+
|
| 322 |
+
---
|
| 323 |
+
|
| 324 |
+
#### Bonus System (Excellence Rewards)
|
| 325 |
+
|
| 326 |
+
**Activation Criteria:** `S_F > 0.32` in mining round
|
| 327 |
+
|
| 328 |
+
**Mathematical Model:**
|
| 329 |
+
```python
|
| 330 |
+
bonus_multiplier = 1.0 + (bonus_count / 10) × 0.15
|
| 331 |
+
```
|
| 332 |
+
|
| 333 |
+
**System Characteristics:**
|
| 334 |
+
| Parameter | Value | Description |
|
| 335 |
+
|-----------|-------|-------------|
|
| 336 |
+
| **Maximum Bonus** | +15% | All 10 rounds achieve S_F > 0.32 |
|
| 337 |
+
| **Scaling Method** | Linear | Based on consistency frequency |
|
| 338 |
+
| **Primary Purpose** | Sustained excellence reward | Long-term performance incentive |
|
| 339 |
+
|
| 340 |
+
**Example Calculation:** 7/10 rounds with S_F > 0.32 → 1.105× multiplier (+10.5% bonus)
|
| 341 |
+
|
| 342 |
+
---
|
| 343 |
+
|
| 344 |
+
#### S_F Penalty System (Performance Penalties)
|
| 345 |
+
|
| 346 |
+
**Activation Criteria:** `S_F < 0.20` in mining round
|
| 347 |
+
|
| 348 |
+
**Mathematical Model:**
|
| 349 |
+
```python
|
| 350 |
+
penalty_f_multiplier = 1.0 - (penalty_f_count / 10) × 0.20
|
| 351 |
+
```
|
| 352 |
+
|
| 353 |
+
**System Characteristics:**
|
| 354 |
+
| Parameter | Value | Description |
|
| 355 |
+
|-----------|-------|-------------|
|
| 356 |
+
| **Maximum Penalty** | -20% | All 10 rounds achieve S_F < 0.20 |
|
| 357 |
+
| **Scaling Method** | Linear | Based on poor performance frequency |
|
| 358 |
+
| **Primary Purpose** | Performance consistency enforcement | Discourages sustained poor results |
|
| 359 |
+
|
| 360 |
+
**Example Calculation:** 4/10 rounds with S_F < 0.20 → 0.92× multiplier (-8% penalty)
|
| 361 |
+
|
| 362 |
+
---
|
| 363 |
+
|
| 364 |
+
#### S_Q Penalty System (Quality Penalties)
|
| 365 |
+
|
| 366 |
+
**Activation Criteria:** `S_Q < 0.25` in mining round
|
| 367 |
+
|
| 368 |
+
**Mathematical Model:**
|
| 369 |
+
```python
|
| 370 |
+
penalty_q_multiplier = 1.0 - (penalty_q_count / 10) × 0.25
|
| 371 |
+
```
|
| 372 |
+
|
| 373 |
+
**System Characteristics:**
|
| 374 |
+
| Parameter | Value | Description |
|
| 375 |
+
|-----------|-------|-------------|
|
| 376 |
+
| **Maximum Penalty** | -25% | All 10 rounds achieve S_Q < 0.25 |
|
| 377 |
+
| **Scaling Method** | Linear | Based on quality failure frequency |
|
| 378 |
+
| **Primary Purpose** | Quality standard enforcement | Strongest penalty (quality is critical) |
|
| 379 |
+
|
| 380 |
+
**Example Calculation:** 3/10 rounds with S_Q < 0.25 → 0.925× multiplier (-7.5% penalty)
|
| 381 |
+
|
| 382 |
+
---
|
| 383 |
+
|
| 384 |
+
#### Performance Multiplier Case Studies
|
| 385 |
+
|
| 386 |
+
| Miner Category | Avg S_F | Avg S_Q | Bonus Rate | S_F Penalty | S_Q Penalty | **Final Multiplier** | **Net Effect** |
|
| 387 |
+
|----------------|---------|---------|------------|-------------|-------------|---------------------|----------------|
|
| 388 |
+
| **Elite Miner** | 0.854 | 0.717 | 10/10 | 0/10 | 0/10 | **1.150×** | **+15.0%** |
|
| 389 |
+
| **Good Miner** | 0.653 | 0.571 | 0/10 | 0/10 | 0/10 | **1.000×** | **±0.0%** |
|
| 390 |
+
| **Average Miner** | 0.511 | 0.505 | 0/10 | 0/10 | 3/10 | **0.925×** | **-7.5%** |
|
| 391 |
+
| **Poor Miner** | 0.311 | 0.411 | 0/10 | 10/10 | 10/10 | **0.600×** | **-40.0%** |
|
| 392 |
+
|
| 393 |
+
#### Penalty Analysis
|
| 394 |
+
|
| 395 |
+
!<img src="./images/graph4.png" alt="Length Score Analysis" width="1200" height="800">
|
| 396 |
+
|
| 397 |
+
---
|
| 398 |
+
|
| 399 |
+
#### System Benefits & Strategic Impact
|
| 400 |
+
|
| 401 |
+
**Core System Benefits:**
|
| 402 |
+
|
| 403 |
+
| Benefit | Description | Strategic Impact |
|
| 404 |
+
|---------|-------------|------------------|
|
| 405 |
+
| 🎯 **Consistency Rewards** | Elite miners maintain sustained +15% bonus | Long-term competitive advantage |
|
| 406 |
+
| ⚡ **Responsive Penalties** | Poor performance accumulates immediate penalties | Rapid feedback mechanism |
|
| 407 |
+
| 🔄 **Recovery Incentive** | Miners can improve multipliers over 10 rounds | Encourages continuous improvement |
|
| 408 |
+
| ⚖️ **Balanced Impact** | Quality penalties are strongest (-25% max) | Emphasizes quality importance |
|
| 409 |
+
| 📈 **Progressive Scaling** | Linear scaling prevents extreme swings | Maintains system stability |
|
| 410 |
+
|
| 411 |
+
---
|
| 412 |
+
|
| 413 |
+
## Compression System
|
| 414 |
+
|
| 415 |
+
### Compression Scoring
|
| 416 |
+
|
| 417 |
+
#### Task Parameters
|
| 418 |
+
|
| 419 |
+
Each compression task includes specific requirements:
|
| 420 |
+
|
| 421 |
+
| Parameter | Description | Range | Impact |
|
| 422 |
+
|-----------|-------------|-------|---------|
|
| 423 |
+
| **VMAF Threshold** | Minimum acceptable quality score | 0-100 | Quality validation |
|
| 424 |
+
| **Original File Size** | Baseline for compression calculation | Variable | Compression rate baseline |
|
| 425 |
+
| **Target Optimization** | Balance between compression and quality | Dynamic | Performance strategy |
|
| 426 |
+
|
| 427 |
+
#### Compression Rate Calculation
|
| 428 |
+
|
| 429 |
+
**Formula:**
|
| 430 |
+
```
|
| 431 |
+
C = compressed_file_size / original_file_size
|
| 432 |
+
```
|
| 433 |
+
|
| 434 |
+
**Where:**
|
| 435 |
+
- `C`: Compression rate (0 < C ≤ 1)
|
| 436 |
+
- `compressed_file_size`: Size of processed video file
|
| 437 |
+
- `original_file_size`: Size of original video file
|
| 438 |
+
|
| 439 |
+
**Characteristics:**
|
| 440 |
+
- **Lower C values** indicate better compression (smaller files)
|
| 441 |
+
- **C = 1.0** means no compression achieved
|
| 442 |
+
- **C < 1.0** indicates successful compression
|
| 443 |
+
|
| 444 |
+
#### VMAF Quality Assessment
|
| 445 |
+
|
| 446 |
+
**Implementation:**
|
| 447 |
+
```
|
| 448 |
+
VMAF_score = Harmonic_Mean(VMAF_frame_1, VMAF_frame_2, ..., VMAF_frame_n)
|
| 449 |
+
```
|
| 450 |
+
|
| 451 |
+
**Where:**
|
| 452 |
+
- `VMAF_frame_i`: VMAF score for frame `i`
|
| 453 |
+
- `n`: Number of sampled frames (5 random frames)
|
| 454 |
+
- `Harmonic_Mean`: Emphasizes poor-quality frame impact
|
| 455 |
+
|
| 456 |
+
#### Final Compression Score Calculation
|
| 457 |
+
|
| 458 |
+
**Threshold Validation:**
|
| 459 |
+
```
|
| 460 |
+
If VMAF_score < VMAF_threshold:
|
| 461 |
+
S_f = 0 (Zero score for quality violation)
|
| 462 |
+
Else:
|
| 463 |
+
S_f = w_c × (1 - C^1.5) + w_vmaf × (VMAF_score - VMAF_threshold) / (100 - VMAF_threshold)
|
| 464 |
+
```
|
| 465 |
+
|
| 466 |
+
**Parameters:**
|
| 467 |
+
- `S_f`: Final compression score
|
| 468 |
+
- `w_c`: Weight for compression rate (default: 0.8)
|
| 469 |
+
- `w_vmaf`: Weight for VMAF score (default: 0.2)
|
| 470 |
+
- `C`: Compression rate
|
| 471 |
+
- `VMAF_score`: Achieved VMAF quality score
|
| 472 |
+
- `VMAF_threshold`: Minimum required VMAF score
|
| 473 |
+
|
| 474 |
+
#### Mathematical Properties
|
| 475 |
+
|
| 476 |
+
**Compression Rate Component:**
|
| 477 |
+
- **Formula**: `w_c × (1 - C^1.5)`
|
| 478 |
+
- **Range**: [0, w_c] (0 when C = 1, w_c when C = 0)
|
| 479 |
+
- **Curve**: Concave function emphasizing compression efficiency
|
| 480 |
+
- **Exponent 1.5**: Provides balanced reward for compression achievements
|
| 481 |
+
|
| 482 |
+
**VMAF Quality Component:**
|
| 483 |
+
- **Formula**: `w_vmaf × (VMAF_score - VMAF_threshold) / (100 - VMAF_threshold)`
|
| 484 |
+
- **Range**: [0, w_vmaf] (0 at threshold, w_vmaf at maximum quality)
|
| 485 |
+
- **Normalization**: Scales quality improvement relative to achievable range
|
| 486 |
+
- **Linear scaling**: Direct correlation between quality improvement and score
|
| 487 |
+
|
| 488 |
+
#### Graph Analysis
|
| 489 |
+
|
| 490 |
+
!<img src="./images/graph5.png" alt="Scoring impact" width="1200" height="600">
|
| 491 |
+
|
| 492 |
+
---
|
| 493 |
+
|
| 494 |
+
#### Performance Analysis Examples
|
| 495 |
+
|
| 496 |
+
| Scenario | C | VMAF_score | VMAF_threshold | S_f | Performance Tier |
|
| 497 |
+
|----------|---|------------|----------------|-----|------------------|
|
| 498 |
+
| **Excellent** | 0.3 | 85 | 70 | 0.669 + 0.100 = **0.769** | Outstanding |
|
| 499 |
+
| **Good** | 0.5 | 80 | 70 | 0.517 + 0.067 = **0.584** | Strong |
|
| 500 |
+
| **Average** | 0.7 | 75 | 70 | 0.331 + 0.033 = **0.364** | Acceptable |
|
| 501 |
+
| **Poor Quality** | 0.4 | 65 | 70 | **0.000** | Failed |
|
| 502 |
+
| **No Compression** | 1.0 | 90 | 70 | 0.000 + 0.133 = **0.133** | Inefficient |
|
| 503 |
+
|
| 504 |
+
#### Strategic Guidelines
|
| 505 |
+
|
| 506 |
+
| Miner Strategy | Focus Area | Target Metrics | Expected Outcome |
|
| 507 |
+
|----------------|------------|---------------|------------------|
|
| 508 |
+
| **Quality-First** | Maintain high VMAF scores | VMAF_score >> VMAF_threshold | Consistent moderate scores |
|
| 509 |
+
| **Compression-First** | Maximize file size reduction | C << 1.0 | Variable scores based on quality |
|
| 510 |
+
| **Balanced Approach** | Optimize both factors | Moderate C + Good VMAF | Optimal long-term performance |
|
| 511 |
+
| **Threshold Gaming** | Minimal quality compliance | VMAF_score ≈ VMAF_threshold | Low scores, high risk |
|
| 512 |
+
|
| 513 |
+
---
|
| 514 |
+
|
| 515 |
+
### Compression Penalty & Bonus System
|
| 516 |
+
|
| 517 |
+
#### Historical Performance Multiplier Architecture
|
| 518 |
+
|
| 519 |
+
The compression scoring system incorporates a **rolling 10-round historical performance window** to evaluate consistency patterns and apply dynamic multipliers based on sustained performance trends.
|
| 520 |
+
|
| 521 |
+
#### System Formula
|
| 522 |
+
|
| 523 |
+
```
|
| 524 |
+
Final Adjusted Compression Score = S_f × Performance Multiplier
|
| 525 |
+
Performance Multiplier = Bonus Multiplier × S_f Penalty × VMAF Penalty
|
| 526 |
+
```
|
| 527 |
+
|
| 528 |
+
---
|
| 529 |
+
|
| 530 |
+
#### Bonus System (Excellence Rewards)
|
| 531 |
+
|
| 532 |
+
**Activation Criteria:** `S_f > 0.74` in compression mining round
|
| 533 |
+
|
| 534 |
+
**Mathematical Model:**
|
| 535 |
+
```python
|
| 536 |
+
bonus_multiplier = 1.0 + (bonus_count / 10) × 0.15
|
| 537 |
+
```
|
| 538 |
+
|
| 539 |
+
**System Characteristics:**
|
| 540 |
+
| Parameter | Value | Description |
|
| 541 |
+
|-----------|-------|-------------|
|
| 542 |
+
| **Maximum Bonus** | +15% | All 10 rounds achieve S_f > 0.74 |
|
| 543 |
+
| **Scaling Method** | Linear | Based on consistency frequency |
|
| 544 |
+
| **Primary Purpose** | Sustained excellence reward | Long-term performance incentive |
|
| 545 |
+
|
| 546 |
+
**Example Calculation:** 7/10 rounds with S_f > 0.74 → 1.105× multiplier (+10.5% bonus)
|
| 547 |
+
|
| 548 |
+
---
|
| 549 |
+
|
| 550 |
+
#### S_f Penalty System (Performance Penalties)
|
| 551 |
+
|
| 552 |
+
**Activation Criteria:** `S_f < 0.4` in compression mining round
|
| 553 |
+
|
| 554 |
+
**Mathematical Model:**
|
| 555 |
+
```python
|
| 556 |
+
penalty_f_multiplier = 1.0 - (penalty_f_count / 10) × 0.20
|
| 557 |
+
```
|
| 558 |
+
|
| 559 |
+
**System Characteristics:**
|
| 560 |
+
| Parameter | Value | Description |
|
| 561 |
+
|-----------|-------|-------------|
|
| 562 |
+
| **Maximum Penalty** | -20% | All 10 rounds achieve S_f < 0.4 |
|
| 563 |
+
| **Scaling Method** | Linear | Based on poor performance frequency |
|
| 564 |
+
| **Primary Purpose** | Performance consistency enforcement | Discourages sustained poor results |
|
| 565 |
+
|
| 566 |
+
**Example Calculation:** 4/10 rounds with S_f < 0.4 → 0.92× multiplier (-8% penalty)
|
| 567 |
+
|
| 568 |
+
---
|
| 569 |
+
|
| 570 |
+
#### Compression Performance Multiplier Case Studies
|
| 571 |
+
|
| 572 |
+
| Miner Category | Avg S_f | Avg VMAF Margin | Bonus Rate | S_f Penalty | **Final Multiplier** | **Net Effect** |
|
| 573 |
+
|----------------|---------|-----------------|------------|-------------|---------------|---------------------|----------------|
|
| 574 |
+
| **Elite Compressor** | 0.654 | +15 | 10/10 | 0/10 | **1.150×** | **+15.0%** |
|
| 575 |
+
| **Good Compressor** | 0.453 | +8 | 0/10 | 0/10 | **1.000×** | **±0.0%** |
|
| 576 |
+
| **Average Compressor** | 0.311 | +3 | 0/10 | 0/10 | **0.910×** | **-9.0%** |
|
| 577 |
+
| **Poor Compressor** | 0.211 | -2 | 0/10 | 10/10 | **0.490×** | **-51.0%** |
|
| 578 |
+
|
| 579 |
+
#### Compression Penalty Analysis
|
| 580 |
+
|
| 581 |
+
**Strategic Impact:**
|
| 582 |
+
- **Quality-first approach** strongly incentivized through higher penalties
|
| 583 |
+
- **Consistency rewards** for miners maintaining quality above threshold
|
| 584 |
+
- **Immediate feedback** through zero-score for quality violations
|
| 585 |
+
- **Balanced optimization** encouraged through dual-factor scoring
|
| 586 |
+
|
| 587 |
+
---
|
| 588 |
+
|
| 589 |
+
## Implementation Guidelines
|
| 590 |
+
|
| 591 |
+
### Performance Monitoring
|
| 592 |
+
|
| 593 |
+
**Real-time Operations:**
|
| 594 |
+
- ✅ Scores calculated in real-time during mining operations
|
| 595 |
+
- ✅ Historical performance data maintained for trend analysis and multiplier calculation
|
| 596 |
+
- ✅ Weight adjustments implemented based on network-wide performance metrics
|
| 597 |
+
- ✅ Performance multipliers updated after each mining round
|
| 598 |
+
|
| 599 |
+
### Scoring Strategy Recommendations
|
| 600 |
+
|
| 601 |
+
**Upscaling Performance-Based Guidelines:**
|
| 602 |
+
|
| 603 |
+
| Miner Category | Primary Focus | Strategic Recommendations |
|
| 604 |
+
|----------------|---------------|---------------------------|
|
| 605 |
+
| **New Miners** | Foundation Building | Focus on achieving consistent S_pre > 0.5 before optimizing for length |
|
| 606 |
+
| **Established Miners** | Quality Optimization | Prioritize quality improvements when S_pre > 0.6 to avoid S_Q penalties |
|
| 607 |
+
| **Elite Miners** | Consistency Maintenance | Maintain consistency above S_F > 0.32 to secure maximum bonus multipliers |
|
| 608 |
+
| **Recovery Phase** | Systematic Improvement | Focus on quality (S_Q > 0.25) first, then performance (S_F > 0.20) to restore multipliers |
|
| 609 |
+
|
| 610 |
+
**Compression Performance-Based Guidelines:**
|
| 611 |
+
|
| 612 |
+
| Miner Category | Primary Focus | Strategic Recommendations |
|
| 613 |
+
|----------------|---------------|---------------------------|
|
| 614 |
+
| **New Compressors** | Quality Compliance | Focus on maintaining VMAF_score > VMAF_threshold + 5 |
|
| 615 |
+
| **Established Compressors** | Balanced Optimization | Optimize both compression rate and quality maintenance |
|
| 616 |
+
| **Elite Compressors** | Consistency Excellence | Maintain S_f > 0.74 consistently for bonus multipliers |
|
| 617 |
+
| **Recovery Phase** | Quality Restoration | Focus on VMAF compliance first, then compression optimization |
|
| 618 |
+
|
| 619 |
+
### Future Enhancement Roadmap
|
| 620 |
+
|
| 621 |
+
**Planned Developments:**
|
| 622 |
+
|
| 623 |
+
- [ ] **Extended Content Length Support** - Processing durations up to 320s
|
| 624 |
+
- [ ] **Dynamic Weight Adjustment Algorithms** - Automated optimization based on network performance
|
| 625 |
+
- [ ] **Advanced Quality Metrics Integration** - Additional assessment parameters
|
| 626 |
+
- [ ] **Multi-dimensional Scoring Parameters** - Enhanced evaluation criteria
|
| 627 |
+
- [ ] **Adaptive Difficulty Scaling** - Network performance-based adjustments
|
| 628 |
+
- [ ] **Advanced Penalty/Bonus Optimization** - Network-wide performance distribution analysis
|
| 629 |
+
- [ ] **Seasonal Performance Multiplier Adjustments** - Time-based optimization cycles
|
| 630 |
+
- [ ] **Cross-Task Performance Integration** - Unified scoring across upscaling and compression
|
| 631 |
+
|
| 632 |
+
---
|
| 633 |
+
|
| 634 |
+
## Technical Specifications
|
| 635 |
+
|
| 636 |
+
### Core System Parameters
|
| 637 |
+
|
| 638 |
+
| Parameter | Current Value | Configurable Range | Implementation Notes |
|
| 639 |
+
|-----------|---------------|-------------------|---------------------|
|
| 640 |
+
| **Default Content Length** | 5s | 5s - 10s | Actively configurable by miners |
|
| 641 |
+
| **Quality Weight (W1)** | 0.5 | 0.0 - 1.0 | Dynamically adjusted based on network data |
|
| 642 |
+
| **Length Weight (W2)** | 0.5 | 0.0 - 1.0 | Dynamically adjusted based on network data |
|
| 643 |
+
|
| 644 |
+
### Compression System Parameters
|
| 645 |
+
|
| 646 |
+
| Parameter | Current Value | Configurable Range | Implementation Notes |
|
| 647 |
+
|-----------|---------------|-------------------|---------------------|
|
| 648 |
+
| **Compression Rate Weight (w_c)** | 0.8 | 0.6 - 0.9 | Balances compression efficiency vs quality |
|
| 649 |
+
| **VMAF Score Weight (w_vmaf)** | 0.2 | 0.1 - 0.4 | Balances quality maintenance vs compression |
|
| 650 |
+
| **Compression Rate Exponent** | 1.5 | 1.2 - 2.0 | Controls compression reward curve steepness |
|
| 651 |
+
| **VMAF Safety Margin** | +5 | +3 - +10 | Quality buffer above threshold |
|
| 652 |
+
| **Zero-Score Threshold** | VMAF_score < VMAF_threshold | Fixed | Immediate penalty for quality violations |
|
| 653 |
+
|
| 654 |
+
### Performance Multiplier System Parameters
|
| 655 |
+
|
| 656 |
+
| Parameter | Current Value | Configurable Range | System Impact |
|
| 657 |
+
|-----------|---------------|-------------------|---------------|
|
| 658 |
+
| **Performance History Window** | 10 rounds | 5-10 rounds | Configurable for different network conditions |
|
| 659 |
+
| **Upscaling Bonus Threshold** | S_F > 0.32 | 0.3-0.4 | Adjustable based on network performance |
|
| 660 |
+
| **Upscaling S_F Penalty Threshold** | S_F < 0.20 | 0.15-0.25 | Adjustable based on network performance |
|
| 661 |
+
| **Upscaling S_Q Penalty Threshold** | S_Q < 0.25 | 0.2-0.3 | Adjustable based on network performance |
|
| 662 |
+
| **Compression Bonus Threshold** | S_f > 0.74 | 0.7-0.8 | Lower threshold reflecting compression difficulty |
|
| 663 |
+
| **Compression S_f Penalty Threshold** | S_f < 0.4 | 0.35-0.45 | Penalty for poor compression performance |
|
| 664 |
+
| **VMAF Penalty Threshold** | VMAF_score < VMAF_threshold + 5 | +3 to +10 | Quality safety margin enforcement |
|
| 665 |
+
| **Maximum Bonus** | +15% | 10%-20% | Scalable reward system |
|
| 666 |
+
| **Maximum S_F Penalty** | -20% | 15%-25% | Scalable penalty system |
|
| 667 |
+
| **Maximum S_Q Penalty** | -25% | 20%-30% | Strongest penalty for quality issues |
|
| 668 |
+
|
| 669 |
+
---
|
| 670 |
+
|
| 671 |
+
## Mathematical Properties
|
| 672 |
+
|
| 673 |
+
### Upscaling Length Score Function Properties
|
| 674 |
+
|
| 675 |
+
**Mathematical Characteristics:**
|
| 676 |
+
- **Domain**: [5, 320] seconds
|
| 677 |
+
- **Range**: [0.3105, 1.0000]
|
| 678 |
+
- **Function Type**: Logarithmic (concave)
|
| 679 |
+
- **Growth Rate**: Decreasing marginal returns
|
| 680 |
+
- **Optimization Point**: Balanced between processing capability and diminishing returns
|
| 681 |
+
|
| 682 |
+
### Upscaling Final Score Function Properties
|
| 683 |
+
|
| 684 |
+
**Mathematical Characteristics:**
|
| 685 |
+
- **Domain**: [0, 1] (S_pre values)
|
| 686 |
+
- **Range**: [0.0025, 40.43] (theoretical maximum)
|
| 687 |
+
- **Function Type**: Exponential (convex)
|
| 688 |
+
- **Critical Point**: S_pre = 0.5 (inflection point for reward/penalty)
|
| 689 |
+
- **Scaling Behavior**: Exponential amplification of performance differences
|
| 690 |
+
|
| 691 |
+
### Compression Score Function Properties
|
| 692 |
+
|
| 693 |
+
**Mathematical Characteristics:**
|
| 694 |
+
- **Domain**: [0, 1] (compression rate C values)
|
| 695 |
+
- **Range**: [0, w_c] (compression component score)
|
| 696 |
+
- **Function Type**: Concave (1 - C^1.5)
|
| 697 |
+
- **Critical Point**: C = 1 (no compression = zero score)
|
| 698 |
+
- **Optimization**: Lower C values yield higher scores with diminishing returns
|
| 699 |
+
|
| 700 |
+
### Compression VMAF Component Properties
|
| 701 |
+
|
| 702 |
+
**Mathematical Characteristics:**
|
| 703 |
+
- **Domain**: [VMAF_threshold, 100] (achievable quality range)
|
| 704 |
+
- **Range**: [0, w_vmaf] (quality component score)
|
| 705 |
+
- **Function Type**: Linear normalization
|
| 706 |
+
- **Zero Point**: VMAF_score = VMAF_threshold
|
| 707 |
+
- **Maximum Point**: VMAF_score = 100
|
| 708 |
+
- **Quality Buffer**: +5 margin for penalty system
|
| 709 |
+
|
| 710 |
+
### Performance Multiplier Properties
|
| 711 |
+
|
| 712 |
+
**Mathematical Characteristics:**
|
| 713 |
+
- **Domain**: [0.55, 1.15] (practical operational range)
|
| 714 |
+
- **Function Type**: Linear combination of historical performance frequencies
|
| 715 |
+
- **Update Frequency**: After each mining round completion
|
| 716 |
+
- **Memory System**: Rolling 10-round window with automatic history management
|
| 717 |
+
- **Convergence**: Stabilizes after 10 rounds of consistent performance patterns
|
| 718 |
+
|
| 719 |
+
---
|
| 720 |
+
|
| 721 |
+
## Conclusion
|
| 722 |
+
|
| 723 |
+
The VIDAIO subnet validation and incentive mechanism represents a comprehensive, mathematically-grounded approach to ensuring high-quality video processing while maintaining fair competition and encouraging continuous improvement. Through the integration of industry-standard metrics (VMAF and PIE-APP), dynamic scoring systems, and sophisticated penalty/bonus mechanisms, the system creates a robust environment that rewards excellence and consistency while providing clear pathways for improvement.
|
| 724 |
+
|
| 725 |
+
The system now supports both **upscaling** and **compression** tasks, each with specialized scoring mechanisms:
|
| 726 |
+
- **Upscaling tasks** focus on quality improvement using PIE-APP scoring with VMAF threshold validation
|
| 727 |
+
- **Compression tasks** balance file size reduction with quality maintenance using dual-factor scoring
|
| 728 |
+
- **Unified penalty systems** ensure consistent quality standards across all task types
|
| 729 |
+
|
| 730 |
+
These metrics together ensure that miners maintain high-quality video processing standards while meeting demands for fast and efficient processing, creating a sustainable and competitive ecosystem for video enhancement and optimization services.
|
| 731 |
+
|
| 732 |
+
---
|
| 733 |
+
|
| 734 |
+
*This documentation is continuously updated to reflect the latest scoring mechanisms, performance optimizations, and system enhancements.*
|
docs/miner_setup.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Running Miner
|
| 2 |
+
|
| 3 |
+
A high-performance decentralized video processing miner that leverages **Video2X** for AI-powered video upscaling. This guide provides detailed instructions to set up, configure, and run the miner effectively
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Machine Requirements
|
| 8 |
+
|
| 9 |
+
To achieve optimal results, we recommend the following setup:
|
| 10 |
+
|
| 11 |
+
- **Operating System**: Ubuntu 24.04 LTS or higher
|
| 12 |
+
[Learn more about Ubuntu 24.04 LTS](https://ubuntu.com/blog/tag/ubuntu-24-04-lts)
|
| 13 |
+
- **GPU**: NVIDIA RTX 4090 or higher
|
| 14 |
+
(Required for efficient video upscaling using Video2X)
|
| 15 |
+
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
## Install PM2 (Process Manager)
|
| 19 |
+
|
| 20 |
+
**PM2** is used to manage and monitor the miner process. If you haven’t installed PM2 yet, follow these steps:
|
| 21 |
+
|
| 22 |
+
1. Install `npm` and PM2:
|
| 23 |
+
```bash
|
| 24 |
+
sudo apt update
|
| 25 |
+
sudo apt install npm -y
|
| 26 |
+
sudo npm install pm2 -g
|
| 27 |
+
pm2 update
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
2. For more details, refer to the [PM2 Documentation](https://pm2.io/docs/runtime/guide/installation/).
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## Install Redis
|
| 35 |
+
|
| 36 |
+
1. Install 'redis'
|
| 37 |
+
```bash
|
| 38 |
+
sudo apt update
|
| 39 |
+
sudo apt install redis-server
|
| 40 |
+
sudo systemctl start redis
|
| 41 |
+
sudo systemctl enable redis-server
|
| 42 |
+
sudo systemctl status redis
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
## Install Project Dependencies
|
| 46 |
+
|
| 47 |
+
### Prerequisites
|
| 48 |
+
|
| 49 |
+
- **Python**: Version 3.10 or higher
|
| 50 |
+
- **pip**: Python package manager
|
| 51 |
+
- **virtualenv** (optional): For dependency isolation
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
### 1. Clone the Repository
|
| 56 |
+
|
| 57 |
+
Clone the project repository to your local machine:
|
| 58 |
+
```bash
|
| 59 |
+
git clone https://github.com/vidaio-subnet/vidaio-subnet.git
|
| 60 |
+
cd vidaio-subnet
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
|
| 65 |
+
### 2. Set Up a Virtual Environment (Recommended)
|
| 66 |
+
|
| 67 |
+
Create and activate a virtual environment to isolate project dependencies:
|
| 68 |
+
```bash
|
| 69 |
+
python3 -m venv venv
|
| 70 |
+
source venv/bin/activate
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
### 3. Install the Package and Dependencies
|
| 76 |
+
|
| 77 |
+
Install the project and its dependencies using `pip`:
|
| 78 |
+
```bash
|
| 79 |
+
pip install -e .
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
### 4. Configure Environment Variables
|
| 85 |
+
|
| 86 |
+
To configure environment variables, follow these steps:
|
| 87 |
+
|
| 88 |
+
1. Create a `.env` file in the project root directory by referencing the provided `.env.template` file:
|
| 89 |
+
```bash
|
| 90 |
+
cp .env.template .env
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
2. Set up a bucket in cloud storage. The base miner code utilizes MinIO to connect with cloud storage services, so you'll need to prepare your bucket using a platform that supports MinIO integration, such as Backblaze. Alternatively, you can modify the code to suit your specific requirements.
|
| 94 |
+
3. Add the required variables to the `.env` file. For example:
|
| 95 |
+
```env
|
| 96 |
+
BUCKET_NAME="S3 buckent name"
|
| 97 |
+
BUCKET_COMPATIBLE_ENDPOINT="S3 bucket endpoint"
|
| 98 |
+
BUTKET_COMPATIBLE_ACCESS_KEY="S3 bucket personal access key"
|
| 99 |
+
BUCKET_COMPATIBLE_SECRET_KEY="S3 bucket personal secret key"
|
| 100 |
+
PEXELS_API_KEY="Your Pexels account api key"
|
| 101 |
+
WANDB_API_KEY="Your WANDB account api key"
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
4. Ensure that the bucket is configured with the appropriate permissions to allow file uploads and enable public access for downloads via presigned URLs.
|
| 105 |
+
|
| 106 |
+
5. Once the `.env` file is properly configured, the application will use the specified credentials for S3 bucket and Pexels.
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Install Video2X
|
| 112 |
+
|
| 113 |
+
The miner requires **Video2X** for AI-powered video upscaling. Follow the steps below to install and configure Video2X.
|
| 114 |
+
|
| 115 |
+
---
|
| 116 |
+
|
| 117 |
+
### Step 1: Install FFMPEG
|
| 118 |
+
|
| 119 |
+
FFMPEG is required for processing video files. Install it using the following commands:
|
| 120 |
+
```bash
|
| 121 |
+
sudo apt update
|
| 122 |
+
sudo apt install ffmpeg -y
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
For more details, refer to the [FFMPEG Documentation](https://www.ffmpeg.org/download.html#build-linux).
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
### Step 2: Install CUDA and NVCC
|
| 130 |
+
|
| 131 |
+
Ensure your CUDA drivers and NVCC (NVIDIA Compiler) are properly installed and configured to support GPU acceleration.
|
| 132 |
+
|
| 133 |
+
1. Verify your CUDA installation:
|
| 134 |
+
```bash
|
| 135 |
+
nvcc --version
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
2. Ensure you CUDA driver is installed correctly
|
| 139 |
+
<!-- Install or update the CUDA drivers if they are not already installed:
|
| 140 |
+
```bash
|
| 141 |
+
sudo apt update
|
| 142 |
+
sudo apt install nvidia-cuda-toolkit -y
|
| 143 |
+
``` -->
|
| 144 |
+
|
| 145 |
+
For more information, refer to the [CUDA Toolkit Installation Guide](https://developer.nvidia.com/cuda-toolkit).
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
### Step 3: Install Video2X
|
| 150 |
+
|
| 151 |
+
---
|
| 152 |
+
|
| 153 |
+
### Option 1: Install Video2X by downloading Debian Package:
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
1. **Download the Video2X `.deb` package**:
|
| 157 |
+
```bash
|
| 158 |
+
wget -P services/upscaling/models https://github.com/k4yt3x/video2x/releases/download/6.3.1/video2x-linux-ubuntu2404-amd64.deb
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
2. **Install the package using `dpkg`**:
|
| 162 |
+
```bash
|
| 163 |
+
sudo dpkg -i services/upscaling/models/video2x-linux-ubuntu2404-amd64.deb
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
3. **Resolve dependencies** (if any):
|
| 167 |
+
```bash
|
| 168 |
+
sudo apt-get install -f
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
### Option 2: Install Video2X by Building Debian Package:
|
| 172 |
+
|
| 173 |
+
1. Install Cargo
|
| 174 |
+
Cargo is required to build the Video2X package:
|
| 175 |
+
```bash
|
| 176 |
+
sudo apt-get update
|
| 177 |
+
sudo apt-get install cargo -y
|
| 178 |
+
cargo install just --version=1.39.0
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
2. Clone the Video2X Repository
|
| 182 |
+
you can clone this repository within the current vidaio-subnet package
|
| 183 |
+
```bash
|
| 184 |
+
git clone --recurse-submodules https://github.com/vidAio-subnet/video2x
|
| 185 |
+
cd video2x
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
3. Build the Video2X Project
|
| 189 |
+
Before building, ensure `~/.cargo/bin` is included in your `PATH` environment variable:
|
| 190 |
+
```bash
|
| 191 |
+
export PATH="$HOME/.cargo/bin:$PATH"
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
Run the following command to build the package:
|
| 195 |
+
```bash
|
| 196 |
+
just ubuntu2404
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
Once the build is complete, the `.deb` package will be located in the current directory.
|
| 200 |
+
|
| 201 |
+
4. Install the Built Package
|
| 202 |
+
Install the `.deb` package using:
|
| 203 |
+
```bash
|
| 204 |
+
sudo dpkg -i video2x-linux-ubuntu-amd64.deb
|
| 205 |
+
```
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
For additional details, refer to the [Video2X Documentation](https://docs.video2x.org/building/linux.html).
|
| 209 |
+
|
| 210 |
+
## Running the Video Upscaling Endpoint
|
| 211 |
+
|
| 212 |
+
You can run the video upscaling endpoint using **PM2** to manage the process:
|
| 213 |
+
|
| 214 |
+
```bash
|
| 215 |
+
pm2 start "python services/upscaling/server.py" --name video-upscaler
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
### Notes:
|
| 219 |
+
- The `video-upscaler` process will handle video upscaling requests.
|
| 220 |
+
- Use the following PM2 commands to manage the process:
|
| 221 |
+
- **View Logs**: `pm2 logs video-upscaler`
|
| 222 |
+
- **Restart**: `pm2 restart video-upscaler`
|
| 223 |
+
- **Stop**: `pm2 stop video-upscaler`
|
| 224 |
+
|
| 225 |
+
---
|
| 226 |
+
|
| 227 |
+
## Running the Video Compression Endpoint
|
| 228 |
+
|
| 229 |
+
You can also run the video Compression endpoint using **PM2** to manage the process:
|
| 230 |
+
|
| 231 |
+
```bash
|
| 232 |
+
pm2 start "python services/compress/server.py" --name video-compressor
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
---
|
| 236 |
+
|
| 237 |
+
## Running the file deletion process
|
| 238 |
+
|
| 239 |
+
You can run the file deletion process using **PM2** to manage the process:
|
| 240 |
+
|
| 241 |
+
```bash
|
| 242 |
+
pm2 start "python services/miner_utilities/file_deletion_server.py" --name video-deleter
|
| 243 |
+
```
|
| 244 |
+
|
| 245 |
+
## Running the Miner with PM2
|
| 246 |
+
|
| 247 |
+
To run the miner, use the following command:
|
| 248 |
+
|
| 249 |
+
```bash
|
| 250 |
+
pm2 start "python3 neurons/miner.py --wallet.name [Your_Wallet_Name] --wallet.hotkey [Your_Hotkey_Name] --subtensor.network finney --netuid 85 --axon.port [port] --logging.debug" --name video-miner
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
### Parameters:
|
| 254 |
+
- **`--wallet.name`**: Replace `[Your_Wallet_Name]` with your wallet name.
|
| 255 |
+
- **`--wallet.hotkey`**: Replace `[Your_Hotkey_Name]` with your hotkey name.
|
| 256 |
+
- **`--subtensor.network`**: Specify the target network (e.g., `finney`).
|
| 257 |
+
- **`--netuid`**: Specify the network UID (e.g., `292`).
|
| 258 |
+
- **`--axon.port`**: Replace `[port]` with the desired port number.
|
| 259 |
+
- **`--logging.debug`**: Enables debug-level logging for detailed output.
|
| 260 |
+
|
| 261 |
+
### Managing the Miner Process:
|
| 262 |
+
- **Start the Miner**: The above command will start the miner as a PM2 process named `video-miner`.
|
| 263 |
+
- **View Logs**: Use `pm2 logs video-miner` to monitor miner logs in real time.
|
| 264 |
+
- **Restart the Miner**: Use `pm2 restart video-miner` to restart the process.
|
| 265 |
+
- **Stop the Miner**: Use `pm2 stop video-miner` to stop the process.
|
| 266 |
+
|
| 267 |
+
---
|
| 268 |
+
|
| 269 |
+
## Additional Notes
|
| 270 |
+
|
| 271 |
+
- Ensure all dependencies are installed and configured correctly before running the miner.
|
| 272 |
+
- Use a high-performance GPU and sufficient system resources for optimal performance.
|
| 273 |
+
- For troubleshooting and debugging, refer to the logs available in PM2.
|
docs/validator_setup.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Running Validator
|
| 2 |
+
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
## Machine Requirements
|
| 6 |
+
|
| 7 |
+
To achieve optimal results, we recommend the following setup:
|
| 8 |
+
|
| 9 |
+
- **Operating System**: Ubuntu 24.04 LTS or higher
|
| 10 |
+
[Learn more about Ubuntu 24.04 LTS](https://ubuntu.com/blog/tag/ubuntu-24-04-lts)
|
| 11 |
+
- **GPU**: NVIDIA RTX A6000 with 48GB VRAM and 30 CPU Cores
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## Install PM2 (Process Manager)
|
| 16 |
+
|
| 17 |
+
**PM2** is used to manage and monitor the validator process. If you haven’t installed PM2 yet, follow these steps:
|
| 18 |
+
|
| 19 |
+
1. Install `npm` and PM2:
|
| 20 |
+
```bash
|
| 21 |
+
sudo apt update
|
| 22 |
+
sudo apt install npm -y
|
| 23 |
+
sudo npm install pm2 -g
|
| 24 |
+
pm2 update
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
2. For more details, refer to the [PM2 Documentation](https://pm2.io/docs/runtime/guide/installation/).
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## Install Redis
|
| 32 |
+
|
| 33 |
+
1. Install 'redis'
|
| 34 |
+
```bash
|
| 35 |
+
sudo apt update
|
| 36 |
+
sudo apt install redis-server
|
| 37 |
+
sudo systemctl start redis
|
| 38 |
+
sudo systemctl enable redis-server
|
| 39 |
+
sudo systemctl status redis
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
## Install Project Dependencies
|
| 44 |
+
|
| 45 |
+
### Prerequisites
|
| 46 |
+
|
| 47 |
+
- **Python**: Version 3.10 or higher
|
| 48 |
+
- **pip**: Python package manager
|
| 49 |
+
- **virtualenv** (optional): For dependency isolation
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
### 1. Clone the Repository
|
| 54 |
+
|
| 55 |
+
Clone the project repository to your local machine:
|
| 56 |
+
```bash
|
| 57 |
+
git clone https://github.com/vidaio-subnet/vidaio-subnet.git
|
| 58 |
+
cd vidaio-subnet
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
---
|
| 62 |
+
|
| 63 |
+
### 2. Set Up a Virtual Environment (Recommended)
|
| 64 |
+
|
| 65 |
+
Create and activate a virtual environment to isolate project dependencies:
|
| 66 |
+
```bash
|
| 67 |
+
python3 -m venv venv
|
| 68 |
+
source venv/bin/activate
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
---
|
| 72 |
+
|
| 73 |
+
### 3. Install the Package and Dependencies
|
| 74 |
+
|
| 75 |
+
Install the project and its dependencies using `pip`:
|
| 76 |
+
```bash
|
| 77 |
+
pip install -e .
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
### 4. Configure Environment Variables
|
| 83 |
+
|
| 84 |
+
To configure environment variables, follow these steps:
|
| 85 |
+
|
| 86 |
+
1. Create a `.env` file in the project root directory by referencing the provided `.env.template` file:
|
| 87 |
+
```bash
|
| 88 |
+
cp .env.template .env
|
| 89 |
+
```
|
| 90 |
+
2. Set up a bucket in cloud storage. The base miner code utilizes MinIO to connect with cloud storage services, so you'll need to prepare your bucket using a platform that supports MinIO integration, such as Backblaze. Alternatively, you can modify the code to suit your specific requirements. *IMPORTANT*: Note that currently the `region` of the storage is hardcoded, and must be adjusted in `vidaio_subnet_core/utilities/storage_client.py` for corresponding storage, such as AWS.
|
| 91 |
+
3. Add the required variables to the `.env` file. For example:
|
| 92 |
+
```env
|
| 93 |
+
BUCKET_NAME="S3 buckent name"
|
| 94 |
+
BUCKET_COMPATIBLE_ENDPOINT="S3 bucket endpoint"
|
| 95 |
+
BUTKET_COMPATIBLE_ACCESS_KEY="S3 bucket personal access key"
|
| 96 |
+
BUCKET_COMPATIBLE_SECRET_KEY="S3 bucket personal secret key"
|
| 97 |
+
PEXELS_API_KEY="Your Pexels account api key"
|
| 98 |
+
WANDB_API_KEY="Your WANDB account api key"
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
4. Ensure that the bucket is configured with the appropriate permissions to allow file uploads and enable public access for downloads via presigned URLs.
|
| 102 |
+
|
| 103 |
+
5. Create your Pexels API key and replace it. (https://www.pexels.com/)
|
| 104 |
+
6. Once the `.env` file is properly configured, the application will use the specified credentials for S3 bucket, Pexels and Wandb.
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
## Install FFMPEG
|
| 110 |
+
|
| 111 |
+
FFMPEG is required for processing video files. Install it using the following commands:
|
| 112 |
+
```bash
|
| 113 |
+
sudo apt update
|
| 114 |
+
sudo apt install ffmpeg -y
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
For more details, refer to the [FFMPEG Documentation](https://www.ffmpeg.org/download.html#build-linux).
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## Install VMAF
|
| 122 |
+
|
| 123 |
+
To enable video quality validation, install **VMAF** by following the steps below to set up a clean virtual environment, install dependencies, and compile the tool.
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
Clone the VMAF repository into the working root directory of your `vidaio-subnet` package. If the `vidaio-subnet` virtual environment is currently active, deactivate it first:
|
| 128 |
+
|
| 129 |
+
```bash
|
| 130 |
+
git clone https://github.com/vidAio-subnet/vmaf.git
|
| 131 |
+
cd vmaf
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
---
|
| 135 |
+
|
| 136 |
+
### Step 1: Set Up a Virtual Environment in VMAF directory
|
| 137 |
+
|
| 138 |
+
1. Install `venv` if it’s not already installed:
|
| 139 |
+
```bash
|
| 140 |
+
python3 -m venv vmaf-venv
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
2. Activate the virtual environment:
|
| 144 |
+
```bash
|
| 145 |
+
source vmaf-venv/bin/activate
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
### Step 2: Install Dependencies
|
| 151 |
+
|
| 152 |
+
1. Install `meson`:
|
| 153 |
+
```bash
|
| 154 |
+
pip install meson
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
2. Install system dependencies:
|
| 158 |
+
```bash
|
| 159 |
+
sudo apt-get update
|
| 160 |
+
sudo apt-get install nasm ninja-build doxygen xxd
|
| 161 |
+
```
|
| 162 |
+
For Ninja, verify whether the package name is `ninja` or `ninja-build` before running the install command.
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
### Step 3: Compile VMAF
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
1. Set up the build environment:
|
| 170 |
+
```bash
|
| 171 |
+
cd libvmaf
|
| 172 |
+
meson setup build --buildtype release -Denable_avx512=true
|
| 173 |
+
```
|
| 174 |
+
|
| 175 |
+
2. Optional flags:
|
| 176 |
+
- Use `-Denable_float=true` to enable floating-point feature extractors.
|
| 177 |
+
- Use `-Denable_avx512=true` to enable AVX-512 SIMD instructions for faster processing on supported CPUs.
|
| 178 |
+
- Use `-Denable_cuda=true` to build with CUDA support (requires `nvcc` and CUDA >= 11).
|
| 179 |
+
**Note:** To enable CUDA successfully, ensure `nvcc` and the CUDA driver are installed. Refer to the [CUDA and NVCC setup guide](miner_setup.md#step-2-install-cuda-and-nvcc).
|
| 180 |
+
- Use `-Denable_nvtx=true` to enable NVTX marker support for profiling with Nsight Systems.
|
| 181 |
+
- **Recommendation:**
|
| 182 |
+
We recommend adding `-Denable_avx512=true` to enhance validation speed. If CUDA is available, include the flag `-Denable_cuda=true` But At present, VMAF does not include support for CUDA integration.
|
| 183 |
+
|
| 184 |
+
3. Build the project:
|
| 185 |
+
```bash
|
| 186 |
+
ninja -vC build
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
### Step 4: Test the Build
|
| 192 |
+
|
| 193 |
+
Run tests to verify the build:
|
| 194 |
+
```bash
|
| 195 |
+
ninja -vC build test
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
---
|
| 199 |
+
|
| 200 |
+
### Step 5: Install VMAF
|
| 201 |
+
|
| 202 |
+
Install the library, headers, and the command-line tool:
|
| 203 |
+
```bash
|
| 204 |
+
ninja -vC build install
|
| 205 |
+
```
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
### Step 6: Generate Documentation
|
| 210 |
+
|
| 211 |
+
Generate HTML documentation:
|
| 212 |
+
```bash
|
| 213 |
+
ninja -vC build doc/html
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
### Step 7: Deactivate vmaf-venv, activate project venv
|
| 217 |
+
|
| 218 |
+
```bash
|
| 219 |
+
deactivate
|
| 220 |
+
cd ..
|
| 221 |
+
cd ..
|
| 222 |
+
source venv/bin/activate
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
## Running the Validator with PM2
|
| 226 |
+
|
| 227 |
+
To run the validator, use the following command:
|
| 228 |
+
|
| 229 |
+
```bash
|
| 230 |
+
pm2 start run.sh --name vidaio_v_autoupdater -- --wallet.name [Your_Wallet_Name] --wallet.hotkey [Your_Hotkey_Name] --subtensor.network finney --netuid 85 --axon.port [port] --logging.debug
|
| 231 |
+
```
|
| 232 |
+
|
| 233 |
+
### Parameters:
|
| 234 |
+
- **`--wallet.name`**: Replace `[Your_Wallet_Name]` with your wallet name.
|
| 235 |
+
- **`--wallet.hotkey`**: Replace `[Your_Hotkey_Name]` with your hotkey name.
|
| 236 |
+
- **`--subtensor.network`**: Specify the target network (e.g., `finney`).
|
| 237 |
+
- **`--netuid`**: Specify the network UID (e.g., `85`).
|
| 238 |
+
- **`--axon.port`**: Replace `[port]` with the desired port number.
|
| 239 |
+
- **`--logging.debug`**: Enables debug-level logging for detailed output.
|
| 240 |
+
|
| 241 |
+
---
|
iqf_scores.csv
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,input_size,output_size,final_compression,final_score,psnr_values,scene_type,luma_qp,metrics_resolution_width,metrics_resolution_height,metrics_frame_rate,quality_noise_level,quality_sharpness,quality_contrast,quality_brightness,quality_color_saturation,quality_motion_blur,quality_compression_artifacts,quality_text_content,quality_edge_density,quality_temporal_consistency,chroma,preset
|
| 2 |
+
04d651c3-3b08-4de9-befe-af12b3b3555b.mp4,11.63755702972412,2.962747573852539,0.2545850100914842,93.50666666666666,12.372051285756838,Glacier,36,1928,952,25.0,0.1429923623800277,0.0728510618209838,0.126477791035855,0.3213473471114912,0.3275197079482676,0.0515626634471215,0.0160338471954067,0.13787563780699,0.092448416088427,0.989902800881153,1,6
|
| 3 |
+
07484eba-7a60-4754-ba0b-f9c0744af11b.mp4,50.56832885742188,6.884061813354492,0.1361338602421329,88.56666666666666,20.0522675981826,Sea,36,3744,2320,29.97,0.0958104953169822,0.0445181988179683,0.1467628849442237,0.3145919473712218,0.5450120102012216,0.0460680767388741,0.0104968805486957,0.3294798113763631,0.0776948865310934,0.9906222568234484,2,8
|
| 4 |
+
0cb873d6-4c3a-4ced-84e9-87939b08565d.mp4,19.441584587097168,1.3194904327392578,0.0678694901039583,94.21666666666664,14.191413000600472,Street,36,3928,2176,25.0,0.0367702804505825,0.0250370651483535,0.1093549868154569,0.4941628043122025,0.2712272513396657,0.013138959918234,0.0052625991714497,0.0428705516702607,0.0243741084933209,0.99460809731599,1,7
|
| 5 |
+
0d0496e7-fe16-443b-9c16-369aa386bb4e.mp4,91.49424362182616,35.15753650665283,0.3842595459007206,89.8,19.181592760557905,Sea,36,3824,2080,29.97,0.4522957503795624,0.1822116822004318,0.1255781912376141,0.3903664236722475,0.3277484580793212,0.0777180727657976,0.0146979351217548,0.1534577499731789,0.1063530938204055,0.9972791267899178,2,6
|
| 6 |
+
0e4ce3ab-c7c1-4dd6-9d9d-983eabf8acf3.mp4,14.362677574157717,2.4586334228515625,0.1711821079431108,86.85000000000001,22.136717544720863,Glacier,36,2176,1312,29.97,0.0441711656749248,0.0190866943448781,0.2714131776661668,0.5323091666051284,0.1473271014321708,0.0106787879976685,0.0052575784890602,0.5868236919386657,0.0213027581935975,0.9938409873174072,1,8
|
| 7 |
+
10e3a5e7-dda1-496d-84bb-f826e6567d82.mp4,12.642633438110352,2.115780830383301,0.1673528573568718,86.82666666666667,21.79987671919208,Sea,36,2024,1160,29.97,0.0658735483884811,0.027121203020215,0.2075980188991426,0.5772056769075355,0.1538377535831014,0.0165804881650084,0.0074098287150263,0.2507814274680841,0.0274054024124301,0.99776000034742,2,8
|
| 8 |
+
12cb34d6-c1d8-4b85-8f0e-60afaf26ffc9.mp4,97.8766794204712,32.654263496398926,0.3336265971602751,99.62,11.553677315658312,Sea,36,3992,2128,29.97,0.1492599844932556,0.0777132287621498,0.1066155836718416,0.4059681511346936,0.3941002960882696,0.0566694557661689,0.016210103717943,0.1896969848217739,0.0992825641885274,0.9999994889713084,2,6
|
| 9 |
+
12dd2d2b-24f3-4c31-b873-fa4e53803778.mp4,13.259980201721191,1.2535648345947266,0.0945374589949997,92.54,16.066609960586902,Buildings,36,3720,2000,24.0,0.0126855373382568,0.0054733753204345,0.1402558005829288,0.3471829562864572,0.7360188973223698,0.001899417562724,0.0039290150161832,0.0334341397849462,0.0028954301075268,0.9861516260805396,1,7
|
| 10 |
+
221cab7a-eb1a-4b14-9568-9ac4a4ec9330.mp4,30.4854040145874,8.698417663574219,0.2853305686685992,93.10666666666668,12.963095414146787,Sea,36,1672,1096,29.97,0.9161150455474854,0.379181295633316,0.0543770877669747,0.5162277035227647,0.293133004869604,0.1587936850254368,0.0282400405655304,0.3147559197429539,0.2448316845946984,0.993860990900394,2,6
|
| 11 |
+
249c9213-36ad-4280-a900-306b96685c0e.mp4,56.18430137634277,6.00376033782959,0.1068583250259574,85.86,26.456385705685307,Sea,36,4352,2512,25.0,0.0763325467705726,0.0321722514927387,0.1179865907502876,0.4856625565126684,0.1818267550762109,0.0330283511692893,0.0083340853452682,0.2281288784423005,0.0552171726448342,0.9998370150268332,2,8
|
| 12 |
+
2884fdc7-6b49-4008-ae33-54956784cf22.mp4,115.50566577911376,39.40678596496582,0.3411675583111658,99.77666666666666,10.641904721237973,Sea,36,4136,2528,29.97,0.1351182460784912,0.0738693550229072,0.0741262606518029,0.4363434005579151,0.3657297264831183,0.0492047099564184,0.013960901958247,0.0938197538949962,0.0921825936359963,0.9924948222041552,2,6
|
| 13 |
+
2efd6ba4-b102-4009-b197-da0cdaa634dc.mp4,24.736478805541992,2.1472091674804688,0.0868033475726305,96.65666666666668,14.345898901049,Street,36,4176,2264,25.0,0.0174604374915361,0.0072254240512847,0.1744482783278272,0.3013473596598065,0.2914109880184468,0.0007968369932623,0.0041565479865918,0.7164728393557441,0.0004970667824215,0.945495845282456,1,7
|
| 14 |
+
31f7bc2c-838f-43c2-9cf8-745cc9ce27ab.mp4,22.826221466064453,1.773118019104004,0.0776789983283077,91.56666666666666,17.673464012040057,Glacier,36,4072,1984,25.0,0.035806518048048,0.0185190923511981,0.1413763511584968,0.45802809033771,0.3093696310212299,0.0120091791965059,0.0057176739598313,0.4397072453894416,0.0214490976931364,0.9821693564696936,2,7
|
| 15 |
+
329f1c4c-f5df-453d-9cfc-6f2cbef5142b.mp4,9.611075401306152,1.6033363342285156,0.1668217413017716,91.58,14.919262283403194,Buildings,36,3840,2160,29.97002997002997,0.0132886199280619,0.009028784930706,0.1450372010687532,0.3001861801079742,0.5296609669395022,0.011812829539609,0.0035800460415581,0.2275868055555555,0.0218736135223765,0.9926868678702192,1,7
|
| 16 |
+
34e7d8b9-5fd5-44d6-ba9c-194d4e2078ae.mp4,8.409784317016602,1.186915397644043,0.1411350580350085,93.26666666666668,17.535968238489765,Street,36,1864,1112,25.0,0.0701295137405395,0.046890240162611,0.1179904369005827,0.4931510383715244,0.3031222125304049,0.0228256772264591,0.0097284382209181,0.2783456711026029,0.0409182310803717,0.9759738649373356,2,7
|
| 17 |
+
39c98272-d26f-4139-afbf-d870b7553fab.mp4,11.697440147399902,2.2339115142822266,0.1909743915021252,85.91333333333334,21.82086911294481,Glacier,36,1912,1152,29.97,0.0379447676241397,0.0166470278054475,0.2989692671433983,0.568221517441918,0.165857213229027,0.0145771588795908,0.0056536531386276,0.511477219897722,0.0302196380317294,0.9934711756986034,1,8
|
| 18 |
+
40949702-a809-4464-9d14-481e76abd967.mp4,11.222305297851562,4.060033798217773,0.3617825117442707,95.78666666666668,19.67346395047992,Street,36,1992,1080,25.0,0.1722472161054611,0.0929764881730079,0.1250032341526129,0.3511127182416424,0.3858102265027187,0.0427754226783677,0.0143549318114916,0.1648349571124002,0.0433976647330061,0.999515551312002,1,6
|
| 19 |
+
424df265-1080-4edf-8cfc-b05f62acfffc.mp4,93.02153778076172,38.198049545288086,0.4106366166007205,90.03666666666668,18.992862809167303,Sea,36,3744,2176,29.97,0.5003300309181213,0.2049688547849655,0.0901389832452703,0.4063670264387649,0.2718818422945892,0.0835086546971887,0.0158878316481908,0.2587865257614798,0.1137096314888134,0.999358035236232,2,6
|
| 20 |
+
59bb4aa2-724b-490a-944c-eaae28496192.mp4,82.74820232391357,38.806517601013184,0.4689711257908306,95.3,17.10664600652759,Sea,36,3632,2096,24.0,0.3662587702274322,0.166818082332611,0.2566843861128125,0.2162887687259836,0.4824837299832892,0.0542928510077232,0.0178386699408292,0.5918705022362714,0.0900308065289033,0.9984225156964288,2,6
|
| 21 |
+
5e800829-24b2-4ca7-bac4-89a02199fbec.mp4,9.155067443847656,1.3330106735229492,0.1456035885807431,90.11333333333334,18.971216361292488,Buildings,36,1928,1144,29.97,0.0794639736413955,0.0400956831872463,0.1179491268665797,0.5208322161695057,0.2232629198900251,0.0302041924189227,0.0122872879728674,0.263849394036116,0.0527989256594028,0.9977240688080936,1,7
|
| 22 |
+
6084559b-4f79-480c-ae41-bdb3d0a6987c.mp4,8.81439208984375,0.454263687133789,0.0515365872658657,94.17,28.143217609294314,Buildings,36,3864,2176,24.0,0.0228502452373504,0.010489453561604,0.2066807213167505,0.2309552700244988,0.2557928800116594,0.0037469981199001,0.0021716706299533,0.6112707197915398,0.0058192349630069,0.9988106814283684,1,8
|
| 23 |
+
61a7d453-d120-440f-abfe-fa05714179b4.mp4,20.49268341064453,2.590888023376465,0.1264299053207778,92.07,16.241323427262568,Buildings,36,3456,2104,24.0,0.0289852488785982,0.0140899522230029,0.2357989120564686,0.193935172888864,0.6341521050899118,0.0047159034232267,0.0054534943774342,0.6904527457987139,0.0081701328378045,0.9847355475512976,1,7
|
| 24 |
+
64e41231-4092-46c3-906d-5170de857162.mp4,28.37565803527832,4.181161880493164,0.1473503055081539,88.35000000000001,18.958802985591056,Mountain,36,2280,1264,23.976,0.3514604568481445,0.1335129588842392,0.1668962952215819,0.2662084490876815,0.4625996798249243,0.1331960406025612,0.0211052832504113,0.575414307128581,0.2066374153342216,0.9915836759698328,1,6
|
| 25 |
+
691de276-7b47-4015-bc54-da4343889084.mp4,21.35616779327393,2.661280632019043,0.1246141469658806,92.38666666666668,19.55607154395117,Glacier,36,4208,2040,25.0,0.0283349398523569,0.0150849567726254,0.147305984712665,0.4657045179866474,0.478251971736034,0.0079452614379084,0.0047814832068979,0.5164016097567037,0.0134434643629314,0.9986614345096868,2,7
|
| 26 |
+
733704b4-12f7-4977-b920-6896bc9ca77c.mp4,42.62840366363525,4.617931365966797,0.1083299154808882,84.61,26.44615321681865,Sea,36,3632,2112,25.0,0.0834942460060119,0.0357695259153842,0.1235101446790105,0.5205489246068594,0.2374145989757425,0.0421646166371868,0.0080157189319531,0.1772868761958795,0.0679565478574289,0.9985238316412702,2,8
|
| 27 |
+
81e87893-da5e-4db2-afd1-b1bb11a03de7.mp4,11.973058700561523,2.250288963317871,0.1879460394871643,94.15666666666664,12.184221110468258,Sea,36,1848,1080,29.97,0.158597245812416,0.0801888853311538,0.0707714670215206,0.4440622167310621,0.4276124882238716,0.0134389530222863,0.0096513582393527,0.2976868553257442,0.0195476591309924,0.9545209309874344,2,6
|
| 28 |
+
8b6d7852-7f21-47df-aa03-fc8c0f29e6d3.mp4,36.639710426330566,6.357483863830566,0.1735134855012899,98.35666666666668,11.7227002191702,Buildings,36,4216,2472,25.0,0.038616944104433,0.0222245398908853,0.1535103188816618,0.5920864945591565,0.2681580551402431,0.0082668454687439,0.0051134812335173,0.1357015141373388,0.0153606061513236,0.9985361279804812,1,7
|
| 29 |
+
8f0464f5-2e81-4d50-b4bb-0667fadd55b6.mp4,34.017616271972656,16.64235496520996,0.4892275470495476,97.63333333333333,14.750361283262205,Street,36,1944,1000,30.0,0.7986264824867249,0.3549840152263641,0.1570230198475594,0.5378053457597031,0.2279299167540815,0.1251349451303155,0.0310485574106375,0.3349941700960219,0.180920524691358,0.986462531267651,1,6
|
| 30 |
+
93495912-fd6c-435a-9fcb-c9cc978c122e.mp4,14.999189376831056,2.030162811279297,0.1353515020228525,90.58,15.28308285240923,Sea,36,2008,1248,29.545,0.1255028247833252,0.0577357523143291,0.1450512624336307,0.5325459562996547,0.2646494876660611,0.0453523778816358,0.0071958565774063,0.0897749812714952,0.0797762475738073,0.993942635378546,2,6
|
| 31 |
+
9a049c4c-bb40-4692-8b95-cb2ca94d339f.mp4,7.705256462097168,0.7953510284423828,0.1032218761769688,89.89333333333333,32.48977789954205,Sea,36,2072,1168,29.97,0.0590476691722869,0.0255420710891485,0.0779400273458398,0.4569901856213298,0.4663660800924096,0.0096129795402055,0.0046418259541193,0.0497310850478658,0.0130963813005765,0.9963151955987876,2,8
|
| 32 |
+
9d860f37-78b5-4e91-bbff-91bbfaaaa32a.mp4,24.54138946533203,3.542244911193848,0.1443375859462944,86.88999999999999,18.815549968769943,Mountain,36,2176,1128,23.976,0.3589254915714264,0.1361661404371261,0.1674924357608167,0.2623429547776905,0.4926483928925658,0.1339137300531915,0.0214552866915861,0.4039163537755528,0.2070070090868794,0.991671204099078,1,6
|
| 33 |
+
a3fa72da-8a4e-466c-9a11-fc2a67863b9e.mp4,66.18983554840088,15.258377075195312,0.2305244747743438,98.00666666666666,11.54028731617105,Sea,36,4064,2328,29.97,0.0872727632522583,0.0455792434513568,0.0999884181943172,0.4212085293801522,0.3973935582882845,0.0268627222177124,0.0114057408645749,0.0736418196597848,0.0521458532043997,0.9822594713112536,2,6
|
| 34 |
+
a508a47e-295b-4be0-ab3f-4b6478dacc65.mp4,150.44969272613525,62.874778747558594,0.4179123108081719,92.61,15.8732601360474,Sea,36,4360,2352,59.94,0.3012926876544952,0.1273589581251144,0.1922401876107427,0.5702624508009116,0.1724064290599012,0.085398268634671,0.0167182801912228,0.3027350657388337,0.1516859553454409,0.9744751753912574,2,6
|
| 35 |
+
a5f32e7e-a5f3-4a7e-b7fd-dc0fb551e243.mp4,27.75857830047608,8.89590835571289,0.3204742065468217,93.04333333333334,18.556904219657586,Sea,36,1896,1120,29.97,0.4847494661808014,0.2182989865541458,0.1002119651271572,0.4154083440689919,0.3129233998451701,0.1315088469459513,0.0165209782620271,0.2850421313039984,0.1916254614978902,0.9980037344579182,2,6
|
| 36 |
+
a75909eb-460b-4eb8-ab69-1f81413fde39.mp4,15.16309642791748,1.656632423400879,0.1092542299177611,91.09333333333332,17.147321929463487,Glacier,36,3504,2216,24.0,0.0099504310637712,0.0039873127825558,0.1837182655713437,0.320486532314655,0.7251103568436426,0.0009946943908697,0.0028258475164572,0.0432883306133887,0.0012447738943013,0.974405000500192,1,8
|
| 37 |
+
a8fb16e1-2992-4ab7-95b7-47d48938f465.mp4,56.77720260620117,6.564875602722168,0.1156252034510265,87.28666666666668,25.996976327787028,Sea,36,4416,2512,25.0,0.0753649845719337,0.0320221297442913,0.1090954151336163,0.5430451944241572,0.1567155809969492,0.0323188429836302,0.0074781634223957,0.2131279520139081,0.0550013467962475,0.9987616523664528,2,8
|
| 38 |
+
af18139c-acaf-4c43-977a-9a86fb3f3602.mp4,15.229940414428713,1.547590255737305,0.1016149908420608,94.05666666666666,14.702759137373825,Glacier,36,3920,2208,24.0,0.0150572964921593,0.006208827253431,0.1435621928273399,0.3124642683160221,0.8005967936691585,0.0014279398353544,0.003335137773926,0.0207769520851819,0.002143411712511,0.988551496232551,1,7
|
| 39 |
+
b2b4e988-b71d-4d6f-a5dd-8db222348faa.mp4,8.236392974853516,1.106584548950195,0.1343530538584914,91.45666666666666,18.26996100063993,Mountain,36,1992,1232,25.0,0.0576150827109813,0.0262715872377157,0.0962681090485671,0.5841186732542597,0.1946375993959711,0.0105294011544011,0.0063949679024517,0.1362384060049722,0.0184834304751473,0.989757914392482,1,7
|
| 40 |
+
b872dec9-a527-443e-807b-8a650fb44aa5.mp4,31.921814918518063,4.167214393615723,0.1305444068344088,90.35,20.685591731501987,Glacier,36,3672,2200,29.97,0.0734859108924865,0.0299688708037138,0.122406591632788,0.3481631003097746,0.4225727329685422,0.0204849970291146,0.0085126602401336,0.083881626724764,0.0360843731431966,0.9853751111654804,1,8
|
| 41 |
+
bb85c24e-dfb2-4e74-a538-7b88444ba7a2.mp4,7.403351783752441,1.078202247619629,0.1456370410475259,86.96666666666668,31.55410760665986,Sea,36,2064,1136,29.97,0.0316026173532009,0.0123316012322902,0.3639183350001875,0.4339056100931475,0.6477346277180488,0.0062771249590566,0.0054633522716661,0.8203375209265932,0.0106365238426684,0.999820633741268,2,8
|
| 42 |
+
bd22f174-e591-4d7a-a5fa-40447e18191e.mp4,14.803239822387695,2.936840057373047,0.1983917096939491,88.06666666666666,19.394910594585504,Mountain,36,1840,1008,59.94,0.172185018658638,0.0587177462875843,0.0989175467097531,0.413866736610103,0.3300058145238772,0.0461997857717966,0.0112428820381561,0.0506351363008971,0.0771057086783988,0.9926592185787358,2,6
|
| 43 |
+
c6b21693-9149-46a7-8062-b4e36d861374.mp4,6.766033172607422,0.8436651229858398,0.1246912483966904,91.64333333333332,34.748478048384115,Mountain,36,1800,1040,25.0,0.1755229383707046,0.0655823424458503,0.1886238112680818,0.3749341251885369,0.472089366515837,0.0616330128205128,0.01280546374619,0.3262396723646723,0.0449030448717948,0.9997276437070556,2,7
|
| 44 |
+
d31d550a-bebb-4124-bd43-8b60d0e18de8.mp4,17.65333652496338,1.5875320434570312,0.0899281584085886,92.79,18.366078180220185,Street,36,2256,1208,23.976,0.321790337562561,0.1782573610544204,0.1331180156782398,0.3457849007320899,0.3365648953207698,0.0981379798584691,0.0206291632105906,0.4263416271350961,0.1503226495350147,0.9993916711217388,1,6
|
| 45 |
+
d863f7e4-dceb-4f47-8a78-dd6d1e4e880d.mp4,29.070735931396484,6.318779945373535,0.2173587885867462,85.97333333333334,20.767000095983462,Sea,36,2200,1240,59.94,0.1871851682662964,0.0643177106976509,0.1103539881428786,0.3952507633258582,0.3379617215801276,0.0673505620723362,0.0123686343431472,0.2884670087976539,0.10835392228739,0.9977341311597951,2,6
|
| 46 |
+
dd477309-fa8a-4317-94ff-d8b54bc3cad1.mp4,21.595118522644043,2.3091840744018555,0.1069308358729547,93.02666666666666,12.38130718901219,Buildings,36,3944,2168,25.0,0.0399475842714309,0.0225663352757692,0.0982543567243773,0.4887902318051936,0.4495643316600509,0.0099232115546307,0.0066649932414293,0.1681051635567065,0.0187829684775042,0.964893394653431,2,7
|
| 47 |
+
e0ece203-f76b-40b9-8285-cb9fd999c5c4.mp4,48.40852928161621,4.847820281982422,0.1001439282276118,95.54333333333334,15.267127113506389,Buildings,36,3912,2192,24.0,0.1240128055214881,0.0678207352757453,0.1008983220628024,0.3571532536652205,0.3761971421338625,0.0539492387886296,0.012217029929161,0.2409559114385085,0.0994769276267669,0.9933512871772088,1,6
|
| 48 |
+
e199cb9b-752f-4aa1-a4b9-1885fa53a026.mp4,67.37089347839355,9.13538932800293,0.1355984588646241,93.14,18.97621105248905,Buildings,36,3704,2192,24.0,0.3928792476654053,0.1705749481916427,0.1482475339820311,0.3366086798822518,0.1580182770650997,0.0791778993446947,0.0160163554052511,0.3772218204295481,0.1360922079700777,0.9976828925927232,1,6
|
| 49 |
+
e7f5193f-e47b-4376-977d-feb98314b110.mp4,18.898674964904785,2.464480400085449,0.1304049307510732,98.17,13.702267426394744,Street,36,1976,1112,29.97,0.2872275412082672,0.1347596794366836,0.1224473220403082,0.450834061852685,0.4047301679970621,0.0687426273556053,0.0160518120974302,0.2834111253507316,0.1179757358081146,0.989530204248128,2,6
|
| 50 |
+
f11482e6-3485-46a8-9ade-92b46a803067.mp4,17.065518379211426,3.2750844955444336,0.1919123945003639,88.09666666666665,23.87321348719044,Sea,36,2016,1184,30.0,0.131056860089302,0.0564496926963329,0.2015653390023331,0.5128782488502158,0.2638884277747635,0.0529823908730158,0.0086417753870288,0.5257810824217074,0.0851019127386314,0.999383320097934,2,7
|
| 51 |
+
f4f67a32-a8f5-43ac-93fb-6a41a45696de.mp4,48.70376110076904,11.58397102355957,0.2378455125794517,94.2,27.01278655262587,Street,36,3920,2072,24.0,0.5899763107299805,0.3269930779933929,0.1248712283772103,0.4635716519555055,0.3184764832573956,0.1427197012975074,0.030318242808183,0.2410834121293304,0.107225469821133,0.9999854710223858,2,6
|
| 52 |
+
fcd1e637-3ac0-49af-920e-6bc5ae2e32cf.mp4,46.41104698181152,11.405869483947754,0.245757642322047,90.84,23.09361398873996,Sea,36,3696,2256,25.0,0.1243470683693885,0.0575859062373638,0.4032672850400358,0.0772454033864781,0.3632161264328427,0.0128380995236048,0.0054249467017749,0.6764255555811407,0.0217236959258235,0.9999686643274333,2,8
|
| 53 |
+
fd4e4b88-600d-4a77-acdc-a1315ce301cc.mp4,20.68675708770752,1.9913969039916992,0.09626433449905136,92.33999999999999,14.531413968873949,Glacier,36,4568,2672,24.0,0.0160443242639303,0.0065641249530017,0.1358391283293425,0.3137757971488773,0.7292011663634032,0.0011647567387117,0.0032998408811787,0.1209636331540072,0.0016134270425873,0.9884601788362084,1,7
|
iqf_scores_1.csv
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,ori_luma_qp,ori_chroma,ori_preset,brightness,contrast,colorfulness,sharpness,quality_factor,mos_zscore,luma_qp,chroma,preset,final_scores
|
| 2 |
+
04d651c3-3b08-4de9-befe-af12b3b3555b.mp4,32,0,4,1.8469307366129406e-16,0.9999998410542806,1.0,0.9999999602635702,0.298225900199812,0.0,35,5,10,90.31333333333333
|
| 3 |
+
07484eba-7a60-4754-ba0b-f9c0744af11b.mp4,31,-1,4,0.6979172627131144,0.6313334306081136,0.6084306836128235,0.6348755955696106,0.6509576241175333,0.7038862506548563,38,0,10,84.82666666666667
|
| 4 |
+
0cb873d6-4c3a-4ced-84e9-87939b08565d.mp4,35,1,4,2.1057123452742498e-07,0.9972970485687256,0.9999999602635702,0.9853918751080832,0.3318715903168057,6.486347245594561e-25,36,6,10,92.60666666666667
|
| 5 |
+
0d0496e7-fe16-443b-9c16-369aa386bb4e.mp4,31,-1,4,0.5955032904942831,0.7397701740264893,0.5834726889928182,0.7210836410522461,0.65736456712087,0.4505663216114044,37,0,10,86.69333333333333
|
| 6 |
+
0e4ce3ab-c7c1-4dd6-9d9d-983eabf8acf3.mp4,31,-1,4,0.6425169706344604,0.6254289746284485,0.5407780408859253,0.6723130941390991,0.7194178899129232,0.6874635815620422,37,-1,10,81.39333333333333
|
| 7 |
+
10e3a5e7-dda1-496d-84bb-f826e6567d82.mp4,31,-1,4,0.666383703549703,0.644948422908783,0.5396574338277181,0.6548968553543091,0.6909757057825724,0.6799997289975485,37,-1,10,80.31333333333335
|
| 8 |
+
12cb34d6-c1d8-4b85-8f0e-60afaf26ffc9.mp4,31,-1,4,0.559302568435669,0.6554734110832214,0.517133355140686,0.6597744027773539,0.6716094811757406,0.6479589343070984,37,-1,10,99.25333333333333
|
| 9 |
+
12dd2d2b-24f3-4c31-b873-fa4e53803778.mp4,33,2,4,0.0304905549855902,0.8879064122835795,0.9823326071103414,0.992975890636444,0.0004040396706827,2.798745185113234e-08,35,7,10,90.84666666666668
|
| 10 |
+
221cab7a-eb1a-4b14-9568-9ac4a4ec9330.mp4,31,-1,4,0.6206316550572714,0.6937900980313619,0.5681640307108561,0.6865283250808716,0.6309727231661478,0.5269437432289124,37,0,10,88.66666666666667
|
| 11 |
+
249c9213-36ad-4280-a900-306b96685c0e.mp4,31,-1,4,0.6744417746861776,0.6287276744842529,0.5382514794667562,0.6795710523923238,0.7059144775072733,0.6740560928980509,37,-1,10,80.68666666666667
|
| 12 |
+
2884fdc7-6b49-4008-ae33-54956784cf22.mp4,31,-1,4,0.5739724636077881,0.6645207007726034,0.5748539765675863,0.6702662507692972,0.6798134446144104,0.6453063488006592,37,0,10,99.71333333333332
|
| 13 |
+
2efd6ba4-b102-4009-b197-da0cdaa634dc.mp4,33,0,4,0.2123574561207135,0.923816998799642,0.5404039584100246,0.557942457186679,0.5570492148494531,0.1948199073473612,42,0,10,94.18333333333334
|
| 14 |
+
31f7bc2c-838f-43c2-9cf8-745cc9ce27ab.mp4,32,0,4,0.2436689063906669,0.865463117758433,0.5987633650268739,0.663530599674465,0.0083957752296021,0.0033863165364441,39,1,11,87.52999999999999
|
| 15 |
+
329f1c4c-f5df-453d-9cfc-6f2cbef5142b.mp4,35,1,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,41,2,10,84.48
|
| 16 |
+
34e7d8b9-5fd5-44d6-ba9c-194d4e2078ae.mp4,33,0,4,0.6085565884908041,0.7023536364237467,0.5811308026313782,0.6709411342938741,0.6804569562276205,0.6665542125701904,39,1,10,88.88666666666666
|
| 17 |
+
39c98272-d26f-4139-afbf-d870b7553fab.mp4,31,-1,4,0.6213184396425883,0.6266296108563741,0.5339837074279785,0.6631546020507812,0.7125220696131388,0.6837653716405233,37,-1,10,78.45333333333333
|
| 18 |
+
40949702-a809-4464-9d14-481e76abd967.mp4,33,0,4,3.0931066325599755e-11,0.6665612919865964,1.0,0.5694492856661543,0.013828640182813,4.3167463616559045e-17,43,5,11,88.24666666666667
|
| 19 |
+
424df265-1080-4edf-8cfc-b05f62acfffc.mp4,31,-1,4,0.572777271270752,0.8049134612083435,0.5622189939022064,0.7238964438438416,0.6895942687988281,0.4115677277247111,36,0,10,87.91000000000001
|
| 20 |
+
4df1a23d-73f2-4cab-b43c-5538b566a458.mp4,35,1,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,41,2,10,84.48
|
| 21 |
+
59bb4aa2-724b-490a-944c-eaae28496192.mp4,31,-1,4,9.296274529428212e-07,0.3862022735666339,0.0247434326447546,1.0,1.0,0.0171932382619767,35,-6,10,94.98666666666668
|
| 22 |
+
5e800829-24b2-4ca7-bac4-89a02199fbec.mp4,35,1,4,0.6375203331311544,0.6609386603037516,0.5363780458768209,0.6825437943140665,0.7010063727696737,0.6790463924407959,41,1,10,82.86
|
| 23 |
+
5fe0ff99-37a3-4760-81a4-f441876cb867.mp4,35,1,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,41,2,10,84.48
|
| 24 |
+
6084559b-4f79-480c-ae41-bdb3d0a6987c.mp4,33,2,4,0.5323069393634796,0.7520566980044047,0.5722926259040833,0.693462073802948,0.5514254768689474,0.2662951275706291,39,3,10,91.99666666666667
|
| 25 |
+
61a7d453-d120-440f-abfe-fa05714179b4.mp4,35,1,4,0.5853416124979655,0.6666666666666666,1.0,0.3333347989194711,2.981454431780764e-25,0.0,50,6,11,79.58666666666667
|
| 26 |
+
64e41231-4092-46c3-906d-5170de857162.mp4,30,0,4,0.745102326075236,0.670194149017334,0.5261250138282776,0.7535956104596456,0.707586407661438,0.3179372052351634,35,0,10,84.63666666666666
|
| 27 |
+
691de276-7b47-4015-bc54-da4343889084.mp4,32,0,4,0.6291013558705648,0.647834857304891,0.6641747156778971,0.6670735081036886,0.7004674077033997,0.6992351412773132,38,2,10,88.96666666666665
|
| 28 |
+
733704b4-12f7-4977-b920-6896bc9ca77c.mp4,31,-1,4,0.6398445963859558,0.6260692278544108,0.5751756628354391,0.6669502059618632,0.7081162532170614,0.6691677371660868,37,0,10,79.04333333333334
|
| 29 |
+
81e87893-da5e-4db2-afd1-b1bb11a03de7.mp4,33,2,4,7.679543605343628e-09,1.0,1.0,0.3358941231754215,4.8890841564280026e-21,4.577665144872662e-36,47,7,11,78.89666666666666
|
| 30 |
+
890c5911-8ee1-4faa-a801-c605f0dde8e0.mp4,35,1,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,41,2,10,84.48
|
| 31 |
+
8b6d7852-7f21-47df-aa03-fc8c0f29e6d3.mp4,35,1,4,0.6386719544728597,0.6828855474789938,0.5790578722953796,0.6756150126457214,0.7062546610832214,0.6745621760686239,41,2,10,96.22000000000001
|
| 32 |
+
8f0464f5-2e81-4d50-b4bb-0667fadd55b6.mp4,33,0,4,0.6737418174743652,0.6781927148501078,0.5551733573277792,0.6866800983746847,0.6682468851407369,0.6677144169807434,39,1,10,93.66000000000001
|
| 33 |
+
93495912-fd6c-435a-9fcb-c9cc978c122e.mp4,31,-1,4,0.3303557277324671,0.572416496075069,0.8744631012280782,0.5750492256289969,0.245182732741038,0.1620057622591654,41,3,10,82.59333333333333
|
| 34 |
+
9a049c4c-bb40-4692-8b95-cb2ca94d339f.mp4,31,-1,4,0.5415121912956238,0.6919364531834921,0.5953385432561239,0.7154134511947632,0.6035712758700053,0.5871815880139669,36,0,10,86.02666666666669
|
| 35 |
+
9d860f37-78b5-4e91-bbff-91bbfaaaa32a.mp4,30,0,4,0.5807816584904989,0.6518141428629557,0.5270594159762064,0.6739723483721415,0.6623684565226237,0.5438634157180786,36,0,10,82.79
|
| 36 |
+
a3fa72da-8a4e-466c-9a11-fc2a67863b9e.mp4,31,-1,4,0.2128963904734471,0.8745509584744772,0.8768628239631653,0.579653590063875,0.24392955436218,0.2292507886886597,39,3,10,95.11666666666667
|
| 37 |
+
a508a47e-295b-4be0-ab3f-4b6478dacc65.mp4,31,-1,4,0.626129150390625,0.6703311999638876,0.562980075677236,0.6703017155329386,0.6834884285926819,0.6630960901578268,37,0,10,88.67
|
| 38 |
+
a5f32e7e-a5f3-4a7e-b7fd-dc0fb551e243.mp4,31,-1,4,0.6408935189247131,0.7019673387209574,0.5994003017743429,0.701171875,0.6946845253308614,0.6745195984840393,36,0,10,90.31333333333333
|
| 39 |
+
a75909eb-460b-4eb8-ab69-1f81413fde39.mp4,35,1,4,0.0452174662328464,0.6664746296834588,0.9997160037358602,0.662308836724454,0.2454252270120681,4.135975021220975e-11,43,6,10,85.72666666666667
|
| 40 |
+
a8fb16e1-2992-4ab7-95b7-47d48938f465.mp4,31,-1,4,0.6491552790006002,0.6554122765858968,0.5638013680775961,0.663050651550293,0.7081366777420044,0.6996987660725912,37,0,10,82.66666666666667
|
| 41 |
+
acfbb4ee-70e5-45bf-8aa8-349e388499ed.mp4,35,1,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,41,2,10,84.48
|
| 42 |
+
af18139c-acaf-4c43-977a-9a86fb3f3602.mp4,33,2,4,0.2569143105560701,0.3665943775946895,0.7403472264607748,0.6492060224215189,0.3337878490177342,0.0003729414893314,41,4,10,90.76999999999998
|
| 43 |
+
b2b4e988-b71d-4d6f-a5dd-8db222348faa.mp4,33,2,4,0.6797566215197245,0.6921680370966593,0.5756566723187765,0.6673476894696554,0.7057122588157654,0.6777393817901611,39,3,10,86.19
|
| 44 |
+
b872dec9-a527-443e-807b-8a650fb44aa5.mp4,33,2,4,0.5625234958715737,0.8364881674448649,0.8529369036356608,0.3062493354858209,0.2298784634719292,0.2218764863639043,47,6,10,76.47
|
| 45 |
+
bb85c24e-dfb2-4e74-a538-7b88444ba7a2.mp4,31,-1,4,0.5719117522239685,0.5746606588363647,0.7491706609725952,0.6051486531893412,0.6949742039044698,0.6399241884549459,38,1,10,79.57333333333332
|
| 46 |
+
bd22f174-e591-4d7a-a5fa-40447e18191e.mp4,31,-1,4,0.3112848651896153,0.9546863834063212,0.6849623595674833,0.8972047170003256,0.5202026833430864,0.2215489234929057,35,1,10,84.64999999999999
|
| 47 |
+
bdc6f64e-fe6c-4f98-b37d-a8405f0674a6.mp4,35,1,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,41,2,10,84.48
|
| 48 |
+
c6b21693-9149-46a7-8062-b4e36d861374.mp4,30,0,4,0.6675481796264648,0.6689058740933737,0.6905355652173361,0.6873549222946167,0.6984812021255493,0.6681686639785767,36,2,10,89.67333333333333
|
| 49 |
+
ce34e0c1-7378-4fc8-96c7-44ddcbbf1487.mp4,31,-1,4,0.626129150390625,0.6703311999638876,0.562980075677236,0.6703017155329386,0.6834884285926819,0.6630960901578268,37,0,10,88.67
|
| 50 |
+
d31d550a-bebb-4124-bd43-8b60d0e18de8.mp4,33,0,4,0.5594322880109152,0.7071808179219564,0.5779878795146942,0.7282688617706299,0.6871213515599569,0.5582586626211802,38,1,10,89.88333333333333
|
| 51 |
+
d863f7e4-dceb-4f47-8a78-dd6d1e4e880d.mp4,33,2,4,0.7645663022994995,0.7623209357261658,0.3754141006926754,0.7709534366925558,0.8046964406967163,0.4424456845930156,37,1,10,80.22333333333334
|
| 52 |
+
dd477309-fa8a-4317-94ff-d8b54bc3cad1.mp4,35,1,4,0.5718956192334493,0.6503244837125143,0.5420317649841309,0.6384625236193339,0.6779917677243551,0.6346049110094706,42,1,10,87.84666666666665
|
| 53 |
+
e0ece203-f76b-40b9-8285-cb9fd999c5c4.mp4,35,1,4,0.7104902664820353,0.6507590810457865,0.6978453199068705,0.6581440766652426,0.7403779625892639,0.2868792239266137,42,3,10,88.86666666666666
|
| 54 |
+
e199cb9b-752f-4aa1-a4b9-1885fa53a026.mp4,35,1,4,0.2177942705724667,0.7863604227701823,0.6638821821833668,0.7953539888064066,0.3359929546713829,0.0008118189095209,40,3,10,86.86666666666666
|
| 55 |
+
e4bb727c-b404-4141-bfbe-af71df54c3ff.mp4,35,1,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,41,2,10,84.48
|
| 56 |
+
e7f5193f-e47b-4376-977d-feb98314b110.mp4,33,0,4,0.6336689988772074,0.6886950929959615,0.5957454244295756,0.7071948448816935,0.67882106701533,0.6200438141822815,38,1,10,93.98
|
| 57 |
+
f11482e6-3485-46a8-9ade-92b46a803067.mp4,31,-1,4,0.5987781286239624,0.6132558385531107,0.6389955282211304,0.6446781953175863,0.7036546866099039,0.6583541433016459,38,0,10,83.53666666666668
|
| 58 |
+
f4f67a32-a8f5-43ac-93fb-6a41a45696de.mp4,33,0,4,0.6998077034950256,0.6582560737927755,0.5905927220980326,0.6627445220947266,0.6663028597831726,0.6565006971359253,39,1,10,90.92
|
| 59 |
+
fcd1e637-3ac0-49af-920e-6bc5ae2e32cf.mp4,31,-1,4,0.0272201643213868,0.3309482576635976,0.4792167643706004,0.998989482720693,0.6663707869944121,0.0050284543152277,35,-1,10,89.79333333333334
|
| 60 |
+
fd4e4b88-600d-4a77-acdc-a1315ce301cc.mp4,33,2,4,0.4362088193496068,0.4098552847281098,0.7522272070248922,0.8208715716997782,0.3990619123778742,0.4480555057525927,37,5,10,91.00666666666666
|
| 61 |
+
test1.mp4,32,0,4,0.5417211453119913,0.6767938733100891,0.557605524857839,0.6215428908665975,0.6766568620999655,0.5881443421045939,39,1,10,85.44333333333333
|
| 62 |
+
test10.mp4,32,0,4,0.3333333706116344,0.6667284226520375,1.0,1.0,2.8805363963881653e-13,0.0,35,5,10,82.65666666666665
|
| 63 |
+
test11.mp4,31,-1,4,0.4099834094134469,0.4834768225749333,0.3692534565925598,0.7831077774365743,0.783106525739034,0.629439910252889,35,-2,10,92.98
|
| 64 |
+
test12_gray.mp4,33,0,4,0.018794454264411,0.9975093007087708,0.5747074456497406,0.9784387747446696,0.2222395691399773,0.0231209589789311,35,1,10,89.79
|
| 65 |
+
test13_gray.mp4,35,1,4,0.5948363542556763,0.7572359442710876,0.5567836960156759,0.7168492476145426,0.7041877706845602,0.4451386034488678,40,2,10,97.55333333333333
|
| 66 |
+
test14.mp4,32,0,4,2.398985454732383e-10,0.999976634979248,0.3325004479909856,0.9213998317718506,0.0472111580893397,2.4258483184696295e-17,35,-2,10,90.44999999999999
|
| 67 |
+
test15.mp4,30,-2,4,0.6469272573788961,0.646748940149943,0.5141305724779764,0.6336438258488973,0.6843962669372559,0.6492714285850525,37,-2,10,85.60000000000001
|
| 68 |
+
test2.mp4,32,0,4,0.591302216053009,0.660931130250295,0.5407073299090067,0.6467535098393759,0.7081582148869833,0.6751541495323181,39,0,10,84.26
|
| 69 |
+
test3.mp4,33,0,4,0.6335959037144979,0.6653205553690592,0.5678872664769491,0.6906165877978007,0.6796593268712362,0.6661312778790792,39,1,10,91.08999999999999
|
| 70 |
+
test4.mp4,31,-1,4,0.6273186802864075,0.6763830780982971,0.577736496925354,0.6883265972137451,0.6920067270596822,0.6691595713297526,37,0,10,80.92333333333333
|
| 71 |
+
test5.mp4,32,0,4,0.3322414370577034,0.836751659711202,0.6209217806657156,0.9802228212356568,0.283252388325516,0.1214854717254638,35,1,10,87.76666666666667
|
| 72 |
+
test6.mp4,32,0,4,7.503743552443134e-10,0.6666666666703399,0.9999998807907104,0.0003142244143721,0.3394560635861126,1.5046710818080233e-23,50,5,11,79.53
|
| 73 |
+
test7.mp4,31,-1,4,0.5248274604479471,0.5492491523424784,0.7139172554016113,0.6525176564852396,0.7086620926856995,0.6403847138086954,38,1,10,79.80333333333334
|
| 74 |
+
test8.mp4,33,2,4,0.0,0.6666666666666666,0.6666666667085034,0.6666666666666666,1.1066234416402631e-20,0.0,41,4,11,88.39999999999999
|
| 75 |
+
test9.mp4,31,-1,4,0.5562724669774374,0.5891389648119608,0.6328761577606201,0.6466207702954611,0.7093602021535238,0.6497571667035421,38,0,10,76.86
|
iqf_scores_2.csv
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,input_size,output_size,final_compression,final_score,ori_luma_qp,ori_chroma,ori_preset,brightness,contrast,colorfulness,sharpness,quality_factor,mos_zscore,luma_qp,chroma,preset
|
| 2 |
+
04d651c3-3b08-4de9-befe-af12b3b3555b.mp4,11.63755702972412,2.6749343872070312,77.01463992507333,91.75666666666666,28.1,0.0,4,1.8469307366129406e-16,0.9999998410542806,1.0,0.9999999602635702,0.298225900199812,0.0,37,0,6
|
| 3 |
+
07484eba-7a60-4754-ba0b-f9c0744af11b.mp4,50.56832885742188,7.134676933288574,85.89101697743483,87.09666666666665,26.691666666666663,-1.0,4,0.6979172627131144,0.6313334306081136,0.6084306836128235,0.6348755955696106,0.6509576241175333,0.7038862506548563,34,0,11
|
| 4 |
+
0cb873d6-4c3a-4ced-84e9-87939b08565d.mp4,19.441584587097168,1.293718338012695,93.34561268801464,92.84,26.13,1.0,4,2.1057123452742498e-07,0.9972970485687256,0.9999999602635702,0.9853918751080832,0.3318715903168057,6.486347245594561e-25,35,0,11
|
| 5 |
+
0d0496e7-fe16-443b-9c16-369aa386bb4e.mp4,91.49424362182616,33.905229568481445,62.94277298075475,87.64666666666666,28.391666666666666,-1.0,4,0.5955032904942831,0.7397701740264893,0.5834726889928182,0.7210836410522461,0.65736456712087,0.4505663216114044,36,0,10
|
| 6 |
+
0e4ce3ab-c7c1-4dd6-9d9d-983eabf8acf3.mp4,14.362677574157717,2.823794364929199,80.33935977223385,87.38,25.425,-1.0,4,0.6425169706344604,0.6254289746284485,0.5407780408859253,0.6723130941390991,0.7194178899129232,0.6874635815620422,33,0,8
|
| 7 |
+
10e3a5e7-dda1-496d-84bb-f826e6567d82.mp4,12.642633438110352,2.3760366439819336,81.2061572803374,87.05,25.875,-1.0,4,0.666383703549703,0.644948422908783,0.5396574338277181,0.6548968553543091,0.6909757057825724,0.6799997289975485,33,0,7
|
| 8 |
+
12cb34d6-c1d8-4b85-8f0e-60afaf26ffc9.mp4,97.8766794204712,30.80884456634521,68.52279342866483,99.04,31.183333333333337,-1.0,4,0.559302568435669,0.6554734110832214,0.517133355140686,0.6597744027773539,0.6716094811757406,0.6479589343070984,38,0,10
|
| 9 |
+
12dd2d2b-24f3-4c31-b873-fa4e53803778.mp4,13.259980201721191,1.3995704650878906,89.44515418728739,91.7,24.0,2.0,4,0.0304905549855902,0.8879064122835795,0.9823326071103414,0.992975890636444,0.0004040396706827,2.798745185113234e-08,33,0,10
|
| 10 |
+
221cab7a-eb1a-4b14-9568-9ac4a4ec9330.mp4,30.4854040145874,8.113468170166016,73.38572857265173,91.52333333333333,28.975,-1.0,4,0.6206316550572714,0.6937900980313619,0.5681640307108561,0.6865283250808716,0.6309727231661478,0.5269437432289124,36,0,6
|
| 11 |
+
249c9213-36ad-4280-a900-306b96685c0e.mp4,56.18430137634277,6.987162590026856,87.56385250174365,84.53999999999999,25.06,-1.0,4,0.6744417746861776,0.6287276744842529,0.5382514794667562,0.6795710523923238,0.7059144775072733,0.6740560928980509,32,0,12
|
| 12 |
+
2884fdc7-6b49-4008-ae33-54956784cf22.mp4,115.50566577911376,39.44039535522461,65.85414655706319,99.71333333333332,29.975,-1.0,4,0.5739724636077881,0.6645207007726034,0.5748539765675863,0.6702662507692972,0.6798134446144104,0.6453063488006592,37,0,11
|
| 13 |
+
2efd6ba4-b102-4009-b197-da0cdaa634dc.mp4,24.736478805541992,3.4170312881469727,86.18626638411682,97.38,26.04,0.0,4,0.2123574561207135,0.923816998799642,0.5404039584100246,0.557942457186679,0.5570492148494531,0.1948199073473612,32,0,11
|
| 14 |
+
31f7bc2c-838f-43c2-9cf8-745cc9ce27ab.mp4,22.826221466064453,2.654613494873047,88.37033322041654,90.9,23.43,0.0,4,0.2436689063906669,0.865463117758433,0.5987633650268739,0.663530599674465,0.0083957752296021,0.0033863165364441,31,0,11
|
| 15 |
+
329f1c4c-f5df-453d-9cfc-6f2cbef5142b.mp4,9.611075401306152,1.5621337890625,83.74652446436738,88.86,26.85483870967742,-1.0,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,34,0,11
|
| 16 |
+
34e7d8b9-5fd5-44d6-ba9c-194d4e2078ae.mp4,8.409784317016602,1.250507354736328,85.13032787052558,93.57666666666668,26.86,0.0,4,0.6085565884908041,0.7023536364237467,0.5811308026313782,0.6709411342938741,0.6804569562276205,0.6665542125701904,34,0,6
|
| 17 |
+
39c98272-d26f-4139-afbf-d870b7553fab.mp4,11.697440147399902,2.55403995513916,78.16582155620715,86.31666666666666,25.95833333333333,-1.0,4,0.6213184396425883,0.6266296108563741,0.5339837074279785,0.6631546020507812,0.7125220696131388,0.6837653716405233,33,0,7
|
| 18 |
+
40949702-a809-4464-9d14-481e76abd967.mp4,11.222305297851562,3.6306753158569336,67.64768717749995,94.60333333333334,28.79,0.0,4,3.0931066325599755e-11,0.6665612919865964,1.0,0.5694492856661543,0.013828640182813,4.3167463616559045e-17,36,0,6
|
| 19 |
+
424df265-1080-4edf-8cfc-b05f62acfffc.mp4,93.02153778076172,36.84726619720459,60.38845725809408,87.91000000000001,28.941666666666663,-1.0,4,0.572777271270752,0.8049134612083435,0.5622189939022064,0.7238964438438416,0.6895942687988281,0.4115677277247111,36,0,10
|
| 20 |
+
4df1a23d-73f2-4cab-b43c-5538b566a458.mp4,9.611075401306152,1.5621337890625,83.74652446436738,88.86,26.85483870967742,-1.0,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,34,0,11
|
| 21 |
+
59bb4aa2-724b-490a-944c-eaae28496192.mp4,82.74820232391357,51.761945724487305,37.44644080379192,96.74,29.125,-1.0,4,9.296274529428212e-07,0.3862022735666339,0.0247434326447546,1.0,1.0,0.0171932382619767,33,0,10
|
| 22 |
+
5e800829-24b2-4ca7-bac4-89a02199fbec.mp4,9.155067443847656,1.032573699951172,88.72128789564432,89.29,28.866666666666667,1.0,4,0.6375203331311544,0.6609386603037516,0.5363780458768209,0.6825437943140665,0.7010063727696737,0.6790463924407959,36,0,6
|
| 23 |
+
5fe0ff99-37a3-4760-81a4-f441876cb867.mp4,9.611075401306152,1.5621337890625,83.74652446436738,88.86,26.85483870967742,-1.0,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,34,0,11
|
| 24 |
+
6084559b-4f79-480c-ae41-bdb3d0a6987c.mp4,8.81439208984375,0.6471805572509766,92.65768358550012,94.24,20.84375,2.0,4,0.5323069393634796,0.7520566980044047,0.5722926259040833,0.693462073802948,0.5514254768689474,0.2662951275706291,28,0,12
|
| 25 |
+
61a7d453-d120-440f-abfe-fa05714179b4.mp4,20.49268341064453,2.6226015090942383,87.20225430442176,91.52333333333333,28.0625,1.0,4,0.5853416124979655,0.6666666666666666,1.0,0.3333347989194711,2.981454431780764e-25,0.0,35,0,9
|
| 26 |
+
64e41231-4092-46c3-906d-5170de857162.mp4,28.37565803527832,5.465407371520996,80.73909910837637,88.54666666666667,26.05208333333333,0.0,4,0.745102326075236,0.670194149017334,0.5261250138282776,0.7535956104596456,0.707586407661438,0.3179372052351634,33,0,7
|
| 27 |
+
691de276-7b47-4015-bc54-da4343889084.mp4,21.35616779327393,3.5211429595947266,83.51229024945336,92.0,24.45,0.0,4,0.6291013558705648,0.647834857304891,0.6641747156778971,0.6670735081036886,0.7004674077033997,0.6992351412773132,32,0,11
|
| 28 |
+
733704b4-12f7-4977-b920-6896bc9ca77c.mp4,42.62840366363525,5.25462818145752,87.67341084850415,83.07333333333332,25.05,-1.0,4,0.6398445963859558,0.6260692278544108,0.5751756628354391,0.6669502059618632,0.7081162532170614,0.6691677371660868,32,0,10
|
| 29 |
+
81e87893-da5e-4db2-afd1-b1bb11a03de7.mp4,11.973058700561523,2.3520984649658203,80.35507447353024,93.7,28.125,2.0,4,7.679543605343628e-09,1.0,1.0,0.3358941231754215,4.8890841564280026e-21,4.577665144872662e-36,35,0,6
|
| 30 |
+
890c5911-8ee1-4faa-a801-c605f0dde8e0.mp4,9.611075401306152,1.5621337890625,83.74652446436738,88.86,26.85483870967742,-1.0,4,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,34,0,11
|
| 31 |
+
8b6d7852-7f21-47df-aa03-fc8c0f29e6d3.mp4,,,,,28.83,1.0,4,0.6386719544728597,0.6828855474789938,0.5790578722953796,0.6756150126457214,0.7062546610832214,0.6745621760686239,36,0,11
|
iqf_scores_3.csv
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,input_size,output_size,final_compression,final_score,ori_luma_qp,ori_chroma,ori_preset,brightness,contrast,colorfulness,sharpness,quality_factor,mos_zscore,luma_qp,chroma,preset
|
| 2 |
+
04d651c3-3b08-4de9-befe-af12b3b3555b.mp4,11.63755702972412,3.1724796295166016,72.73929896615246,93.11666666666667,28.1,0.0,4,1.8469307366129406e-16,0.9999998410542806,1.0,0.9999999602635702,0.298225900199812,0.0,35,0,6
|
| 3 |
+
07484eba-7a60-4754-ba0b-f9c0744af11b.mp4,50.56832885742188,9.012784957885742,82.17701640230703,88.19333333333333,26.691666666666663,-1.0,4,0.6979172627131144,0.6313334306081136,0.6084306836128235,0.6348755955696106,0.6509576241175333,0.7038862506548563,32,0,12
|
| 4 |
+
0cb873d6-4c3a-4ced-84e9-87939b08565d.mp4,19.441584587097168,1.5586957931518557,91.98267103090808,93.29666666666668,26.13,1.0,4,2.1057123452742498e-07,0.9972970485687256,0.9999999602635702,0.9853918751080832,0.3318715903168057,6.486347245594561e-25,33,0,11
|
| 5 |
+
0d0496e7-fe16-443b-9c16-369aa386bb4e.mp4,91.49424362182616,40.06210517883301,56.21352383170465,89.47666666666667,28.391666666666666,-1.0,4,0.5955032904942831,0.7397701740264893,0.5834726889928182,0.7210836410522461,0.65736456712087,0.4505663216114044,34,0,11
|
| 6 |
+
0e4ce3ab-c7c1-4dd6-9d9d-983eabf8acf3.mp4,14.362677574157717,3.4272356033325195,76.13790614154684,88.80666666666666,25.425,-1.0,4,0.6425169706344604,0.6254289746284485,0.5407780408859253,0.6723130941390991,0.7194178899129232,0.6874635815620422,31,0,8
|
| 7 |
+
10e3a5e7-dda1-496d-84bb-f826e6567d82.mp4,12.642633438110352,2.90421199798584,77.02842519161165,88.29,25.875,-1.0,4,0.666383703549703,0.644948422908783,0.5396574338277181,0.6548968553543091,0.6909757057825724,0.6799997289975485,31,0,8
|
| 8 |
+
12cb34d6-c1d8-4b85-8f0e-60afaf26ffc9.mp4,,,,,31.183333333333337,-1.0,4,0.559302568435669,0.6554734110832214,0.517133355140686,0.6597744027773539,0.6716094811757406,0.6479589343070984,36,0,10
|
iqf_scores_4.csv
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,input_size,output_size,final_compression,final_score,ori_luma_qp,psnr_values,scene_type,luma_qp,brightness,contrast,colorfulness,sharpness,quality_factor,mos_zscore,chroma,preset
|
| 2 |
+
07484eba-7a60-4754-ba0b-f9c0744af11b.mp4,50.56832885742188,34.3788480758667,0.6798494008532168,95.12666666666668,23.691666666666663,34.82451435257544,Sea,22,0.6979172627131144,0.6313334306081136,0.6084306836128235,0.6348755955696106,0.6509576241175333,0.7038862506548563,-1.0,6
|
| 3 |
+
0d0496e7-fe16-443b-9c16-369aa386bb4e.mp4,91.49424362182616,100.9215087890625,1.1030367025733565,96.34999999999998,25.391666666666666,27.89417999534232,Sea,22,0.5955032904942831,0.7397701740264893,0.5834726889928182,0.7210836410522461,0.65736456712087,0.4505663216114044,-1.0,6
|
| 4 |
+
0e4ce3ab-c7c1-4dd6-9d9d-983eabf8acf3.mp4,14.362677574157717,8.536499977111816,0.5943529632992148,95.10666666666668,22.425,30.44581862011659,Sea,22,0.6425169706344604,0.6254289746284485,0.5407780408859253,0.6723130941390991,0.7194178899129232,0.6874635815620422,-1.0,6
|
| 5 |
+
10e3a5e7-dda1-496d-84bb-f826e6567d82.mp4,12.642633438110352,7.81199836730957,0.6179091093285072,94.66,22.875,29.992381826532565,Sea,22,0.666383703549703,0.644948422908783,0.5396574338277181,0.6548968553543091,0.6909757057825724,0.6799997289975485,-1.0,6
|
| 6 |
+
12cb34d6-c1d8-4b85-8f0e-60afaf26ffc9.mp4,97.8766794204712,47.07356548309326,0.4809477166758856,99.79666666666668,28.183333333333334,16.3645023883341,Sea,32,0.559302568435669,0.6554734110832214,0.517133355140686,0.6597744027773539,0.6716094811757406,0.6479589343070984,-1.0,6
|
| 7 |
+
12dd2d2b-24f3-4c31-b873-fa4e53803778.mp4,13.259980201721191,3.863698959350586,0.2913804470725427,96.45333333333332,19.0,31.048039674445505,Sea,22,0.0304905549855902,0.8879064122835795,0.9823326071103414,0.992975890636444,0.0004040396706827,2.798745185113234e-08,2.0,6
|
| 8 |
+
221cab7a-eb1a-4b14-9568-9ac4a4ec9330.mp4,30.4854040145874,13.693915367126465,0.4491957974568376,95.35666666666668,25.975,24.29359649078312,Sea,32,0.6206316550572714,0.6937900980313619,0.5681640307108561,0.6865283250808716,0.6309727231661478,0.5269437432289124,-1.0,6
|
| 9 |
+
249c9213-36ad-4280-a900-306b96685c0e.mp4,56.18430137634277,27.01200771331787,0.4807750038997846,92.53333333333336,22.06,35.30867793968724,Sea,22,0.6744417746861776,0.6287276744842529,0.5382514794667562,0.6795710523923238,0.7059144775072733,0.6740560928980509,-1.0,6
|
| 10 |
+
2884fdc7-6b49-4008-ae33-54956784cf22.mp4,115.50566577911376,56.05314254760742,0.4852847881488354,99.82666666666668,26.975,19.456423450946858,Sea,32,0.5739724636077881,0.6645207007726034,0.5748539765675863,0.6702662507692972,0.6798134446144104,0.6453063488006592,-1.0,6
|
| 11 |
+
39c98272-d26f-4139-afbf-d870b7553fab.mp4,11.697440147399902,7.938425064086914,0.678646350317207,94.16666666666669,22.95833333333333,30.240432260663724,Sea,22,0.6213184396425883,0.6266296108563741,0.5339837074279785,0.6631546020507812,0.7125220696131388,0.6837653716405233,-1.0,6
|
| 12 |
+
424df265-1080-4edf-8cfc-b05f62acfffc.mp4,93.02153778076172,111.02602577209473,1.1935518205876898,96.40333333333336,25.941666666666663,26.716159386938628,Sea,22,0.572777271270752,0.8049134612083435,0.5622189939022064,0.7238964438438416,0.6895942687988281,0.4115677277247111,-1.0,6
|
| 13 |
+
494e83e7-548e-408f-af3d-d702cc6ae594.mp4,6.668767929077148,3.722039222717285,0.5581299667796891,97.79,22.02083333333333,27.043169740053514,Sea,22,0.6418240666389465,0.6350036462148031,0.5903654098510742,0.6643276611963908,0.6858240564664205,0.6821937759717306,-1.0,6
|
| 14 |
+
59bb4aa2-724b-490a-944c-eaae28496192.mp4,,,,,26.125,18.335617949324277,Sea,32,9.296274529428211e-07,0.3862022735666339,0.024743432644754648,1.0,1.0,0.017193238261976756,-1.0,6
|
iqf_scores_custom.csv
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,input_size,output_size,final_compression,final_score,ori_luma_qp,ori_chroma,ori_preset,psnr_values,scene_type,brightness,contrast,colorfulness,sharpness,quality_factor,mos_zscore,luma_qp,chroma,preset
|
| 2 |
+
04d651c3-3b08-4de9-befe-af12b3b3555b.mp4,11.63755702972412,2.9998998641967773,74.22225423656728,89.54666666666667,24.1,0.0,4,24.34790082106915,Glacier,1.8469307366129406e-16,0.9999998410542806,1.0,0.9999999602635702,0.298225900199812,0.0,36,0.0,12
|
| 3 |
+
07484eba-7a60-4754-ba0b-f9c0744af11b.mp4,50.56832885742188,5.350363731384277,89.41953619533344,85.96666666666668,23.691666666666663,-1.0,4,34.82451435257544,Sea,0.6979172627131144,0.6313334306081136,0.6084306836128235,0.6348755955696106,0.6509576241175333,0.7038862506548563,36,-1.0,12
|
| 4 |
+
0cb873d6-4c3a-4ced-84e9-87939b08565d.mp4,19.441584587097168,0.6575107574462891,96.61801868823665,90.50666666666666,19.13,1.0,4,32.55061039813054,Buildings,2.1057123452742498e-07,0.9972970485687256,0.9999999602635702,0.9853918751080832,0.3318715903168057,6.486347245594561e-25,43,1.0,12
|
| 5 |
+
0d0496e7-fe16-443b-9c16-369aa386bb4e.mp4,91.49424362182616,33.905229568481445,62.94277298075475,87.64666666666666,25.391666666666666,-1.0,4,27.89417999534232,Sea,0.5955032904942831,0.7397701740264893,0.5834726889928182,0.7210836410522461,0.65736456712087,0.4505663216114044,36,-1.0,12
|
| 6 |
+
0e4ce3ab-c7c1-4dd6-9d9d-983eabf8acf3.mp4,14.362677574157717,3.847278594970703,73.21336098296196,88.19333333333333,22.425,-1.0,4,30.44581862011659,Sea,0.6425169706344604,0.6254289746284485,0.5407780408859253,0.6723130941390991,0.7194178899129232,0.6874635815620422,29,-1.0,12
|
| 7 |
+
10e3a5e7-dda1-496d-84bb-f826e6567d82.mp4,12.642633438110352,3.197593688964844,74.70785098201205,86.99000000000001,22.875,-1.0,4,29.992381826532565,Sea,0.666383703549703,0.644948422908783,0.5396574338277181,0.6548968553543091,0.6909757057825724,0.6799997289975485,29,-1.0,12
|
| 8 |
+
12cb34d6-c1d8-4b85-8f0e-60afaf26ffc9.mp4,97.8766794204712,13.43449592590332,86.27405832988092,87.71333333333332,28.183333333333334,-1.0,4,16.3645023883341,Sea,0.559302568435669,0.6554734110832214,0.517133355140686,0.6597744027773539,0.6716094811757406,0.6479589343070984,50,-1.0,12
|
| 9 |
+
12dd2d2b-24f3-4c31-b873-fa4e53803778.mp4,13.259980201721191,0.6298971176147461,95.24963757085412,86.85333333333334,19.0,2.0,4,31.048039674445505,,0.0304905549855902,0.8879064122835795,0.9823326071103414,0.992975890636444,0.0004040396706827,2.798745185113234e-08,43,2.0,12
|
| 10 |
+
221cab7a-eb1a-4b14-9568-9ac4a4ec9330.mp4,30.4854040145874,8.677053451538086,71.53702326731154,89.46666666666665,25.975,-1.0,4,24.29359649078312,Sea,0.6206316550572714,0.6937900980313619,0.5681640307108561,0.6865283250808716,0.6309727231661478,0.5269437432289124,36,-1.0,12
|
| 11 |
+
249c9213-36ad-4280-a900-306b96685c0e.mp4,56.18430137634277,10.10262393951416,82.01877803580197,86.36,22.06,-1.0,4,35.30867793968724,Sea,0.6744417746861776,0.6287276744842529,0.5382514794667562,0.6795710523923238,0.7059144775072733,0.6740560928980509,29,-1.0,12
|
| 12 |
+
2884fdc7-6b49-4008-ae33-54956784cf22.mp4,115.50566577911376,16.16676139831543,86.0034905740193,89.88666666666667,26.975,-1.0,4,19.456423450946858,Sea,0.5739724636077881,0.6645207007726034,0.5748539765675863,0.6702662507692972,0.6798134446144104,0.6453063488006592,50,-1.0,12
|
| 13 |
+
2efd6ba4-b102-4009-b197-da0cdaa634dc.mp4,24.736478805541992,0.8398094177246094,96.60497589682628,90.75333333333332,21.04,0.0,4,17.135833803028024,Street,0.2123574561207135,0.923816998799642,0.5404039584100246,0.557942457186679,0.5570492148494531,0.1948199073473612,50,0.0,12
|
| 14 |
+
31f7bc2c-838f-43c2-9cf8-745cc9ce27ab.mp4,22.826221466064453,1.5528364181518557,93.1971376845684,88.98333333333333,19.43,0.0,4,35.789955905092,Glacier,0.2436689063906669,0.865463117758433,0.5987633650268739,0.663530599674465,0.0083957752296021,0.0033863165364441,36,0.0,12
|
| 15 |
+
329f1c4c-f5df-453d-9cfc-6f2cbef5142b.mp4,9.611075401306152,1.3005437850952148,86.46828028299029,87.64,19.85483870967742,-1.0,4,29.396985786067585,Buildings,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,36,-1.0,12
|
| 16 |
+
34e7d8b9-5fd5-44d6-ba9c-194d4e2078ae.mp4,8.409784317016602,1.3808231353759766,83.58075447212148,91.11333333333334,21.86,0.0,4,27.697119754095937,Street,0.6085565884908041,0.7023536364237467,0.5811308026313782,0.6709411342938741,0.6804569562276205,0.6665542125701904,34,0.0,12
|
| 17 |
+
39c98272-d26f-4139-afbf-d870b7553fab.mp4,11.697440147399902,3.302997589111328,71.7630733947759,85.51999999999998,22.95833333333333,-1.0,4,30.240432260663724,Sea,0.6213184396425883,0.6266296108563741,0.5339837074279785,0.6631546020507812,0.7125220696131388,0.6837653716405233,29,-1.0,12
|
| 18 |
+
40949702-a809-4464-9d14-481e76abd967.mp4,11.222305297851562,3.0290184020996094,73.00894672078209,90.15666666666664,23.79,0.0,4,23.28805529599666,Street,3.0931066325599755e-11,0.6665612919865964,1.0,0.5694492856661543,0.013828640182813,4.3167463616559045e-17,41,0.0,12
|
| 19 |
+
424df265-1080-4edf-8cfc-b05f62acfffc.mp4,93.02153778076172,36.84726619720459,60.38845725809408,87.91000000000001,25.941666666666663,-1.0,4,26.716159386938628,Sea,0.572777271270752,0.8049134612083435,0.5622189939022064,0.7238964438438416,0.6895942687988281,0.4115677277247111,36,-1.0,12
|
| 20 |
+
4df1a23d-73f2-4cab-b43c-5538b566a458.mp4,9.611075401306152,1.3005437850952148,86.46828028299029,87.64,19.85483870967742,-1.0,4,29.396985786067585,Buildings,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,36,-1.0,12
|
| 21 |
+
59bb4aa2-724b-490a-944c-eaae28496192.mp4,82.74820232391357,40.78819561004639,50.7080583450223,93.95,26.125,-1.0,4,18.33561794932428,Sea,9.296274529428212e-07,0.3862022735666339,0.0247434326447546,1.0,1.0,0.0171932382619767,36,-1.0,12
|
| 22 |
+
5e800829-24b2-4ca7-bac4-89a02199fbec.mp4,9.155067443847656,2.107905387878418,76.97553403284907,89.20666666666666,21.866666666666667,1.0,4,36.65562661105682,Buildings,0.6375203331311544,0.6609386603037516,0.5363780458768209,0.6825437943140665,0.7010063727696737,0.6790463924407959,29,1.0,12
|
| 23 |
+
5fe0ff99-37a3-4760-81a4-f441876cb867.mp4,9.611075401306152,1.3005437850952148,86.46828028299029,87.64,19.85483870967742,-1.0,4,29.396985786067585,Buildings,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,36,-1.0,12
|
| 24 |
+
6084559b-4f79-480c-ae41-bdb3d0a6987c.mp4,8.81439208984375,0.2493619918823242,97.17096778381747,92.81,15.84375,2.0,4,44.19402395567592,,0.5323069393634796,0.7520566980044047,0.5722926259040833,0.693462073802948,0.5514254768689474,0.2662951275706291,36,2.0,12
|
| 25 |
+
61a7d453-d120-440f-abfe-fa05714179b4.mp4,20.49268341064453,4.774928092956543,76.69935167945698,95.10333333333334,21.0625,1.0,4,23.28430139070841,Buildings,0.5853416124979655,0.6666666666666666,1.0,0.3333347989194711,2.981454431780764e-25,0.0,29,1.0,12
|
| 26 |
+
64e41231-4092-46c3-906d-5170de857162.mp4,28.37565803527832,3.930176734924317,86.1494780842154,84.14333333333333,24.05208333333333,0.0,4,25.41429757462589,Mountain,0.745102326075236,0.670194149017334,0.5261250138282776,0.7535956104596456,0.707586407661438,0.3179372052351634,36,0.0,12
|
| 27 |
+
691de276-7b47-4015-bc54-da4343889084.mp4,21.35616779327393,2.450800895690918,88.52415414874764,90.08333333333331,20.45,0.0,4,28.257029596777684,Glacier,0.6291013558705648,0.647834857304891,0.6641747156778971,0.6670735081036886,0.7004674077033997,0.6992351412773132,36,0.0,12
|
| 28 |
+
733704b4-12f7-4977-b920-6896bc9ca77c.mp4,42.62840366363525,7.459269523620605,82.50164471914336,84.60666666666667,22.05,-1.0,4,34.503775113036255,Sea,0.6398445963859558,0.6260692278544108,0.5751756628354391,0.6669502059618632,0.7081162532170614,0.6691677371660868,29,-1.0,12
|
| 29 |
+
81e87893-da5e-4db2-afd1-b1bb11a03de7.mp4,11.973058700561523,2.4130001068115234,79.846418804425,89.86666666666666,23.125,2.0,4,29.955702790992284,,7.679543605343628e-09,1.0,1.0,0.3358941231754215,4.8890841564280026e-21,4.577665144872662e-36,36,2.0,12
|
| 30 |
+
890c5911-8ee1-4faa-a801-c605f0dde8e0.mp4,9.611075401306152,1.3005437850952148,86.46828028299029,87.64,19.85483870967742,-1.0,4,29.396985786067585,Buildings,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,36,-1.0,12
|
| 31 |
+
8b6d7852-7f21-47df-aa03-fc8c0f29e6d3.mp4,36.639710426330566,2.526042938232422,93.1057235200824,89.26333333333334,21.83,1.0,4,19.31974385124029,Buildings,0.6386719544728597,0.6828855474789938,0.5790578722953796,0.6756150126457214,0.7062546610832214,0.6745621760686239,50,1.0,12
|
| 32 |
+
8f0464f5-2e81-4d50-b4bb-0667fadd55b6.mp4,34.017616271972656,5.3361406326293945,84.31359625563806,74.34666666666668,27.291666666666668,0.0,4,17.875918400820858,Street,0.6737418174743652,0.6781927148501078,0.5551733573277792,0.6866800983746847,0.6682468851407369,0.6677144169807434,50,0.0,12
|
| 33 |
+
93495912-fd6c-435a-9fcb-c9cc978c122e.mp4,14.999189376831056,1.5714950561523438,89.52280008825143,85.81,23.04201680672269,-1.0,4,29.72427086056156,Sea,0.3303557277324671,0.572416496075069,0.8744631012280782,0.5750492256289969,0.245182732741038,0.1620057622591654,36,-1.0,12
|
| 34 |
+
9a049c4c-bb40-4692-8b95-cb2ca94d339f.mp4,7.705256462097168,0.5107479095458984,93.37143530447932,86.02666666666669,19.433333333333334,-1.0,4,47.35919096909639,Sea,0.5415121912956238,0.6919364531834921,0.5953385432561239,0.7154134511947632,0.6035712758700053,0.5871815880139669,36,-1.0,12
|
| 35 |
+
9d860f37-78b5-4e91-bbff-91bbfaaaa32a.mp4,24.54138946533203,3.238698005676269,86.8031188280869,82.79,24.05208333333333,0.0,4,25.67234493498757,Mountain,0.5807816584904989,0.6518141428629557,0.5270594159762064,0.6739723483721415,0.6623684565226237,0.5438634157180786,36,0.0,12
|
| 36 |
+
a3fa72da-8a4e-466c-9a11-fc2a67863b9e.mp4,66.18983554840088,5.890133857727051,91.10115048794776,83.83333333333333,24.291666666666668,-1.0,4,16.2941854660188,Sea,0.2128963904734471,0.8745509584744772,0.8768628239631653,0.579653590063875,0.24392955436218,0.2292507886886597,50,-1.0,12
|
| 37 |
+
a508a47e-295b-4be0-ab3f-4b6478dacc65.mp4,150.44969272613525,66.06287097930908,56.08972688328196,89.68666666666667,28.57017543859649,-1.0,4,20.067948955885825,Sea,0.626129150390625,0.6703311999638876,0.562980075677236,0.6703017155329386,0.6834884285926819,0.6630960901578268,36,-1.0,12
|
| 38 |
+
a5f32e7e-a5f3-4a7e-b7fd-dc0fb551e243.mp4,27.75857830047608,8.555665016174316,69.17830256448119,90.31333333333332,25.991666666666667,-1.0,4,20.73713088892713,Sea,0.6408935189247131,0.7019673387209574,0.5994003017743429,0.701171875,0.6946845253308614,0.6745195984840393,36,-1.0,12
|
| 39 |
+
a75909eb-460b-4eb8-ab69-1f81413fde39.mp4,15.16309642791748,0.7881450653076172,94.80221556952888,85.72666666666667,19.17708333333333,1.0,4,30.9834009328239,Buildings,0.0452174662328464,0.6664746296834588,0.9997160037358602,0.662308836724454,0.2454252270120681,4.1359750212209746e-11,43,1.0,12
|
| 40 |
+
a8fb16e1-2992-4ab7-95b7-47d48938f465.mp4,56.77720260620117,10.663334846496582,81.21898516125214,87.81666666666666,22.03,-1.0,4,32.6959957036605,Sea,0.6491552790006002,0.6554122765858968,0.5638013680775961,0.663050651550293,0.7081366777420044,0.6996987660725912,29,-1.0,12
|
| 41 |
+
acfbb4ee-70e5-45bf-8aa8-349e388499ed.mp4,9.611075401306152,1.3005437850952148,86.46828028299029,87.64,19.85483870967742,-1.0,4,29.396985786067585,Buildings,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,36,-1.0,12
|
| 42 |
+
af18139c-acaf-4c43-977a-9a86fb3f3602.mp4,15.229940414428713,1.370335578918457,91.00235758230404,92.81666666666666,18.510416666666668,2.0,4,32.8864798429277,,0.2569143105560701,0.3665943775946895,0.7403472264607748,0.6492060224215189,0.3337878490177342,0.0003729414893314,36,2.0,12
|
| 43 |
+
b2b4e988-b71d-4d6f-a5dd-8db222348faa.mp4,8.236392974853516,1.0207548141479492,87.60677377506865,88.11666666666666,20.3,2.0,4,30.94902322197443,,0.6797566215197245,0.6921680370966593,0.5756566723187765,0.6673476894696554,0.7057122588157654,0.6777393817901611,36,2.0,12
|
| 44 |
+
b872dec9-a527-443e-807b-8a650fb44aa5.mp4,31.921814918518063,6.28378963470459,80.31506150028042,89.21666666666665,21.316666666666663,2.0,4,31.778037288875964,,0.5625234958715737,0.8364881674448649,0.8529369036356608,0.3062493354858209,0.2298784634719292,0.2218764863639043,29,2.0,12
|
| 45 |
+
bb85c24e-dfb2-4e74-a538-7b88444ba7a2.mp4,7.403351783752441,1.4399042129516602,80.55064442416871,86.10000000000001,21.10833333333333,-1.0,4,43.96922372729665,Sea,0.5719117522239685,0.5746606588363647,0.7491706609725952,0.6051486531893412,0.6949742039044698,0.6399241884549459,29,-1.0,12
|
| 46 |
+
bd22f174-e591-4d7a-a5fa-40447e18191e.mp4,14.803239822387695,2.4618701934814453,83.36938249316049,84.21,25.00438596491228,-1.0,4,30.95666927265913,Sea,0.3112848651896153,0.9546863834063212,0.6849623595674833,0.8972047170003256,0.5202026833430864,0.2215489234929057,36,-1.0,12
|
| 47 |
+
bdc6f64e-fe6c-4f98-b37d-a8405f0674a6.mp4,9.611075401306152,1.3005437850952148,86.46828028299029,87.64,19.85483870967742,-1.0,4,29.396985786067585,Buildings,0.583096981048584,0.6963175336519877,0.5527470707893372,0.6834075252215067,0.6812475522359213,0.5811437368392944,36,-1.0,12
|
| 48 |
+
c6b21693-9149-46a7-8062-b4e36d861374.mp4,6.766033172607422,0.555415153503418,91.79112576994093,89.67333333333333,19.1,0.0,4,49.603593158290806,Mountain,0.6675481796264648,0.6689058740933737,0.6905355652173361,0.6873549222946167,0.6984812021255493,0.6681686639785767,36,0.0,12
|
iqf_scores_video1.csv
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
file_name,input_size,output_size,final_compression,final_score,psnr_values,scene_type,luma_qp,quality_noise_level,quality_sharpness,quality_contrast,quality_brightness,quality_color_saturation,quality_motion_blur,quality_compression_artifacts,quality_text_content,quality_edge_density,quality_temporal_consistency,chroma,preset
|
| 2 |
+
026fe674-d8c4-40a4-96c5-0cb6f1fb7c06.mp4,22.074841499328613,2.785863876342773,0.1262008552327546,93.08666666666666,26.13181026051079,Sea,33,0.0281049907207489,0.0167996753007173,0.1342756832257432,0.3967183437811308,0.3348408286037159,0.0102033827781422,0.0042888157380123,0.2226463602789319,0.0173612439864421,0.9997948641607676,2,8
|
| 3 |
+
1fda2da4-b877-415b-a0b2-9643e8c19343.mp4,42.13912010192871,4.191298484802246,0.0994633602852663,88.55666666666667,19.74997773179496,Mountain,36,0.1175154820084571,0.0461746454238891,0.1698552824803696,0.4773372729081188,0.1762627786880638,0.0413492260696467,0.0111680872117479,0.7232348793690635,0.0703951503710317,0.9971271971765552,1,7
|
| 4 |
+
223c0d7d-c60b-4ce0-b867-a6292c9aa465.mp4,45.65006923675537,4.773685455322266,0.1045712642967627,90.91,18.361035670056648,Mountain,35,0.1735588163137436,0.0710327699780464,0.1924423491662839,0.3859789412249714,0.1846702573626785,0.0851046132594358,0.015286094819506,0.9235667442983528,0.1461225657623207,0.991769307651044,1,6
|
| 5 |
+
2b4b64a6-a07f-4bce-93eb-0b3ce53e0575.mp4,45.77764892578125,4.4001970291137695,0.0961210794431089,89.87333333333333,20.31808044588177,Mountain,35,0.1654309034347534,0.0693681240081787,0.1945907355813904,0.4009358367907413,0.1967351534458278,0.0817902631896655,0.0148024419322609,0.9143815857079082,0.1401244914633221,0.9930504353683388,1,6
|
| 6 |
+
2b8983b8-2396-4020-a6db-35b19bb2d287.mp4,47.75133895874024,4.317131996154785,0.0904086061311291,88.83666666666666,20.24408139521997,Mountain,38,0.1422079205513,0.0581630282104015,0.2012230718832824,0.3787347649628497,0.2031581901056525,0.073356599381059,0.0149929175774256,0.7752869102153293,0.1257005266287348,0.9969557560560646,1,7
|
| 7 |
+
43a9cd6b-81bc-438d-819c-6352d7193145.mp4,43.062859535217285,4.463125228881836,0.1036421008045654,88.68333333333334,19.1756830753702,Mountain,35,0.1361274570226669,0.0552828311920166,0.1703905049275502,0.4539282307889309,0.1831038004086405,0.0580689047895802,0.0123649441326657,0.731457977111989,0.1049097084179139,0.9815015737117856,1,7
|
| 8 |
+
494e83e7-548e-408f-af3d-d702cc6ae594.mp4,6.668767929077148,1.1391572952270508,0.1708197537149402,92.08666666666666,15.28414787432056,Sea,36,0.0644450113177299,0.0318450145423412,0.1218587687048134,0.5155074374479302,0.3446948237605802,0.0206735079199236,0.0081399170060952,0.3944745150014188,0.0389192013002089,0.9925388100348878,2,7
|
| 9 |
+
5533624b-8b2f-476e-93c3-26ecc8abbbfc.mp4,48.74678897857666,4.002272605895996,0.0821033075153919,88.94,20.368462730755084,Mountain,37,0.1413482576608657,0.0594401359558105,0.1886964347338915,0.4077637824619008,0.2283467568483899,0.0767561909194802,0.0143880064909656,0.6034597344262048,0.1327680358778163,0.9937148534950766,1,7
|
| 10 |
+
58628666-a50b-4b53-86f3-9bce954fd87a.mp4,17.49576473236084,4.228129386901856,0.2416658803762573,98.91666666666669,21.25486157432972,Sea,33,0.0431025587022304,0.0199701022356748,0.3801464858933288,0.1332456478568448,0.611579316008798,0.0024631645124081,0.0025733727961778,0.6121459220339367,0.0034776776627143,0.9973595402976382,2,8
|
| 11 |
+
5cbb381b-c803-4c00-9f7c-eb34a93f0f11.mp4,43.69903087615967,4.624711036682129,0.1058309748284412,89.24666666666667,17.629037236788477,Mountain,35,0.1505059599876403,0.0603656210005283,0.1814356638339386,0.4207115634726497,0.1782449488412061,0.0653040586070096,0.0135400975123047,0.8411135233554857,0.119503881935261,0.9788613655686916,1,7
|
| 12 |
+
6f22e710-af89-4704-8e14-022222e9748b.mp4,26.640109062194824,2.7298479080200195,0.1024713488089268,92.90000000000002,25.658407466420066,Sea,38,0.0485589206218719,0.0231323186308145,0.1153739397145537,0.463199056968312,0.3847542499002731,0.0137213119667827,0.0031315109226852,0.1833413000403856,0.0216421512948659,0.9949059255782428,2,8
|
| 13 |
+
708b5e08-9d59-4b41-9dba-f7735682a9b4.mp4,27.11849975585937,3.5335893630981445,0.1303018011656289,88.83666666666666,19.987420021489257,Glacier,39,0.0166761334985494,0.0071951509453356,0.2228156889157377,0.3924565241366342,0.7564402887392186,0.0021965440270935,0.0035960952906558,0.5808692813811348,0.0034892198617952,0.9858591746085408,2,8
|
| 14 |
+
70b2de9e-3c32-4b01-8505-8a78d5bccd59.mp4,37.72974681854248,6.375006675720215,0.1689650001199368,90.67,26.28220540391436,Sea,33,0.0360238887369632,0.0152332307770848,0.1206950385830347,0.511027495028049,0.2998014956295594,0.0078212501433094,0.0043184760337074,0.282369536052244,0.0135870261694049,0.9988060480115136,2,8
|
| 15 |
+
7fb26377-5336-45c3-8648-14c7df0d1822.mp4,8.315752983093262,1.3558111190795898,0.1630412930538084,94.14333333333332,18.98384660344204,Glacier,35,0.0722586363554,0.0439577437937259,0.1263059475750047,0.5686536038260235,0.377071315116488,0.0240558069117096,0.009015806329747,0.5724709479049758,0.0389493897306397,0.9592674120012764,2,7
|
| 16 |
+
814a4743-06ee-4a4b-9e77-e11bee77ceeb.mp4,5.243513107299805,0.6225442886352539,0.1187265628779631,93.65333333333332,22.36412702479837,Glacier,34,0.0474878959357738,0.0205531790852546,0.1985460133073029,0.2297653506247488,0.7212429583505275,0.010479251782424,0.0074468950430552,0.7969974880122681,0.0183791027225598,0.9985523841836662,1,8
|
| 17 |
+
8fb42072-b53c-4ee3-b54d-4c2e1cf6d1ea.mp4,27.705836296081543,9.31836223602295,0.33633210477537,92.65666666666668,15.691813835264224,Street,33,0.4326435327529907,0.1827086210250854,0.1589946302332441,0.4811455634902676,0.2646468814980862,0.1195052192903368,0.0232531875371933,0.484191548456252,0.1929814757768828,0.9696954825913656,1,6
|
| 18 |
+
918a9d77-727b-4255-bf27-d67c73ab109e.mp4,53.68707656860352,12.61237907409668,0.2349239310503353,95.35,13.299115682322263,Buildings,33,0.1324290484189987,0.0732438713312149,0.1579082716568159,0.2904904572709194,0.4459472019345969,0.0440004614111756,0.0098852856705586,0.3789205217776646,0.077397912777377,0.977604551435959,1,6
|
| 19 |
+
b7340fda-e72e-4b12-9029-316dbd0c9ce8.mp4,16.855341911315918,1.5283565521240234,0.0906749065172005,93.06333333333332,30.707378416224845,Buildings,39,0.075921282172203,0.034921821206808,0.1509332493571397,0.3837449406136722,0.3531127043688376,0.0164538663940422,0.0059778722934424,0.0796282384863345,0.018695976111073,0.961157444517792,2,8
|
| 20 |
+
be4a76cd-c69a-433b-b55c-9dfd478c9594.mp4,91.5072259902954,20.27893829345703,0.2216102397815848,94.58333333333331,13.14202322363013,Buildings,33,0.2657359540462494,0.1347031891345977,0.1066279604487404,0.4325665933039882,0.5377423675610595,0.0564917502088554,0.0161084520320097,0.1908051378446115,0.097688081811672,0.9839293682385236,1,6
|
| 21 |
+
c7d92eab-29ca-490c-ab85-e18229efdf96.mp4,97.28178691864014,33.901222229003906,0.3484847811991425,94.10333333333334,13.507726959867174,Sea,33,0.192488357424736,0.1012503281235694,0.0961965353301766,0.4876347176471124,0.2784021018853362,0.0490402666342134,0.0148710856835047,0.1341335683256892,0.0872538335327444,0.98749093327581,2,6
|
| 22 |
+
d4346f5c-0084-437f-a2ab-3eaedfc55c11.mp4,21.600625038146973,2.7130613327026367,0.1256010568171677,86.63333333333333,29.301524936051145,Mountain,35,0.0324001275002956,0.0119790434837341,0.0899387215730463,0.4335584335421187,0.3625504576623417,0.0055937685794973,0.0050580248547097,0.1096177840090553,0.0100087439759552,0.9993855382951184,2,8
|
| 23 |
+
d69e637f-ced7-4a94-b11f-7e3a7fc584c5.mp4,42.82363224029541,7.869690895080566,0.1837698131471316,88.47000000000001,21.705490783403683,Sea,38,0.0484559424221515,0.0249015484005212,0.1273258361643435,0.435330105384125,0.4183141918787222,0.0173588259087107,0.0059984366719921,0.2724476860040493,0.0288331306644454,0.99976134757579,2,8
|
| 24 |
+
da4c28f0-dce4-49b6-b992-df633c640d41.mp4,127.13426303863524,33.8479118347168,0.2662375273645205,98.95,14.78405775820291,Forest,33,0.6543295979499817,0.2910408973693847,0.1373758221723152,0.5046602271961581,0.3814980234424291,0.1978286082474226,0.0325104215492804,0.4851184786004373,0.2947656396438612,0.9992652571806604,2,6
|
| 25 |
+
dcc6663d-5979-4b94-8780-8f08f1459367.mp4,54.03766345977783,16.396857261657715,0.3034338683770529,94.12333333333332,23.759774352151624,Street,33,0.370427519083023,0.2039624601602554,0.133535066310104,0.339642842742698,0.3444164673070249,0.1023351721047729,0.0232562640060981,0.3504134883616999,0.1369291179189364,0.9939171225978608,1,6
|
| 26 |
+
e2dd775b-6dbe-49fd-876e-3e9b4a2e80cd.mp4,52.91536045074463,9.409367561340332,0.1778192094164961,93.73666666666666,13.593667907575028,Buildings,36,0.1354481726884842,0.0743122622370719,0.1634576248055144,0.3116403942588515,0.4550657866970771,0.0439209715183308,0.0097172350312272,0.1103883875206228,0.0768300366208766,0.9770803141912644,1,6
|
| 27 |
+
ea63ccc6-22fa-4b9b-91eb-67da4241ed6d.mp4,51.6378231048584,11.61158561706543,0.2248659009789462,94.10333333333334,10.662733148504527,Buildings,35,0.0870254561305046,0.0478470772504806,0.1353242805091566,0.4523556977557734,0.3012495941855623,0.0307206434675966,0.008035152995338,0.143403323368975,0.0540664242644898,0.9066783540598498,2,6
|
| 28 |
+
f0bab96c-7918-40b7-8e75-470ee67f2ce4.mp4,50.66206169128418,19.06877613067627,0.3763916329910599,93.69666666666669,22.65985438616542,Sea,31,0.138214036822319,0.0643599405884742,0.4485925112259545,0.0523797637873839,0.3629621079527614,0.0136145792224255,0.0061063171985248,0.8541742905320562,0.0238152737416137,0.9990574532561204,2,8
|
| 29 |
+
f67fdc5d-c16c-434f-88b6-2885b6e16244.mp4,4.923687934875488,0.8116235733032227,0.16484057967084526,95.21,12.12852561266281,Street,37,0.037277165800333,0.0159068945795297,0.1130266998501148,0.5849823456829463,0.2362355845124187,0.0032554956997445,0.005870988437285,0.3444003885611866,0.0038838354199269,0.9677222326473224,1,7
|
neurons/miner.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
import uuid
|
| 3 |
+
import traceback
|
| 4 |
+
import os
|
| 5 |
+
from typing import Tuple
|
| 6 |
+
from loguru import logger
|
| 7 |
+
import bittensor as bt
|
| 8 |
+
from vidaio_subnet_core.base.miner import BaseMiner
|
| 9 |
+
from vidaio_subnet_core.protocol import VideoUpscalingProtocol, LengthCheckProtocol, ContentLength, VideoCompressionProtocol, TaskWarrantProtocol, TaskType
|
| 10 |
+
from services.miner_utilities.miner_utils import video_upscaler, video_compressor
|
| 11 |
+
|
| 12 |
+
from vidaio_subnet_core.utilities.version import check_version
|
| 13 |
+
|
| 14 |
+
MAX_CONTENT_LEN = ContentLength.FIVE
|
| 15 |
+
warrant_task = TaskType.COMPRESSION
|
| 16 |
+
|
| 17 |
+
VALIDATOR_HOTKEYS = [
|
| 18 |
+
"5FR392L6eKrTGwUjQurpxw89YUe7LzKANktDwRKLYYSgwxhb",
|
| 19 |
+
"5E2LP6EnZ54m3wS8s1yPvD5c3xo71kQroBw7aUVK32TKeZ5u",
|
| 20 |
+
"5CyiseaJEUjRpE1nLLYZBNk3Akty4yugUZSyBg8H4BfRsn85",
|
| 21 |
+
"5EUqqYFhNeYbZCm6UTQ3SULNDibNs2ZVjukV8pqeYZA6ms85"
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
class Miner(BaseMiner):
|
| 25 |
+
def __init__(self, config: dict | None = None) -> None:
|
| 26 |
+
"""
|
| 27 |
+
Initializes the Miner instance.
|
| 28 |
+
"""
|
| 29 |
+
super().__init__()
|
| 30 |
+
|
| 31 |
+
async def forward_upscaling_requests(self, synapse: VideoUpscalingProtocol) -> VideoUpscalingProtocol:
|
| 32 |
+
"""
|
| 33 |
+
Processes a video upscaling request by downloading, upscaling,
|
| 34 |
+
uploading, and returning a sharing link.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
should_blacklist, reason = self.blacklist_all_requests(synapse)
|
| 38 |
+
if should_blacklist:
|
| 39 |
+
logger.warning(f"Upscaling Request blacklisted: {reason}")
|
| 40 |
+
return synapse
|
| 41 |
+
|
| 42 |
+
start_time = time.time()
|
| 43 |
+
|
| 44 |
+
task_type: str = synapse.miner_payload.task_type
|
| 45 |
+
payload_url: str = synapse.miner_payload.reference_video_url
|
| 46 |
+
validator_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 47 |
+
|
| 48 |
+
logger.info(f"✅✅✅ Receiving {task_type} Request from validator: {synapse.dendrite.hotkey} with uid: {validator_uid}: round_id : {synapse.round_id}")
|
| 49 |
+
|
| 50 |
+
check_version(synapse.version)
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
processed_video_url = await video_upscaler(payload_url, task_type)
|
| 54 |
+
|
| 55 |
+
if processed_video_url is None:
|
| 56 |
+
logger.info(f"💔 Failed to upscaling video 💔")
|
| 57 |
+
return synapse
|
| 58 |
+
|
| 59 |
+
synapse.miner_response.optimized_video_url = processed_video_url
|
| 60 |
+
|
| 61 |
+
processed_time = time.time() - start_time
|
| 62 |
+
|
| 63 |
+
logger.info(f"💜 Returning Response, Processed in {processed_time:.2f} seconds 💜")
|
| 64 |
+
|
| 65 |
+
return synapse
|
| 66 |
+
|
| 67 |
+
except Exception as e:
|
| 68 |
+
logger.error(f"Failed to process upscaling request: {e}")
|
| 69 |
+
traceback.print_exc()
|
| 70 |
+
return synapse
|
| 71 |
+
|
| 72 |
+
async def forward_compression_requests(self, synapse: VideoCompressionProtocol) -> VideoCompressionProtocol:
|
| 73 |
+
"""
|
| 74 |
+
Processes a video compression request by downloading, compressing,
|
| 75 |
+
uploading, and returning a sharing link.
|
| 76 |
+
"""
|
| 77 |
+
should_blacklist, reason = self.blacklist_all_requests(synapse)
|
| 78 |
+
if should_blacklist:
|
| 79 |
+
logger.warning(f"Compression Request blacklisted: {reason}")
|
| 80 |
+
return synapse
|
| 81 |
+
|
| 82 |
+
start_time = time.time()
|
| 83 |
+
|
| 84 |
+
payload_url: str = synapse.miner_payload.reference_video_url
|
| 85 |
+
vmaf_threshold: float = synapse.miner_payload.vmaf_threshold
|
| 86 |
+
validator_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 87 |
+
|
| 88 |
+
logger.info(f"🛜🛜🛜 Receiving CompressionRequest from validator: {synapse.dendrite.hotkey} with uid: {validator_uid} 🛜🛜🛜")
|
| 89 |
+
logger.info(f"Compression Parameters - VMAF Threshold: {vmaf_threshold}, Payload URL: {payload_url}")
|
| 90 |
+
|
| 91 |
+
check_version(synapse.version)
|
| 92 |
+
|
| 93 |
+
try:
|
| 94 |
+
processed_video_url = await video_compressor(payload_url, vmaf_threshold)
|
| 95 |
+
|
| 96 |
+
if processed_video_url is None:
|
| 97 |
+
logger.info(f"💔 Failed to compress video 💔")
|
| 98 |
+
return synapse
|
| 99 |
+
|
| 100 |
+
synapse.miner_response.optimized_video_url = processed_video_url
|
| 101 |
+
|
| 102 |
+
processed_time = time.time() - start_time
|
| 103 |
+
|
| 104 |
+
logger.info(f"💜 Returning Response, Processed in {processed_time:.2f} seconds 💜\n"
|
| 105 |
+
f"✅ Video compressed successfully. Optimized video URL: {processed_video_url}")
|
| 106 |
+
|
| 107 |
+
return synapse
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
logger.error(f"Failed to process compression request: {e}")
|
| 111 |
+
traceback.print_exc()
|
| 112 |
+
return synapse
|
| 113 |
+
|
| 114 |
+
async def forward_length_check_requests(self, synapse: LengthCheckProtocol) -> LengthCheckProtocol:
|
| 115 |
+
|
| 116 |
+
validator_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 117 |
+
logger.info(f"⭐️⭐️⭐️ Receiving LengthCheckRequest from validator: {synapse.dendrite.hotkey} with uid: {validator_uid} ⭐️⭐️⭐️")
|
| 118 |
+
|
| 119 |
+
check_version(synapse.version)
|
| 120 |
+
|
| 121 |
+
synapse.max_content_length = MAX_CONTENT_LEN
|
| 122 |
+
|
| 123 |
+
return synapse
|
| 124 |
+
|
| 125 |
+
async def forward_task_warrant_requests(self, synapse: TaskWarrantProtocol) -> TaskWarrantProtocol:
|
| 126 |
+
"""
|
| 127 |
+
Processes a task warrant request by verifying the task type and returning a warrant.
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
validator_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 131 |
+
logger.info(f"🌕🌕🌕 Receiving TaskWarrantRequest from validator: {synapse.dendrite.hotkey} with uid: {validator_uid} 🌕🌕🌕")
|
| 132 |
+
|
| 133 |
+
check_version(synapse.version)
|
| 134 |
+
|
| 135 |
+
synapse.warrant_task = warrant_task
|
| 136 |
+
|
| 137 |
+
return synapse
|
| 138 |
+
|
| 139 |
+
async def blacklist_upscaling_requests(self, synapse: VideoUpscalingProtocol) -> Tuple[bool, str]:
|
| 140 |
+
"""
|
| 141 |
+
Determines whether a request should be blacklisted based on the hotkey status.
|
| 142 |
+
"""
|
| 143 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 144 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 145 |
+
return True, "Missing dendrite or hotkey"
|
| 146 |
+
|
| 147 |
+
uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 148 |
+
|
| 149 |
+
if not self.metagraph.validator_permit[uid]:
|
| 150 |
+
logger.warning(f"Blacklisting non-validator hotkey {synapse.dendrite.hotkey}")
|
| 151 |
+
return True, "Non-validator hotkey"
|
| 152 |
+
|
| 153 |
+
logger.trace(f"Hotkey {synapse.dendrite.hotkey} recognized and allowed.")
|
| 154 |
+
return False, "Hotkey recognized!"
|
| 155 |
+
|
| 156 |
+
async def priority_upscaling_requests(self, synapse: VideoUpscalingProtocol) -> float:
|
| 157 |
+
"""
|
| 158 |
+
Assigns a priority to requests based on the stake of the requesting entity.
|
| 159 |
+
Higher stakes result in higher priority.
|
| 160 |
+
"""
|
| 161 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 162 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 163 |
+
return 0.0
|
| 164 |
+
|
| 165 |
+
caller_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 166 |
+
priority: float = float(self.metagraph.S[caller_uid])
|
| 167 |
+
|
| 168 |
+
logger.trace(f"Prioritizing {synapse.dendrite.hotkey} with value: {priority}")
|
| 169 |
+
return priority
|
| 170 |
+
|
| 171 |
+
async def blacklist_length_check_requests(self, synapse: LengthCheckProtocol) -> Tuple[bool, str]:
|
| 172 |
+
"""
|
| 173 |
+
Determines whether a request should be blacklisted based on the hotkey status.
|
| 174 |
+
"""
|
| 175 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 176 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 177 |
+
return True, "Missing dendrite or hotkey"
|
| 178 |
+
|
| 179 |
+
uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 180 |
+
|
| 181 |
+
if not self.metagraph.validator_permit[uid]:
|
| 182 |
+
logger.warning(f"Blacklisting non-validator hotkey {synapse.dendrite.hotkey}")
|
| 183 |
+
return True, "Non-validator hotkey"
|
| 184 |
+
|
| 185 |
+
logger.trace(f"Hotkey {synapse.dendrite.hotkey} recognized and allowed.")
|
| 186 |
+
return False, "Hotkey recognized!"
|
| 187 |
+
|
| 188 |
+
async def priority_length_check_requests(self, synapse: LengthCheckProtocol) -> float:
|
| 189 |
+
"""
|
| 190 |
+
Assigns a priority to requests based on the stake of the requesting entity.
|
| 191 |
+
Higher stakes result in higher priority.
|
| 192 |
+
"""
|
| 193 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 194 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 195 |
+
return 0.0
|
| 196 |
+
|
| 197 |
+
caller_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 198 |
+
priority: float = float(self.metagraph.S[caller_uid])
|
| 199 |
+
|
| 200 |
+
logger.trace(f"Prioritizing {synapse.dendrite.hotkey} with value: {priority}")
|
| 201 |
+
return priority
|
| 202 |
+
|
| 203 |
+
async def blacklist_compression_requests(self, synapse: VideoCompressionProtocol) -> Tuple[bool, str]:
|
| 204 |
+
"""
|
| 205 |
+
Determines whether a request should be blacklisted based on the hotkey status.
|
| 206 |
+
"""
|
| 207 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 208 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 209 |
+
return True, "Missing dendrite or hotkey"
|
| 210 |
+
|
| 211 |
+
uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 212 |
+
|
| 213 |
+
if not self.metagraph.validator_permit[uid]:
|
| 214 |
+
logger.warning(f"Blacklisting non-validator hotkey {synapse.dendrite.hotkey}")
|
| 215 |
+
return True, "Non-validator hotkey"
|
| 216 |
+
|
| 217 |
+
logger.trace(f"Hotkey {synapse.dendrite.hotkey} recognized and allowed.")
|
| 218 |
+
return False, "Hotkey recognized!"
|
| 219 |
+
|
| 220 |
+
async def priority_compression_requests(self, synapse: VideoCompressionProtocol) -> float:
|
| 221 |
+
"""
|
| 222 |
+
Assigns a priority to requests based on the stake of the requesting entity.
|
| 223 |
+
Higher stakes result in higher priority.
|
| 224 |
+
"""
|
| 225 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 226 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 227 |
+
return 0.0
|
| 228 |
+
|
| 229 |
+
caller_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 230 |
+
priority: float = float(self.metagraph.S[caller_uid])
|
| 231 |
+
|
| 232 |
+
logger.trace(f"Prioritizing {synapse.dendrite.hotkey} with value: {priority}")
|
| 233 |
+
return priority
|
| 234 |
+
|
| 235 |
+
async def blacklist_task_warrant_requests(self, synapse: TaskWarrantProtocol) -> Tuple[bool, str]:
|
| 236 |
+
"""
|
| 237 |
+
Determines whether a request should be blacklisted based on the hotkey status.
|
| 238 |
+
"""
|
| 239 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 240 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 241 |
+
return True, "Missing dendrite or hotkey"
|
| 242 |
+
|
| 243 |
+
uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 244 |
+
|
| 245 |
+
if not self.metagraph.validator_permit[uid]:
|
| 246 |
+
logger.warning(f"Blacklisting non-validator hotkey {synapse.dendrite.hotkey}")
|
| 247 |
+
return True, "Non-validator hotkey"
|
| 248 |
+
|
| 249 |
+
logger.trace(f"Hotkey {synapse.dendrite.hotkey} recognized and allowed.")
|
| 250 |
+
return False, "Hotkey recognized!"
|
| 251 |
+
|
| 252 |
+
async def priority_task_warrant_requests(self, synapse: TaskWarrantProtocol) -> float:
|
| 253 |
+
"""
|
| 254 |
+
Assigns a priority to requests based on the stake of the requesting entity.
|
| 255 |
+
Higher stakes result in higher priority.
|
| 256 |
+
"""
|
| 257 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 258 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 259 |
+
return 0.0
|
| 260 |
+
|
| 261 |
+
caller_uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 262 |
+
priority: float = float(self.metagraph.S[caller_uid])
|
| 263 |
+
|
| 264 |
+
logger.trace(f"Prioritizing {synapse.dendrite.hotkey} with value: {priority}")
|
| 265 |
+
return priority
|
| 266 |
+
|
| 267 |
+
def blacklist_all_requests(self, synapse) -> Tuple[bool, str]:
|
| 268 |
+
"""
|
| 269 |
+
Determines whether a request should be blacklisted based on the hotkey status.
|
| 270 |
+
"""
|
| 271 |
+
if not synapse.dendrite or not synapse.dendrite.hotkey:
|
| 272 |
+
logger.warning("Received a request without a dendrite or hotkey.")
|
| 273 |
+
return True, "Missing dendrite or hotkey"
|
| 274 |
+
|
| 275 |
+
uid: int = self.metagraph.hotkeys.index(synapse.dendrite.hotkey)
|
| 276 |
+
|
| 277 |
+
if not self.metagraph.validator_permit[uid]:
|
| 278 |
+
logger.warning(f"Blacklisting non-validator hotkey {synapse.dendrite.hotkey}")
|
| 279 |
+
return True, "Non-validator hotkey"
|
| 280 |
+
|
| 281 |
+
hotkey: str = synapse.dendrite.hotkey
|
| 282 |
+
if hotkey not in VALIDATOR_HOTKEYS:
|
| 283 |
+
logger.warning(f"Validator hotkey {hotkey} is not recognized.")
|
| 284 |
+
return True, "Validator hotkey not recognized."
|
| 285 |
+
|
| 286 |
+
if hotkey != VALIDATOR_HOTKEYS[0]:
|
| 287 |
+
logger.warning(f"Validator hotkey {hotkey} is not authorized to make requests at this time because of staking amount.",
|
| 288 |
+
f" Only {VALIDATOR_HOTKEYS[0]} is authorized.",
|
| 289 |
+
f" Request denied.")
|
| 290 |
+
return True, "Validator hotkey not authorized."
|
| 291 |
+
|
| 292 |
+
logger.trace(f"Hotkey {synapse.dendrite.hotkey} recognized and allowed.")
|
| 293 |
+
return False, "Hotkey recognized!"
|
| 294 |
+
|
| 295 |
+
if __name__ == "__main__":
|
| 296 |
+
with Miner() as miner:
|
| 297 |
+
while True:
|
| 298 |
+
logger.info(f"Miner running... {time.time()}")
|
| 299 |
+
time.sleep(50)
|
neurons/validator.py
ADDED
|
@@ -0,0 +1,1227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
import uuid
|
| 4 |
+
import httpx
|
| 5 |
+
import random
|
| 6 |
+
import asyncio
|
| 7 |
+
import traceback
|
| 8 |
+
import pandas as pd
|
| 9 |
+
import bittensor as bt
|
| 10 |
+
from loguru import logger
|
| 11 |
+
from datetime import datetime, timezone
|
| 12 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 13 |
+
from vidaio_subnet_core.utilities.version import get_version
|
| 14 |
+
from vidaio_subnet_core import validating, CONFIG, base, protocol
|
| 15 |
+
from vidaio_subnet_core.utilities.wandb_manager import WandbManager
|
| 16 |
+
from services.video_scheduler.video_utils import get_trim_video_path
|
| 17 |
+
from vidaio_subnet_core.utilities.uids import get_organic_forward_uids
|
| 18 |
+
from vidaio_subnet_core.protocol import LengthCheckProtocol, TaskWarrantProtocol, TaskType
|
| 19 |
+
from vidaio_subnet_core.validating.managing.sql_schemas import MinerMetadata, MinerPerformanceHistory, Base
|
| 20 |
+
from sqlalchemy import desc, asc, func, select, delete
|
| 21 |
+
from sqlalchemy.ext.asyncio import AsyncSession
|
| 22 |
+
from datetime import datetime, timedelta
|
| 23 |
+
from services.dashboard.server import send_upscaling_data_to_dashboard, send_compression_data_to_dashboard
|
| 24 |
+
from services.video_scheduler.redis_utils import (
|
| 25 |
+
get_redis_connection,
|
| 26 |
+
get_organic_upscaling_queue_size,
|
| 27 |
+
get_organic_compression_queue_size,
|
| 28 |
+
set_scheduler_ready
|
| 29 |
+
)
|
| 30 |
+
from typing import List, Tuple, Any
|
| 31 |
+
|
| 32 |
+
VMAF_QUALITY_THRESHOLDS = [
|
| 33 |
+
85, #Low
|
| 34 |
+
90, #Medium
|
| 35 |
+
95, #High
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
SLEEP_TIME_LOW = 60 * 3 # 3 minutes
|
| 39 |
+
SLEEP_TIME_HIGH = 60 * 4 # 4 minutes
|
| 40 |
+
|
| 41 |
+
class Validator(base.BaseValidator):
|
| 42 |
+
def __init__(self):
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.miner_manager = validating.managing.MinerManager(
|
| 45 |
+
uid=self.uid, config=self.config, wallet=self.wallet, metagraph=self.metagraph
|
| 46 |
+
)
|
| 47 |
+
logger.info("💧 Initialized miner manager 💧")
|
| 48 |
+
|
| 49 |
+
self.challenge_synthesizer = validating.synthesizing.Synthesizer()
|
| 50 |
+
logger.info("💧 Initialized challenge synthesizer 💧")
|
| 51 |
+
|
| 52 |
+
self.dendrite = bt.dendrite(wallet=self.wallet)
|
| 53 |
+
logger.info("💧 Initialized dendrite 💧")
|
| 54 |
+
|
| 55 |
+
self.score_client = httpx.AsyncClient(
|
| 56 |
+
base_url=f"http://{CONFIG.score.host}:{CONFIG.score.port}"
|
| 57 |
+
)
|
| 58 |
+
logger.info(
|
| 59 |
+
f"💧 Initialized score client with base URL: http://{CONFIG.score.host}:{CONFIG.score.port} 💧"
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
self.set_weights_executor = ThreadPoolExecutor(max_workers=1)
|
| 63 |
+
logger.info("💙 Initialized setting weights executor 💙")
|
| 64 |
+
|
| 65 |
+
self.redis_conn = get_redis_connection()
|
| 66 |
+
logger.info("💙 Initialized Redis connection 💙")
|
| 67 |
+
|
| 68 |
+
self.wandb_manager = WandbManager(validator=self)
|
| 69 |
+
logger.info("🔑 Initialized Wandb Manager 🔑")
|
| 70 |
+
|
| 71 |
+
self.organic_gateway_base_url = f"http://localhost:{CONFIG.organic_gateway.port}"
|
| 72 |
+
|
| 73 |
+
self.push_result_endpoint = f"http://{CONFIG.video_scheduler.host}:{CONFIG.video_scheduler.port}/api/push_result"
|
| 74 |
+
|
| 75 |
+
self.scheduler_ready_endpoint = f"http://{CONFIG.video_scheduler.host}:{CONFIG.video_scheduler.port}/api/scheduler_ready"
|
| 76 |
+
|
| 77 |
+
async def check_scheduler_ready(self) -> bool:
|
| 78 |
+
"""
|
| 79 |
+
Check if the video scheduler is ready to process synthetic requests.
|
| 80 |
+
|
| 81 |
+
Returns:
|
| 82 |
+
bool: True if scheduler is ready, False otherwise
|
| 83 |
+
"""
|
| 84 |
+
try:
|
| 85 |
+
async with httpx.AsyncClient() as client:
|
| 86 |
+
response = await client.get(self.scheduler_ready_endpoint, timeout=10)
|
| 87 |
+
response.raise_for_status()
|
| 88 |
+
data = response.json()
|
| 89 |
+
return data.get("ready", False)
|
| 90 |
+
except httpx.HTTPStatusError as e:
|
| 91 |
+
logger.error(f"HTTP error checking scheduler readiness: {e.response.status_code}")
|
| 92 |
+
return False
|
| 93 |
+
except httpx.RequestError as e:
|
| 94 |
+
logger.error(f"Request error checking scheduler readiness: {e}")
|
| 95 |
+
return False
|
| 96 |
+
except Exception as e:
|
| 97 |
+
logger.error(f"Unexpected error checking scheduler readiness: {e}")
|
| 98 |
+
return False
|
| 99 |
+
|
| 100 |
+
async def wait_for_scheduler_ready(self) -> None:
|
| 101 |
+
"""
|
| 102 |
+
Wait for the scheduler to be ready before proceeding with synthetic requests.
|
| 103 |
+
"""
|
| 104 |
+
logger.info("🔄 Checking if video scheduler is ready...")
|
| 105 |
+
|
| 106 |
+
while not await self.check_scheduler_ready():
|
| 107 |
+
logger.info("⏳ Waiting for scheduler server to be ready (all synthetic queues need to be populated)...")
|
| 108 |
+
await asyncio.sleep(10)
|
| 109 |
+
|
| 110 |
+
logger.info("✅ Scheduler is ready! Proceeding with synthetic requests.")
|
| 111 |
+
|
| 112 |
+
async def refresh_miner_manager(self, miner_uids: list[int]):
|
| 113 |
+
if not miner_uids:
|
| 114 |
+
return
|
| 115 |
+
|
| 116 |
+
miner_hotkeys = [self.metagraph.hotkeys[uid] for uid in miner_uids]
|
| 117 |
+
|
| 118 |
+
# Single query to fetch all miners
|
| 119 |
+
stmt = select(MinerMetadata).where(MinerMetadata.uid.in_(miner_uids))
|
| 120 |
+
result = self.miner_manager.session.execute(stmt)
|
| 121 |
+
miners = {miner.uid: miner for miner in result.scalars().all()}
|
| 122 |
+
|
| 123 |
+
delete_uids = []
|
| 124 |
+
|
| 125 |
+
for uid, latest_hotkey in zip(miner_uids, miner_hotkeys):
|
| 126 |
+
miner = miners.get(uid)
|
| 127 |
+
if miner is None:
|
| 128 |
+
continue
|
| 129 |
+
|
| 130 |
+
if miner.hotkey != latest_hotkey:
|
| 131 |
+
delete_uids.append(uid)
|
| 132 |
+
logger.info(
|
| 133 |
+
f"UID {uid} hotkey changed from {miner.hotkey} → {latest_hotkey}"
|
| 134 |
+
)
|
| 135 |
+
miner.hotkey = latest_hotkey # update record in-memory
|
| 136 |
+
|
| 137 |
+
if delete_uids:
|
| 138 |
+
delete_stmt = delete(MinerPerformanceHistory).where(
|
| 139 |
+
MinerPerformanceHistory.uid.in_(delete_uids)
|
| 140 |
+
)
|
| 141 |
+
self.miner_manager.session.execute(delete_stmt)
|
| 142 |
+
|
| 143 |
+
self.miner_manager.session.commit()
|
| 144 |
+
|
| 145 |
+
async def start_synthetic_epoch(self):
|
| 146 |
+
# Wait for scheduler to be ready first
|
| 147 |
+
await self.wait_for_scheduler_ready()
|
| 148 |
+
|
| 149 |
+
logger.info("✅✅✅✅✅ Starting synthetic forward ✅✅✅✅✅")
|
| 150 |
+
epoch_start_time = time.time()
|
| 151 |
+
|
| 152 |
+
miner_uids = self.filter_miners()
|
| 153 |
+
logger.debug(f"Initialized {len(miner_uids)} subnet neurons of total {len(self.metagraph.S)} neurons")
|
| 154 |
+
|
| 155 |
+
uids = self.miner_manager.consume(miner_uids)
|
| 156 |
+
|
| 157 |
+
logger.info(f"Filtered UIDs after consumption: {uids}")
|
| 158 |
+
|
| 159 |
+
random_uids = uids.copy()
|
| 160 |
+
random.shuffle(random_uids)
|
| 161 |
+
logger.info(f"Randomized UIDs: {random_uids}")
|
| 162 |
+
|
| 163 |
+
axons = [self.metagraph.axons[uid] for uid in random_uids]
|
| 164 |
+
|
| 165 |
+
logger.info(f"Sending TaskWarrantProtocol requests to {len(axons)} miners")
|
| 166 |
+
|
| 167 |
+
version = get_version()
|
| 168 |
+
task_warrant_synapse = TaskWarrantProtocol(version=version)
|
| 169 |
+
|
| 170 |
+
task_warrant_responses = await self.dendrite.forward(
|
| 171 |
+
axons=axons, synapse=task_warrant_synapse, timeout=10
|
| 172 |
+
)
|
| 173 |
+
logger.info(f"💊 Received {len(task_warrant_responses)} responses from miners for TaskWarrantProtocol requests💊")
|
| 174 |
+
|
| 175 |
+
upscaling_miners = []
|
| 176 |
+
compression_miners = []
|
| 177 |
+
unknown_task_miners = []
|
| 178 |
+
|
| 179 |
+
for i, response in enumerate(task_warrant_responses):
|
| 180 |
+
uid = random_uids[i]
|
| 181 |
+
axon = axons[i]
|
| 182 |
+
|
| 183 |
+
if response.warrant_task == TaskType.UPSCALING:
|
| 184 |
+
upscaling_miners.append((axon, uid))
|
| 185 |
+
elif response.warrant_task == TaskType.COMPRESSION:
|
| 186 |
+
compression_miners.append((axon, uid))
|
| 187 |
+
else:
|
| 188 |
+
unknown_task_miners.append(uid)
|
| 189 |
+
|
| 190 |
+
if unknown_task_miners:
|
| 191 |
+
logger.info(f"🔍 Checking database for {len(unknown_task_miners)} unknown task warrant miners")
|
| 192 |
+
db_task_types = self.miner_manager.get_miner_processing_task_types(unknown_task_miners)
|
| 193 |
+
|
| 194 |
+
resolved_from_db = []
|
| 195 |
+
defaulted_to_upscaling = []
|
| 196 |
+
|
| 197 |
+
for i, uid in enumerate(unknown_task_miners):
|
| 198 |
+
axon = axons[random_uids.index(uid)]
|
| 199 |
+
|
| 200 |
+
if uid in db_task_types:
|
| 201 |
+
db_task_type = db_task_types[uid]
|
| 202 |
+
if db_task_type == "upscaling":
|
| 203 |
+
upscaling_miners.append((axon, uid))
|
| 204 |
+
resolved_from_db.append(f"{uid}(upscaling)")
|
| 205 |
+
elif db_task_type == "compression":
|
| 206 |
+
compression_miners.append((axon, uid))
|
| 207 |
+
resolved_from_db.append(f"{uid}(compression)")
|
| 208 |
+
else:
|
| 209 |
+
upscaling_miners.append((axon, uid))
|
| 210 |
+
defaulted_to_upscaling.append(f"{uid}(unknown_db_value:{db_task_type})")
|
| 211 |
+
else:
|
| 212 |
+
upscaling_miners.append((axon, uid))
|
| 213 |
+
defaulted_to_upscaling.append(f"{uid}(no_db_record)")
|
| 214 |
+
|
| 215 |
+
upscaling_uids = [uid for _, uid in upscaling_miners]
|
| 216 |
+
compression_uids = [uid for _, uid in compression_miners]
|
| 217 |
+
all_uids = upscaling_uids + compression_uids
|
| 218 |
+
|
| 219 |
+
await self.refresh_miner_manager(all_uids)
|
| 220 |
+
|
| 221 |
+
logger.info(f"🛜 Grouped miners: {len(upscaling_miners)} upscaling, {len(compression_miners)} compression 🛜")
|
| 222 |
+
if upscaling_uids:
|
| 223 |
+
logger.info(f"📈 Upscaling UIDs: {upscaling_uids}")
|
| 224 |
+
if compression_uids:
|
| 225 |
+
logger.info(f"📉 Compression UIDs: {compression_uids}")
|
| 226 |
+
if unknown_task_miners:
|
| 227 |
+
logger.info(f"❓ Unknown task UIDs processed: {unknown_task_miners}")
|
| 228 |
+
|
| 229 |
+
if upscaling_miners:
|
| 230 |
+
logger.info(f"Sending LengthCheckProtocol requests to {len(upscaling_miners)} upscaling miners")
|
| 231 |
+
|
| 232 |
+
upscaling_start_time = time.time()
|
| 233 |
+
upscaling_axons = [miner[0] for miner in upscaling_miners]
|
| 234 |
+
length_check_synapse = LengthCheckProtocol(version=version)
|
| 235 |
+
|
| 236 |
+
length_check_responses = await self.dendrite.forward(
|
| 237 |
+
axons=upscaling_axons, synapse=length_check_synapse, timeout=10
|
| 238 |
+
)
|
| 239 |
+
logger.info(f"💊 Received {len(length_check_responses)} responses from upscaling miners for LengthCheckProtocol requests💊")
|
| 240 |
+
|
| 241 |
+
upscaling_content_lengths = []
|
| 242 |
+
for response in length_check_responses:
|
| 243 |
+
avail_max_len = response.max_content_length.value
|
| 244 |
+
if avail_max_len == 10:
|
| 245 |
+
upscaling_content_lengths.append(avail_max_len)
|
| 246 |
+
else:
|
| 247 |
+
upscaling_content_lengths.append(5)
|
| 248 |
+
|
| 249 |
+
logger.info(f"Upscaling content lengths: {upscaling_content_lengths}")
|
| 250 |
+
|
| 251 |
+
upscaling_miners_with_lengths = []
|
| 252 |
+
for i, (axon, uid) in enumerate(upscaling_miners):
|
| 253 |
+
content_length = upscaling_content_lengths[i] if i < len(upscaling_content_lengths) else 5
|
| 254 |
+
upscaling_miners_with_lengths.append((axon, uid, content_length))
|
| 255 |
+
|
| 256 |
+
await self.process_upscaling_miners(upscaling_miners_with_lengths, version)
|
| 257 |
+
|
| 258 |
+
upscaling_processed_time = time.time() - upscaling_start_time
|
| 259 |
+
|
| 260 |
+
logger.info(f"Upscaling tasks processed in {upscaling_processed_time:.2f} seconds")
|
| 261 |
+
|
| 262 |
+
await asyncio.sleep(2)
|
| 263 |
+
|
| 264 |
+
if compression_miners:
|
| 265 |
+
logger.info(f"Processing {len(compression_miners)} compression miners")
|
| 266 |
+
|
| 267 |
+
compression_start_time = time.time()
|
| 268 |
+
await self.process_compression_miners(compression_miners, version)
|
| 269 |
+
|
| 270 |
+
compression_processed_time = time.time() - compression_start_time
|
| 271 |
+
|
| 272 |
+
logger.info(f"Compression tasks processed in {compression_processed_time:.2f} seconds")
|
| 273 |
+
|
| 274 |
+
await asyncio.sleep(2)
|
| 275 |
+
|
| 276 |
+
epoch_processed_time = time.time() - epoch_start_time
|
| 277 |
+
logger.info(f"Completed one epoch within {epoch_processed_time:.2f} seconds")
|
| 278 |
+
|
| 279 |
+
if epoch_processed_time < 60: # if epoch completed within 60 seconds in case of no miners requiring synth checking, sleep for 10 minutes
|
| 280 |
+
await asyncio.sleep(60 * 10)
|
| 281 |
+
else:
|
| 282 |
+
await asyncio.sleep(2)
|
| 283 |
+
|
| 284 |
+
# --------------------------------------------------------------------------- #
|
| 285 |
+
# Helper – turn the raw list of (axon, uid) into a dict {uid: (axon, uid)}
|
| 286 |
+
# --------------------------------------------------------------------------- #
|
| 287 |
+
def _miner_lookup(self, miners: List[Tuple[Any, int]]) -> dict[int, Tuple[Any, int]]:
|
| 288 |
+
"""{uid: (axon, uid)} – fast lookup while preserving the original tuple."""
|
| 289 |
+
return {uid: (axon, uid) for axon, uid in miners}
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
async def create_miner_batches(
|
| 293 |
+
self,
|
| 294 |
+
miners: List[Tuple[Any, int]],
|
| 295 |
+
batch_size: int,
|
| 296 |
+
task_type: str = "upscaling",
|
| 297 |
+
) -> List[List[Tuple[Any, int]]]:
|
| 298 |
+
"""
|
| 299 |
+
Build batches of miners for *task_type* (upscaling / compression).
|
| 300 |
+
|
| 301 |
+
Rules
|
| 302 |
+
-----
|
| 303 |
+
1. Miners with **fewest** performance-history rows go first.
|
| 304 |
+
2. Miners whose **most recent entry** is older than 2 h are eligible, subject to 70% probability of selection in batching.
|
| 305 |
+
3. Miners that already have >= CONFIG.score.max_performance_records rows
|
| 306 |
+
**and** whose latest entry is < 2 h are **skipped**.
|
| 307 |
+
"""
|
| 308 |
+
session = self.miner_manager.session
|
| 309 |
+
hours_threshold = datetime.utcnow() - timedelta(hours=CONFIG.score.synthetics_hours_threshold)
|
| 310 |
+
max_records = CONFIG.score.max_performance_records
|
| 311 |
+
|
| 312 |
+
info_new_uids = []
|
| 313 |
+
|
| 314 |
+
# ------------------------------------------------------------------- #
|
| 315 |
+
# 1. One-shot aggregated stats per UID (count + latest timestamp)
|
| 316 |
+
# ------------------------------------------------------------------- #
|
| 317 |
+
uid_stats_subq = (
|
| 318 |
+
session.query(
|
| 319 |
+
MinerPerformanceHistory.uid,
|
| 320 |
+
func.count(MinerPerformanceHistory.id).label("record_count"),
|
| 321 |
+
func.max(MinerPerformanceHistory.timestamp).label("latest_timestamp"),
|
| 322 |
+
)
|
| 323 |
+
.filter(
|
| 324 |
+
MinerPerformanceHistory.processed_task_type == task_type,
|
| 325 |
+
MinerPerformanceHistory.uid.in_([uid for _axon, uid in miners]),
|
| 326 |
+
)
|
| 327 |
+
.group_by(MinerPerformanceHistory.uid)
|
| 328 |
+
.subquery()
|
| 329 |
+
)
|
| 330 |
+
|
| 331 |
+
uid_stats = session.query(
|
| 332 |
+
uid_stats_subq.c.uid,
|
| 333 |
+
uid_stats_subq.c.record_count,
|
| 334 |
+
uid_stats_subq.c.latest_timestamp,
|
| 335 |
+
).all()
|
| 336 |
+
|
| 337 |
+
# uid → (record_count, latest_timestamp)
|
| 338 |
+
uid_info: dict[int, Tuple[int, datetime | None]] = {
|
| 339 |
+
row.uid: (row.record_count or 0, row.latest_timestamp) for row in uid_stats
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
# ------------------------------------------------------------------- #
|
| 343 |
+
# 2. Miners that have *zero* rows are the highest priority
|
| 344 |
+
# ------------------------------------------------------------------- #
|
| 345 |
+
all_uids = {uid for _axon, uid in miners}
|
| 346 |
+
for uid in all_uids - uid_info.keys():
|
| 347 |
+
uid_info[uid] = (0, None)
|
| 348 |
+
|
| 349 |
+
# ------------------------------------------------------------------- #
|
| 350 |
+
# 3. Build a fast lookup {uid: original_tuple}
|
| 351 |
+
# ------------------------------------------------------------------- #
|
| 352 |
+
miner_by_uid = self._miner_lookup(miners)
|
| 353 |
+
|
| 354 |
+
# ------------------------------------------------------------------- #
|
| 355 |
+
# 4. Filter + priority key
|
| 356 |
+
# ------------------------------------------------------------------- #
|
| 357 |
+
eligible: List[Tuple[Tuple[int, datetime], Tuple[Any, int]]] = []
|
| 358 |
+
|
| 359 |
+
for uid in all_uids:
|
| 360 |
+
miner_tuple = miner_by_uid[uid]
|
| 361 |
+
rec_cnt, latest_ts = uid_info[uid]
|
| 362 |
+
|
| 363 |
+
# ---- Rule 3: skip capped miners with recent performance record ----
|
| 364 |
+
if rec_cnt >= max_records and latest_ts and latest_ts >= hours_threshold:
|
| 365 |
+
logger.info(
|
| 366 |
+
f"Skipping miner uid={uid}: {rec_cnt} records, last update {latest_ts}"
|
| 367 |
+
)
|
| 368 |
+
continue
|
| 369 |
+
|
| 370 |
+
# ---- Rule 2: include if never seen, below capacity or stale ----
|
| 371 |
+
if rec_cnt < max_records or not latest_ts \
|
| 372 |
+
or (latest_ts < hours_threshold and random.random() < CONFIG.score.synthetics_select_probability):
|
| 373 |
+
priority = (
|
| 374 |
+
rec_cnt, # fewer records first
|
| 375 |
+
latest_ts if latest_ts else datetime.min, # oldest first
|
| 376 |
+
)
|
| 377 |
+
if rec_cnt == 0:
|
| 378 |
+
info_new_uids.append(uid)
|
| 379 |
+
eligible.append((priority, miner_tuple))
|
| 380 |
+
|
| 381 |
+
# ------------------------------------------------------------------- #
|
| 382 |
+
# 5. Sort & batch
|
| 383 |
+
# ------------------------------------------------------------------- #
|
| 384 |
+
eligible.sort(key=lambda x: x[0]) # by priority tuple
|
| 385 |
+
sorted_miners = [miner for _prio, miner in eligible]
|
| 386 |
+
|
| 387 |
+
batches = [
|
| 388 |
+
sorted_miners[i : i + batch_size]
|
| 389 |
+
for i in range(0, len(sorted_miners), batch_size)
|
| 390 |
+
]
|
| 391 |
+
|
| 392 |
+
logger.info(
|
| 393 |
+
f"Created {len(batches)} {task_type} batches (size≤{batch_size}). "
|
| 394 |
+
f"Selected {len(sorted_miners)}/{len(miners)} miners."
|
| 395 |
+
f" New UIDs with no history: {info_new_uids}"
|
| 396 |
+
)
|
| 397 |
+
if eligible:
|
| 398 |
+
top_uid = eligible[0][1][1] # (axon, uid) → uid
|
| 399 |
+
logger.info(
|
| 400 |
+
f"Top-priority miner uid={top_uid} – records={uid_info[top_uid][0]}"
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
return batches
|
| 404 |
+
|
| 405 |
+
async def call_miner(self, axon, synapse, uid, timeout=60):
|
| 406 |
+
start = time.perf_counter()
|
| 407 |
+
|
| 408 |
+
try:
|
| 409 |
+
raw = await self.dendrite.forward(
|
| 410 |
+
axons=[axon],
|
| 411 |
+
synapse=synapse,
|
| 412 |
+
timeout=timeout,
|
| 413 |
+
)
|
| 414 |
+
duration = (time.perf_counter() - start) * 1000 # ms
|
| 415 |
+
|
| 416 |
+
try:
|
| 417 |
+
result = raw[0]
|
| 418 |
+
except Exception as e:
|
| 419 |
+
logger.error(
|
| 420 |
+
f"UID {uid} → invalid response structure after {duration:.2f} ms | {e}",
|
| 421 |
+
exc_info=True
|
| 422 |
+
)
|
| 423 |
+
return {
|
| 424 |
+
"uid": uid,
|
| 425 |
+
"result": None,
|
| 426 |
+
"error": e,
|
| 427 |
+
"latency_ms": duration,
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
logger.info(f"UID {uid} → success | {duration:.2f} ms")
|
| 431 |
+
|
| 432 |
+
return {
|
| 433 |
+
"uid": uid,
|
| 434 |
+
"result": result,
|
| 435 |
+
"error": None,
|
| 436 |
+
"latency_ms": duration,
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
except Exception as e:
|
| 440 |
+
duration = (time.perf_counter() - start) * 1000 # ms
|
| 441 |
+
|
| 442 |
+
logger.error(
|
| 443 |
+
f"UID {uid} → failure after {duration:.2f} ms | {type(e).__name__}: {e}",
|
| 444 |
+
exc_info=True
|
| 445 |
+
)
|
| 446 |
+
|
| 447 |
+
return {
|
| 448 |
+
"uid": uid,
|
| 449 |
+
"result": None, # EXACT same shape as original gather usage
|
| 450 |
+
"error": e,
|
| 451 |
+
"latency_ms": duration,
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
async def process_upscaling_miners(self, upscaling_miners_with_lengths, version):
|
| 455 |
+
"""Process upscaling miners in batches similar to the original implementation."""
|
| 456 |
+
batch_size = CONFIG.bandwidth.requests_per_synthetic_interval
|
| 457 |
+
|
| 458 |
+
upscaling_miners = [(axon, uid) for axon, uid, _ in upscaling_miners_with_lengths]
|
| 459 |
+
upscaling_content_lengths = {uid: length for _, uid, length in upscaling_miners_with_lengths}
|
| 460 |
+
|
| 461 |
+
miner_batches = await self.create_miner_batches(upscaling_miners, batch_size, task_type="upscaling")
|
| 462 |
+
|
| 463 |
+
logger.info(f"Created {len(miner_batches)} upscaling batches of size {batch_size}")
|
| 464 |
+
|
| 465 |
+
for batch_idx, batch in enumerate(miner_batches):
|
| 466 |
+
batch_start_time = time.time()
|
| 467 |
+
logger.info(f"🧩 Processing upscaling batch {batch_idx + 1}/{len(miner_batches)} 🧩")
|
| 468 |
+
|
| 469 |
+
uids = []
|
| 470 |
+
axons = []
|
| 471 |
+
content_lengths = []
|
| 472 |
+
for miner in batch:
|
| 473 |
+
content_lengths.append(upscaling_content_lengths[miner[1]])
|
| 474 |
+
uids.append(miner[1])
|
| 475 |
+
axons.append(miner[0])
|
| 476 |
+
|
| 477 |
+
round_id = str(uuid.uuid4())
|
| 478 |
+
payload_urls, video_ids, uploaded_object_names, synapses, task_types = await self.challenge_synthesizer.build_synthetic_protocol(content_lengths, version, round_id)
|
| 479 |
+
logger.info(f"Built compression challenge protocol: payload URLs: {payload_urls}\nvideo IDs: {video_ids}")
|
| 480 |
+
logger.debug(f"Built upscaling challenge protocol")
|
| 481 |
+
|
| 482 |
+
timestamp = datetime.now(timezone.utc).isoformat()
|
| 483 |
+
|
| 484 |
+
forward_tasks = [
|
| 485 |
+
self.call_miner(axon, synapse, uid, timeout=60)
|
| 486 |
+
for uid, axon, synapse in zip(uids, axons, synapses)
|
| 487 |
+
]
|
| 488 |
+
raw_responses = await asyncio.gather(*forward_tasks)
|
| 489 |
+
responses = [response['result'] for response in raw_responses]
|
| 490 |
+
|
| 491 |
+
logger.info(f"🎲 Received {len(responses)} upscaling responses from miners 🎲")
|
| 492 |
+
|
| 493 |
+
reference_video_paths = []
|
| 494 |
+
for video_id in video_ids:
|
| 495 |
+
reference_video_path = get_trim_video_path(video_id)
|
| 496 |
+
if not os.path.exists(reference_video_path):
|
| 497 |
+
logger.warning(f"⚠️ Reference video file missing for video_id {video_id}: {reference_video_path}")
|
| 498 |
+
reference_video_paths.append(reference_video_path)
|
| 499 |
+
|
| 500 |
+
asyncio.create_task(self.score_upscalings(uids, responses, payload_urls, reference_video_paths, timestamp, video_ids, uploaded_object_names, content_lengths, task_types, round_id))
|
| 501 |
+
|
| 502 |
+
batch_processed_time = time.time() - batch_start_time
|
| 503 |
+
|
| 504 |
+
sleep_time = random.uniform(SLEEP_TIME_LOW, SLEEP_TIME_HIGH) - batch_processed_time
|
| 505 |
+
logger.info(f"Completed upscaling batch within {batch_processed_time:.2f} seconds")
|
| 506 |
+
logger.info(f"Sleeping for 3-4 minutes before next upscaling batch")
|
| 507 |
+
|
| 508 |
+
await asyncio.sleep(sleep_time)
|
| 509 |
+
|
| 510 |
+
async def process_compression_miners(self, compression_miners, version):
|
| 511 |
+
"""Process compression miners in batches similar to upscaling but with compression protocols."""
|
| 512 |
+
batch_size = CONFIG.bandwidth.requests_per_synthetic_interval
|
| 513 |
+
|
| 514 |
+
miner_batches = await self.create_miner_batches(compression_miners, batch_size, task_type="compression")
|
| 515 |
+
|
| 516 |
+
logger.info(f"Created {len(miner_batches)} compression batches of size {batch_size}")
|
| 517 |
+
|
| 518 |
+
for batch_idx, batch in enumerate(miner_batches):
|
| 519 |
+
batch_start_time = time.time()
|
| 520 |
+
logger.info(f"🧩 Processing compression batch {batch_idx + 1}/{len(miner_batches)} 🧩")
|
| 521 |
+
|
| 522 |
+
uids = []
|
| 523 |
+
axons = []
|
| 524 |
+
for miner in batch:
|
| 525 |
+
uids.append(miner[1])
|
| 526 |
+
axons.append(miner[0])
|
| 527 |
+
|
| 528 |
+
vmaf_threshold = random.choice(VMAF_QUALITY_THRESHOLDS)
|
| 529 |
+
|
| 530 |
+
round_id = str(uuid.uuid4())
|
| 531 |
+
|
| 532 |
+
num_miners = len(uids)
|
| 533 |
+
|
| 534 |
+
payload_urls, video_ids, uploaded_object_names, synapses = await self.challenge_synthesizer.build_compression_protocol(vmaf_threshold, num_miners, version, round_id)
|
| 535 |
+
logger.info(f"Built compression challenge protocol: payload URLs: {payload_urls}\nvideo IDs: {video_ids}")
|
| 536 |
+
logger.debug(f"Built compression challenge protocol")
|
| 537 |
+
|
| 538 |
+
timestamp = datetime.now(timezone.utc).isoformat()
|
| 539 |
+
|
| 540 |
+
logger.debug(f"Processing compression UIDs in batch: {uids}")
|
| 541 |
+
|
| 542 |
+
forward_tasks = [
|
| 543 |
+
self.call_miner(axon, synapse, uid, timeout=60)
|
| 544 |
+
for uid, axon, synapse in zip(uids, axons, synapses)
|
| 545 |
+
]
|
| 546 |
+
raw_responses = await asyncio.gather(*forward_tasks)
|
| 547 |
+
responses = [response['result'] for response in raw_responses]
|
| 548 |
+
|
| 549 |
+
logger.info(f"🎲 Received {len(responses)} compression responses from miners 🎲")
|
| 550 |
+
|
| 551 |
+
reference_video_paths = []
|
| 552 |
+
for video_id in video_ids:
|
| 553 |
+
reference_video_path = get_trim_video_path(video_id)
|
| 554 |
+
if not os.path.exists(reference_video_path):
|
| 555 |
+
logger.warning(f"⚠️ Reference video file missing for video_id {video_id}: {reference_video_path}")
|
| 556 |
+
reference_video_paths.append(reference_video_path)
|
| 557 |
+
|
| 558 |
+
asyncio.create_task(self.score_compressions(uids, responses, payload_urls, reference_video_paths, timestamp, video_ids, uploaded_object_names, vmaf_threshold, round_id))
|
| 559 |
+
|
| 560 |
+
batch_processed_time = time.time() - batch_start_time
|
| 561 |
+
sleep_time = random.uniform(SLEEP_TIME_LOW, SLEEP_TIME_HIGH) - batch_processed_time
|
| 562 |
+
|
| 563 |
+
logger.info(f"Completed compression batch within {batch_processed_time:.2f} seconds")
|
| 564 |
+
logger.info(f"Sleeping for 3-4 minutes before next compression batch")
|
| 565 |
+
|
| 566 |
+
await asyncio.sleep(sleep_time)
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
async def start_organic_loop(self):
|
| 570 |
+
"""Start organic processing loop for both upscaling and compression tasks asynchronously."""
|
| 571 |
+
try:
|
| 572 |
+
# Create tasks for both upscaling and compression processing
|
| 573 |
+
upscaling_task = asyncio.create_task(self._process_organic_upscaling_loop())
|
| 574 |
+
compression_task = asyncio.create_task(self._process_organic_compression_loop())
|
| 575 |
+
|
| 576 |
+
# Wait for both tasks to complete (they run indefinitely)
|
| 577 |
+
await asyncio.gather(upscaling_task, compression_task)
|
| 578 |
+
except Exception as e:
|
| 579 |
+
logger.error(f"Error during organic processing loop: {e}")
|
| 580 |
+
|
| 581 |
+
async def _process_organic_upscaling_loop(self):
|
| 582 |
+
"""Process organic upscaling tasks in a loop."""
|
| 583 |
+
while True:
|
| 584 |
+
try:
|
| 585 |
+
await self.should_process_organic_upscaling()
|
| 586 |
+
await asyncio.sleep(5)
|
| 587 |
+
except Exception as e:
|
| 588 |
+
logger.error(f"Error during organic upscaling processing: {e}")
|
| 589 |
+
await asyncio.sleep(5)
|
| 590 |
+
|
| 591 |
+
async def _process_organic_compression_loop(self):
|
| 592 |
+
"""Process organic compression tasks in a loop."""
|
| 593 |
+
while True:
|
| 594 |
+
try:
|
| 595 |
+
await self.should_process_organic_compression()
|
| 596 |
+
await asyncio.sleep(5)
|
| 597 |
+
except Exception as e:
|
| 598 |
+
logger.error(f"Error during organic compression processing: {e}")
|
| 599 |
+
await asyncio.sleep(5)
|
| 600 |
+
|
| 601 |
+
async def score_upscalings(
|
| 602 |
+
self,
|
| 603 |
+
uids: list[int],
|
| 604 |
+
responses: list[protocol.Synapse],
|
| 605 |
+
payload_urls: list[str],
|
| 606 |
+
reference_video_paths: list[str],
|
| 607 |
+
timestamp: str,
|
| 608 |
+
video_ids: list[str],
|
| 609 |
+
uploaded_object_names: list[str],
|
| 610 |
+
content_lengths: list[int],
|
| 611 |
+
task_types: list[str],
|
| 612 |
+
round_id: str
|
| 613 |
+
):
|
| 614 |
+
distorted_urls = []
|
| 615 |
+
for uid, response in zip(uids, responses):
|
| 616 |
+
distorted_urls.append(response.miner_response.optimized_video_url)
|
| 617 |
+
|
| 618 |
+
logger.info(f"responses: {responses}")
|
| 619 |
+
|
| 620 |
+
score_response = await self.score_client.post(
|
| 621 |
+
"/score_upscaling_synthetics",
|
| 622 |
+
json = {
|
| 623 |
+
"uids": uids,
|
| 624 |
+
"payload_urls": payload_urls,
|
| 625 |
+
"distorted_urls": distorted_urls,
|
| 626 |
+
"reference_paths": reference_video_paths,
|
| 627 |
+
"video_ids": video_ids,
|
| 628 |
+
"uploaded_object_names": uploaded_object_names,
|
| 629 |
+
"content_lengths": content_lengths,
|
| 630 |
+
"task_types": task_types
|
| 631 |
+
},
|
| 632 |
+
timeout=240
|
| 633 |
+
)
|
| 634 |
+
|
| 635 |
+
response_data = score_response.json()
|
| 636 |
+
|
| 637 |
+
quality_scores = response_data.get("quality_scores", [])
|
| 638 |
+
length_scores = response_data.get("length_scores", [])
|
| 639 |
+
final_scores = response_data.get("final_scores", [])
|
| 640 |
+
vmaf_scores = response_data.get("vmaf_scores", [])
|
| 641 |
+
pieapp_scores = response_data.get("pieapp_scores", [])
|
| 642 |
+
reasons = response_data.get("reasons", [])
|
| 643 |
+
|
| 644 |
+
logger.info(f"Updating miner manager with {len(quality_scores)} miner scores after synthetic requests processing")
|
| 645 |
+
|
| 646 |
+
miner_hotkeys = [self.metagraph.hotkeys[uid] for uid in uids]
|
| 647 |
+
|
| 648 |
+
accumulate_scores, applied_multipliers = self.miner_manager.step_synthetics_upscaling(
|
| 649 |
+
round_id, uids, miner_hotkeys, vmaf_scores, pieapp_scores, quality_scores, length_scores, final_scores, content_lengths
|
| 650 |
+
)
|
| 651 |
+
|
| 652 |
+
max_length = max(
|
| 653 |
+
len(uids),
|
| 654 |
+
len(quality_scores),
|
| 655 |
+
len(length_scores),
|
| 656 |
+
len(final_scores),
|
| 657 |
+
len(vmaf_scores),
|
| 658 |
+
len(pieapp_scores),
|
| 659 |
+
len(reasons),
|
| 660 |
+
len(content_lengths),
|
| 661 |
+
len(applied_multipliers),
|
| 662 |
+
len(accumulate_scores),
|
| 663 |
+
)
|
| 664 |
+
|
| 665 |
+
vmaf_scores.extend([0.0] * (max_length - len(vmaf_scores)))
|
| 666 |
+
pieapp_scores.extend([0.0] * (max_length - len(pieapp_scores)))
|
| 667 |
+
quality_scores.extend([0.0] * (max_length - len(quality_scores)))
|
| 668 |
+
length_scores.extend([0.0] * (max_length - len(length_scores)))
|
| 669 |
+
final_scores.extend([0.0] * (max_length - len(final_scores)))
|
| 670 |
+
reasons.extend(["No reason provided"] * (max_length - len(reasons)))
|
| 671 |
+
content_lengths.extend([0.0] * (max_length - len(content_lengths)))
|
| 672 |
+
applied_multipliers.extend([0.0] * (max_length - len(applied_multipliers)))
|
| 673 |
+
|
| 674 |
+
logger.info(f"Synthetic scoring results for {len(uids)} miners")
|
| 675 |
+
logger.info(f"Uids: {uids}")
|
| 676 |
+
|
| 677 |
+
for uid, vmaf_score, pieapp_score, quality_score, length_score, final_score, reason, content_length, applied_multiplier in zip(
|
| 678 |
+
uids, vmaf_scores, pieapp_scores, quality_scores, length_scores, final_scores, reasons, content_lengths, applied_multipliers
|
| 679 |
+
):
|
| 680 |
+
logger.info(
|
| 681 |
+
f"{uid} ** VMAF: {vmaf_score:.2f} ** PieAPP: {pieapp_score:.2f} ** Quality: {quality_score:.4f} "
|
| 682 |
+
f"** Length: {length_score:.4f} ** Content Length: {content_length} ** Applied_multiplier {applied_multiplier} ** Final: {final_score:.4f} || {reason}"
|
| 683 |
+
)
|
| 684 |
+
|
| 685 |
+
miner_data = {
|
| 686 |
+
"validator_uid": self.my_subnet_uid,
|
| 687 |
+
"validator_hotkey": self.wallet.hotkey.ss58_address,
|
| 688 |
+
"request_type": "Synthetic",
|
| 689 |
+
"processing_task_type": "upscaling",
|
| 690 |
+
"miner_uids": uids,
|
| 691 |
+
"miner_hotkeys": miner_hotkeys,
|
| 692 |
+
"vmaf_scores": vmaf_scores,
|
| 693 |
+
"pieapp_scores": pieapp_scores,
|
| 694 |
+
"quality_scores": quality_scores,
|
| 695 |
+
"length_scores": length_scores,
|
| 696 |
+
"final_scores": final_scores,
|
| 697 |
+
"accumulate_scores": accumulate_scores,
|
| 698 |
+
"applied_multipliers": applied_multipliers,
|
| 699 |
+
"status": reasons,
|
| 700 |
+
"task_urls": payload_urls,
|
| 701 |
+
"processed_urls": distorted_urls,
|
| 702 |
+
"timestamp": timestamp
|
| 703 |
+
}
|
| 704 |
+
|
| 705 |
+
success = send_upscaling_data_to_dashboard(miner_data)
|
| 706 |
+
if success:
|
| 707 |
+
logger.info("Data successfully sent to dashboard")
|
| 708 |
+
else:
|
| 709 |
+
logger.info("Failed to send data to dashboard")
|
| 710 |
+
|
| 711 |
+
async def score_compressions(
|
| 712 |
+
self,
|
| 713 |
+
uids: list[int],
|
| 714 |
+
responses: list[protocol.Synapse],
|
| 715 |
+
payload_urls: list[str],
|
| 716 |
+
reference_video_paths: list[str],
|
| 717 |
+
timestamp: str,
|
| 718 |
+
video_ids: list[str],
|
| 719 |
+
uploaded_object_names: list[str],
|
| 720 |
+
vmaf_threshold: float,
|
| 721 |
+
round_id: str
|
| 722 |
+
):
|
| 723 |
+
distorted_urls = []
|
| 724 |
+
for uid, response in zip(uids, responses):
|
| 725 |
+
distorted_urls.append(response.miner_response.optimized_video_url)
|
| 726 |
+
|
| 727 |
+
score_response = await self.score_client.post(
|
| 728 |
+
"/score_compression_synthetics",
|
| 729 |
+
json = {
|
| 730 |
+
"uids": uids,
|
| 731 |
+
"distorted_urls": distorted_urls,
|
| 732 |
+
"reference_paths": reference_video_paths,
|
| 733 |
+
"video_ids": video_ids,
|
| 734 |
+
"uploaded_object_names": uploaded_object_names,
|
| 735 |
+
"vmaf_threshold": vmaf_threshold
|
| 736 |
+
},
|
| 737 |
+
timeout=240
|
| 738 |
+
)
|
| 739 |
+
|
| 740 |
+
response_data = score_response.json()
|
| 741 |
+
|
| 742 |
+
compression_rates = response_data.get("compression_rates", [])
|
| 743 |
+
final_scores = response_data.get("final_scores", [])
|
| 744 |
+
vmaf_scores = response_data.get("vmaf_scores", [])
|
| 745 |
+
reasons = response_data.get("reasons", [])
|
| 746 |
+
|
| 747 |
+
logger.info(f"Updating miner manager with {len(compression_rates)} compression miner scores after synthetic requests processing")
|
| 748 |
+
|
| 749 |
+
miner_hotkeys = [self.metagraph.hotkeys[uid] for uid in uids]
|
| 750 |
+
|
| 751 |
+
if not compression_rates:
|
| 752 |
+
compression_rates = [0.5] * len(uids)
|
| 753 |
+
|
| 754 |
+
accumulate_scores, applied_multipliers = self.miner_manager.step_synthetics_compression(
|
| 755 |
+
round_id, uids, miner_hotkeys, vmaf_scores,
|
| 756 |
+
final_scores, [10] * len(uids), vmaf_threshold, compression_rates
|
| 757 |
+
)
|
| 758 |
+
|
| 759 |
+
max_length = max(
|
| 760 |
+
len(uids),
|
| 761 |
+
len(final_scores),
|
| 762 |
+
len(vmaf_scores),
|
| 763 |
+
len(reasons),
|
| 764 |
+
len(applied_multipliers),
|
| 765 |
+
len(accumulate_scores),
|
| 766 |
+
)
|
| 767 |
+
|
| 768 |
+
vmaf_scores.extend([0.0] * (max_length - len(vmaf_scores)))
|
| 769 |
+
final_scores.extend([0.0] * (max_length - len(final_scores)))
|
| 770 |
+
reasons.extend(["No reason provided"] * (max_length - len(reasons)))
|
| 771 |
+
compression_rates.extend([0.5] * (max_length - len(compression_rates)))
|
| 772 |
+
applied_multipliers.extend([0.0] * (max_length - len(applied_multipliers)))
|
| 773 |
+
|
| 774 |
+
vmaf_thresholds = [vmaf_threshold] * len(uids)
|
| 775 |
+
|
| 776 |
+
logger.info(f"Synthetic compression scoring results for {len(uids)} miners")
|
| 777 |
+
logger.info(f"Uids: {uids}")
|
| 778 |
+
|
| 779 |
+
for uid, vmaf_score, final_score, reason, compression_rate, applied_multiplier in zip(
|
| 780 |
+
uids, vmaf_scores, final_scores, reasons, compression_rates, applied_multipliers
|
| 781 |
+
):
|
| 782 |
+
logger.info(
|
| 783 |
+
f"{uid} ** VMAF: {vmaf_score:.2f} "
|
| 784 |
+
f"** VMAF Threshold: {vmaf_threshold} ** Compression Rate: {compression_rate:.4f} ** Final: {final_score:.4f} || {reason}"
|
| 785 |
+
)
|
| 786 |
+
|
| 787 |
+
miner_data = {
|
| 788 |
+
"validator_uid": self.my_subnet_uid,
|
| 789 |
+
"validator_hotkey": self.wallet.hotkey.ss58_address,
|
| 790 |
+
"request_type": "Synthetic",
|
| 791 |
+
"processing_task_type": "compression",
|
| 792 |
+
"miner_uids": uids,
|
| 793 |
+
"miner_hotkeys": miner_hotkeys,
|
| 794 |
+
"vmaf_scores": vmaf_scores,
|
| 795 |
+
"vmaf_thresholds": vmaf_thresholds,
|
| 796 |
+
"compression_rates": compression_rates,
|
| 797 |
+
"final_scores": final_scores,
|
| 798 |
+
"accumulate_scores": accumulate_scores,
|
| 799 |
+
"applied_multipliers": applied_multipliers,
|
| 800 |
+
"status": reasons,
|
| 801 |
+
"task_urls": payload_urls,
|
| 802 |
+
"processed_urls": distorted_urls,
|
| 803 |
+
"timestamp": timestamp
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
success = send_compression_data_to_dashboard(miner_data)
|
| 807 |
+
if success:
|
| 808 |
+
logger.info("Compression data successfully sent to dashboard")
|
| 809 |
+
else:
|
| 810 |
+
logger.info("Failed to send compression data to dashboard")
|
| 811 |
+
|
| 812 |
+
async def score_organics_upscaling(self, uids: list[int], responses: list[protocol.Synapse], reference_urls: list[str], task_types: list[str], timestamp: str):
|
| 813 |
+
"""Score organic upscaling tasks."""
|
| 814 |
+
distorted_urls = [response.miner_response.optimized_video_url for response in responses]
|
| 815 |
+
|
| 816 |
+
combined = list(zip(uids, distorted_urls, reference_urls, task_types))
|
| 817 |
+
random.shuffle(combined)
|
| 818 |
+
uids, distorted_urls, reference_urls, task_types = map(list, zip(*combined))
|
| 819 |
+
|
| 820 |
+
num_pairs_to_validate = min(1, len(combined))
|
| 821 |
+
selected_indices = random.sample(range(len(combined)), num_pairs_to_validate)
|
| 822 |
+
|
| 823 |
+
selected_uids = [uids[i] for i in selected_indices]
|
| 824 |
+
selected_distorted_urls = [distorted_urls[i] for i in selected_indices]
|
| 825 |
+
selected_reference_urls = [reference_urls[i] for i in selected_indices]
|
| 826 |
+
selected_task_types = [task_types[i] for i in selected_indices]
|
| 827 |
+
|
| 828 |
+
logger.info(f"Randomly selected {len(selected_uids)} pairs out of {len(uids)} total pairs for validation")
|
| 829 |
+
|
| 830 |
+
score_response = await self.score_client.post(
|
| 831 |
+
"/score_organics_upscaling",
|
| 832 |
+
json={
|
| 833 |
+
"uids": selected_uids,
|
| 834 |
+
"distorted_urls": selected_distorted_urls,
|
| 835 |
+
"reference_urls": selected_reference_urls,
|
| 836 |
+
"task_types": selected_task_types
|
| 837 |
+
},
|
| 838 |
+
timeout=38
|
| 839 |
+
)
|
| 840 |
+
|
| 841 |
+
response_data = score_response.json()
|
| 842 |
+
scores = response_data.get("final_scores", [])
|
| 843 |
+
vmaf_scores = response_data.get("vmaf_scores", [])
|
| 844 |
+
pieapp_scores = response_data.get("pieapp_scores", [])
|
| 845 |
+
quality_scores = response_data.get("quality_scores", [])
|
| 846 |
+
length_scores = response_data.get("length_scores", [])
|
| 847 |
+
reasons = response_data.get("reasons", [])
|
| 848 |
+
|
| 849 |
+
max_length = max(len(uids), len(scores), len(vmaf_scores), len(pieapp_scores), len(reasons))
|
| 850 |
+
scores.extend([0.0] * (max_length - len(scores)))
|
| 851 |
+
vmaf_scores.extend([0.0] * (max_length - len(vmaf_scores)))
|
| 852 |
+
pieapp_scores.extend([0.0] * (max_length - len(pieapp_scores)))
|
| 853 |
+
quality_scores.extend([0.0] * (max_length - len(quality_scores)))
|
| 854 |
+
length_scores.extend([0.0] * (max_length - len(length_scores)))
|
| 855 |
+
reasons.extend(["no reason provided"] * (max_length - len(reasons)))
|
| 856 |
+
|
| 857 |
+
logger.info(f"organic upscaling scoring results for {len(selected_uids)} miners")
|
| 858 |
+
logger.info(f"uids: {selected_uids}")
|
| 859 |
+
for uid, vmaf_score, score, reason in zip(selected_uids, vmaf_scores, scores, reasons):
|
| 860 |
+
logger.info(f"{uid} ** {vmaf_score:.2f} ** {score:.4f} || {reason}")
|
| 861 |
+
|
| 862 |
+
round_id = f"organic_upscaling_{int(time.time())}"
|
| 863 |
+
|
| 864 |
+
logger.info(f"updating miner manager with {len(scores)} miner scores after organic upscaling requests processing…")
|
| 865 |
+
accumulate_scores, applied_multipliers = self.miner_manager.step_organic_upscaling(scores, selected_uids, round_id)
|
| 866 |
+
|
| 867 |
+
miner_hotkeys = [self.metagraph.hotkeys[uid] for uid in selected_uids]
|
| 868 |
+
|
| 869 |
+
miner_data = {
|
| 870 |
+
"validator_uid": self.my_subnet_uid,
|
| 871 |
+
"validator_hotkey": self.wallet.hotkey.ss58_address,
|
| 872 |
+
"request_type": "Organic",
|
| 873 |
+
"processing_task_type": "upscaling",
|
| 874 |
+
"miner_uids": selected_uids,
|
| 875 |
+
"miner_hotkeys": miner_hotkeys,
|
| 876 |
+
"vmaf_scores": vmaf_scores,
|
| 877 |
+
"pieapp_scores": pieapp_scores,
|
| 878 |
+
"quality_scores": quality_scores,
|
| 879 |
+
"length_scores": length_scores,
|
| 880 |
+
"final_scores": scores,
|
| 881 |
+
"accumulate_scores": accumulate_scores,
|
| 882 |
+
"applied_multipliers": applied_multipliers,
|
| 883 |
+
"status": reasons,
|
| 884 |
+
"task_urls": selected_reference_urls,
|
| 885 |
+
"processed_urls": selected_distorted_urls,
|
| 886 |
+
"timestamp": timestamp
|
| 887 |
+
}
|
| 888 |
+
|
| 889 |
+
success = send_upscaling_data_to_dashboard(miner_data)
|
| 890 |
+
if success:
|
| 891 |
+
logger.info("Data successfully sent to dashboard")
|
| 892 |
+
else:
|
| 893 |
+
logger.info("Failed to send data to dashboard")
|
| 894 |
+
|
| 895 |
+
async def score_organics_compression(self, uids: list[int], responses: list[protocol.Synapse], reference_urls: list[str], vmaf_thresholds: list[float], timestamp: str):
|
| 896 |
+
"""Score organic compression tasks."""
|
| 897 |
+
distorted_urls = [response.miner_response.optimized_video_url for response in responses]
|
| 898 |
+
|
| 899 |
+
combined = list(zip(uids, distorted_urls, reference_urls, vmaf_thresholds))
|
| 900 |
+
random.shuffle(combined)
|
| 901 |
+
uids, distorted_urls, reference_urls, vmaf_thresholds = map(list, zip(*combined))
|
| 902 |
+
|
| 903 |
+
num_pairs_to_validate = min(5, len(combined))
|
| 904 |
+
selected_indices = random.sample(range(len(combined)), num_pairs_to_validate)
|
| 905 |
+
|
| 906 |
+
selected_uids = [uids[i] for i in selected_indices]
|
| 907 |
+
selected_distorted_urls = [distorted_urls[i] for i in selected_indices]
|
| 908 |
+
selected_reference_urls = [reference_urls[i] for i in selected_indices]
|
| 909 |
+
selected_vmaf_thresholds = [vmaf_thresholds[i] for i in selected_indices]
|
| 910 |
+
|
| 911 |
+
logger.info(f"Randomly selected {len(selected_uids)} pairs out of {len(uids)} total pairs for compression validation")
|
| 912 |
+
|
| 913 |
+
score_response = await self.score_client.post(
|
| 914 |
+
"/score_organics_compression",
|
| 915 |
+
json={
|
| 916 |
+
"uids": selected_uids,
|
| 917 |
+
"distorted_urls": selected_distorted_urls,
|
| 918 |
+
"reference_urls": selected_reference_urls,
|
| 919 |
+
"vmaf_thresholds": selected_vmaf_thresholds
|
| 920 |
+
},
|
| 921 |
+
timeout=115
|
| 922 |
+
)
|
| 923 |
+
|
| 924 |
+
response_data = score_response.json()
|
| 925 |
+
scores = response_data.get("final_scores", [])
|
| 926 |
+
vmaf_scores = response_data.get("vmaf_scores", [])
|
| 927 |
+
compression_rates = response_data.get("compression_rates", [])
|
| 928 |
+
reasons = response_data.get("reasons", [])
|
| 929 |
+
|
| 930 |
+
max_length = max(len(selected_uids), len(scores), len(vmaf_scores), len(compression_rates), len(reasons))
|
| 931 |
+
scores.extend([0.0] * (max_length - len(scores)))
|
| 932 |
+
vmaf_scores.extend([0.0] * (max_length - len(vmaf_scores)))
|
| 933 |
+
compression_rates.extend([0.5] * (max_length - len(compression_rates)))
|
| 934 |
+
reasons.extend(["no reason provided"] * (max_length - len(reasons)))
|
| 935 |
+
|
| 936 |
+
round_id = f"organic_compression_{int(time.time())}"
|
| 937 |
+
|
| 938 |
+
logger.info(f"updating miner manager with {len(scores)} miner scores after organic compression requests processing…")
|
| 939 |
+
accumulate_scores, applied_multipliers = self.miner_manager.step_organic_compression(scores, selected_uids, vmaf_scores, compression_rates, selected_vmaf_thresholds, round_id)
|
| 940 |
+
|
| 941 |
+
logger.info(f"organic compression scoring results for {len(selected_uids)} miners")
|
| 942 |
+
logger.info(f"uids: {selected_uids}")
|
| 943 |
+
for uid, vmaf_score, final_score, reason, compression_rate, applied_multiplier, vmaf_threshold in zip(
|
| 944 |
+
selected_uids, vmaf_scores, scores, reasons, compression_rates, applied_multipliers, selected_vmaf_thresholds
|
| 945 |
+
):
|
| 946 |
+
logger.info(
|
| 947 |
+
f"{uid} ** VMAF: {vmaf_score:.2f} "
|
| 948 |
+
f"** VMAF Threshold: {vmaf_threshold} ** Compression Rate: {compression_rate:.4f} ** Final: {final_score:.4f} || {reason}"
|
| 949 |
+
)
|
| 950 |
+
|
| 951 |
+
miner_hotkeys = [self.metagraph.hotkeys[uid] for uid in selected_uids]
|
| 952 |
+
|
| 953 |
+
miner_data = {
|
| 954 |
+
"validator_uid": self.my_subnet_uid,
|
| 955 |
+
"validator_hotkey": self.wallet.hotkey.ss58_address,
|
| 956 |
+
"request_type": "Organic",
|
| 957 |
+
"processing_task_type": "compression",
|
| 958 |
+
"miner_uids": selected_uids,
|
| 959 |
+
"miner_hotkeys": miner_hotkeys,
|
| 960 |
+
"vmaf_scores": vmaf_scores,
|
| 961 |
+
"vmaf_thresholds": selected_vmaf_thresholds,
|
| 962 |
+
"compression_rates": compression_rates,
|
| 963 |
+
"final_scores": scores,
|
| 964 |
+
"accumulate_scores": accumulate_scores,
|
| 965 |
+
"applied_multipliers": applied_multipliers,
|
| 966 |
+
"status": reasons,
|
| 967 |
+
"task_urls": selected_reference_urls,
|
| 968 |
+
"processed_urls": selected_distorted_urls,
|
| 969 |
+
"timestamp": timestamp
|
| 970 |
+
}
|
| 971 |
+
|
| 972 |
+
success = send_compression_data_to_dashboard(miner_data)
|
| 973 |
+
if success:
|
| 974 |
+
logger.info("Compression data successfully sent to dashboard")
|
| 975 |
+
else:
|
| 976 |
+
logger.info("Failed to send compression data to dashboard")
|
| 977 |
+
|
| 978 |
+
def filter_miners(self):
|
| 979 |
+
min_stake = CONFIG.bandwidth.min_stake
|
| 980 |
+
stake_array = self.metagraph.S
|
| 981 |
+
miner_uids = [i for i, stake in enumerate(stake_array) if stake < min_stake]
|
| 982 |
+
|
| 983 |
+
return miner_uids
|
| 984 |
+
|
| 985 |
+
async def should_process_organic_upscaling(self):
|
| 986 |
+
"""Check if organic upscaling tasks should be processed."""
|
| 987 |
+
num_organic_upscaling_chunks = get_organic_upscaling_queue_size(self.redis_conn)
|
| 988 |
+
|
| 989 |
+
if num_organic_upscaling_chunks > 0:
|
| 990 |
+
logger.info(f"🔷 | UPSCALING | The organic_upscaling_queue_size: {num_organic_upscaling_chunks}, processing organic upscaling requests. 🔷")
|
| 991 |
+
await self.process_organic_upscaling_chunks(num_organic_upscaling_chunks)
|
| 992 |
+
return True
|
| 993 |
+
else:
|
| 994 |
+
return False
|
| 995 |
+
|
| 996 |
+
async def should_process_organic_compression(self):
|
| 997 |
+
"""Check if organic compression tasks should be processed."""
|
| 998 |
+
num_organic_compression_chunks = get_organic_compression_queue_size(self.redis_conn)
|
| 999 |
+
|
| 1000 |
+
if num_organic_compression_chunks > 0:
|
| 1001 |
+
logger.info(f"🔷 | COMPRESSION | The organic_compression_queue_size: {num_organic_compression_chunks}, processing organic compression requests. 🔷")
|
| 1002 |
+
await self.process_organic_compression_chunks(num_organic_compression_chunks)
|
| 1003 |
+
return True
|
| 1004 |
+
else:
|
| 1005 |
+
return False
|
| 1006 |
+
|
| 1007 |
+
async def process_organic_upscaling_chunks(self, num_organic_chunks):
|
| 1008 |
+
"""Process organic upscaling chunks."""
|
| 1009 |
+
organic_start_time = time.time()
|
| 1010 |
+
|
| 1011 |
+
needed = min(CONFIG.bandwidth.requests_per_organic_interval, num_organic_chunks)
|
| 1012 |
+
|
| 1013 |
+
logger.info(f"☘️ | UPSCALING | Start processing organic upscaling query. need {needed} miners ☘️")
|
| 1014 |
+
|
| 1015 |
+
forward_uids = get_organic_forward_uids(self, needed, "upscaling", CONFIG.bandwidth.min_stake)
|
| 1016 |
+
|
| 1017 |
+
if len(forward_uids) < needed:
|
| 1018 |
+
logger.info(f"There are just {len(forward_uids)} miners available for organic upscaling, handling {len(forward_uids)} chunks")
|
| 1019 |
+
needed = len(forward_uids)
|
| 1020 |
+
|
| 1021 |
+
axon_list = [self.metagraph.axons[uid] for uid in forward_uids]
|
| 1022 |
+
|
| 1023 |
+
task_ids, original_urls, task_types, synapses = await self.challenge_synthesizer.build_organic_upscaling_protocol(needed)
|
| 1024 |
+
|
| 1025 |
+
if len(task_ids) != needed or len(synapses) != needed:
|
| 1026 |
+
logger.error(
|
| 1027 |
+
f"Mismatch in organic upscaling synapses after building organic protocol: {len(task_ids)} != {needed} or {len(synapses)} != {needed}"
|
| 1028 |
+
)
|
| 1029 |
+
return
|
| 1030 |
+
|
| 1031 |
+
logger.info("Updating task status to 'processing' for upscaling")
|
| 1032 |
+
for task_id, original_url in zip(task_ids, original_urls):
|
| 1033 |
+
await self.update_task_status(task_id, original_url, "processing")
|
| 1034 |
+
|
| 1035 |
+
timestamp = datetime.now(timezone.utc).isoformat()
|
| 1036 |
+
|
| 1037 |
+
logger.info("🌜 | UPSCALING | Performing forward operations asynchronously for upscaling 🌜")
|
| 1038 |
+
forward_tasks = [
|
| 1039 |
+
self.call_miner(axon, synapse, uid, timeout=100)
|
| 1040 |
+
for uid, axon, synapse in zip(forward_uids, axon_list, synapses)
|
| 1041 |
+
]
|
| 1042 |
+
raw_responses = await asyncio.gather(*forward_tasks)
|
| 1043 |
+
responses = [response['result'] for response in raw_responses]
|
| 1044 |
+
|
| 1045 |
+
processed_urls = [response.miner_response.optimized_video_url for response in responses]
|
| 1046 |
+
|
| 1047 |
+
logger.info(f"Processing organic upscaling chunks with uids: {forward_uids.tolist()}")
|
| 1048 |
+
logger.info("Updating task status to 'completed' and pushing results for upscaling")
|
| 1049 |
+
for task_id, original_url, processed_url in zip(task_ids, original_urls, processed_urls):
|
| 1050 |
+
await self.update_task_status(task_id, original_url, "completed")
|
| 1051 |
+
await self.push_result(task_id, original_url, processed_url)
|
| 1052 |
+
|
| 1053 |
+
asyncio.create_task(self.score_organics_upscaling(forward_uids.tolist(), responses, original_urls, task_types, timestamp))
|
| 1054 |
+
|
| 1055 |
+
end_time = time.time()
|
| 1056 |
+
total_time = end_time - organic_start_time
|
| 1057 |
+
logger.info(f"🍏 Organic upscaling chunk processing complete in {total_time:.2f} seconds 🍏")
|
| 1058 |
+
|
| 1059 |
+
async def process_organic_compression_chunks(self, num_organic_chunks):
|
| 1060 |
+
"""Process organic compression chunks."""
|
| 1061 |
+
organic_start_time = time.time()
|
| 1062 |
+
|
| 1063 |
+
needed = min(CONFIG.bandwidth.requests_per_organic_interval, num_organic_chunks)
|
| 1064 |
+
|
| 1065 |
+
logger.info(f"☘️ | COMPRESSION | Start processing organic compression query. need {needed} miners ☘️")
|
| 1066 |
+
|
| 1067 |
+
forward_uids = get_organic_forward_uids(self, needed, "compression", CONFIG.bandwidth.min_stake)
|
| 1068 |
+
|
| 1069 |
+
if len(forward_uids) < needed:
|
| 1070 |
+
logger.info(f"There are just {len(forward_uids)} miners available for organic compression, handling {len(forward_uids)} chunks")
|
| 1071 |
+
needed = len(forward_uids)
|
| 1072 |
+
|
| 1073 |
+
axon_list = [self.metagraph.axons[uid] for uid in forward_uids]
|
| 1074 |
+
|
| 1075 |
+
task_ids, original_urls, vmaf_thresholds, synapses = await self.challenge_synthesizer.build_organic_compression_protocol(needed)
|
| 1076 |
+
|
| 1077 |
+
if len(task_ids) != needed or len(synapses) != needed:
|
| 1078 |
+
logger.error(
|
| 1079 |
+
f"Mismatch in organic compression synapses after building organic protocol: {len(task_ids)} != {needed} or {len(synapses)} != {needed}"
|
| 1080 |
+
)
|
| 1081 |
+
return
|
| 1082 |
+
|
| 1083 |
+
logger.info(f"Processing organic compression chunks with uids: {forward_uids.tolist()}")
|
| 1084 |
+
logger.info("Updating task status to 'processing' for compression")
|
| 1085 |
+
for task_id, original_url in zip(task_ids, original_urls):
|
| 1086 |
+
await self.update_task_status(task_id, original_url, "processing")
|
| 1087 |
+
|
| 1088 |
+
timestamp = datetime.now(timezone.utc).isoformat()
|
| 1089 |
+
|
| 1090 |
+
logger.info("🌜 | COMPRESSION | Performing forward operations asynchronously for compression 🌜")
|
| 1091 |
+
|
| 1092 |
+
forward_tasks = [
|
| 1093 |
+
self.call_miner(axon, synapse, uid, timeout=120)
|
| 1094 |
+
for uid, axon, synapse in zip(forward_uids, axon_list, synapses)
|
| 1095 |
+
]
|
| 1096 |
+
raw_responses = await asyncio.gather(*forward_tasks)
|
| 1097 |
+
responses = [response['result'] for response in raw_responses]
|
| 1098 |
+
|
| 1099 |
+
processed_urls = [response.miner_response.optimized_video_url for response in responses]
|
| 1100 |
+
|
| 1101 |
+
logger.info("Updating task status to 'completed' and pushing results for compression")
|
| 1102 |
+
for task_id, original_url, processed_url in zip(task_ids, original_urls, processed_urls):
|
| 1103 |
+
await self.update_task_status(task_id, original_url, "completed")
|
| 1104 |
+
await self.push_result(task_id, original_url, processed_url)
|
| 1105 |
+
|
| 1106 |
+
asyncio.create_task(self.score_organics_compression(forward_uids.tolist(), responses, original_urls, vmaf_thresholds, timestamp))
|
| 1107 |
+
|
| 1108 |
+
end_time = time.time()
|
| 1109 |
+
total_time = end_time - organic_start_time
|
| 1110 |
+
logger.info(f"🍏 Organic compression chunk processing complete in {total_time:.2f} seconds 🍏")
|
| 1111 |
+
|
| 1112 |
+
|
| 1113 |
+
async def update_task_status(self, task_id, original_url, status):
|
| 1114 |
+
status_update_endpoint = f"{self.organic_gateway_base_url}/admin/task/{task_id}/status"
|
| 1115 |
+
status_update_payload = {
|
| 1116 |
+
"status": status,
|
| 1117 |
+
"original_video_url": original_url
|
| 1118 |
+
}
|
| 1119 |
+
try:
|
| 1120 |
+
async with httpx.AsyncClient() as client:
|
| 1121 |
+
response = await client.post(status_update_endpoint, json=status_update_payload)
|
| 1122 |
+
response.raise_for_status()
|
| 1123 |
+
except httpx.HTTPStatusError as e:
|
| 1124 |
+
logger.error(f"Error updating status for task {task_id}: {e.response.status_code} - {e.response.text}")
|
| 1125 |
+
except httpx.RequestError as e:
|
| 1126 |
+
logger.error(f"Request error updating status for task {task_id}: {e}")
|
| 1127 |
+
|
| 1128 |
+
async def push_result(self, task_id, original_url, processed_url):
|
| 1129 |
+
result_payload = {
|
| 1130 |
+
"processed_video_url": processed_url,
|
| 1131 |
+
"original_video_url": original_url,
|
| 1132 |
+
"score": 0,
|
| 1133 |
+
"task_id": task_id,
|
| 1134 |
+
}
|
| 1135 |
+
try:
|
| 1136 |
+
async with httpx.AsyncClient() as client:
|
| 1137 |
+
response = await client.post(self.push_result_endpoint, json=result_payload)
|
| 1138 |
+
response.raise_for_status()
|
| 1139 |
+
logger.info(f"Successfully pushed result for task {task_id}")
|
| 1140 |
+
except httpx.RequestError as e:
|
| 1141 |
+
logger.error(f"Error pushing result for task {task_id}: {e}")
|
| 1142 |
+
|
| 1143 |
+
def set_weights(self):
|
| 1144 |
+
self.current_block = self.subtensor.get_current_block()
|
| 1145 |
+
self.last_update = self.metagraph.last_update[self.uid]
|
| 1146 |
+
uids, weights = self.miner_manager.weights
|
| 1147 |
+
(
|
| 1148 |
+
processed_weight_uids,
|
| 1149 |
+
processed_weights,
|
| 1150 |
+
) = bt.utils.weight_utils.process_weights_for_netuid(
|
| 1151 |
+
uids = uids,
|
| 1152 |
+
weights=weights,
|
| 1153 |
+
netuid=self.config.netuid,
|
| 1154 |
+
subtensor=self.subtensor,
|
| 1155 |
+
metagraph=self.metagraph,
|
| 1156 |
+
)
|
| 1157 |
+
(
|
| 1158 |
+
uint_uids,
|
| 1159 |
+
uint_weights,
|
| 1160 |
+
) = bt.utils.weight_utils.convert_weights_and_uids_for_emit(
|
| 1161 |
+
uids=processed_weight_uids, weights=processed_weights
|
| 1162 |
+
)
|
| 1163 |
+
if self.current_block > self.last_update + CONFIG.SUBNET_TEMPO:
|
| 1164 |
+
weight_info = list(zip(uint_uids, uint_weights))
|
| 1165 |
+
weight_info_df = pd.DataFrame(weight_info, columns=["uid", "weight"])
|
| 1166 |
+
logger.info(f"Weight info:\n{weight_info_df.to_markdown()}")
|
| 1167 |
+
logger.info("Trying to set weights.")
|
| 1168 |
+
try:
|
| 1169 |
+
future = self.set_weights_executor.submit(
|
| 1170 |
+
self.subtensor.set_weights,
|
| 1171 |
+
netuid=self.config.netuid,
|
| 1172 |
+
wallet=self.wallet,
|
| 1173 |
+
uids=uint_uids,
|
| 1174 |
+
weights=uint_weights,
|
| 1175 |
+
)
|
| 1176 |
+
success, msg = future.result(timeout=120)
|
| 1177 |
+
if not success:
|
| 1178 |
+
logger.error(f"😠 Failed to set weights: {msg}")
|
| 1179 |
+
else:
|
| 1180 |
+
logger.debug("😎 Set weights successfully ")
|
| 1181 |
+
except Exception as e:
|
| 1182 |
+
logger.error(f"Failed to set weights: {e}")
|
| 1183 |
+
traceback.print_exc()
|
| 1184 |
+
|
| 1185 |
+
else:
|
| 1186 |
+
logger.info(
|
| 1187 |
+
f"Not setting weights because current block {self.current_block} is not greater than last update {self.last_update} + tempo {CONFIG.SUBNET_TEMPO}"
|
| 1188 |
+
)
|
| 1189 |
+
|
| 1190 |
+
|
| 1191 |
+
class WeightSynthesizer:
|
| 1192 |
+
def __init__(self, validator: Validator):
|
| 1193 |
+
self.validator = validator
|
| 1194 |
+
|
| 1195 |
+
async def run(self):
|
| 1196 |
+
while True:
|
| 1197 |
+
try:
|
| 1198 |
+
logger.info("Running weight_manager...")
|
| 1199 |
+
self.validator.set_weights()
|
| 1200 |
+
except Exception as e:
|
| 1201 |
+
logger.error(f"Error in WeightSynthesizer: {e}", exc_info=True)
|
| 1202 |
+
await asyncio.sleep(1200)
|
| 1203 |
+
|
| 1204 |
+
|
| 1205 |
+
if __name__ == "__main__":
|
| 1206 |
+
validator = Validator()
|
| 1207 |
+
weight_synthesizer = WeightSynthesizer(validator)
|
| 1208 |
+
time.sleep(1300) # wait till the video scheduler is ready
|
| 1209 |
+
|
| 1210 |
+
set_scheduler_ready(validator.redis_conn, False)
|
| 1211 |
+
logger.info("Set scheduler readiness flag to False")
|
| 1212 |
+
|
| 1213 |
+
async def main():
|
| 1214 |
+
validator_synthetic_task = asyncio.create_task(validator.run_synthetic())
|
| 1215 |
+
validator_organic_task = asyncio.create_task(validator.run_organic())
|
| 1216 |
+
weight_setter = asyncio.create_task(weight_synthesizer.run())
|
| 1217 |
+
|
| 1218 |
+
await asyncio.gather(validator_synthetic_task, validator_organic_task, weight_setter)
|
| 1219 |
+
|
| 1220 |
+
try:
|
| 1221 |
+
asyncio.run(main())
|
| 1222 |
+
except KeyboardInterrupt:
|
| 1223 |
+
logger.info("Program terminated by user.")
|
| 1224 |
+
except Exception as e:
|
| 1225 |
+
logger.error(f"Unhandled exception: {e}", exc_info=True)
|
| 1226 |
+
|
| 1227 |
+
|
pretrained/AIM_Training_2branche_KAN-Head.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:70c243d7324c76df43df8ab6a44eb535ee9f4f3acb928e5dfe9deb2bb3b7b0ab
|
| 3 |
+
size 85484315
|
pretrained/AIM_Training_2branche_MLP-Head.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:68116e2d548b6bec6d80982ce3bbd7fb1136b20e2db728669b3b307fb3e7a094
|
| 3 |
+
size 81899582
|
pretrained/single_branch_pretrained/Authentic.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fe09b8a81d2527165188cefd63e3f699a8df16aa4b0b631525734756696f2e6c
|
| 3 |
+
size 22135322
|
pretrained/single_branch_pretrained/Synthetic.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e9e7122c4e079f81227ff692f4680be2fdd99431ba90cdcf4a0656cfe93adc63
|
| 3 |
+
size 22135322
|
run.sh
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
script="neurons/validator.py"
|
| 4 |
+
autoRunLoc=$(readlink -f "$0")
|
| 5 |
+
proc_name="video-validator"
|
| 6 |
+
args=()
|
| 7 |
+
version_location="./vidaio_subnet_core/__init__.py"
|
| 8 |
+
version="__version__"
|
| 9 |
+
old_args=$@
|
| 10 |
+
subnet=85
|
| 11 |
+
restart_video_scheduler=true
|
| 12 |
+
|
| 13 |
+
if ! command -v pm2 &> /dev/null
|
| 14 |
+
then
|
| 15 |
+
echo "pm2 could not be found. To install see: https://pm2.keymetrics.io/docs/usage/quick-start/"
|
| 16 |
+
exit 1
|
| 17 |
+
fi
|
| 18 |
+
|
| 19 |
+
version_less_than_or_equal() {
|
| 20 |
+
[ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ]
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
version_less_than() {
|
| 24 |
+
[ "$1" = "$2" ] && return 1 || version_less_than_or_equal $1 $2
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
read_version_value() {
|
| 28 |
+
while IFS= read -r line; do
|
| 29 |
+
if [[ "$line" == *"$version"* ]]; then
|
| 30 |
+
local value=$(echo "$line" | awk -F '=' '{print $2}' | tr -d ' ')
|
| 31 |
+
strip_quotes "$value"
|
| 32 |
+
return 0
|
| 33 |
+
fi
|
| 34 |
+
done < "$version_location"
|
| 35 |
+
echo ""
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
check_package_installed() {
|
| 39 |
+
local package_name="$1"
|
| 40 |
+
if ! command -v "$package_name" &> /dev/null; then
|
| 41 |
+
echo "Error: '$package_name' is not installed."
|
| 42 |
+
echo "Installing '$package_name'..."
|
| 43 |
+
sudo apt-get install -y "$package_name"
|
| 44 |
+
fi
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
strip_quotes() {
|
| 48 |
+
local input="$1"
|
| 49 |
+
local stripped="${input#\"}"
|
| 50 |
+
stripped="${stripped%\"}"
|
| 51 |
+
echo "$stripped"
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
check_variable_value_on_github() {
|
| 55 |
+
local repo="vidaio-subnet/vidaio-subnet"
|
| 56 |
+
local branch="$1"
|
| 57 |
+
local file_path="vidaio_subnet_core/__init__.py"
|
| 58 |
+
local variable="$2"
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
local content
|
| 62 |
+
content=$(curl -s "https://api.github.com/repos/$repo/contents/$file_path?ref=$branch" | jq -r '.content' | base64 --decode)
|
| 63 |
+
|
| 64 |
+
if [[ $? -ne 0 || -z "$content" ]]; then
|
| 65 |
+
echo "Error: Could not retrieve file content from GitHub."
|
| 66 |
+
return 1
|
| 67 |
+
fi
|
| 68 |
+
|
| 69 |
+
local value
|
| 70 |
+
value=$(echo "$content" | grep "$variable" | awk -F '=' '{print $2}' | tr -d ' ')
|
| 71 |
+
|
| 72 |
+
# Replace the strip_quotes call with direct quote removal
|
| 73 |
+
echo "$value" | tr -d '"' | tr -d "'"
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
ensure_process() {
|
| 77 |
+
local name="$1"
|
| 78 |
+
local cmd="$2"
|
| 79 |
+
local restart_flag="$3" # "true" or "false"
|
| 80 |
+
|
| 81 |
+
if pm2 describe "$name" &>/dev/null; then
|
| 82 |
+
echo "Process '$name' already running."
|
| 83 |
+
if [[ "$restart_flag" == "true" ]]; then
|
| 84 |
+
echo "Reloading $name..."
|
| 85 |
+
pm2 reload "$name"
|
| 86 |
+
fi
|
| 87 |
+
else
|
| 88 |
+
echo "Starting $name..."
|
| 89 |
+
pm2 start bash --name "$name" -- -c "$cmd"
|
| 90 |
+
fi
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
ensure_config_process() {
|
| 94 |
+
local config="$1"
|
| 95 |
+
local name="$2"
|
| 96 |
+
local restart_flag="$3" # "true" or "false"
|
| 97 |
+
|
| 98 |
+
if pm2 describe "$name" &>/dev/null; then
|
| 99 |
+
echo "Process '$name' already running (from $config)."
|
| 100 |
+
if [[ "$restart_flag" == "true" ]]; then
|
| 101 |
+
echo "Reloading $name from config..."
|
| 102 |
+
pm2 startOrReload "$config"
|
| 103 |
+
fi
|
| 104 |
+
else
|
| 105 |
+
echo "Starting $name from config..."
|
| 106 |
+
pm2 startOrReload "$config"
|
| 107 |
+
fi
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
while [[ $# -gt 0 ]]; do
|
| 111 |
+
arg="$1"
|
| 112 |
+
if [[ "$arg" == -* ]]; then
|
| 113 |
+
if [[ $# -gt 1 && "$2" != -* ]]; then
|
| 114 |
+
if [[ "$arg" == "--script" ]]; then
|
| 115 |
+
script="$2"
|
| 116 |
+
shift 2
|
| 117 |
+
elif [[ "$arg" == "--subnet" ]]; then
|
| 118 |
+
subnet="$2"
|
| 119 |
+
args+=("'--netuid'")
|
| 120 |
+
args+=("'$2'")
|
| 121 |
+
shift 2
|
| 122 |
+
elif [[ "$arg" == "--netuid" ]]; then
|
| 123 |
+
subnet="$2"
|
| 124 |
+
args+=("'$arg'")
|
| 125 |
+
args+=("'$2'")
|
| 126 |
+
shift 2
|
| 127 |
+
else
|
| 128 |
+
args+=("'$arg'")
|
| 129 |
+
args+=("'$2'")
|
| 130 |
+
shift 2
|
| 131 |
+
fi
|
| 132 |
+
else
|
| 133 |
+
if [[ "$arg" == "--no-video-scheduler" ]]; then
|
| 134 |
+
restart_video_scheduler=false
|
| 135 |
+
shift
|
| 136 |
+
else
|
| 137 |
+
args+=("'$arg'")
|
| 138 |
+
shift
|
| 139 |
+
fi
|
| 140 |
+
fi
|
| 141 |
+
else
|
| 142 |
+
args+=("'$arg'")
|
| 143 |
+
shift
|
| 144 |
+
fi
|
| 145 |
+
done
|
| 146 |
+
|
| 147 |
+
branch=$(git branch --show-current)
|
| 148 |
+
echo "Watching branch: $branch"
|
| 149 |
+
echo "Reapplying git stash"
|
| 150 |
+
git stash pop
|
| 151 |
+
echo "PM2 process name: $proc_name"
|
| 152 |
+
if [[ -n "$subnet" ]]; then
|
| 153 |
+
echo "Subnet: $subnet"
|
| 154 |
+
fi
|
| 155 |
+
echo "Restart video scheduler: $restart_video_scheduler"
|
| 156 |
+
|
| 157 |
+
current_version=$(read_version_value)
|
| 158 |
+
|
| 159 |
+
if pm2 status | grep -q $proc_name; then
|
| 160 |
+
echo "The script is already running with pm2. Stopping and restarting..."
|
| 161 |
+
pm2 delete $proc_name
|
| 162 |
+
fi
|
| 163 |
+
|
| 164 |
+
echo "Running $script with the following PM2 config:"
|
| 165 |
+
|
| 166 |
+
# Ensure netuid is always included in args
|
| 167 |
+
netuid_found=false
|
| 168 |
+
for arg in "${args[@]}"; do
|
| 169 |
+
if [[ "$arg" == "'--netuid'" ]]; then
|
| 170 |
+
netuid_found=true
|
| 171 |
+
break
|
| 172 |
+
fi
|
| 173 |
+
done
|
| 174 |
+
|
| 175 |
+
if [[ "$netuid_found" == "false" ]]; then
|
| 176 |
+
args+=("'--netuid'")
|
| 177 |
+
args+=("'$subnet'")
|
| 178 |
+
fi
|
| 179 |
+
|
| 180 |
+
joined_args=$(printf "%s," "${args[@]}")
|
| 181 |
+
joined_args=${joined_args%,}
|
| 182 |
+
|
| 183 |
+
echo "module.exports = {
|
| 184 |
+
apps : [{
|
| 185 |
+
name : '$proc_name',
|
| 186 |
+
script : '$script',
|
| 187 |
+
interpreter: 'python3',
|
| 188 |
+
min_uptime: '5m',
|
| 189 |
+
max_restarts: '5',
|
| 190 |
+
args: [$joined_args],
|
| 191 |
+
cwd: '$(pwd)',
|
| 192 |
+
env: {
|
| 193 |
+
PYTHONPATH: '.'
|
| 194 |
+
}
|
| 195 |
+
}]
|
| 196 |
+
}" > app.config.js
|
| 197 |
+
|
| 198 |
+
cat app.config.js
|
| 199 |
+
ensure_config_process "app.config.js" "$proc_name" "true"
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
check_package_installed "jq"
|
| 203 |
+
|
| 204 |
+
# 🚀 START THE ADDITIONAL PM2 PROCESSES
|
| 205 |
+
ensure_process "scoring_endpoint" "bash -c 'PYTHONPATH=. python services/scoring/server.py'" "true"
|
| 206 |
+
ensure_process "video_scheduler_worker" "bash -c 'PYTHONPATH=. python services/video_scheduler/worker.py'" "$restart_video_scheduler"
|
| 207 |
+
ensure_process "video_scheduler_endpoint" "bash -c 'PYTHONPATH=. python services/video_scheduler/server.py'" "$restart_video_scheduler"
|
| 208 |
+
ensure_process "organic-gateway" "bash -c 'PYTHONPATH=. python services/organic_gateway/server.py'" "true"
|
| 209 |
+
|
| 210 |
+
# Auto-update loop
|
| 211 |
+
last_restart_time=$(date +%s)
|
| 212 |
+
restart_interval=$((30 * 3600)) # 30 hours in seconds
|
| 213 |
+
|
| 214 |
+
while true; do
|
| 215 |
+
current_time=$(date +%s)
|
| 216 |
+
time_since_last_restart=$((current_time - last_restart_time))
|
| 217 |
+
|
| 218 |
+
if [ -d "./.git" ]; then
|
| 219 |
+
latest_version=$(check_variable_value_on_github "$branch" "$version")
|
| 220 |
+
|
| 221 |
+
if version_less_than $current_version $latest_version; then
|
| 222 |
+
echo "Latest version: $latest_version"
|
| 223 |
+
echo "Current version: $current_version"
|
| 224 |
+
|
| 225 |
+
git stash
|
| 226 |
+
if git pull origin "$branch"; then
|
| 227 |
+
echo "New version detected. Updating..."
|
| 228 |
+
pip install -e .
|
| 229 |
+
|
| 230 |
+
current_version=$(read_version_value)
|
| 231 |
+
last_restart_time=$current_time
|
| 232 |
+
|
| 233 |
+
echo "Restarting script..."
|
| 234 |
+
exec ./$(basename "$0") $old_args
|
| 235 |
+
else
|
| 236 |
+
echo "** Will not update **"
|
| 237 |
+
echo "You have uncommitted changes. Please stash them using 'git stash'."
|
| 238 |
+
fi
|
| 239 |
+
else
|
| 240 |
+
echo "** No update needed **"
|
| 241 |
+
echo "$current_version is up-to-date with $latest_version."
|
| 242 |
+
|
| 243 |
+
if [ $time_since_last_restart -ge $restart_interval ]; then
|
| 244 |
+
echo "30 hours passed. Performing periodic PM2 restart..."
|
| 245 |
+
pm2 restart scoring_endpoint
|
| 246 |
+
if [[ "$restart_video_scheduler" == "true" ]]; then
|
| 247 |
+
pm2 restart video_scheduler_worker
|
| 248 |
+
pm2 restart video_scheduler_endpoint
|
| 249 |
+
fi
|
| 250 |
+
pm2 restart video-validator
|
| 251 |
+
|
| 252 |
+
last_restart_time=$current_time
|
| 253 |
+
echo "Periodic restart completed."
|
| 254 |
+
fi
|
| 255 |
+
fi
|
| 256 |
+
else
|
| 257 |
+
echo "The installation does not appear to be from Git. Please install from source at https://github.com/vidaio-subnet/vidaio-subnet."
|
| 258 |
+
fi
|
| 259 |
+
|
| 260 |
+
sleep 1800 # Sleep 30 minutes before checking again
|
| 261 |
+
done
|
scripts/capture_video_frames.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
import os
|
| 3 |
+
import argparse
|
| 4 |
+
import numpy as np
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
def capture_frames(video_path, output_dir, num_frames=3):
|
| 8 |
+
"""
|
| 9 |
+
Captures the first num_frames frames from a video file and resizes them to 128x128 pixels.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
video_path (str): Path to the video file
|
| 13 |
+
output_dir (str): Directory to save the captured frames
|
| 14 |
+
num_frames (int): Number of frames to capture from the start
|
| 15 |
+
|
| 16 |
+
Returns:
|
| 17 |
+
list: Paths to the saved frames
|
| 18 |
+
"""
|
| 19 |
+
# Check if video exists
|
| 20 |
+
if not os.path.isfile(video_path):
|
| 21 |
+
raise FileNotFoundError(f"Video file not found: {video_path}")
|
| 22 |
+
|
| 23 |
+
# Create output directory if it doesn't exist
|
| 24 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 25 |
+
|
| 26 |
+
# Get video filename without extension
|
| 27 |
+
video_name = Path(video_path).stem
|
| 28 |
+
|
| 29 |
+
# Open the video file
|
| 30 |
+
cap = cv2.VideoCapture(video_path)
|
| 31 |
+
|
| 32 |
+
if not cap.isOpened():
|
| 33 |
+
raise Exception(f"Error opening video file: {video_path}")
|
| 34 |
+
|
| 35 |
+
# Get video properties
|
| 36 |
+
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 37 |
+
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 38 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 39 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 40 |
+
|
| 41 |
+
print(f"Video Info:")
|
| 42 |
+
print(f" Resolution: {width}x{height}")
|
| 43 |
+
print(f" FPS: {fps:.2f}")
|
| 44 |
+
print(f" Total Frames: {total_frames}")
|
| 45 |
+
|
| 46 |
+
# Adjust num_frames if video is shorter
|
| 47 |
+
if total_frames < num_frames:
|
| 48 |
+
print(f"Warning: Video has only {total_frames} frames, adjusting to capture {total_frames} frames.")
|
| 49 |
+
num_frames = total_frames
|
| 50 |
+
|
| 51 |
+
saved_frames = []
|
| 52 |
+
for i in range(num_frames):
|
| 53 |
+
ret, frame = cap.read()
|
| 54 |
+
if not ret:
|
| 55 |
+
print(f"Error reading frame {i}")
|
| 56 |
+
break
|
| 57 |
+
|
| 58 |
+
resized_frame = cv2.resize(frame, (10, 10), interpolation=cv2.INTER_AREA)
|
| 59 |
+
|
| 60 |
+
# Save resized frame
|
| 61 |
+
frame_path = os.path.join(output_dir, f"{video_name}_frame_{i:03d}.png")
|
| 62 |
+
cv2.imwrite(frame_path, resized_frame)
|
| 63 |
+
saved_frames.append(frame_path)
|
| 64 |
+
print(f"Saved frame {i+1}/{num_frames}: {frame_path}")
|
| 65 |
+
|
| 66 |
+
cap.release()
|
| 67 |
+
return saved_frames
|
| 68 |
+
|
| 69 |
+
def main():
|
| 70 |
+
parser = argparse.ArgumentParser(description="Capture and resize frames from a video to 128x128 pixels")
|
| 71 |
+
parser.add_argument("--video", required=True, help="Path to the video file")
|
| 72 |
+
parser.add_argument("--output", default="frames", help="Directory to save the frames")
|
| 73 |
+
parser.add_argument("--frames", type=int, default=3, help="Number of frames to capture from the start")
|
| 74 |
+
args = parser.parse_args()
|
| 75 |
+
|
| 76 |
+
try:
|
| 77 |
+
saved_frames = capture_frames(args.video, args.output, args.frames)
|
| 78 |
+
print(f"Successfully captured {len(saved_frames)} frames from the video")
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"Error: {e}")
|
| 81 |
+
|
| 82 |
+
if __name__ == "__main__":
|
| 83 |
+
main()
|
scripts/check_queues.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Quick script to check video scheduler queue sizes
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
# Add the services directory to Python path
|
| 10 |
+
# sys.path.append(os.path.join(os.path.dirname(__file__), 'services', 'video_scheduler'))
|
| 11 |
+
|
| 12 |
+
from services.video_scheduler.redis_utils import (
|
| 13 |
+
get_redis_connection,
|
| 14 |
+
get_5s_queue_size,
|
| 15 |
+
get_10s_queue_size,
|
| 16 |
+
get_organic_upscaling_queue_size,
|
| 17 |
+
get_organic_compression_queue_size,
|
| 18 |
+
get_pexels_queue_size,
|
| 19 |
+
get_youtube_queue_size,
|
| 20 |
+
is_scheduler_ready
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
def main():
|
| 24 |
+
"""Check and display all queue sizes"""
|
| 25 |
+
try:
|
| 26 |
+
redis_conn = get_redis_connection()
|
| 27 |
+
|
| 28 |
+
print("=" * 50)
|
| 29 |
+
print("📊 VIDEO SCHEDULER QUEUE STATUS")
|
| 30 |
+
print("=" * 50)
|
| 31 |
+
|
| 32 |
+
# Scheduler readiness status
|
| 33 |
+
ready = is_scheduler_ready(redis_conn)
|
| 34 |
+
status_icon = "🟢" if ready else "🔴"
|
| 35 |
+
print(f"{status_icon} Scheduler Ready: {'YES' if ready else 'NO'}")
|
| 36 |
+
print()
|
| 37 |
+
|
| 38 |
+
# Synthetic queues
|
| 39 |
+
print("🎬 SYNTHETIC QUEUES:")
|
| 40 |
+
print(f" 5s chunks: {get_5s_queue_size(redis_conn):>4}")
|
| 41 |
+
print(f" 10s chunks: {get_10s_queue_size(redis_conn):>4}")
|
| 42 |
+
print(f" Compressed chunks: {get_organic_compression_queue_size(redis_conn):>4}")
|
| 43 |
+
print(f" Upscaling chunks: {get_organic_upscaling_queue_size(redis_conn):>4}")
|
| 44 |
+
print()
|
| 45 |
+
|
| 46 |
+
# Source queues
|
| 47 |
+
print("🎥 SOURCE QUEUES:")
|
| 48 |
+
print(f" Pexels IDs: {get_pexels_queue_size(redis_conn):>4}")
|
| 49 |
+
print(f" YouTube IDs: {get_youtube_queue_size(redis_conn):>4}")
|
| 50 |
+
print()
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# Total counts
|
| 54 |
+
total_synthetic = get_5s_queue_size(redis_conn) + get_10s_queue_size(redis_conn) + get_organic_compression_queue_size(redis_conn) + get_organic_upscaling_queue_size(redis_conn)
|
| 55 |
+
total_source = get_pexels_queue_size(redis_conn) + get_youtube_queue_size(redis_conn)
|
| 56 |
+
|
| 57 |
+
print("📈 TOTALS:")
|
| 58 |
+
print(f" Total Synthetic: {total_synthetic:>4}")
|
| 59 |
+
print(f" Total Source: {total_source:>4}")
|
| 60 |
+
print("=" * 50)
|
| 61 |
+
|
| 62 |
+
except Exception as e:
|
| 63 |
+
print(f"❌ Error checking queues: {str(e)}")
|
| 64 |
+
print("💡 Make sure Redis is running and accessible")
|
| 65 |
+
sys.exit(1)
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|
scripts/clip_video.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Video Clipping Script
|
| 4 |
+
|
| 5 |
+
This script clips a video file to exactly 19 seconds starting from the beginning.
|
| 6 |
+
Uses ffmpeg for efficient video processing.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import sys
|
| 11 |
+
import subprocess
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import Optional, Tuple
|
| 14 |
+
import argparse
|
| 15 |
+
|
| 16 |
+
def _validate_video_file(file_path: str) -> bool:
|
| 17 |
+
"""
|
| 18 |
+
Validate that the input file exists and is a readable video file.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
file_path: Path to the video file
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
True if file is valid, False otherwise
|
| 25 |
+
"""
|
| 26 |
+
if not os.path.exists(file_path):
|
| 27 |
+
print(f"Error: File '{file_path}' does not exist.")
|
| 28 |
+
return False
|
| 29 |
+
|
| 30 |
+
if not os.path.isfile(file_path):
|
| 31 |
+
print(f"Error: '{file_path}' is not a file.")
|
| 32 |
+
return False
|
| 33 |
+
|
| 34 |
+
if not os.access(file_path, os.R_OK):
|
| 35 |
+
print(f"Error: File '{file_path}' is not readable.")
|
| 36 |
+
return False
|
| 37 |
+
|
| 38 |
+
return True
|
| 39 |
+
|
| 40 |
+
def _get_video_duration(file_path: str) -> Optional[float]:
|
| 41 |
+
"""
|
| 42 |
+
Get the duration of a video file using ffprobe.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
file_path: Path to the video file
|
| 46 |
+
|
| 47 |
+
Returns:
|
| 48 |
+
Duration in seconds, or None if ffprobe fails
|
| 49 |
+
"""
|
| 50 |
+
try:
|
| 51 |
+
cmd = [
|
| 52 |
+
'ffprobe',
|
| 53 |
+
'-v', 'quiet',
|
| 54 |
+
'-show_entries', 'format=duration',
|
| 55 |
+
'-of', 'csv=p=0',
|
| 56 |
+
file_path
|
| 57 |
+
]
|
| 58 |
+
|
| 59 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 60 |
+
duration = float(result.stdout.strip())
|
| 61 |
+
return duration
|
| 62 |
+
except (subprocess.CalledProcessError, ValueError, FileNotFoundError) as e:
|
| 63 |
+
print(f"Error getting video duration: {e}")
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
def _generate_output_filename(input_path: str) -> str:
|
| 67 |
+
"""
|
| 68 |
+
Generate output filename by adding '_19s' suffix before the extension.
|
| 69 |
+
|
| 70 |
+
Args:
|
| 71 |
+
input_path: Path to the input video file
|
| 72 |
+
|
| 73 |
+
Returns:
|
| 74 |
+
Output filename with '_19s' suffix
|
| 75 |
+
"""
|
| 76 |
+
input_path_obj = Path(input_path)
|
| 77 |
+
stem = input_path_obj.stem
|
| 78 |
+
suffix = input_path_obj.suffix
|
| 79 |
+
|
| 80 |
+
# Create output filename
|
| 81 |
+
output_filename = f"{stem}_19s{suffix}"
|
| 82 |
+
|
| 83 |
+
# If output would overwrite input, add timestamp
|
| 84 |
+
if output_filename == input_path_obj.name:
|
| 85 |
+
from datetime import datetime
|
| 86 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 87 |
+
output_filename = f"{stem}_19s_{timestamp}{suffix}"
|
| 88 |
+
|
| 89 |
+
return output_filename
|
| 90 |
+
|
| 91 |
+
def _clip_video(input_path: str, output_path: str, duration: int = 19) -> bool:
|
| 92 |
+
"""
|
| 93 |
+
Clip the video to the specified duration using ffmpeg.
|
| 94 |
+
|
| 95 |
+
Args:
|
| 96 |
+
input_path: Path to the input video file
|
| 97 |
+
output_path: Path for the output video file
|
| 98 |
+
duration: Duration to clip to in seconds (default: 19)
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
True if clipping was successful, False otherwise
|
| 102 |
+
"""
|
| 103 |
+
try:
|
| 104 |
+
cmd = [
|
| 105 |
+
'ffmpeg',
|
| 106 |
+
'-i', input_path,
|
| 107 |
+
'-t', str(duration),
|
| 108 |
+
'-c', 'copy', # Copy streams without re-encoding for speed
|
| 109 |
+
'-avoid_negative_ts', 'make_zero',
|
| 110 |
+
'-y', # Overwrite output file if it exists
|
| 111 |
+
output_path
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
print(f"Cliping video to {duration} seconds...")
|
| 115 |
+
print(f"Input: {input_path}")
|
| 116 |
+
print(f"Output: {output_path}")
|
| 117 |
+
|
| 118 |
+
# Run ffmpeg command
|
| 119 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 120 |
+
|
| 121 |
+
print("Video clipping completed successfully!")
|
| 122 |
+
return True
|
| 123 |
+
|
| 124 |
+
except subprocess.CalledProcessError as e:
|
| 125 |
+
print(f"Error during video clipping: {e}")
|
| 126 |
+
if e.stderr:
|
| 127 |
+
print(f"FFmpeg error: {e.stderr}")
|
| 128 |
+
return False
|
| 129 |
+
except Exception as e:
|
| 130 |
+
print(f"Unexpected error: {e}")
|
| 131 |
+
return False
|
| 132 |
+
|
| 133 |
+
def clip_video_to_19s(video_path: str) -> bool:
|
| 134 |
+
"""
|
| 135 |
+
Main function to clip a video file to 19 seconds.
|
| 136 |
+
|
| 137 |
+
Args:
|
| 138 |
+
video_path: Path to the input video file
|
| 139 |
+
|
| 140 |
+
Returns:
|
| 141 |
+
True if clipping was successful, False otherwise
|
| 142 |
+
"""
|
| 143 |
+
# Validate input file
|
| 144 |
+
if not _validate_video_file(video_path):
|
| 145 |
+
return False
|
| 146 |
+
|
| 147 |
+
# Get original video duration
|
| 148 |
+
original_duration = _get_video_duration(video_path)
|
| 149 |
+
if original_duration is None:
|
| 150 |
+
return False
|
| 151 |
+
|
| 152 |
+
print(f"Original video duration: {original_duration:.2f} seconds")
|
| 153 |
+
|
| 154 |
+
# Check if video is already shorter than 19 seconds
|
| 155 |
+
if original_duration <= 19:
|
| 156 |
+
print(f"Video is already {original_duration:.2f} seconds long (≤ 19s). No clipping needed.")
|
| 157 |
+
return True
|
| 158 |
+
|
| 159 |
+
# Generate output filename
|
| 160 |
+
output_filename = _generate_output_filename(video_path)
|
| 161 |
+
output_path = os.path.join(os.path.dirname(video_path), output_filename)
|
| 162 |
+
|
| 163 |
+
# Perform video clipping
|
| 164 |
+
success = _clip_video(video_path, output_path, 19)
|
| 165 |
+
|
| 166 |
+
if success:
|
| 167 |
+
# Verify output file exists and get its duration
|
| 168 |
+
if os.path.exists(output_path):
|
| 169 |
+
output_duration = _get_video_duration(output_path)
|
| 170 |
+
if output_duration:
|
| 171 |
+
print(f"Output video duration: {output_duration:.2f} seconds")
|
| 172 |
+
print(f"Output saved to: {output_path}")
|
| 173 |
+
else:
|
| 174 |
+
print("Warning: Could not verify output video duration")
|
| 175 |
+
else:
|
| 176 |
+
print("Warning: Output file was not created")
|
| 177 |
+
|
| 178 |
+
return success
|
| 179 |
+
|
| 180 |
+
def main():
|
| 181 |
+
"""Main entry point for command line usage."""
|
| 182 |
+
parser = argparse.ArgumentParser(
|
| 183 |
+
description="Clip a video file to 19 seconds",
|
| 184 |
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
| 185 |
+
epilog="""
|
| 186 |
+
Examples:
|
| 187 |
+
python clip_video.py video.mp4
|
| 188 |
+
python clip_video.py /path/to/video.mov
|
| 189 |
+
python clip_video.py "video with spaces.avi"
|
| 190 |
+
"""
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
parser.add_argument(
|
| 194 |
+
'video_path',
|
| 195 |
+
help='Path to the input video file'
|
| 196 |
+
)
|
| 197 |
+
#python clip_video.py --video_path /workspace/vidaio-subnet/2025-03-18 12-27-33.mov
|
| 198 |
+
args = parser.parse_args()
|
| 199 |
+
|
| 200 |
+
# Perform video clipping
|
| 201 |
+
success = clip_video_to_19s(args.video_path)
|
| 202 |
+
|
| 203 |
+
# Exit with appropriate code
|
| 204 |
+
sys.exit(0 if success else 1)
|
| 205 |
+
|
| 206 |
+
if __name__ == "__main__":
|
| 207 |
+
main()
|
scripts/compress_miner_king1_0.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
python neurons/miner.py \
|
| 4 |
+
--wallet.name king85 \
|
| 5 |
+
--wallet.hotkey bothot00 \
|
| 6 |
+
--subtensor.network finney \
|
| 7 |
+
--netuid 85 \
|
| 8 |
+
--axon.port 8091 \
|
| 9 |
+
--logging.debug
|
| 10 |
+
|
scripts/compress_miner_king85_1.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
python neurons/miner.py \
|
| 4 |
+
--wallet.name king85 \
|
| 5 |
+
--wallet.hotkey bothot01 \
|
| 6 |
+
--subtensor.network finney \
|
| 7 |
+
--netuid 85 \
|
| 8 |
+
--axon.port 8092 \
|
| 9 |
+
--logging.debug
|
| 10 |
+
|
scripts/compress_miner_king85_2.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
python neurons/miner.py \
|
| 4 |
+
--wallet.name king85 \
|
| 5 |
+
--wallet.hotkey bothot02 \
|
| 6 |
+
--subtensor.network finney \
|
| 7 |
+
--netuid 85 \
|
| 8 |
+
--axon.port 8093 \
|
| 9 |
+
--logging.debug
|
| 10 |
+
|
scripts/compress_miner_king85_3.sh
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
python neurons/miner.py \
|
| 4 |
+
--wallet.name king85 \
|
| 5 |
+
--wallet.hotkey bothot03 \
|
| 6 |
+
--subtensor.network finney \
|
| 7 |
+
--netuid 85 \
|
| 8 |
+
--axon.port 8094 \
|
| 9 |
+
--logging.debug
|
| 10 |
+
|
scripts/compress_server.sh
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
python services/custom_compress/server.py
|
scripts/get_video_lengths.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Video Length Scanner Script
|
| 4 |
+
|
| 5 |
+
This script scans the /videos directory and prints a list of all video files
|
| 6 |
+
along with their durations in a readable format.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import os
|
| 10 |
+
import subprocess
|
| 11 |
+
import sys
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import List, Tuple, Optional
|
| 14 |
+
|
| 15 |
+
def get_video_duration(file_path: str) -> Optional[float]:
|
| 16 |
+
"""
|
| 17 |
+
Get the duration of a video file using ffprobe.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
file_path: Path to the video file
|
| 21 |
+
|
| 22 |
+
Returns:
|
| 23 |
+
Duration in seconds, or None if ffprobe fails
|
| 24 |
+
"""
|
| 25 |
+
try:
|
| 26 |
+
# Use ffprobe to get video duration
|
| 27 |
+
cmd = [
|
| 28 |
+
'ffprobe',
|
| 29 |
+
'-v', 'quiet',
|
| 30 |
+
'-show_entries', 'format=duration',
|
| 31 |
+
'-of', 'csv=p=0',
|
| 32 |
+
file_path
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 36 |
+
duration = float(result.stdout.strip())
|
| 37 |
+
return duration
|
| 38 |
+
except (subprocess.CalledProcessError, ValueError, FileNotFoundError):
|
| 39 |
+
return None
|
| 40 |
+
|
| 41 |
+
def format_duration(seconds: float) -> str:
|
| 42 |
+
"""
|
| 43 |
+
Format duration in seconds to a human-readable string.
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
seconds: Duration in seconds
|
| 47 |
+
|
| 48 |
+
Returns:
|
| 49 |
+
Formatted duration string (e.g., "1:23.45")
|
| 50 |
+
"""
|
| 51 |
+
if seconds < 0:
|
| 52 |
+
return "Invalid"
|
| 53 |
+
|
| 54 |
+
minutes = int(seconds // 60)
|
| 55 |
+
remaining_seconds = seconds % 60
|
| 56 |
+
|
| 57 |
+
if minutes == 0:
|
| 58 |
+
return f"{remaining_seconds:.2f}s"
|
| 59 |
+
else:
|
| 60 |
+
return f"{minutes}:{remaining_seconds:05.2f}"
|
| 61 |
+
|
| 62 |
+
def categorize_duration(seconds: float) -> str:
|
| 63 |
+
"""
|
| 64 |
+
Categorize duration into chunks (e.g., 10s, 20s, 30s).
|
| 65 |
+
Videos within ±0.5 seconds of a whole number are grouped together.
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
seconds: Duration in seconds
|
| 69 |
+
|
| 70 |
+
Returns:
|
| 71 |
+
Category string (e.g., "10s", "20s", "1m", "2m")
|
| 72 |
+
"""
|
| 73 |
+
if seconds < 0:
|
| 74 |
+
return "Invalid"
|
| 75 |
+
|
| 76 |
+
# Round to nearest second for categorization
|
| 77 |
+
rounded_seconds = round(seconds)
|
| 78 |
+
|
| 79 |
+
if rounded_seconds < 60:
|
| 80 |
+
return f"{rounded_seconds}s"
|
| 81 |
+
else:
|
| 82 |
+
minutes = rounded_seconds // 60
|
| 83 |
+
remaining_seconds = rounded_seconds % 60
|
| 84 |
+
if remaining_seconds == 0:
|
| 85 |
+
return f"{minutes}m"
|
| 86 |
+
else:
|
| 87 |
+
return f"{minutes}m{remaining_seconds}s"
|
| 88 |
+
|
| 89 |
+
def scan_videos_directory(videos_dir: str = "videos") -> List[Tuple[str, float]]:
|
| 90 |
+
"""
|
| 91 |
+
Scan the videos directory and return a list of video files with their durations.
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
videos_dir: Path to the videos directory
|
| 95 |
+
|
| 96 |
+
Returns:
|
| 97 |
+
List of tuples containing (filename, duration_in_seconds)
|
| 98 |
+
"""
|
| 99 |
+
video_files = []
|
| 100 |
+
|
| 101 |
+
if not os.path.exists(videos_dir):
|
| 102 |
+
print(f"Error: Directory '{videos_dir}' does not exist.")
|
| 103 |
+
return []
|
| 104 |
+
|
| 105 |
+
# Supported video extensions
|
| 106 |
+
video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.wmv', '.flv', '.webm', '.m4v'}
|
| 107 |
+
|
| 108 |
+
for filename in os.listdir(videos_dir):
|
| 109 |
+
file_path = os.path.join(videos_dir, filename)
|
| 110 |
+
|
| 111 |
+
# Check if it's a file and has a video extension
|
| 112 |
+
if os.path.isfile(file_path) and Path(filename).suffix.lower() in video_extensions:
|
| 113 |
+
duration = get_video_duration(file_path)
|
| 114 |
+
if duration is not None:
|
| 115 |
+
video_files.append((filename, duration))
|
| 116 |
+
else:
|
| 117 |
+
print(f"Warning: Could not get duration for {filename}")
|
| 118 |
+
|
| 119 |
+
return sorted(video_files, key=lambda x: x[1]) # Sort by duration
|
| 120 |
+
|
| 121 |
+
def main():
|
| 122 |
+
"""Main function to run the video length scanner."""
|
| 123 |
+
print("Video Length Scanner")
|
| 124 |
+
print("=" * 50)
|
| 125 |
+
|
| 126 |
+
# Check if ffprobe is available
|
| 127 |
+
try:
|
| 128 |
+
subprocess.run(['ffprobe', '-version'], capture_output=True, check=True)
|
| 129 |
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
| 130 |
+
print("Error: ffprobe is not installed or not in PATH.")
|
| 131 |
+
print("Please install FFmpeg to use this script.")
|
| 132 |
+
print("On Ubuntu/Debian: sudo apt install ffmpeg")
|
| 133 |
+
print("On CentOS/RHEL: sudo yum install ffmpeg")
|
| 134 |
+
print("On macOS: brew install ffmpeg")
|
| 135 |
+
sys.exit(1)
|
| 136 |
+
|
| 137 |
+
# Scan for videos
|
| 138 |
+
print("Scanning videos directory for video files...")
|
| 139 |
+
video_files = scan_videos_directory()
|
| 140 |
+
|
| 141 |
+
if not video_files:
|
| 142 |
+
print("No video files found in /videos directory.")
|
| 143 |
+
return
|
| 144 |
+
|
| 145 |
+
# Print results
|
| 146 |
+
print(f"\nFound {len(video_files)} video files:\n")
|
| 147 |
+
print(f"{'Filename':<50} {'Duration':<15} {'Size':<10}")
|
| 148 |
+
print("-" * 75)
|
| 149 |
+
|
| 150 |
+
total_duration = 0
|
| 151 |
+
|
| 152 |
+
for filename, duration in video_files:
|
| 153 |
+
# Get file size
|
| 154 |
+
file_path = os.path.join("videos", filename)
|
| 155 |
+
file_size = os.path.getsize(file_path)
|
| 156 |
+
size_mb = file_size / (1024 * 1024)
|
| 157 |
+
|
| 158 |
+
formatted_duration = format_duration(duration)
|
| 159 |
+
print(f"{filename:<50} {formatted_duration:<15} {size_mb:.1f}MB")
|
| 160 |
+
total_duration += duration
|
| 161 |
+
|
| 162 |
+
print("-" * 75)
|
| 163 |
+
print(f"{'Total':<50} {format_duration(total_duration):<15} {len(video_files)} files")
|
| 164 |
+
|
| 165 |
+
# Summary statistics
|
| 166 |
+
if video_files:
|
| 167 |
+
avg_duration = total_duration / len(video_files)
|
| 168 |
+
min_duration = min(video_files, key=lambda x: x[1])[1]
|
| 169 |
+
max_duration = max(video_files, key=lambda x: x[1])[1]
|
| 170 |
+
|
| 171 |
+
print(f"\nSummary Statistics:")
|
| 172 |
+
print(f"Average duration: {format_duration(avg_duration)}")
|
| 173 |
+
print(f"Shortest video: {format_duration(min_duration)}")
|
| 174 |
+
print(f"Longest video: {format_duration(max_duration)}")
|
| 175 |
+
|
| 176 |
+
# Duration category summary
|
| 177 |
+
print(f"\nDuration Categories:")
|
| 178 |
+
print("=" * 40)
|
| 179 |
+
|
| 180 |
+
# Group videos by duration category
|
| 181 |
+
duration_categories = {}
|
| 182 |
+
for filename, duration in video_files:
|
| 183 |
+
category = categorize_duration(duration)
|
| 184 |
+
if category not in duration_categories:
|
| 185 |
+
duration_categories[category] = []
|
| 186 |
+
duration_categories[category].append((filename, duration))
|
| 187 |
+
|
| 188 |
+
# Sort categories by duration (convert to seconds for sorting)
|
| 189 |
+
def category_sort_key(cat):
|
| 190 |
+
if 'm' in cat:
|
| 191 |
+
if 's' in cat:
|
| 192 |
+
# Format like "1m30s"
|
| 193 |
+
parts = cat.replace('m', ' ').replace('s', '').split()
|
| 194 |
+
return int(parts[0]) * 60 + int(parts[1])
|
| 195 |
+
else:
|
| 196 |
+
# Format like "2m"
|
| 197 |
+
return int(cat.replace('m', '')) * 60
|
| 198 |
+
else:
|
| 199 |
+
# Format like "30s"
|
| 200 |
+
return int(cat.replace('s', ''))
|
| 201 |
+
|
| 202 |
+
sorted_categories = sorted(duration_categories.items(), key=lambda x: category_sort_key(x[0]))
|
| 203 |
+
|
| 204 |
+
for category, videos in sorted_categories:
|
| 205 |
+
total_category_duration = sum(duration for _, duration in videos)
|
| 206 |
+
print(f"{category:<10} | {len(videos):>3} videos | Total: {format_duration(total_category_duration):<10} | Avg: {format_duration(total_category_duration/len(videos))}")
|
| 207 |
+
|
| 208 |
+
print("=" * 40)
|
| 209 |
+
print(f"Total categories: {len(duration_categories)}")
|
| 210 |
+
|
| 211 |
+
if __name__ == "__main__":
|
| 212 |
+
main()
|
scripts/video_info_cap.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import json
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
def get_video_info(file_path):
|
| 6 |
+
"""
|
| 7 |
+
Get detailed video information using FFprobe.
|
| 8 |
+
:param file_path: Path to the video file
|
| 9 |
+
"""
|
| 10 |
+
try:
|
| 11 |
+
# Run FFprobe to get video details in JSON format
|
| 12 |
+
command = [
|
| 13 |
+
"ffprobe",
|
| 14 |
+
"-v", "error", # Suppress unnecessary logs
|
| 15 |
+
"-show_format", # Show container format
|
| 16 |
+
"-show_streams", # Show codec and stream details
|
| 17 |
+
"-count_frames", # Count the number of frames
|
| 18 |
+
"-print_format", "json", # Output as JSON
|
| 19 |
+
file_path
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
| 23 |
+
|
| 24 |
+
# Parse the JSON output
|
| 25 |
+
video_info = json.loads(result.stdout)
|
| 26 |
+
|
| 27 |
+
# Print details
|
| 28 |
+
print("\n=== Video File Information ===")
|
| 29 |
+
print(f"File Path: {file_path}")
|
| 30 |
+
print("=" * 40)
|
| 31 |
+
|
| 32 |
+
# Format information
|
| 33 |
+
if "format" in video_info:
|
| 34 |
+
format_info = video_info["format"]
|
| 35 |
+
print(f"File Format: {format_info.get('format_name', 'N/A')}")
|
| 36 |
+
print(f"File Size: {int(format_info.get('size', 0)) / (1024 ** 2):.2f} MB")
|
| 37 |
+
print(f"Duration: {float(format_info.get('duration', 0)):.2f} seconds")
|
| 38 |
+
print(f"Bitrate: {int(format_info.get('bit_rate', 0)) / 1000:.2f} kbps")
|
| 39 |
+
print(f"Compressed: {'Yes' if 'bit_rate' in format_info else 'No'}")
|
| 40 |
+
|
| 41 |
+
print("\n=== Stream Information ===")
|
| 42 |
+
for stream in video_info.get("streams", []):
|
| 43 |
+
codec_type = stream.get("codec_type", "unknown").capitalize()
|
| 44 |
+
print(f"\nStream Type: {codec_type}")
|
| 45 |
+
print(f"Codec: {stream.get('codec_name', 'N/A')}")
|
| 46 |
+
print(f"Codec Long Name: {stream.get('codec_long_name', 'N/A')}")
|
| 47 |
+
if codec_type == "Video":
|
| 48 |
+
print(f"Resolution: {stream.get('width', 'N/A')}x{stream.get('height', 'N/A')}")
|
| 49 |
+
print(f"Frame Rate: {eval(stream.get('r_frame_rate', '0')):.2f} fps")
|
| 50 |
+
print(f"Number of Frames: {stream.get('nb_frames', 'N/A')}")
|
| 51 |
+
print(f"Pixel Format: {stream.get('pix_fmt', 'N/A')}")
|
| 52 |
+
elif codec_type == "Audio":
|
| 53 |
+
print(f"Sample Rate: {stream.get('sample_rate', 'N/A')} Hz")
|
| 54 |
+
print(f"Channels: {stream.get('channels', 'N/A')}")
|
| 55 |
+
print(f"Channel Layout: {stream.get('channel_layout', 'N/A')}")
|
| 56 |
+
print(f"Bitrate: {int(stream.get('bit_rate', 0)) / 1000:.2f} kbps")
|
| 57 |
+
|
| 58 |
+
print("\nDetailed Info Extraction Complete.")
|
| 59 |
+
|
| 60 |
+
except Exception as e:
|
| 61 |
+
print(f"Error: {e}")
|
| 62 |
+
sys.exit(1)
|
| 63 |
+
|
| 64 |
+
# Example usage
|
| 65 |
+
if __name__ == "__main__":
|
| 66 |
+
# Input video file path
|
| 67 |
+
video_path = input("Enter the path to the video file: ").strip()
|
| 68 |
+
get_video_info(video_path)
|
services/compress/__init__.py
ADDED
|
File without changes
|
services/compress/encoder.py
ADDED
|
@@ -0,0 +1,893 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import time
|
| 4 |
+
import torch
|
| 5 |
+
import pickle
|
| 6 |
+
import traceback
|
| 7 |
+
import subprocess
|
| 8 |
+
from sklearn.exceptions import NotFittedError
|
| 9 |
+
from sklearn.preprocessing import StandardScaler
|
| 10 |
+
from sklearn.utils.validation import check_is_fitted
|
| 11 |
+
from utils.processing_utils import (
|
| 12 |
+
encode_scene_with_size_check,
|
| 13 |
+
classify_scene_from_path
|
| 14 |
+
)
|
| 15 |
+
from utils.encode_video import encode_video
|
| 16 |
+
from utils.classify_scene import load_scene_classifier_model, CombinedModel
|
| 17 |
+
|
| 18 |
+
def get_cq_from_lookup_table(scene_type, config, target_vmaf=None, target_quality_level=None):
|
| 19 |
+
"""
|
| 20 |
+
Look up the recommended CQ (Constant Quality) value for a scene type and target quality.
|
| 21 |
+
|
| 22 |
+
CQ values control encoding quality:
|
| 23 |
+
- Lower CQ = Higher quality, bigger file size (CQ 15-20)
|
| 24 |
+
- Higher CQ = Lower quality, smaller file size (CQ 30-35)
|
| 25 |
+
|
| 26 |
+
The lookup table now supports two selection modes:
|
| 27 |
+
- By explicit quality level via config['video_processing']['basic_cq_lookup_by_quality']
|
| 28 |
+
- By VMAF-derived tiers via config['video_processing']['basic_cq_lookup_tiered']
|
| 29 |
+
- High quality tier (VMAF 95+): Lower CQ values for better quality
|
| 30 |
+
- Medium quality tier (VMAF ~90): Balanced CQ values
|
| 31 |
+
- Low quality tier (VMAF ~85): Higher CQ values for smaller files
|
| 32 |
+
|
| 33 |
+
Args:
|
| 34 |
+
scene_type (str): Scene classification from AI model
|
| 35 |
+
config (dict): Configuration containing CQ lookup tables
|
| 36 |
+
target_vmaf (float, optional): Target VMAF score for quality tier (fallback)
|
| 37 |
+
target_quality_level (str, optional): 'High'|'Medium'|'Low' to select from by-quality table
|
| 38 |
+
|
| 39 |
+
Returns:
|
| 40 |
+
int: Recommended CQ value for encoding this scene type at target quality
|
| 41 |
+
"""
|
| 42 |
+
vp = config.get('video_processing', {})
|
| 43 |
+
|
| 44 |
+
if target_quality_level:
|
| 45 |
+
norm = {'high': 'High', 'medium': 'Medium', 'low': 'Low'}
|
| 46 |
+
q_key = norm.get(str(target_quality_level).lower(), target_quality_level)
|
| 47 |
+
by_quality = vp.get('basic_cq_lookup_by_quality', {})
|
| 48 |
+
if isinstance(by_quality, dict):
|
| 49 |
+
table = by_quality.get(q_key) or by_quality.get(q_key.lower()) or {}
|
| 50 |
+
if isinstance(table, dict):
|
| 51 |
+
return table.get(scene_type, table.get('default', 25))
|
| 52 |
+
|
| 53 |
+
# Determine quality tier based on target VMAF (fallback)
|
| 54 |
+
if target_vmaf is None:
|
| 55 |
+
quality_tier = 'medium' # Default to medium if no target specified
|
| 56 |
+
elif target_vmaf >= 93:
|
| 57 |
+
quality_tier = 'high'
|
| 58 |
+
elif target_vmaf >= 88:
|
| 59 |
+
quality_tier = 'medium'
|
| 60 |
+
else:
|
| 61 |
+
quality_tier = 'low'
|
| 62 |
+
|
| 63 |
+
default_cq_map = {
|
| 64 |
+
# High quality tier (lower CQ = higher quality)
|
| 65 |
+
'high': {
|
| 66 |
+
'animation': 25, # Cartoons compress well naturally
|
| 67 |
+
'low-action': 23, # Text/faces need moderate CQ for clarity
|
| 68 |
+
'medium-action': 21, # Balanced CQ for general content
|
| 69 |
+
'high-action': 19, # Gaming/sports need lower CQ for quality
|
| 70 |
+
'default': 22 # Safe middle-ground when unsure
|
| 71 |
+
},
|
| 72 |
+
# Medium quality tier (balanced CQ)
|
| 73 |
+
'medium': {
|
| 74 |
+
'animation': 28, # Cartoons compress well, can use higher CQ
|
| 75 |
+
'low-action': 26, # Text/faces need moderate CQ for clarity
|
| 76 |
+
'medium-action': 24, # Balanced CQ for general content
|
| 77 |
+
'high-action': 22, # Gaming/sports need lower CQ for quality
|
| 78 |
+
'default': 25 # Safe middle-ground when unsure
|
| 79 |
+
},
|
| 80 |
+
# Low quality tier (higher CQ = smaller files)
|
| 81 |
+
'low': {
|
| 82 |
+
'animation': 31, # Cartoons compress well, can use higher CQ
|
| 83 |
+
'low-action': 29, # Text/faces need moderate CQ for clarity
|
| 84 |
+
'medium-action': 27, # Balanced CQ for general content
|
| 85 |
+
'high-action': 25, # Gaming/sports need lower CQ for quality
|
| 86 |
+
'default': 28 # Safe middle-ground when unsure
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
# Get the lookup table from config, or use our defaults
|
| 91 |
+
tier_cq_maps = vp.get('basic_cq_lookup_tiered', default_cq_map)
|
| 92 |
+
|
| 93 |
+
# Use the appropriate tier based on target quality
|
| 94 |
+
cq_lookup_table = tier_cq_maps.get(quality_tier, tier_cq_maps.get('medium', default_cq_map['medium']))
|
| 95 |
+
|
| 96 |
+
# Return the CQ for this scene type within the selected quality tier, with fallback to default
|
| 97 |
+
return cq_lookup_table.get(scene_type, cq_lookup_table.get('default', 25))
|
| 98 |
+
|
| 99 |
+
def get_scalers_from_pipeline(pipeline_path='services/compress/models/preprocessing_pipeline.pkl',
|
| 100 |
+
verbose=True, logging_enabled=True):
|
| 101 |
+
"""
|
| 102 |
+
Load preprocessing pipeline and extract individual components with verbosity control.
|
| 103 |
+
|
| 104 |
+
This function loads a complete scikit-learn preprocessing pipeline and extracts
|
| 105 |
+
the individual components needed for VMAF prediction:
|
| 106 |
+
- Feature scaler for normalizing video metrics
|
| 107 |
+
- VMAF scaler for converting quality scores to model range
|
| 108 |
+
- CQ (Constant Quality) parameter bounds for optimization
|
| 109 |
+
|
| 110 |
+
The function also sets verbosity levels for all pipeline components to control
|
| 111 |
+
the amount of output during batch processing operations.
|
| 112 |
+
|
| 113 |
+
Pipeline Structure Expected:
|
| 114 |
+
- feature_scaler: StandardScaler or similar for video features
|
| 115 |
+
- vmaf_scaler: Custom scaler for VMAF quality scores
|
| 116 |
+
- cq_scaler: Custom scaler containing CQ parameter bounds
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
pipeline_path (str): Path to the saved preprocessing pipeline pickle file
|
| 120 |
+
verbose (bool): Whether individual pipeline steps should produce verbose output
|
| 121 |
+
logging_enabled (bool): Master logging control (overrides verbose if False)
|
| 122 |
+
|
| 123 |
+
Returns:
|
| 124 |
+
tuple: (pipeline_obj, feature_scaler_step, vmaf_scaler, cq_min, cq_max)
|
| 125 |
+
- pipeline_obj: Complete fitted preprocessing pipeline
|
| 126 |
+
- feature_scaler_step: Feature scaling component
|
| 127 |
+
- vmaf_scaler: VMAF scaling component
|
| 128 |
+
- cq_min: Minimum CQ value for optimization bounds
|
| 129 |
+
- cq_max: Maximum CQ value for optimization bounds
|
| 130 |
+
Returns (None, None, None, None, None) if loading fails
|
| 131 |
+
"""
|
| 132 |
+
|
| 133 |
+
try:
|
| 134 |
+
from utils import ColumnDropper, VMAFScaler, TargetExtractor, CQScaler, ResolutionTransformer, FeatureScaler, FrameRateTransformer
|
| 135 |
+
|
| 136 |
+
if logging_enabled:
|
| 137 |
+
print(f"✅ Preprocessing classes are available through utils module")
|
| 138 |
+
|
| 139 |
+
except ImportError as e:
|
| 140 |
+
if logging_enabled:
|
| 141 |
+
print(f"❌ ERROR: Could not access preprocessing classes: {e}")
|
| 142 |
+
print(f" 💡 Check that utils/__init__.py properly imports data_preprocessing classes")
|
| 143 |
+
return None, None, None, None, None
|
| 144 |
+
|
| 145 |
+
actual_verbose = verbose and logging_enabled
|
| 146 |
+
|
| 147 |
+
pipeline_obj = None
|
| 148 |
+
feature_scaler_step = None
|
| 149 |
+
vmaf_scaler = None
|
| 150 |
+
cq_min_original, cq_max_original = None, None
|
| 151 |
+
|
| 152 |
+
# =================================================================
|
| 153 |
+
# PIPELINE FILE LOADING AND VALIDATION
|
| 154 |
+
# =================================================================
|
| 155 |
+
if not os.path.exists(pipeline_path):
|
| 156 |
+
if actual_verbose:
|
| 157 |
+
print(f"❌ ERROR: Pipeline file not found at '{pipeline_path}'")
|
| 158 |
+
print(f" 💡 Suggestion: Ensure preprocessing pipeline exists in /models/ directory")
|
| 159 |
+
return None, None, None, None, None
|
| 160 |
+
|
| 161 |
+
try:
|
| 162 |
+
# Load the pickled preprocessing pipeline
|
| 163 |
+
if actual_verbose:
|
| 164 |
+
print(f"📖 Loading preprocessing pipeline from: {pipeline_path}")
|
| 165 |
+
|
| 166 |
+
# Load the pickled preprocessing pipeline with custom unpickler to handle module namespace issues
|
| 167 |
+
class CustomUnpickler(pickle.Unpickler):
|
| 168 |
+
def find_class(self, module, name):
|
| 169 |
+
if module == '__main__':
|
| 170 |
+
if actual_verbose:
|
| 171 |
+
print(f" 🔧 Handling __main__ context for class: {name}")
|
| 172 |
+
# Try to get the class from utils module
|
| 173 |
+
try:
|
| 174 |
+
from utils import ColumnDropper, VMAFScaler, TargetExtractor, CQScaler, ResolutionTransformer, FeatureScaler, FrameRateTransformer
|
| 175 |
+
class_map = {
|
| 176 |
+
'ColumnDropper': ColumnDropper,
|
| 177 |
+
'VMAFScaler': VMAFScaler,
|
| 178 |
+
'TargetExtractor': TargetExtractor,
|
| 179 |
+
'CQScaler': CQScaler,
|
| 180 |
+
'ResolutionTransformer': ResolutionTransformer,
|
| 181 |
+
'FeatureScaler': FeatureScaler,
|
| 182 |
+
'FrameRateTransformer': FrameRateTransformer
|
| 183 |
+
}
|
| 184 |
+
if name in class_map:
|
| 185 |
+
if actual_verbose:
|
| 186 |
+
print(f" ✅ Found {name} in utils module")
|
| 187 |
+
return class_map[name]
|
| 188 |
+
else:
|
| 189 |
+
if actual_verbose:
|
| 190 |
+
print(f" ❌ Class {name} not found in utils module")
|
| 191 |
+
except ImportError as e:
|
| 192 |
+
if actual_verbose:
|
| 193 |
+
print(f" ❌ Failed to import from utils: {e}")
|
| 194 |
+
|
| 195 |
+
# Handle utils.data_preprocessing module context
|
| 196 |
+
elif module == 'utils.data_preprocessing':
|
| 197 |
+
if actual_verbose:
|
| 198 |
+
print(f" 🔧 Handling utils.data_preprocessing context for class: {name}")
|
| 199 |
+
try:
|
| 200 |
+
from utils import ColumnDropper, VMAFScaler, TargetExtractor, CQScaler, ResolutionTransformer, FeatureScaler, FrameRateTransformer
|
| 201 |
+
class_map = {
|
| 202 |
+
'ColumnDropper': ColumnDropper,
|
| 203 |
+
'VMAFScaler': VMAFScaler,
|
| 204 |
+
'TargetExtractor': TargetExtractor,
|
| 205 |
+
'CQScaler': CQScaler,
|
| 206 |
+
'ResolutionTransformer': ResolutionTransformer,
|
| 207 |
+
'FeatureScaler': FeatureScaler,
|
| 208 |
+
'FrameRateTransformer': FrameRateTransformer
|
| 209 |
+
}
|
| 210 |
+
if name in class_map:
|
| 211 |
+
if actual_verbose:
|
| 212 |
+
print(f" ✅ Found {name} in utils module")
|
| 213 |
+
return class_map[name]
|
| 214 |
+
else:
|
| 215 |
+
if actual_verbose:
|
| 216 |
+
print(f" ❌ Class {name} not found in utils module")
|
| 217 |
+
except ImportError as e:
|
| 218 |
+
if actual_verbose:
|
| 219 |
+
print(f" ❌ Failed to import from utils: {e}")
|
| 220 |
+
|
| 221 |
+
# For other modules, use the default behavior
|
| 222 |
+
if actual_verbose:
|
| 223 |
+
print(f" 🔧 Using default unpickler for {module}.{name}")
|
| 224 |
+
return super().find_class(module, name)
|
| 225 |
+
|
| 226 |
+
# Try to load with custom unpickler
|
| 227 |
+
try:
|
| 228 |
+
with open(pipeline_path, 'rb') as f:
|
| 229 |
+
unpickler = CustomUnpickler(f)
|
| 230 |
+
pipeline_obj = unpickler.load()
|
| 231 |
+
if actual_verbose:
|
| 232 |
+
print(f" ✅ Pipeline loaded with custom unpickler")
|
| 233 |
+
except Exception as e:
|
| 234 |
+
if actual_verbose:
|
| 235 |
+
print(f" ❌ Custom unpickler failed: {e}")
|
| 236 |
+
print(f" 🔧 Attempting to load with standard pickle...")
|
| 237 |
+
|
| 238 |
+
# Fallback to standard pickle loading
|
| 239 |
+
with open(pipeline_path, 'rb') as f:
|
| 240 |
+
pipeline_obj = pickle.load(f)
|
| 241 |
+
|
| 242 |
+
if actual_verbose:
|
| 243 |
+
print(f" ✅ Pipeline loaded with standard pickle")
|
| 244 |
+
|
| 245 |
+
if actual_verbose:
|
| 246 |
+
print(f"✅ Pipeline loaded successfully")
|
| 247 |
+
|
| 248 |
+
def set_pipeline_verbosity(pipeline, verbose_flag):
|
| 249 |
+
"""
|
| 250 |
+
Recursively set verbose parameter for all pipeline components.
|
| 251 |
+
|
| 252 |
+
Handles nested pipelines, feature unions, column transformers,
|
| 253 |
+
and custom transformer classes.
|
| 254 |
+
"""
|
| 255 |
+
if hasattr(pipeline, 'steps'):
|
| 256 |
+
# Handle sklearn Pipeline objects
|
| 257 |
+
for step_name, step_obj in pipeline.steps:
|
| 258 |
+
set_step_verbosity(step_obj, verbose_flag, step_name)
|
| 259 |
+
elif hasattr(pipeline, 'named_steps'):
|
| 260 |
+
# Handle pipelines with named step access
|
| 261 |
+
for step_name, step_obj in pipeline.named_steps.items():
|
| 262 |
+
set_step_verbosity(step_obj, verbose_flag, step_name)
|
| 263 |
+
else:
|
| 264 |
+
# Handle single transformer objects
|
| 265 |
+
set_step_verbosity(pipeline, verbose_flag, "pipeline")
|
| 266 |
+
|
| 267 |
+
def set_step_verbosity(step_obj, verbose_flag, step_name="unknown"):
|
| 268 |
+
"""
|
| 269 |
+
Set verbosity for a single pipeline step.
|
| 270 |
+
|
| 271 |
+
Handles various transformer types and their verbosity parameters.
|
| 272 |
+
"""
|
| 273 |
+
try:
|
| 274 |
+
# ==========================================================
|
| 275 |
+
# STANDARD VERBOSITY PARAMETERS
|
| 276 |
+
# ==========================================================
|
| 277 |
+
# Common verbose parameters found in sklearn and custom transformers
|
| 278 |
+
verbose_attrs = ['verbose', 'verbose_', 'logging_enabled']
|
| 279 |
+
|
| 280 |
+
for attr in verbose_attrs:
|
| 281 |
+
if hasattr(step_obj, attr):
|
| 282 |
+
setattr(step_obj, attr, verbose_flag)
|
| 283 |
+
if actual_verbose and verbose_flag:
|
| 284 |
+
print(f" 🔧 Set {attr}={verbose_flag} for step '{step_name}'")
|
| 285 |
+
|
| 286 |
+
# ==========================================================
|
| 287 |
+
# NESTED PIPELINE HANDLING
|
| 288 |
+
# ==========================================================
|
| 289 |
+
# Handle Pipeline objects nested within other pipelines
|
| 290 |
+
if hasattr(step_obj, 'steps') or hasattr(step_obj, 'named_steps'):
|
| 291 |
+
set_pipeline_verbosity(step_obj, verbose_flag)
|
| 292 |
+
|
| 293 |
+
# Handle wrapped transformers
|
| 294 |
+
if hasattr(step_obj, 'transformer') and step_obj.transformer is not None:
|
| 295 |
+
set_step_verbosity(step_obj.transformer, verbose_flag, f"{step_name}.transformer")
|
| 296 |
+
|
| 297 |
+
# ==========================================================
|
| 298 |
+
# COMPOSITE TRANSFORMER HANDLING
|
| 299 |
+
# ==========================================================
|
| 300 |
+
# Handle FeatureUnion and ColumnTransformer which contain lists of transformers
|
| 301 |
+
if hasattr(step_obj, 'transformer_list'):
|
| 302 |
+
for name, transformer in step_obj.transformer_list:
|
| 303 |
+
set_step_verbosity(transformer, verbose_flag, f"{step_name}.{name}")
|
| 304 |
+
|
| 305 |
+
# ==========================================================
|
| 306 |
+
# CUSTOM TRANSFORMER SPECIFIC PARAMETERS
|
| 307 |
+
# ==========================================================
|
| 308 |
+
# Handle verbosity parameters specific to our custom transformers
|
| 309 |
+
custom_transformer_attrs = ['debug', 'show_warnings', 'print_info']
|
| 310 |
+
for attr in custom_transformer_attrs:
|
| 311 |
+
if hasattr(step_obj, attr):
|
| 312 |
+
setattr(step_obj, attr, verbose_flag)
|
| 313 |
+
if actual_verbose and verbose_flag:
|
| 314 |
+
print(f" 🎛️ Set custom {attr}={verbose_flag} for step '{step_name}'")
|
| 315 |
+
|
| 316 |
+
except Exception as e:
|
| 317 |
+
if actual_verbose:
|
| 318 |
+
print(f" ⚠️ Warning: Could not set verbosity for step '{step_name}': {e}")
|
| 319 |
+
|
| 320 |
+
# Apply verbosity settings to the entire pipeline
|
| 321 |
+
if actual_verbose:
|
| 322 |
+
print(f"🔧 Setting verbosity to {actual_verbose} for all pipeline components...")
|
| 323 |
+
|
| 324 |
+
set_pipeline_verbosity(pipeline_obj, actual_verbose)
|
| 325 |
+
|
| 326 |
+
# =================================================================
|
| 327 |
+
# PIPELINE FITNESS VALIDATION
|
| 328 |
+
# =================================================================
|
| 329 |
+
# Check if the loaded pipeline has been fitted (trained)
|
| 330 |
+
# Unfitted pipelines cannot be used for transformation
|
| 331 |
+
try:
|
| 332 |
+
check_is_fitted(pipeline_obj)
|
| 333 |
+
if actual_verbose:
|
| 334 |
+
print("✅ Loaded pipeline is fitted and ready for use")
|
| 335 |
+
except NotFittedError:
|
| 336 |
+
if actual_verbose:
|
| 337 |
+
print("⚠️ WARNING: Loaded pipeline is NOT fitted!")
|
| 338 |
+
print(" 🔧 Attempting to extract components anyway for fallback creation...")
|
| 339 |
+
except Exception as e:
|
| 340 |
+
if actual_verbose:
|
| 341 |
+
print(f"⚠️ Warning: Could not verify pipeline fitness: {e}")
|
| 342 |
+
|
| 343 |
+
# =================================================================
|
| 344 |
+
# FEATURE SCALER EXTRACTION
|
| 345 |
+
# =================================================================
|
| 346 |
+
# Extract the feature scaling component used for normalizing video metrics
|
| 347 |
+
feature_scaler_step = None
|
| 348 |
+
scaler_step_name = 'feature_scaler'
|
| 349 |
+
|
| 350 |
+
# Try to find by name in named_steps
|
| 351 |
+
if hasattr(pipeline_obj, 'named_steps') and scaler_step_name in pipeline_obj.named_steps:
|
| 352 |
+
feature_scaler_step = pipeline_obj.named_steps[scaler_step_name]
|
| 353 |
+
if actual_verbose:
|
| 354 |
+
print(f"✅ Found feature scaler step: '{scaler_step_name}'")
|
| 355 |
+
|
| 356 |
+
# Fallback: search through all steps by name or type
|
| 357 |
+
elif hasattr(pipeline_obj, 'steps'):
|
| 358 |
+
for name, step in pipeline_obj.steps:
|
| 359 |
+
if scaler_step_name in name or isinstance(step, (StandardScaler)):
|
| 360 |
+
feature_scaler_step = step
|
| 361 |
+
if actual_verbose:
|
| 362 |
+
print(f"✅ Found feature scaler step: '{name}'")
|
| 363 |
+
break
|
| 364 |
+
|
| 365 |
+
# Create default if not found
|
| 366 |
+
if feature_scaler_step is None:
|
| 367 |
+
if actual_verbose:
|
| 368 |
+
print("⚠️ Warning: Could not find feature scaler step. Creating default StandardScaler...")
|
| 369 |
+
feature_scaler_step = StandardScaler()
|
| 370 |
+
|
| 371 |
+
# =================================================================
|
| 372 |
+
# VMAF SCALER EXTRACTION
|
| 373 |
+
# =================================================================
|
| 374 |
+
# Extract the VMAF scaling component for quality score normalization
|
| 375 |
+
vmaf_scaler = None
|
| 376 |
+
vmaf_scaler_step_name = 'vmaf_scaler'
|
| 377 |
+
|
| 378 |
+
if hasattr(pipeline_obj, 'named_steps') and vmaf_scaler_step_name in pipeline_obj.named_steps:
|
| 379 |
+
vmaf_scaler = pipeline_obj.named_steps[vmaf_scaler_step_name]
|
| 380 |
+
if actual_verbose:
|
| 381 |
+
print(f"✅ Found VMAF scaler step: '{vmaf_scaler_step_name}'")
|
| 382 |
+
|
| 383 |
+
# Create default VMAF scaler if not found
|
| 384 |
+
if vmaf_scaler is None:
|
| 385 |
+
if actual_verbose:
|
| 386 |
+
print("⚠️ Warning: Could not find VMAF scaler. Creating default...")
|
| 387 |
+
|
| 388 |
+
class DefaultVMAFScaler:
|
| 389 |
+
"""Default VMAF scaler with typical VMAF range."""
|
| 390 |
+
def __init__(self):
|
| 391 |
+
self.min_val, self.max_val = 20.0, 100.0 # Typical VMAF range
|
| 392 |
+
|
| 393 |
+
vmaf_scaler = DefaultVMAFScaler()
|
| 394 |
+
if actual_verbose:
|
| 395 |
+
print(f" 📊 Using default VMAF range: {vmaf_scaler.min_val}-{vmaf_scaler.max_val}")
|
| 396 |
+
|
| 397 |
+
# =================================================================
|
| 398 |
+
# CQ PARAMETER BOUNDS EXTRACTION
|
| 399 |
+
# =================================================================
|
| 400 |
+
# Extract CQ (Constant Quality) parameter bounds for optimization
|
| 401 |
+
default_cq_min, default_cq_max = 10, 51 # Conservative CQ range
|
| 402 |
+
cq_min, cq_max = default_cq_min, default_cq_max
|
| 403 |
+
cq_scaler_step_name = 'cq_scaler'
|
| 404 |
+
|
| 405 |
+
# Try to find CQ scaler component
|
| 406 |
+
if hasattr(pipeline_obj, 'named_steps') and cq_scaler_step_name in pipeline_obj.named_steps:
|
| 407 |
+
cq_scaler = pipeline_obj.named_steps[cq_scaler_step_name]
|
| 408 |
+
|
| 409 |
+
# Check for CQ bounds in various attribute naming conventions
|
| 410 |
+
if hasattr(cq_scaler, 'min_cq') and hasattr(cq_scaler, 'max_cq'):
|
| 411 |
+
cq_min, cq_max = cq_scaler.min_cq, cq_scaler.max_cq
|
| 412 |
+
if actual_verbose:
|
| 413 |
+
print(f"✅ Found CQ range in step '{cq_scaler_step_name}': {cq_min}-{cq_max}")
|
| 414 |
+
elif hasattr(cq_scaler, 'min_val') and hasattr(cq_scaler, 'max_val'):
|
| 415 |
+
cq_min, cq_max = cq_scaler.min_val, cq_scaler.max_val
|
| 416 |
+
if actual_verbose:
|
| 417 |
+
print(f"✅ Found CQ range in scaler attributes: {cq_min}-{cq_max}")
|
| 418 |
+
|
| 419 |
+
if actual_verbose:
|
| 420 |
+
print(f"📊 Using CQ optimization range: {cq_min}-{cq_max}")
|
| 421 |
+
print(f" 💡 This range will constrain the binary search for optimal quality parameters")
|
| 422 |
+
|
| 423 |
+
return pipeline_obj, feature_scaler_step, vmaf_scaler, int(cq_min), int(cq_max)
|
| 424 |
+
|
| 425 |
+
except Exception as e:
|
| 426 |
+
if actual_verbose:
|
| 427 |
+
print(f"❌ ERROR: Failed to load or extract from pipeline '{pipeline_path}': {e}")
|
| 428 |
+
print("🔍 Detailed error information:")
|
| 429 |
+
traceback.print_exc()
|
| 430 |
+
return None, None, None, None, None
|
| 431 |
+
|
| 432 |
+
def load_encoding_resources(config, logging_enabled=True):
|
| 433 |
+
"""
|
| 434 |
+
|
| 435 |
+
1. Scene classifier AI model - to identify content type (animation, gaming, etc.)
|
| 436 |
+
2. Preprocessing pipeline - to prepare video frames for the AI model
|
| 437 |
+
3. GPU/CPU device selection - for running the AI inference
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
Args:
|
| 441 |
+
config (dict): Configuration containing model file paths
|
| 442 |
+
logging_enabled (bool): Whether to print loading status messages
|
| 443 |
+
|
| 444 |
+
Returns:
|
| 445 |
+
tuple: (scene_model, preprocessing_pipeline, device) ready for encoding
|
| 446 |
+
"""
|
| 447 |
+
# Get model file paths from config
|
| 448 |
+
model_paths = config.get('model_paths', {})
|
| 449 |
+
scene_model_path = model_paths.get('scene_classifier_model', "services/compress/models/scene_classifier_model.pth")
|
| 450 |
+
preprocessing_pipeline_path = model_paths.get('preprocessing_pipeline', "services/compress/models/preprocessing_pipeline.pkl")
|
| 451 |
+
|
| 452 |
+
# Choose GPU if available, otherwise use CPU
|
| 453 |
+
device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 454 |
+
|
| 455 |
+
# Load scene classifier AI model
|
| 456 |
+
model_state_dict, available_metrics, class_mapping = load_scene_classifier_model(scene_model_path, device, logging_enabled)
|
| 457 |
+
print(f" ✅ Scene classifier model loaded from {scene_model_path} on {device}")
|
| 458 |
+
scene_classifier_model = CombinedModel(num_classes=len(class_mapping), metrics_dim=len(available_metrics))
|
| 459 |
+
scene_classifier_model.load_state_dict(model_state_dict)
|
| 460 |
+
scene_classifier_model.to(device)
|
| 461 |
+
scene_classifier_model.eval()
|
| 462 |
+
|
| 463 |
+
pipeline_obj, feature_scaler_step, vmaf_scaler, cq_min, cq_max = get_scalers_from_pipeline(preprocessing_pipeline_path, verbose=logging_enabled, logging_enabled=logging_enabled)
|
| 464 |
+
print(f" ✅ Preprocessing pipeline loaded from {preprocessing_pipeline_path}")
|
| 465 |
+
|
| 466 |
+
return {
|
| 467 |
+
"scene_classifier_model": scene_classifier_model,
|
| 468 |
+
"available_metrics": available_metrics,
|
| 469 |
+
"device": device,
|
| 470 |
+
"class_mapping": class_mapping,
|
| 471 |
+
"pipeline_obj": pipeline_obj,
|
| 472 |
+
"feature_scaler_step": feature_scaler_step,
|
| 473 |
+
"vmaf_scaler": vmaf_scaler,
|
| 474 |
+
"cq_min": cq_min,
|
| 475 |
+
"cq_max": cq_max
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
def ai_encoding(scene_metadata, config, resources, target_vmaf=None, target_quality_level=None, logging_enabled=True):
|
| 479 |
+
"""
|
| 480 |
+
|
| 481 |
+
1. Use AI to classify the scene content (animation, gaming, text, etc.)
|
| 482 |
+
2. Look up a pre-determined CQ value from a table based on scene type AND quality tier
|
| 483 |
+
3. Encode the scene once with that CQ value
|
| 484 |
+
|
| 485 |
+
- High quality tier (VMAF ~95): Lower CQ values for better quality
|
| 486 |
+
- Medium quality tier (VMAF ~90): Balanced CQ values
|
| 487 |
+
- Low quality tier (VMAF ~85): Higher CQ values for smaller files
|
| 488 |
+
|
| 489 |
+
- Runs multiple encoding attempts with different CQ values
|
| 490 |
+
- Uses AI to predict VMAF quality for each attempt
|
| 491 |
+
- Uses binary search to find the optimal CQ
|
| 492 |
+
|
| 493 |
+
Args:
|
| 494 |
+
scene_metadata (dict): Contains scene file path, duration, etc.
|
| 495 |
+
config (dict): Configuration settings for encoding
|
| 496 |
+
resources (dict): Pre-loaded AI models and preprocessing tools
|
| 497 |
+
target_vmaf (float): Target quality used to select appropriate quality tier
|
| 498 |
+
target_quality_level (str): Desired quality level ('High', 'Medium', 'Low') used for CQ lookup
|
| 499 |
+
logging_enabled (bool): Whether to print progress messages
|
| 500 |
+
|
| 501 |
+
Returns:
|
| 502 |
+
tuple: (success_status, scene_data_dict)
|
| 503 |
+
- success_status: True if encoding succeeded, False otherwise
|
| 504 |
+
- scene_data_dict: Contains encoding results, file sizes, CQ used, etc.
|
| 505 |
+
"""
|
| 506 |
+
logging_enabled=True
|
| 507 |
+
|
| 508 |
+
if not scene_metadata:
|
| 509 |
+
return None, {
|
| 510 |
+
'scene_number': 0,
|
| 511 |
+
'encoding_success': False,
|
| 512 |
+
'error_reason': 'Scene metadata is None or empty',
|
| 513 |
+
'processing_time_seconds': 0.0,
|
| 514 |
+
'encoded_path': None,
|
| 515 |
+
'path': None
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
def safe_float(value, default=0.0):
|
| 519 |
+
"""Convert value to float, return default if invalid"""
|
| 520 |
+
if value is None: return default
|
| 521 |
+
try:
|
| 522 |
+
result = float(value)
|
| 523 |
+
return result if not (result != result) else default
|
| 524 |
+
except (TypeError, ValueError):
|
| 525 |
+
return default
|
| 526 |
+
|
| 527 |
+
def safe_positive_float(value, default=1.0):
|
| 528 |
+
"""Convert to float and ensure it's positive"""
|
| 529 |
+
result = safe_float(value, default)
|
| 530 |
+
return max(result, 0.1)
|
| 531 |
+
|
| 532 |
+
scene_path = scene_metadata.get('path')
|
| 533 |
+
scene_number = int(safe_float(scene_metadata.get('scene_number', 1), 1))
|
| 534 |
+
start_time = safe_float(scene_metadata.get('start_time'), 0.0)
|
| 535 |
+
end_time = safe_float(scene_metadata.get('end_time'), 0.0)
|
| 536 |
+
scene_duration = safe_positive_float(scene_metadata.get('duration'), 1.0)
|
| 537 |
+
|
| 538 |
+
if end_time <= start_time or scene_duration <= 0:
|
| 539 |
+
if end_time > start_time:
|
| 540 |
+
scene_duration = end_time - start_time
|
| 541 |
+
else:
|
| 542 |
+
try:
|
| 543 |
+
cmd = ['ffprobe', '-v', 'quiet', '-show_entries', 'format=duration', '-of', 'csv=p=0', scene_path]
|
| 544 |
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=10)
|
| 545 |
+
if result.returncode == 0 and result.stdout.strip():
|
| 546 |
+
scene_duration = safe_positive_float(result.stdout.strip(), 1.0)
|
| 547 |
+
end_time = start_time + scene_duration
|
| 548 |
+
else:
|
| 549 |
+
scene_duration = 1.0
|
| 550 |
+
except Exception:
|
| 551 |
+
scene_duration = 1.0
|
| 552 |
+
|
| 553 |
+
is_very_short_scene = scene_duration < 1.0
|
| 554 |
+
if is_very_short_scene and logging_enabled:
|
| 555 |
+
print(f" ⚠️ Very short scene detected ({scene_duration:.1f}s) - using special handling")
|
| 556 |
+
|
| 557 |
+
original_video_metadata = scene_metadata.get('original_video_metadata', {})
|
| 558 |
+
|
| 559 |
+
if logging_enabled:
|
| 560 |
+
print(f"\n🎬 Processing Scene {scene_number}")
|
| 561 |
+
print(f" 📁 File: {os.path.basename(scene_path) if scene_path else 'None'}")
|
| 562 |
+
print(f" ⏱️ Timing: {start_time:.1f}s - {end_time:.1f}s (duration: {scene_duration:.1f}s)")
|
| 563 |
+
|
| 564 |
+
if not scene_path or not os.path.exists(scene_path) or os.path.getsize(scene_path) == 0:
|
| 565 |
+
return None, {
|
| 566 |
+
'scene_number': scene_number,
|
| 567 |
+
'encoding_success': False,
|
| 568 |
+
'error_reason': 'Scene file is missing, empty, or inaccessible',
|
| 569 |
+
'processing_time_seconds': 0.0,
|
| 570 |
+
'encoded_path': None,
|
| 571 |
+
'path': scene_path
|
| 572 |
+
}
|
| 573 |
+
|
| 574 |
+
processing_start_time = time.time()
|
| 575 |
+
|
| 576 |
+
# Get temporary directory for intermediate files
|
| 577 |
+
temp_dir = config.get('directories', {}).get('temp_dir', './videos/temp_scenes')
|
| 578 |
+
|
| 579 |
+
if not target_quality_level:
|
| 580 |
+
target_quality_level = original_video_metadata.get('target_quality') or config.get('video_processing', {}).get('target_quality')
|
| 581 |
+
target_vmaf = safe_float(target_vmaf or original_video_metadata.get('target_vmaf') or
|
| 582 |
+
config.get('video_processing', {}).get('target_vmaf', 0.0), 0.0)
|
| 583 |
+
|
| 584 |
+
original_codec = original_video_metadata.get('original_codec', 'unknown')
|
| 585 |
+
target_codec_from_part1 = original_video_metadata.get('target_codec', 'auto')
|
| 586 |
+
current_codec = original_video_metadata.get('codec', original_codec)
|
| 587 |
+
config_codec = config.get('video_processing', {}).get('codec', 'auto')
|
| 588 |
+
|
| 589 |
+
if target_codec_from_part1 and target_codec_from_part1 != 'auto':
|
| 590 |
+
codec = target_codec_from_part1
|
| 591 |
+
elif config_codec != 'auto':
|
| 592 |
+
codec = config_codec
|
| 593 |
+
else:
|
| 594 |
+
codec_upgrade_map = {'h264': 'av1_nvenc', 'hevc': 'av1_nvenc', 'vp9': 'av1_nvenc', 'av1': 'av1_nvenc'}
|
| 595 |
+
codec = codec_upgrade_map.get(current_codec.lower(), 'av1_nvenc')
|
| 596 |
+
|
| 597 |
+
if logging_enabled:
|
| 598 |
+
print(f" 🎥 Selected Codec: {codec}")
|
| 599 |
+
|
| 600 |
+
# STEP 1: BASIC ANALYSIS - Classify scene and extract video features in one call
|
| 601 |
+
if logging_enabled:
|
| 602 |
+
print(f" 🤖 Running scene classification and feature extraction...")
|
| 603 |
+
|
| 604 |
+
scene_type = 'default'
|
| 605 |
+
confidence_score = 0.0
|
| 606 |
+
video_features = {}
|
| 607 |
+
detailed_results = {}
|
| 608 |
+
try:
|
| 609 |
+
classification_result = classify_scene_from_path(
|
| 610 |
+
scene_path=scene_path,
|
| 611 |
+
temp_dir=temp_dir,
|
| 612 |
+
scene_classifier_model=resources['scene_classifier_model'],
|
| 613 |
+
available_metrics=resources['available_metrics'],
|
| 614 |
+
device=resources['device'],
|
| 615 |
+
metrics_scaler=resources['feature_scaler_step'],
|
| 616 |
+
class_mapping=resources['class_mapping'],
|
| 617 |
+
logging_enabled=logging_enabled,
|
| 618 |
+
)
|
| 619 |
+
|
| 620 |
+
# Handle the tuple return from classify_scene_from_path (now returns 3 items)
|
| 621 |
+
if isinstance(classification_result, tuple) and len(classification_result) == 3:
|
| 622 |
+
scene_type, detailed_results, video_features = classification_result
|
| 623 |
+
confidence_score = detailed_results.get('confidence_score', 0.0)
|
| 624 |
+
elif isinstance(classification_result, tuple) and len(classification_result) == 2:
|
| 625 |
+
# Fallback for older return format
|
| 626 |
+
scene_type, detailed_results = classification_result
|
| 627 |
+
confidence_score = detailed_results.get('confidence_score', 0.0)
|
| 628 |
+
video_features = {}
|
| 629 |
+
elif isinstance(classification_result, dict):
|
| 630 |
+
# Fallback if it returns a dict instead of tuple
|
| 631 |
+
scene_type = classification_result.get('scene_type', 'default')
|
| 632 |
+
confidence_score = classification_result.get('confidence_score', 0.0)
|
| 633 |
+
video_features = {}
|
| 634 |
+
else:
|
| 635 |
+
# Unknown return type, use defaults
|
| 636 |
+
scene_type = 'default'
|
| 637 |
+
confidence_score = 0.0
|
| 638 |
+
video_features = {}
|
| 639 |
+
|
| 640 |
+
if logging_enabled:
|
| 641 |
+
print(f" 🎭 Scene classified as: '{scene_type}' (Confidence: {confidence_score:.2f})")
|
| 642 |
+
if video_features:
|
| 643 |
+
print(f" 📊 Video features extracted: {len(video_features)} metrics")
|
| 644 |
+
|
| 645 |
+
except Exception as e:
|
| 646 |
+
if logging_enabled:
|
| 647 |
+
print(f" ❌ Scene classification failed: {e}")
|
| 648 |
+
traceback.print_exc()
|
| 649 |
+
|
| 650 |
+
# Map the scene type to lookup table key
|
| 651 |
+
original_scene_type = scene_type
|
| 652 |
+
mapped_scene_type = map_scene_type_to_lookup_key(scene_type)
|
| 653 |
+
|
| 654 |
+
if logging_enabled and original_scene_type != mapped_scene_type:
|
| 655 |
+
print(f" 🔄 Mapped scene type '{original_scene_type}' -> '{mapped_scene_type}' for CQ lookup")
|
| 656 |
+
|
| 657 |
+
# Get quality tier information for logging
|
| 658 |
+
print(f" 🎯 Target quality level: {target_quality_level}")
|
| 659 |
+
# If only quality level provided, derive indicative VMAF for logging purposes
|
| 660 |
+
if target_quality_level and not target_vmaf:
|
| 661 |
+
target_vmaf = get_target_vmaf_from_quality(target_quality_level)
|
| 662 |
+
if logging_enabled:
|
| 663 |
+
print(f" ℹ️ Indicative VMAF from quality level '{target_quality_level}': {target_vmaf}")
|
| 664 |
+
|
| 665 |
+
# Get CQ from lookup table using mapped scene type and target quality/VMAF
|
| 666 |
+
base_cq = get_cq_from_lookup_table(
|
| 667 |
+
mapped_scene_type,
|
| 668 |
+
config,
|
| 669 |
+
target_vmaf=target_vmaf,
|
| 670 |
+
target_quality_level=target_quality_level,
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
# Log quality tier and CQ selection
|
| 674 |
+
if logging_enabled:
|
| 675 |
+
# Determine label for logging
|
| 676 |
+
if target_quality_level:
|
| 677 |
+
tier_label = str(target_quality_level).upper()
|
| 678 |
+
else:
|
| 679 |
+
if target_vmaf >= 93:
|
| 680 |
+
tier_label = "HIGH"
|
| 681 |
+
elif target_vmaf >= 88:
|
| 682 |
+
tier_label = "MEDIUM"
|
| 683 |
+
else:
|
| 684 |
+
tier_label = "LOW"
|
| 685 |
+
|
| 686 |
+
print(f" 🎯 Using quality tier: {tier_label} (Indicative VMAF: {target_vmaf if target_vmaf else 'n/a'})")
|
| 687 |
+
print(f" 🎚️ Base CQ from lookup table for '{mapped_scene_type}' at {tier_label} quality: {base_cq}")
|
| 688 |
+
|
| 689 |
+
# Apply conservative adjustment from config
|
| 690 |
+
conservative_cq_adjustment = safe_float(config.get('video_processing', {}).get('conservative_cq_adjustment', 2), 2)
|
| 691 |
+
final_cq = min(base_cq + conservative_cq_adjustment, 51.0)
|
| 692 |
+
if logging_enabled:
|
| 693 |
+
print(f" 🔧 Applied conservative adjustment: +{conservative_cq_adjustment} -> Final CQ: {final_cq}")
|
| 694 |
+
|
| 695 |
+
# Create a placeholder scene_data object
|
| 696 |
+
scene_data = {
|
| 697 |
+
'path': scene_path,
|
| 698 |
+
'scene_number': scene_number,
|
| 699 |
+
'start_time': start_time,
|
| 700 |
+
'end_time': end_time,
|
| 701 |
+
'duration': scene_duration,
|
| 702 |
+
'original_video_metadata': original_video_metadata,
|
| 703 |
+
'scene_type': original_scene_type, # Keep original scene type from classifier
|
| 704 |
+
'mapped_scene_type': mapped_scene_type, # Add mapped scene type for CQ lookup
|
| 705 |
+
'confidence_score': confidence_score,
|
| 706 |
+
'optimal_cq': base_cq,
|
| 707 |
+
'adjusted_cq': final_cq,
|
| 708 |
+
'final_adjusted_cq': final_cq,
|
| 709 |
+
'codec_selection_process': {'final_selected_codec': codec},
|
| 710 |
+
'model_training_data': {}, # Placeholder
|
| 711 |
+
'target_quality_level': target_quality_level,
|
| 712 |
+
'base_cq_for_quality': base_cq,
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
if scene_duration < 1.0:
|
| 716 |
+
output_scene_path = os.path.join(
|
| 717 |
+
temp_dir,
|
| 718 |
+
f"encoded_scene_{scene_number:03d}_{start_time:.1f}s-{end_time:.1f}s_{codec.lower()}.mp4"
|
| 719 |
+
)
|
| 720 |
+
else:
|
| 721 |
+
output_scene_path = os.path.join(
|
| 722 |
+
temp_dir,
|
| 723 |
+
f"encoded_scene_{scene_number:03d}_{start_time:.0f}s-{end_time:.0f}s_{codec.lower()}.mp4"
|
| 724 |
+
)
|
| 725 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 726 |
+
|
| 727 |
+
size_increase_protection = config.get('video_processing', {}).get('size_increase_protection', True)
|
| 728 |
+
max_encoding_retries = int(safe_float(config.get('video_processing', {}).get('max_encoding_retries', 2), 2))
|
| 729 |
+
|
| 730 |
+
encoding_start_time = time.time()
|
| 731 |
+
encoded_path = None
|
| 732 |
+
encoding_time = 0
|
| 733 |
+
|
| 734 |
+
if size_increase_protection:
|
| 735 |
+
if logging_enabled:
|
| 736 |
+
print(f" 🛡️ Size increase protection enabled")
|
| 737 |
+
try:
|
| 738 |
+
encoded_path, encoding_time = encode_scene_with_size_check(
|
| 739 |
+
scene_path=scene_path,
|
| 740 |
+
output_path=output_scene_path,
|
| 741 |
+
codec=codec,
|
| 742 |
+
adjusted_cq=final_cq,
|
| 743 |
+
content_type=scene_type,
|
| 744 |
+
contrast_value=0.5,
|
| 745 |
+
max_retries=max_encoding_retries,
|
| 746 |
+
logging_enabled=logging_enabled
|
| 747 |
+
)
|
| 748 |
+
except Exception as e:
|
| 749 |
+
if logging_enabled:
|
| 750 |
+
print(f" ❌ Size-protected encoding failed: {e}")
|
| 751 |
+
else:
|
| 752 |
+
if logging_enabled:
|
| 753 |
+
print(f" ⚡ Standard encoding (size protection disabled)")
|
| 754 |
+
try:
|
| 755 |
+
_, encoding_time = encode_video(
|
| 756 |
+
input_path=scene_path,
|
| 757 |
+
output_path=output_scene_path,
|
| 758 |
+
codec=codec,
|
| 759 |
+
rate=final_cq,
|
| 760 |
+
scene_type=scene_type,
|
| 761 |
+
contrast_value=0.5, # Default contrast
|
| 762 |
+
logging_enabled=logging_enabled
|
| 763 |
+
)
|
| 764 |
+
encoded_path = output_scene_path
|
| 765 |
+
except Exception as e:
|
| 766 |
+
if logging_enabled:
|
| 767 |
+
print(f" ❌ Standard encoding failed: {e}")
|
| 768 |
+
|
| 769 |
+
# Final update of scene_data
|
| 770 |
+
encoding_success = bool(encoded_path and os.path.exists(encoded_path) and os.path.getsize(encoded_path) > 0)
|
| 771 |
+
scene_data['encoding_success'] = encoding_success
|
| 772 |
+
scene_data['encoded_path'] = encoded_path if encoding_success else None
|
| 773 |
+
scene_data['encoding_time'] = safe_float(encoding_time, 0)
|
| 774 |
+
scene_data['processing_time_seconds'] = time.time() - processing_start_time
|
| 775 |
+
|
| 776 |
+
# Add file size information and codec details
|
| 777 |
+
if encoding_success:
|
| 778 |
+
# Calculate file sizes in MB
|
| 779 |
+
input_file_size = os.path.getsize(scene_path) if os.path.exists(scene_path) else 0
|
| 780 |
+
output_file_size = os.path.getsize(encoded_path) if os.path.exists(encoded_path) else 0
|
| 781 |
+
|
| 782 |
+
scene_data['input_size_mb'] = input_file_size / (1024 * 1024)
|
| 783 |
+
scene_data['encoded_file_size_mb'] = output_file_size / (1024 * 1024)
|
| 784 |
+
|
| 785 |
+
# Calculate compression ratio
|
| 786 |
+
if input_file_size > 0:
|
| 787 |
+
compression_ratio = ((output_file_size - input_file_size) / input_file_size) * 100
|
| 788 |
+
scene_data['compression_ratio'] = compression_ratio
|
| 789 |
+
else:
|
| 790 |
+
scene_data['compression_ratio'] = 0
|
| 791 |
+
|
| 792 |
+
# Store codec information for filename generation
|
| 793 |
+
scene_data['codec_used'] = codec
|
| 794 |
+
else:
|
| 795 |
+
scene_data['input_size_mb'] = 0
|
| 796 |
+
scene_data['encoded_file_size_mb'] = 0
|
| 797 |
+
scene_data['compression_ratio'] = 0
|
| 798 |
+
scene_data['codec_used'] = 'unknown'
|
| 799 |
+
|
| 800 |
+
|
| 801 |
+
scene_data['model_training_data'] = {
|
| 802 |
+
'raw_video_features': video_features,
|
| 803 |
+
'processed_video_features': {},
|
| 804 |
+
'vmaf_model_features': {},
|
| 805 |
+
'scene_classifier_features': video_features,
|
| 806 |
+
'scene_classifier_probabilities': detailed_results,
|
| 807 |
+
'processing_timings': {
|
| 808 |
+
'total_processing_time': time.time() - processing_start_time,
|
| 809 |
+
'encoding_time': safe_float(encoding_time, 0) if 'encoding_time' in locals() else 0
|
| 810 |
+
}
|
| 811 |
+
}
|
| 812 |
+
|
| 813 |
+
if encoding_success:
|
| 814 |
+
if logging_enabled:
|
| 815 |
+
print(f" ✅ Scene {scene_number} encoded successfully.")
|
| 816 |
+
return encoded_path, scene_data
|
| 817 |
+
else:
|
| 818 |
+
if logging_enabled:
|
| 819 |
+
print(f" ❌ Encoding failed for scene {scene_number}.")
|
| 820 |
+
scene_data['error_reason'] = 'Encoding process failed or produced an empty file'
|
| 821 |
+
return None, scene_data
|
| 822 |
+
|
| 823 |
+
def map_scene_type_to_lookup_key(scene_type):
|
| 824 |
+
"""
|
| 825 |
+
Convert AI scene classifier output to CQ lookup table keys.
|
| 826 |
+
|
| 827 |
+
The AI scene classifier returns descriptive names like:
|
| 828 |
+
- 'Screen Content / Text'
|
| 829 |
+
- 'Animation / Cartoon / Rendered Graphics'
|
| 830 |
+
- 'Faces / People'
|
| 831 |
+
- 'Gaming Content'
|
| 832 |
+
- 'other'
|
| 833 |
+
- 'unclear'
|
| 834 |
+
|
| 835 |
+
But our CQ lookup table uses simpler keys:
|
| 836 |
+
- 'animation' (for cartoons/rendered content)
|
| 837 |
+
- 'low-action' (for text/faces - less motion)
|
| 838 |
+
- 'medium-action' (for general content)
|
| 839 |
+
- 'high-action' (for gaming/sports - lots of motion)
|
| 840 |
+
- 'default' (fallback for unclear content)
|
| 841 |
+
|
| 842 |
+
Args:
|
| 843 |
+
scene_type (str): Output from AI scene classifier
|
| 844 |
+
|
| 845 |
+
Returns:
|
| 846 |
+
str: Corresponding lookup table key
|
| 847 |
+
"""
|
| 848 |
+
scene_lower = scene_type.lower() if scene_type else 'default'
|
| 849 |
+
|
| 850 |
+
scene_mapping = {
|
| 851 |
+
'screen content / text': 'low-action', # Text doesn't need high quality
|
| 852 |
+
'animation / cartoon / rendered graphics': 'animation', # Cartoons compress well
|
| 853 |
+
'faces / people': 'low-action', # Faces need clarity but less motion
|
| 854 |
+
'gaming content': 'high-action', # Games have lots of motion/detail
|
| 855 |
+
'other': 'medium-action', # General content gets medium quality
|
| 856 |
+
'unclear': 'default', # When unsure, use safe default
|
| 857 |
+
'default': 'default'
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
if scene_lower in scene_mapping:
|
| 861 |
+
return scene_mapping[scene_lower]
|
| 862 |
+
|
| 863 |
+
for key, value in scene_mapping.items():
|
| 864 |
+
if key in scene_lower or scene_lower in key:
|
| 865 |
+
return value
|
| 866 |
+
|
| 867 |
+
return 'default'
|
| 868 |
+
|
| 869 |
+
def get_target_vmaf_from_quality(quality_level):
|
| 870 |
+
"""
|
| 871 |
+
Map quality level to target VMAF score for Basic AI encoding.
|
| 872 |
+
|
| 873 |
+
|
| 874 |
+
Quality Level Mappings:
|
| 875 |
+
- High: 95 VMAF (Maximum quality preservation)
|
| 876 |
+
- Medium: 90 VMAF (Balanced quality and file size)
|
| 877 |
+
- Low: 85 VMAF (Aggressive compression for smaller files)
|
| 878 |
+
|
| 879 |
+
Args:
|
| 880 |
+
quality_level (str): Target quality level ('High', 'Medium', 'Low')
|
| 881 |
+
|
| 882 |
+
Returns:
|
| 883 |
+
float: Target VMAF score for the specified quality level
|
| 884 |
+
"""
|
| 885 |
+
quality_vmaf_map = {
|
| 886 |
+
'High': 95.0,
|
| 887 |
+
'Medium': 90.0,
|
| 888 |
+
'Low': 85.0
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
return quality_vmaf_map.get(quality_level, 90.0)
|
| 892 |
+
|
| 893 |
+
|
services/compress/models/preprocessing_pipeline.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:341e6c97c4092410edfd7ec9ba64f4d8916e397768138a345530ee2bcca95333
|
| 3 |
+
size 3594
|
services/compress/models/scene_classifier_model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c31861eae4d5e39dd4e54994e44bc34a099342f237ee96548d6d663f3f10e3df
|
| 3 |
+
size 11130108
|
services/compress/scene_detector.py
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import subprocess
|
| 4 |
+
from utils.fast_scene_detect import adaptive_scene_detection_check
|
| 5 |
+
from utils.split_video_into_scenes import split_video_into_scenes
|
| 6 |
+
from utils.video_utils import get_keyframes
|
| 7 |
+
|
| 8 |
+
def scene_detection(video_metadata):
|
| 9 |
+
"""
|
| 10 |
+
Part 2: Scene detection based on video length using metadata from part 1.
|
| 11 |
+
|
| 12 |
+
Args:
|
| 13 |
+
video_metadata (dict): Metadata from part1_pre_processing containing:
|
| 14 |
+
- 'path': video file path
|
| 15 |
+
- 'codec': current video codec
|
| 16 |
+
- 'original_codec': original codec (if reencoded)
|
| 17 |
+
- 'target_codec': target codec for encoding (NEW)
|
| 18 |
+
- 'duration': video duration in seconds
|
| 19 |
+
- 'was_reencoded': boolean
|
| 20 |
+
- 'target_vmaf': target VMAF score
|
| 21 |
+
- 'target_quality': target quality level
|
| 22 |
+
- 'processing_info': processing details
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
list: List of scene metadata dictionaries, each containing:
|
| 26 |
+
- 'path': scene file path
|
| 27 |
+
- 'scene_number': scene index (1-based)
|
| 28 |
+
- 'start_time': scene start time in seconds
|
| 29 |
+
- 'end_time': scene end time in seconds
|
| 30 |
+
- 'duration': scene duration in seconds
|
| 31 |
+
- 'original_video_metadata': complete metadata from part 1
|
| 32 |
+
"""
|
| 33 |
+
# Load configuration
|
| 34 |
+
try:
|
| 35 |
+
with open('services/compress/config.json', 'r') as f:
|
| 36 |
+
config = json.load(f)
|
| 37 |
+
print("✅ Configuration loaded successfully")
|
| 38 |
+
except FileNotFoundError:
|
| 39 |
+
print("⚠️ Config file not found, using default configuration")
|
| 40 |
+
config = {
|
| 41 |
+
'directories': {
|
| 42 |
+
'temp_dir': './videos/temp_scenes',
|
| 43 |
+
'output_dir': './output'
|
| 44 |
+
},
|
| 45 |
+
'video_processing': {
|
| 46 |
+
'SHORT_VIDEO_THRESHOLD': 20,
|
| 47 |
+
'target_vmaf': 93.0
|
| 48 |
+
},
|
| 49 |
+
'scene_detection': {
|
| 50 |
+
'enable_time_based_fallback': True,
|
| 51 |
+
'time_based_scene_duration': 60
|
| 52 |
+
}
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
# Extract metadata
|
| 56 |
+
video_path = video_metadata['path']
|
| 57 |
+
duration = video_metadata['duration']
|
| 58 |
+
codec = video_metadata['codec']
|
| 59 |
+
original_codec = video_metadata.get('original_codec', codec)
|
| 60 |
+
target_codec = video_metadata.get('target_codec', 'auto')
|
| 61 |
+
print("***********target_codec", target_codec) # ✅ NEW: Extract target codec
|
| 62 |
+
was_reencoded = video_metadata.get('was_reencoded', False)
|
| 63 |
+
target_vmaf = video_metadata.get('target_vmaf', 93.0)
|
| 64 |
+
target_quality = video_metadata.get('target_quality', 'Medium')
|
| 65 |
+
|
| 66 |
+
print(f"🎬 Processing video: {os.path.basename(video_path)}")
|
| 67 |
+
print(f" ⏱️ Duration: {duration:.1f}s, Codec: {codec}, Reencoded: {was_reencoded}")
|
| 68 |
+
print(f" 🎯 Target: {target_quality} (VMAF: {target_vmaf})")
|
| 69 |
+
print(f" 🎥 Codec flow: {original_codec} → {codec} → {target_codec}") # ✅ NEW: Show codec flow
|
| 70 |
+
|
| 71 |
+
# Get processing configuration
|
| 72 |
+
video_processing_config = config.get('video_processing', {})
|
| 73 |
+
short_video_threshold = video_processing_config.get('SHORT_VIDEO_THRESHOLD', 20)
|
| 74 |
+
# Get directories from config only
|
| 75 |
+
temp_dir = config.get('directories', {}).get('temp_dir', './videos/temp_scenes')
|
| 76 |
+
|
| 77 |
+
if duration <= short_video_threshold:
|
| 78 |
+
print(f"📏 Video is shorter than {short_video_threshold}s. Treating as a single scene.")
|
| 79 |
+
return [{
|
| 80 |
+
'path': video_metadata['path'],
|
| 81 |
+
'scene_number': 1,
|
| 82 |
+
'start_time': 0.0,
|
| 83 |
+
'end_time': duration,
|
| 84 |
+
'duration': duration,
|
| 85 |
+
'original_video_metadata': video_metadata # ✅ Pass complete metadata including target_codec
|
| 86 |
+
}]
|
| 87 |
+
scene_config = config.get('scene_detection', {})
|
| 88 |
+
detection_mode = scene_config.get('mode', 'adaptive').lower()
|
| 89 |
+
|
| 90 |
+
print(f" 🔍 Scene detection mode: {detection_mode}")
|
| 91 |
+
|
| 92 |
+
if detection_mode == 'force_time_based':
|
| 93 |
+
# Force time-based splitting regardless of content
|
| 94 |
+
force_duration = scene_config.get('force_time_based_duration', 10)
|
| 95 |
+
print(f" ⏰ Forcing time-based splitting with {force_duration}s segments")
|
| 96 |
+
return force_time_based_splitting(video_path, temp_dir, force_duration, duration, video_metadata)
|
| 97 |
+
|
| 98 |
+
elif detection_mode == 'adaptive':
|
| 99 |
+
# Original adaptive scene detection logic
|
| 100 |
+
print(f" 🔍 Running adaptive scene detection...")
|
| 101 |
+
has_multiple_scenes, estimated_scenes, _ = adaptive_scene_detection_check(
|
| 102 |
+
video_path, duration, logging_enabled=True
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
if has_multiple_scenes:
|
| 106 |
+
print(f" ✅ Multiple scenes detected ({estimated_scenes} estimated). Proceeding with full scene splitting.")
|
| 107 |
+
scene_files, _ = split_video_into_scenes(video_path, temp_dir)
|
| 108 |
+
|
| 109 |
+
if scene_files and len(scene_files) > 1:
|
| 110 |
+
print(f" 📄 Created {len(scene_files)} scene files")
|
| 111 |
+
return create_scene_metadata_from_files(scene_files, video_metadata, config)
|
| 112 |
+
else:
|
| 113 |
+
print(f" ⚠️ Scene splitting failed or found only 1 scene. Falling back to time-based splitting.")
|
| 114 |
+
return time_based_splitting(video_path, temp_dir, config, duration, video_metadata)
|
| 115 |
+
else:
|
| 116 |
+
print(f" 📏 Single scene detected.")
|
| 117 |
+
enable_fallback = scene_config.get('enable_time_based_fallback', False)
|
| 118 |
+
|
| 119 |
+
if enable_fallback:
|
| 120 |
+
print(f" 🔄 Time-based fallback enabled. Attempting time-based scene splitting...")
|
| 121 |
+
return time_based_splitting(video_path, temp_dir, config, duration, video_metadata)
|
| 122 |
+
else:
|
| 123 |
+
print(f" ✅ Using single scene (time-based fallback disabled)")
|
| 124 |
+
return [{
|
| 125 |
+
'path': video_path,
|
| 126 |
+
'scene_number': 1,
|
| 127 |
+
'start_time': 0.0,
|
| 128 |
+
'end_time': duration,
|
| 129 |
+
'duration': duration,
|
| 130 |
+
'original_video_metadata': video_metadata # ✅ Pass complete metadata including target_codec
|
| 131 |
+
}]
|
| 132 |
+
|
| 133 |
+
else:
|
| 134 |
+
print(f" ❌ Unknown scene detection mode: {detection_mode}. Falling back to adaptive.")
|
| 135 |
+
# Fallback to adaptive mode
|
| 136 |
+
print(f" 🔍 Running adaptive scene detection...")
|
| 137 |
+
has_multiple_scenes, estimated_scenes, _ = adaptive_scene_detection_check(
|
| 138 |
+
video_path, duration, logging_enabled=True
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
if has_multiple_scenes:
|
| 142 |
+
print(f" ✅ Multiple scenes detected ({estimated_scenes} estimated). Proceeding with full scene splitting.")
|
| 143 |
+
scene_files, _ = split_video_into_scenes(video_path, temp_dir)
|
| 144 |
+
|
| 145 |
+
if scene_files and len(scene_files) > 1:
|
| 146 |
+
print(f" 📄 Created {len(scene_files)} scene files")
|
| 147 |
+
return create_scene_metadata_from_files(scene_files, video_metadata, config)
|
| 148 |
+
else:
|
| 149 |
+
print(f" ⚠️ Scene splitting failed or found only 1 scene. Falling back to time-based splitting.")
|
| 150 |
+
return time_based_splitting(video_path, temp_dir, config, duration, video_metadata)
|
| 151 |
+
else:
|
| 152 |
+
print(f" 📏 Single scene detected. Using single scene.")
|
| 153 |
+
return [{
|
| 154 |
+
'path': video_path,
|
| 155 |
+
'scene_number': 1,
|
| 156 |
+
'start_time': 0.0,
|
| 157 |
+
'end_time': duration,
|
| 158 |
+
'duration': duration,
|
| 159 |
+
'original_video_metadata': video_metadata # ✅ Pass complete metadata including target_codec
|
| 160 |
+
}]
|
| 161 |
+
|
| 162 |
+
def force_time_based_splitting(video_path, temp_dir, segment_duration, total_duration, original_metadata, logging_enabled=True):
|
| 163 |
+
"""
|
| 164 |
+
Force time-based splitting with keyframe-aligned boundaries to prevent stuttering.
|
| 165 |
+
|
| 166 |
+
Args:
|
| 167 |
+
video_path (str): Path to the video file
|
| 168 |
+
temp_dir (str): Temporary directory for scene files
|
| 169 |
+
segment_duration (float): Target duration for each segment in seconds
|
| 170 |
+
total_duration (float): Total video duration in seconds
|
| 171 |
+
original_metadata (dict): Complete metadata from Part 1
|
| 172 |
+
logging_enabled (bool): Enable detailed logging
|
| 173 |
+
|
| 174 |
+
Returns:
|
| 175 |
+
list: List of scene metadata dictionaries
|
| 176 |
+
"""
|
| 177 |
+
if logging_enabled:
|
| 178 |
+
print(f"\n--- Force time-based splitting: {segment_duration}s segments ---")
|
| 179 |
+
print(f" 🎥 Source codec: {original_metadata.get('codec', 'unknown')}")
|
| 180 |
+
print(f" ⏱️ Total duration: {total_duration:.1f}s")
|
| 181 |
+
print(f" ✂️ Segment duration: {segment_duration}s")
|
| 182 |
+
|
| 183 |
+
# Get keyframes for precise cutting
|
| 184 |
+
try:
|
| 185 |
+
keyframes = get_keyframes(video_path)
|
| 186 |
+
if logging_enabled:
|
| 187 |
+
print(f" 🔑 Found {len(keyframes)} keyframes for alignment")
|
| 188 |
+
except Exception as e:
|
| 189 |
+
# Try alternative import path
|
| 190 |
+
try:
|
| 191 |
+
keyframes = get_keyframes(video_path)
|
| 192 |
+
if logging_enabled:
|
| 193 |
+
print(f" 🔑 Found {len(keyframes)} keyframes for alignment")
|
| 194 |
+
except Exception as e2:
|
| 195 |
+
keyframes = []
|
| 196 |
+
if logging_enabled:
|
| 197 |
+
print(f" ⚠️ Could not get keyframes: {e}")
|
| 198 |
+
print(f" 📐 Using time-based boundaries (may cause stuttering)")
|
| 199 |
+
|
| 200 |
+
# Calculate scene boundaries, preferring keyframe alignment
|
| 201 |
+
scene_boundaries = []
|
| 202 |
+
current_start = 0
|
| 203 |
+
|
| 204 |
+
while current_start < total_duration:
|
| 205 |
+
target_end = min(current_start + segment_duration, total_duration)
|
| 206 |
+
|
| 207 |
+
# If we have keyframes, find the closest keyframe to target_end
|
| 208 |
+
if keyframes and target_end < total_duration:
|
| 209 |
+
# Find keyframe closest to target_end
|
| 210 |
+
closest_keyframe = min(keyframes, key=lambda kf: abs(kf - target_end))
|
| 211 |
+
|
| 212 |
+
# Only use keyframe if it's reasonably close (within 2 seconds)
|
| 213 |
+
if abs(closest_keyframe - target_end) <= 2.0 and closest_keyframe > current_start:
|
| 214 |
+
scene_end = closest_keyframe
|
| 215 |
+
else:
|
| 216 |
+
scene_end = target_end
|
| 217 |
+
else:
|
| 218 |
+
scene_end = target_end
|
| 219 |
+
|
| 220 |
+
# Handle very short final segments (< 2 seconds) by merging with previous scene
|
| 221 |
+
remaining_duration = total_duration - scene_end
|
| 222 |
+
if remaining_duration > 0 and remaining_duration < 2.0 and scene_boundaries:
|
| 223 |
+
# Extend current scene to end of video instead of creating tiny final scene
|
| 224 |
+
scene_end = total_duration
|
| 225 |
+
if logging_enabled:
|
| 226 |
+
print(f" 🔧 Merging final {remaining_duration:.1f}s with current scene to avoid stuttering")
|
| 227 |
+
|
| 228 |
+
scene_boundaries.append((current_start, scene_end))
|
| 229 |
+
current_start = scene_end
|
| 230 |
+
|
| 231 |
+
if logging_enabled:
|
| 232 |
+
print(f" 📊 Will create {len(scene_boundaries)} segments:")
|
| 233 |
+
for i, (start, end) in enumerate(scene_boundaries):
|
| 234 |
+
print(f" Segment {i+1}: {start:.1f}s - {end:.1f}s (duration: {end-start:.1f}s)")
|
| 235 |
+
|
| 236 |
+
# If only one scene, return original video
|
| 237 |
+
if len(scene_boundaries) <= 1:
|
| 238 |
+
if logging_enabled:
|
| 239 |
+
print(f" 📏 Video shorter than segment duration, using as single scene")
|
| 240 |
+
return [{
|
| 241 |
+
'path': video_path,
|
| 242 |
+
'scene_number': 1,
|
| 243 |
+
'start_time': 0.0,
|
| 244 |
+
'end_time': total_duration,
|
| 245 |
+
'duration': total_duration,
|
| 246 |
+
'original_video_metadata': original_metadata
|
| 247 |
+
}]
|
| 248 |
+
|
| 249 |
+
# Create output directory
|
| 250 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 251 |
+
|
| 252 |
+
# Choose scene file extension based on source codec
|
| 253 |
+
source_codec = original_metadata.get('codec', 'h264').lower()
|
| 254 |
+
if source_codec in ['ffv1', 'prores', 'dnxhd']:
|
| 255 |
+
scene_extension = '.mkv' # Use MKV for lossless codecs
|
| 256 |
+
elif source_codec in ['av1', 'vp9']:
|
| 257 |
+
scene_extension = '.webm' # Use WebM for modern codecs
|
| 258 |
+
else:
|
| 259 |
+
scene_extension = '.mp4' # Default to MP4 for compatibility
|
| 260 |
+
|
| 261 |
+
if logging_enabled:
|
| 262 |
+
print(f" 📦 Using {scene_extension} container for {source_codec} codec")
|
| 263 |
+
|
| 264 |
+
# Split video and create metadata
|
| 265 |
+
scene_metadata_list = []
|
| 266 |
+
|
| 267 |
+
for i, (start_time_sec, end_time_sec) in enumerate(scene_boundaries):
|
| 268 |
+
scene_number = i + 1
|
| 269 |
+
scene_filename = f"scene_{scene_number:03d}_time_{start_time_sec:.0f}s{scene_extension}"
|
| 270 |
+
scene_path = os.path.join(temp_dir, scene_filename)
|
| 271 |
+
|
| 272 |
+
duration_scene = end_time_sec - start_time_sec
|
| 273 |
+
|
| 274 |
+
# FFmpeg command with precise cutting to prevent frame alignment issues
|
| 275 |
+
# Use input seeking for efficiency and output seeking for precision
|
| 276 |
+
cmd = [
|
| 277 |
+
'ffmpeg', '-y',
|
| 278 |
+
'-ss', str(start_time_sec), # Input seeking for efficiency
|
| 279 |
+
'-i', video_path,
|
| 280 |
+
'-t', str(duration_scene), # Precise duration
|
| 281 |
+
'-avoid_negative_ts', 'make_zero',
|
| 282 |
+
'-fflags', '+genpts', # Generate presentation timestamps
|
| 283 |
+
'-map', '0:v:0', # Map video stream explicitly
|
| 284 |
+
'-map', '0:a?', # Map audio if exists (optional)
|
| 285 |
+
]
|
| 286 |
+
|
| 287 |
+
# Add codec-specific format options before encoding
|
| 288 |
+
format_options = []
|
| 289 |
+
if source_codec == 'ffv1':
|
| 290 |
+
format_options.extend(['-f', 'matroska'])
|
| 291 |
+
elif source_codec in ['av1', 'vp9']:
|
| 292 |
+
format_options.extend(['-f', 'webm'])
|
| 293 |
+
|
| 294 |
+
# Always use fast re-encoding to ensure clean cuts and prevent stuttering
|
| 295 |
+
# Stream copy can cause artifacts at scene boundaries even with keyframe alignment
|
| 296 |
+
cmd.extend([
|
| 297 |
+
'-c:v', 'libx264',
|
| 298 |
+
'-preset', 'ultrafast',
|
| 299 |
+
'-crf', '18',
|
| 300 |
+
'-c:a', 'copy'
|
| 301 |
+
])
|
| 302 |
+
cmd.extend(format_options) # Add format options
|
| 303 |
+
cmd.append(scene_path) # Add output file path
|
| 304 |
+
|
| 305 |
+
if logging_enabled:
|
| 306 |
+
print(f" ✂️ Creating segment {scene_number}: {scene_filename}")
|
| 307 |
+
|
| 308 |
+
try:
|
| 309 |
+
# Execute FFmpeg command
|
| 310 |
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
| 311 |
+
|
| 312 |
+
if result.returncode == 0:
|
| 313 |
+
# Verify file exists and has content
|
| 314 |
+
if os.path.exists(scene_path) and os.path.getsize(scene_path) > 0:
|
| 315 |
+
file_size_mb = os.path.getsize(scene_path) / (1024 * 1024)
|
| 316 |
+
|
| 317 |
+
scene_metadata = {
|
| 318 |
+
'path': scene_path,
|
| 319 |
+
'scene_number': scene_number,
|
| 320 |
+
'start_time': start_time_sec,
|
| 321 |
+
'end_time': end_time_sec,
|
| 322 |
+
'duration': duration_scene,
|
| 323 |
+
'scene_codec': source_codec,
|
| 324 |
+
'scene_container': scene_extension[1:], # Remove the dot
|
| 325 |
+
'file_size_mb': file_size_mb,
|
| 326 |
+
'scene_detection_method': 'force_time_based',
|
| 327 |
+
'original_video_metadata': original_metadata
|
| 328 |
+
}
|
| 329 |
+
scene_metadata_list.append(scene_metadata)
|
| 330 |
+
|
| 331 |
+
if logging_enabled:
|
| 332 |
+
print(f" ✅ Created: {scene_filename} ({file_size_mb:.1f} MB)")
|
| 333 |
+
else:
|
| 334 |
+
if logging_enabled:
|
| 335 |
+
print(f" ❌ Failed: {scene_filename} (file not created or empty)")
|
| 336 |
+
else:
|
| 337 |
+
if logging_enabled:
|
| 338 |
+
print(f" ❌ FFmpeg failed for {scene_filename}: {result.stderr}")
|
| 339 |
+
|
| 340 |
+
except subprocess.TimeoutExpired:
|
| 341 |
+
if logging_enabled:
|
| 342 |
+
print(f" ❌ Timeout creating {scene_filename}")
|
| 343 |
+
except Exception as e:
|
| 344 |
+
if logging_enabled:
|
| 345 |
+
print(f" ❌ Error creating {scene_filename}: {e}")
|
| 346 |
+
|
| 347 |
+
if not scene_metadata_list:
|
| 348 |
+
if logging_enabled:
|
| 349 |
+
print(f" ❌ All segment creation failed, using original video as single scene")
|
| 350 |
+
return [{
|
| 351 |
+
'path': video_path,
|
| 352 |
+
'scene_number': 1,
|
| 353 |
+
'start_time': 0.0,
|
| 354 |
+
'end_time': total_duration,
|
| 355 |
+
'duration': total_duration,
|
| 356 |
+
'original_video_metadata': original_metadata
|
| 357 |
+
}]
|
| 358 |
+
|
| 359 |
+
if logging_enabled:
|
| 360 |
+
print(f" ✅ Successfully created {len(scene_metadata_list)} time-based segments")
|
| 361 |
+
|
| 362 |
+
return scene_metadata_list
|
| 363 |
+
|
| 364 |
+
def time_based_splitting(video_path, temp_dir, config, duration, original_metadata, logging_enabled=True):
|
| 365 |
+
"""
|
| 366 |
+
Split video into scenes and return metadata for each scene.
|
| 367 |
+
|
| 368 |
+
Args:
|
| 369 |
+
video_path (str): Path to the video file
|
| 370 |
+
temp_dir (str): Temporary directory for scene files
|
| 371 |
+
config (dict): Configuration dictionary
|
| 372 |
+
duration (float): Video duration in seconds
|
| 373 |
+
original_metadata (dict): Complete metadata from Part 1 including codec info
|
| 374 |
+
logging_enabled (bool): Enable detailed logging
|
| 375 |
+
|
| 376 |
+
Returns:
|
| 377 |
+
list: List of scene metadata dictionaries
|
| 378 |
+
"""
|
| 379 |
+
if logging_enabled:
|
| 380 |
+
print(f"\n--- Running time-based scene splitting with metadata ---")
|
| 381 |
+
# ✅ NEW: Log codec information for scene splitting
|
| 382 |
+
print(f" 🎥 Source codec: {original_metadata.get('codec', 'unknown')}")
|
| 383 |
+
print(f" 🎯 Target codec: {original_metadata.get('target_codec', 'auto')}")
|
| 384 |
+
|
| 385 |
+
try:
|
| 386 |
+
total_duration = duration
|
| 387 |
+
if logging_enabled:
|
| 388 |
+
print(f"Using provided duration: {total_duration:.1f}s")
|
| 389 |
+
|
| 390 |
+
if not total_duration:
|
| 391 |
+
return [{
|
| 392 |
+
'path': video_path,
|
| 393 |
+
'scene_number': 1,
|
| 394 |
+
'start_time': 0.0,
|
| 395 |
+
'end_time': duration,
|
| 396 |
+
'duration': duration,
|
| 397 |
+
'original_video_metadata': original_metadata
|
| 398 |
+
}]
|
| 399 |
+
|
| 400 |
+
# Get scene duration from config
|
| 401 |
+
scene_config = config.get('scene_detection', {})
|
| 402 |
+
scene_duration = scene_config.get('time_based_scene_duration', None)
|
| 403 |
+
|
| 404 |
+
# Auto-calculate scene duration if not specified
|
| 405 |
+
if scene_duration is None:
|
| 406 |
+
if total_duration <= 120:
|
| 407 |
+
scene_duration = 60
|
| 408 |
+
elif total_duration <= 600:
|
| 409 |
+
scene_duration = 120
|
| 410 |
+
elif total_duration <= 1800:
|
| 411 |
+
scene_duration = 300
|
| 412 |
+
else:
|
| 413 |
+
scene_duration = 600
|
| 414 |
+
|
| 415 |
+
if logging_enabled:
|
| 416 |
+
print(f"Auto-calculated scene duration: {scene_duration}s for {total_duration:.1f}s video")
|
| 417 |
+
|
| 418 |
+
# Calculate scene boundaries
|
| 419 |
+
scene_boundaries = []
|
| 420 |
+
current_start = 0
|
| 421 |
+
|
| 422 |
+
while current_start < total_duration:
|
| 423 |
+
scene_end = min(current_start + scene_duration, total_duration)
|
| 424 |
+
scene_boundaries.append((current_start, scene_end))
|
| 425 |
+
current_start = scene_end
|
| 426 |
+
|
| 427 |
+
if logging_enabled:
|
| 428 |
+
print(f"Calculated {len(scene_boundaries)} time-based scenes:")
|
| 429 |
+
for i, (start, end) in enumerate(scene_boundaries):
|
| 430 |
+
print(f" Scene {i+1}: {start:.1f}s - {end:.1f}s (duration: {end-start:.1f}s)")
|
| 431 |
+
|
| 432 |
+
# If only one scene, return original video
|
| 433 |
+
if len(scene_boundaries) <= 1:
|
| 434 |
+
return [{
|
| 435 |
+
'path': video_path,
|
| 436 |
+
'scene_number': 1,
|
| 437 |
+
'start_time': 0.0,
|
| 438 |
+
'end_time': total_duration,
|
| 439 |
+
'duration': total_duration,
|
| 440 |
+
'original_video_metadata': original_metadata
|
| 441 |
+
}]
|
| 442 |
+
|
| 443 |
+
# Create output directory
|
| 444 |
+
os.makedirs(temp_dir, exist_ok=True)
|
| 445 |
+
|
| 446 |
+
# ✅ IMPROVED: Choose scene file extension based on source codec
|
| 447 |
+
source_codec = original_metadata.get('codec', 'h264').lower()
|
| 448 |
+
if source_codec in ['ffv1', 'prores', 'dnxhd']:
|
| 449 |
+
scene_extension = '.mkv' # Use MKV for lossless codecs
|
| 450 |
+
elif source_codec in ['av1', 'vp9']:
|
| 451 |
+
scene_extension = '.webm' # Use WebM for modern codecs
|
| 452 |
+
else:
|
| 453 |
+
scene_extension = '.mp4' # Default to MP4 for compatibility
|
| 454 |
+
|
| 455 |
+
if logging_enabled:
|
| 456 |
+
print(f" 📦 Using {scene_extension} container for {source_codec} codec")
|
| 457 |
+
|
| 458 |
+
# Split video and create metadata
|
| 459 |
+
scene_metadata_list = []
|
| 460 |
+
|
| 461 |
+
for i, (start_time_sec, end_time_sec) in enumerate(scene_boundaries):
|
| 462 |
+
scene_number = i + 1
|
| 463 |
+
scene_filename = f"scene_{scene_number:03d}{scene_extension}"
|
| 464 |
+
scene_path = os.path.join(temp_dir, scene_filename)
|
| 465 |
+
|
| 466 |
+
duration_scene = end_time_sec - start_time_sec
|
| 467 |
+
|
| 468 |
+
# ✅ IMPROVED: FFmpeg command with codec-aware copy settings
|
| 469 |
+
cmd = [
|
| 470 |
+
'ffmpeg', '-y',
|
| 471 |
+
'-ss', str(start_time_sec),
|
| 472 |
+
'-i', video_path,
|
| 473 |
+
'-t', str(duration_scene),
|
| 474 |
+
'-c', 'copy', # Stream copy for speed
|
| 475 |
+
'-map', '0', # Copy all streams
|
| 476 |
+
'-avoid_negative_ts', 'make_zero',
|
| 477 |
+
scene_path
|
| 478 |
+
]
|
| 479 |
+
|
| 480 |
+
# ✅ NEW: Add codec-specific options if needed
|
| 481 |
+
if source_codec == 'ffv1':
|
| 482 |
+
# For FFV1, ensure proper MKV muxing
|
| 483 |
+
cmd.extend(['-f', 'matroska'])
|
| 484 |
+
elif source_codec in ['av1', 'vp9']:
|
| 485 |
+
# For modern codecs, ensure proper WebM muxing
|
| 486 |
+
cmd.extend(['-f', 'webm'])
|
| 487 |
+
|
| 488 |
+
if logging_enabled:
|
| 489 |
+
print(f"Extracting scene {scene_number}/{len(scene_boundaries)}: {start_time_sec:.1f}s-{end_time_sec:.1f}s")
|
| 490 |
+
|
| 491 |
+
try:
|
| 492 |
+
result = subprocess.run(
|
| 493 |
+
cmd,
|
| 494 |
+
stdout=subprocess.PIPE,
|
| 495 |
+
stderr=subprocess.PIPE,
|
| 496 |
+
text=True,
|
| 497 |
+
check=True
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
if os.path.exists(scene_path) and os.path.getsize(scene_path) > 0:
|
| 501 |
+
# Create scene metadata
|
| 502 |
+
scene_metadata = {
|
| 503 |
+
'path': scene_path,
|
| 504 |
+
'scene_number': scene_number,
|
| 505 |
+
'start_time': start_time_sec,
|
| 506 |
+
'end_time': end_time_sec,
|
| 507 |
+
'duration': duration_scene,
|
| 508 |
+
'original_video_metadata': original_metadata, # ✅ Complete metadata including codec info
|
| 509 |
+
'file_size_mb': os.path.getsize(scene_path) / (1024 * 1024),
|
| 510 |
+
'scene_codec': source_codec, # ✅ NEW: Track scene codec
|
| 511 |
+
'scene_container': scene_extension[1:] # ✅ NEW: Track container format
|
| 512 |
+
}
|
| 513 |
+
scene_metadata_list.append(scene_metadata)
|
| 514 |
+
|
| 515 |
+
if logging_enabled:
|
| 516 |
+
print(f" ✅ Created: {scene_filename} ({scene_metadata['file_size_mb']:.1f} MB)")
|
| 517 |
+
else:
|
| 518 |
+
if logging_enabled:
|
| 519 |
+
print(f" ❌ Failed: {scene_filename}")
|
| 520 |
+
|
| 521 |
+
except subprocess.CalledProcessError as e:
|
| 522 |
+
if logging_enabled:
|
| 523 |
+
print(f" ❌ FFmpeg error for scene {scene_number}: {e.stderr}")
|
| 524 |
+
continue
|
| 525 |
+
except Exception as e:
|
| 526 |
+
if logging_enabled:
|
| 527 |
+
print(f" ❌ Unexpected error for scene {scene_number}: {e}")
|
| 528 |
+
continue
|
| 529 |
+
|
| 530 |
+
if logging_enabled:
|
| 531 |
+
print(f"Time-based splitting completed: {len(scene_metadata_list)} scenes created")
|
| 532 |
+
if scene_metadata_list:
|
| 533 |
+
total_size = sum(scene['file_size_mb'] for scene in scene_metadata_list)
|
| 534 |
+
print(f" 📊 Total scenes size: {total_size:.1f} MB")
|
| 535 |
+
|
| 536 |
+
return scene_metadata_list if scene_metadata_list else [{
|
| 537 |
+
'path': video_path,
|
| 538 |
+
'scene_number': 1,
|
| 539 |
+
'start_time': 0.0,
|
| 540 |
+
'end_time': total_duration,
|
| 541 |
+
'duration': total_duration,
|
| 542 |
+
'original_video_metadata': original_metadata
|
| 543 |
+
}]
|
| 544 |
+
|
| 545 |
+
except Exception as e:
|
| 546 |
+
if logging_enabled:
|
| 547 |
+
print(f"Time-based splitting failed: {e}")
|
| 548 |
+
return [{
|
| 549 |
+
'path': video_path,
|
| 550 |
+
'scene_number': 1,
|
| 551 |
+
'start_time': 0.0,
|
| 552 |
+
'end_time': duration,
|
| 553 |
+
'duration': duration,
|
| 554 |
+
'original_video_metadata': original_metadata
|
| 555 |
+
}]
|
| 556 |
+
|
| 557 |
+
def create_scene_metadata_from_files(scene_files, original_metadata, config):
|
| 558 |
+
"""
|
| 559 |
+
Create metadata for scenes created by PySceneDetect.
|
| 560 |
+
Note: PySceneDetect doesn't give us exact timing, so we estimate.
|
| 561 |
+
|
| 562 |
+
Args:
|
| 563 |
+
scene_files (list): List of scene file paths
|
| 564 |
+
original_metadata (dict): Complete metadata from Part 1 including codec info
|
| 565 |
+
config (dict): Configuration dictionary
|
| 566 |
+
|
| 567 |
+
Returns:
|
| 568 |
+
list: List of scene metadata dictionaries
|
| 569 |
+
"""
|
| 570 |
+
scene_metadata_list = []
|
| 571 |
+
total_duration = original_metadata['duration']
|
| 572 |
+
source_codec = original_metadata.get('codec', 'unknown')
|
| 573 |
+
|
| 574 |
+
if not scene_files:
|
| 575 |
+
return [{
|
| 576 |
+
'path': original_metadata['path'],
|
| 577 |
+
'scene_number': 1,
|
| 578 |
+
'start_time': 0.0,
|
| 579 |
+
'end_time': total_duration,
|
| 580 |
+
'duration': total_duration,
|
| 581 |
+
'original_video_metadata': original_metadata
|
| 582 |
+
}]
|
| 583 |
+
|
| 584 |
+
# Estimate timing based on file count (not perfect, but reasonable)
|
| 585 |
+
estimated_scene_duration = total_duration / len(scene_files)
|
| 586 |
+
|
| 587 |
+
print(f" 🎥 Creating metadata for {len(scene_files)} scenes from {source_codec} source")
|
| 588 |
+
|
| 589 |
+
for i, scene_file in enumerate(scene_files):
|
| 590 |
+
scene_number = i + 1
|
| 591 |
+
start_time = i * estimated_scene_duration
|
| 592 |
+
end_time = min((i + 1) * estimated_scene_duration, total_duration)
|
| 593 |
+
|
| 594 |
+
# Adjust last scene to cover remaining time
|
| 595 |
+
if i == len(scene_files) - 1:
|
| 596 |
+
end_time = total_duration
|
| 597 |
+
|
| 598 |
+
# ✅ NEW: Extract scene file extension for container tracking
|
| 599 |
+
scene_extension = os.path.splitext(scene_file)[1][1:] if os.path.splitext(scene_file)[1] else 'unknown'
|
| 600 |
+
|
| 601 |
+
scene_metadata = {
|
| 602 |
+
'path': scene_file,
|
| 603 |
+
'scene_number': scene_number,
|
| 604 |
+
'start_time': start_time,
|
| 605 |
+
'end_time': end_time,
|
| 606 |
+
'duration': end_time - start_time,
|
| 607 |
+
'original_video_metadata': original_metadata, # ✅ Complete metadata including codec info
|
| 608 |
+
'file_size_mb': os.path.getsize(scene_file) / (1024 * 1024) if os.path.exists(scene_file) else 0,
|
| 609 |
+
'scene_codec': source_codec, # ✅ NEW: Track scene codec
|
| 610 |
+
'scene_container': scene_extension # ✅ NEW: Track container format
|
| 611 |
+
}
|
| 612 |
+
scene_metadata_list.append(scene_metadata)
|
| 613 |
+
|
| 614 |
+
return scene_metadata_list
|
services/compress/server.py
ADDED
|
@@ -0,0 +1,798 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Video Compression Service API Server
|
| 3 |
+
|
| 4 |
+
This module provides a FastAPI server for video compression services.
|
| 5 |
+
It handles video upload, compression, and storage operations.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
import time
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from typing import Optional
|
| 14 |
+
|
| 15 |
+
from fastapi import FastAPI, HTTPException
|
| 16 |
+
from pydantic import BaseModel
|
| 17 |
+
from loguru import logger
|
| 18 |
+
|
| 19 |
+
from video_preprocessor import pre_processing
|
| 20 |
+
from scene_detector import scene_detection
|
| 21 |
+
from encoder import ai_encoding, load_encoding_resources
|
| 22 |
+
from vmaf_calculator import scene_vmaf_calculation
|
| 23 |
+
from validator_merger import validation_and_merging
|
| 24 |
+
from vidaio_subnet_core.utilities import storage_client, download_video
|
| 25 |
+
from vidaio_subnet_core import CONFIG
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# ============================================================================
|
| 29 |
+
# FastAPI Application Setup
|
| 30 |
+
# ============================================================================
|
| 31 |
+
|
| 32 |
+
app = FastAPI(title="Video Compression Service", version="1.0.0")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# ============================================================================
|
| 36 |
+
# Data Models
|
| 37 |
+
# ============================================================================
|
| 38 |
+
|
| 39 |
+
class CompressPayload(BaseModel):
|
| 40 |
+
"""Payload for video compression requests."""
|
| 41 |
+
payload_url: str
|
| 42 |
+
vmaf_threshold: float
|
| 43 |
+
target_quality: str = 'Medium' # High, Medium, Low
|
| 44 |
+
max_duration: int = 3600 # Maximum allowed video duration in seconds
|
| 45 |
+
output_dir: str = './output' # Output directory for final files
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class TestCompressPayload(BaseModel):
|
| 49 |
+
"""Payload for test compression requests."""
|
| 50 |
+
video_path: str
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# ============================================================================
|
| 54 |
+
# API Endpoints
|
| 55 |
+
# ============================================================================
|
| 56 |
+
|
| 57 |
+
@app.post("/compress-video")
|
| 58 |
+
async def compress_video(video: CompressPayload):
|
| 59 |
+
"""
|
| 60 |
+
Compress a video from a URL payload.
|
| 61 |
+
|
| 62 |
+
Args:
|
| 63 |
+
video: Compression request payload
|
| 64 |
+
|
| 65 |
+
Returns:
|
| 66 |
+
dict: Compression results with uploaded video URL
|
| 67 |
+
"""
|
| 68 |
+
print(f"video url: {video.payload_url}")
|
| 69 |
+
print(f"vmaf threshold: {video.vmaf_threshold}")
|
| 70 |
+
|
| 71 |
+
# Download video from URL
|
| 72 |
+
input_path = await download_video(video.payload_url)
|
| 73 |
+
input_file = Path(input_path)
|
| 74 |
+
vmaf_threshold = video.vmaf_threshold
|
| 75 |
+
|
| 76 |
+
# Map VMAF threshold to target quality
|
| 77 |
+
if vmaf_threshold == 85:
|
| 78 |
+
target_quality = 'Low'
|
| 79 |
+
elif vmaf_threshold == 90:
|
| 80 |
+
target_quality = 'Medium'
|
| 81 |
+
elif vmaf_threshold == 95:
|
| 82 |
+
target_quality = 'High'
|
| 83 |
+
else:
|
| 84 |
+
raise HTTPException(status_code=400, detail="Invalid VMAF threshold.")
|
| 85 |
+
|
| 86 |
+
# Validate input file
|
| 87 |
+
if not input_file.is_file():
|
| 88 |
+
raise HTTPException(status_code=400, detail="Input video file does not exist.")
|
| 89 |
+
|
| 90 |
+
# Create output directory
|
| 91 |
+
output_dir = Path(video.output_dir)
|
| 92 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 93 |
+
|
| 94 |
+
# Perform video compression
|
| 95 |
+
try:
|
| 96 |
+
compressed_video_path = video_compressor(
|
| 97 |
+
input_file=str(input_file),
|
| 98 |
+
target_quality=target_quality,
|
| 99 |
+
max_duration=video.max_duration,
|
| 100 |
+
output_dir=str(output_dir)
|
| 101 |
+
)
|
| 102 |
+
print(f"compressed_video_path: {compressed_video_path}")
|
| 103 |
+
|
| 104 |
+
if compressed_video_path and Path(compressed_video_path).exists():
|
| 105 |
+
# Upload compressed video to storage
|
| 106 |
+
try:
|
| 107 |
+
compressed_video_name = os.path.basename(compressed_video_path)
|
| 108 |
+
object_name: str = compressed_video_name
|
| 109 |
+
|
| 110 |
+
# Upload file
|
| 111 |
+
await storage_client.upload_file(object_name, compressed_video_path)
|
| 112 |
+
print(f"object_name: {object_name}")
|
| 113 |
+
print("Video uploaded successfully.")
|
| 114 |
+
|
| 115 |
+
# Clean up local file
|
| 116 |
+
if os.path.exists(compressed_video_path):
|
| 117 |
+
os.remove(compressed_video_path)
|
| 118 |
+
print(f"{compressed_video_path} has been deleted.")
|
| 119 |
+
else:
|
| 120 |
+
print(f"{compressed_video_path} does not exist.")
|
| 121 |
+
|
| 122 |
+
# Get sharing link
|
| 123 |
+
sharing_link: Optional[str] = await storage_client.get_presigned_url(object_name)
|
| 124 |
+
print(f"sharing_link: {sharing_link}")
|
| 125 |
+
|
| 126 |
+
if not sharing_link:
|
| 127 |
+
print("Upload failed")
|
| 128 |
+
return {"uploaded_video_url": None}
|
| 129 |
+
|
| 130 |
+
return {
|
| 131 |
+
"uploaded_video_url": sharing_link,
|
| 132 |
+
"status": "success",
|
| 133 |
+
"compressed_video_path": str(compressed_video_path)
|
| 134 |
+
}
|
| 135 |
+
except Exception as upload_error:
|
| 136 |
+
raise HTTPException(
|
| 137 |
+
status_code=500,
|
| 138 |
+
detail=f"Failed to upload compressed video: {str(upload_error)}"
|
| 139 |
+
)
|
| 140 |
+
else:
|
| 141 |
+
raise HTTPException(status_code=500, detail="Video compression failed")
|
| 142 |
+
except Exception as e:
|
| 143 |
+
raise HTTPException(status_code=500, detail=f"Video compression error: {str(e)}")
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
@app.post("/test-compress")
|
| 147 |
+
async def test_compress_video(test_payload: TestCompressPayload):
|
| 148 |
+
"""
|
| 149 |
+
Test endpoint for video compression using local video path.
|
| 150 |
+
|
| 151 |
+
Args:
|
| 152 |
+
test_payload: Test compression request payload
|
| 153 |
+
|
| 154 |
+
Returns:
|
| 155 |
+
dict: Test compression results
|
| 156 |
+
"""
|
| 157 |
+
video_path = Path(test_payload.video_path)
|
| 158 |
+
|
| 159 |
+
# Validate input file
|
| 160 |
+
if not video_path.is_file():
|
| 161 |
+
raise HTTPException(
|
| 162 |
+
status_code=400,
|
| 163 |
+
detail=f"Video file does not exist: {video_path}"
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
try:
|
| 167 |
+
# Perform test compression
|
| 168 |
+
compressed_video_path = test_video_compression(str(video_path))
|
| 169 |
+
|
| 170 |
+
if compressed_video_path and Path(compressed_video_path).exists():
|
| 171 |
+
return {
|
| 172 |
+
"status": "success",
|
| 173 |
+
"message": "Video compression test completed successfully",
|
| 174 |
+
"input_path": str(video_path),
|
| 175 |
+
"output_path": compressed_video_path,
|
| 176 |
+
"output_size_mb": round(
|
| 177 |
+
Path(compressed_video_path).stat().st_size / (1024 * 1024), 2
|
| 178 |
+
)
|
| 179 |
+
}
|
| 180 |
+
else:
|
| 181 |
+
raise HTTPException(status_code=500, detail="Video compression test failed")
|
| 182 |
+
|
| 183 |
+
except Exception as e:
|
| 184 |
+
raise HTTPException(
|
| 185 |
+
status_code=500,
|
| 186 |
+
detail=f"Video compression test error: {str(e)}"
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# ============================================================================
|
| 191 |
+
# Core Video Compression Functions
|
| 192 |
+
# ============================================================================
|
| 193 |
+
|
| 194 |
+
def video_compressor(
|
| 195 |
+
input_file: str,
|
| 196 |
+
target_quality: str = 'Medium',
|
| 197 |
+
max_duration: int = 3600,
|
| 198 |
+
output_dir: str = './output'
|
| 199 |
+
) -> Optional[str]:
|
| 200 |
+
"""
|
| 201 |
+
Main video compression pipeline orchestrator.
|
| 202 |
+
|
| 203 |
+
Args:
|
| 204 |
+
input_file: Path to input video file
|
| 205 |
+
target_quality: Target quality level ('High', 'Medium', 'Low')
|
| 206 |
+
max_duration: Maximum allowed video duration in seconds
|
| 207 |
+
output_dir: Output directory for final files
|
| 208 |
+
|
| 209 |
+
Returns:
|
| 210 |
+
str: Path to compressed video file, or None if failed
|
| 211 |
+
"""
|
| 212 |
+
# Record pipeline start time
|
| 213 |
+
pipeline_start_time = time.time()
|
| 214 |
+
|
| 215 |
+
# Get current directory and setup paths
|
| 216 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 217 |
+
output_dir_path = Path(output_dir)
|
| 218 |
+
output_dir_path.mkdir(parents=True, exist_ok=True)
|
| 219 |
+
|
| 220 |
+
# Load configuration
|
| 221 |
+
config = _load_configuration(current_dir)
|
| 222 |
+
config['directories']['output_dir'] = str(output_dir_path)
|
| 223 |
+
if 'video_processing' not in config:
|
| 224 |
+
config['video_processing'] = {}
|
| 225 |
+
config['video_processing']['target_quality'] = target_quality
|
| 226 |
+
|
| 227 |
+
# Create temp directory
|
| 228 |
+
temp_dir = Path(config['directories']['temp_dir'])
|
| 229 |
+
temp_dir.mkdir(parents=True, exist_ok=True)
|
| 230 |
+
|
| 231 |
+
# Display pipeline information
|
| 232 |
+
_display_pipeline_info(input_file, target_quality, max_duration, output_dir)
|
| 233 |
+
|
| 234 |
+
# PART 1: Pre-processing
|
| 235 |
+
part1_result = _execute_preprocessing(input_file, target_quality, max_duration, output_dir_path)
|
| 236 |
+
if not part1_result:
|
| 237 |
+
print("❌ Part 1 failed. Pipeline terminated.")
|
| 238 |
+
return False
|
| 239 |
+
|
| 240 |
+
part1_time = time.time() - pipeline_start_time
|
| 241 |
+
_display_preprocessing_results(part1_result, part1_time)
|
| 242 |
+
|
| 243 |
+
# PART 2: Scene Detection
|
| 244 |
+
part2_start_time = time.time()
|
| 245 |
+
scenes_metadata = scene_detection(part1_result)
|
| 246 |
+
if not scenes_metadata:
|
| 247 |
+
print("❌ Part 2 failed. Pipeline terminated.")
|
| 248 |
+
return False
|
| 249 |
+
|
| 250 |
+
part2_time = time.time() - part2_start_time
|
| 251 |
+
_display_scene_detection_results(scenes_metadata, part2_time)
|
| 252 |
+
|
| 253 |
+
# PART 3: AI Encoding
|
| 254 |
+
part3_result = _execute_ai_encoding(scenes_metadata, config, target_quality)
|
| 255 |
+
if not part3_result:
|
| 256 |
+
print("❌ Part 3 failed completely. Pipeline terminated.")
|
| 257 |
+
return False
|
| 258 |
+
|
| 259 |
+
part3_time = part3_result['processing_time']
|
| 260 |
+
encoded_scenes_data = part3_result['encoded_scenes_data']
|
| 261 |
+
successful_encodings = part3_result['successful_encodings']
|
| 262 |
+
|
| 263 |
+
# PART 4: Validation and Merging
|
| 264 |
+
part4_result = _execute_validation_and_merging(
|
| 265 |
+
part1_result, encoded_scenes_data, config
|
| 266 |
+
)
|
| 267 |
+
if not part4_result:
|
| 268 |
+
print("❌ Part 4 failed. Could not create final video.")
|
| 269 |
+
return False
|
| 270 |
+
|
| 271 |
+
part4_time = part4_result['processing_time']
|
| 272 |
+
final_video_path = part4_result['final_video_path']
|
| 273 |
+
final_vmaf = part4_result['final_vmaf']
|
| 274 |
+
comprehensive_report = part4_result['comprehensive_report']
|
| 275 |
+
|
| 276 |
+
_display_validation_results(part4_result)
|
| 277 |
+
|
| 278 |
+
# Pipeline completion summary
|
| 279 |
+
total_pipeline_time = time.time() - pipeline_start_time
|
| 280 |
+
_display_pipeline_summary(
|
| 281 |
+
input_file, final_video_path, part1_result, scenes_metadata,
|
| 282 |
+
successful_encodings, output_dir, pipeline_start_time,
|
| 283 |
+
part1_time, part2_time, part3_time, part4_time, total_pipeline_time,
|
| 284 |
+
final_vmaf, comprehensive_report
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
return final_video_path
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def test_video_compression(video_path: str) -> Optional[str]:
|
| 291 |
+
"""
|
| 292 |
+
Test function for video compression using default parameters.
|
| 293 |
+
|
| 294 |
+
Args:
|
| 295 |
+
video_path: Path to input video file
|
| 296 |
+
|
| 297 |
+
Returns:
|
| 298 |
+
str: Path to compressed video file, or None if failed
|
| 299 |
+
"""
|
| 300 |
+
print(f"\n🧪 === Testing Video Compression ===")
|
| 301 |
+
print(f" 📁 Input: {video_path}")
|
| 302 |
+
print(f" 🎯 Using default test parameters")
|
| 303 |
+
|
| 304 |
+
# Default test parameters
|
| 305 |
+
test_params = {
|
| 306 |
+
'target_quality': 'Medium',
|
| 307 |
+
'max_duration': 3600,
|
| 308 |
+
'output_dir': './test_output'
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
try:
|
| 312 |
+
# Validate input file
|
| 313 |
+
input_path = Path(video_path)
|
| 314 |
+
if not input_path.is_file():
|
| 315 |
+
print(f"❌ Input file does not exist: {video_path}")
|
| 316 |
+
return None
|
| 317 |
+
# Create test output directory
|
| 318 |
+
test_output_dir = Path(test_params['output_dir'])
|
| 319 |
+
test_output_dir.mkdir(parents=True, exist_ok=True)
|
| 320 |
+
|
| 321 |
+
# Perform compression
|
| 322 |
+
result = video_compressor(
|
| 323 |
+
input_file=str(input_path),
|
| 324 |
+
target_quality=test_params['target_quality'],
|
| 325 |
+
max_duration=test_params['max_duration'],
|
| 326 |
+
output_dir=str(test_output_dir)
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
if result and Path(result).exists():
|
| 330 |
+
print(f"\n✅ Test completed successfully!")
|
| 331 |
+
print(f" 📁 Compressed video: {result}")
|
| 332 |
+
return result
|
| 333 |
+
else:
|
| 334 |
+
print(f"\n❌ Test failed - no output file generated")
|
| 335 |
+
return None
|
| 336 |
+
|
| 337 |
+
except Exception as e:
|
| 338 |
+
print(f"\n❌ Test failed with exception: {e}")
|
| 339 |
+
return None
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# ============================================================================
|
| 343 |
+
# Helper Functions
|
| 344 |
+
# ============================================================================
|
| 345 |
+
|
| 346 |
+
def _load_configuration(current_dir: str) -> dict:
|
| 347 |
+
"""Load configuration from config.json or use defaults."""
|
| 348 |
+
try:
|
| 349 |
+
config_path = os.path.join(current_dir, 'config.json')
|
| 350 |
+
with open(config_path, 'r') as f:
|
| 351 |
+
config = json.load(f)
|
| 352 |
+
print("✅ Configuration loaded successfully")
|
| 353 |
+
return config
|
| 354 |
+
except FileNotFoundError:
|
| 355 |
+
print("⚠️ Config file not found, using default configuration")
|
| 356 |
+
return _get_default_config()
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
def _get_default_config() -> dict:
|
| 360 |
+
"""Get default configuration when config.json is not available."""
|
| 361 |
+
return {
|
| 362 |
+
'directories': {
|
| 363 |
+
'temp_dir': './videos/temp_scenes',
|
| 364 |
+
'output_dir': './output'
|
| 365 |
+
},
|
| 366 |
+
'video_processing': {
|
| 367 |
+
'SHORT_VIDEO_THRESHOLD': 20,
|
| 368 |
+
'target_vmaf': 93.0,
|
| 369 |
+
'codec': 'auto',
|
| 370 |
+
'size_increase_protection': True,
|
| 371 |
+
'conservative_cq_adjustment': 2,
|
| 372 |
+
'max_output_size_ratio': 1.15,
|
| 373 |
+
'max_encoding_retries': 2,
|
| 374 |
+
'basic_cq_lookup_by_quality': {
|
| 375 |
+
'High': {
|
| 376 |
+
'animation': 22,
|
| 377 |
+
'low-action': 20,
|
| 378 |
+
'medium-action': 18,
|
| 379 |
+
'high-action': 16,
|
| 380 |
+
'default': 19
|
| 381 |
+
},
|
| 382 |
+
'Medium': {
|
| 383 |
+
'animation': 25,
|
| 384 |
+
'low-action': 23,
|
| 385 |
+
'medium-action': 21,
|
| 386 |
+
'high-action': 19,
|
| 387 |
+
'default': 22
|
| 388 |
+
},
|
| 389 |
+
'Low': {
|
| 390 |
+
'animation': 28,
|
| 391 |
+
'low-action': 26,
|
| 392 |
+
'medium-action': 24,
|
| 393 |
+
'high-action': 22,
|
| 394 |
+
'default': 25
|
| 395 |
+
}
|
| 396 |
+
},
|
| 397 |
+
},
|
| 398 |
+
'scene_detection': {
|
| 399 |
+
'enable_time_based_fallback': True,
|
| 400 |
+
'time_based_scene_duration': 90
|
| 401 |
+
},
|
| 402 |
+
'vmaf_calculation': {
|
| 403 |
+
'calculate_full_video_vmaf': True,
|
| 404 |
+
'vmaf_use_sampling': True,
|
| 405 |
+
'vmaf_num_clips': 3,
|
| 406 |
+
'vmaf_clip_duration': 2
|
| 407 |
+
},
|
| 408 |
+
'output_settings': {
|
| 409 |
+
'save_individual_scene_reports': True,
|
| 410 |
+
'save_comprehensive_report': True
|
| 411 |
+
},
|
| 412 |
+
'model_paths': {
|
| 413 |
+
'scene_classifier_model': 'services/compress/models/scene_classifier_model.pth'
|
| 414 |
+
}
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
|
| 418 |
+
def _display_pipeline_info(input_file: str, target_quality: str, max_duration: int, output_dir: str):
|
| 419 |
+
"""Display pipeline initialization information."""
|
| 420 |
+
print(f"\n🎬 === AI Video Compression Pipeline ===")
|
| 421 |
+
print(f" 📁 Input: {Path(input_file).name}")
|
| 422 |
+
print(f" 🎯 Target Quality: {target_quality}")
|
| 423 |
+
print(f" ⏱️ Max Duration: {max_duration}s")
|
| 424 |
+
print(f" 📁 Output Dir: {output_dir}")
|
| 425 |
+
print(f" 🕐 Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def _execute_preprocessing(input_file: str, target_quality: str, max_duration: int, output_dir_path: Path) -> Optional[dict]:
|
| 429 |
+
"""Execute Part 1: Pre-processing."""
|
| 430 |
+
print(f"\n🔧 === Part 1: Pre-processing ===")
|
| 431 |
+
part1_start_time = time.time()
|
| 432 |
+
|
| 433 |
+
part1_result = pre_processing(
|
| 434 |
+
video_path=input_file,
|
| 435 |
+
target_quality=target_quality,
|
| 436 |
+
max_duration=max_duration,
|
| 437 |
+
output_dir=output_dir_path,
|
| 438 |
+
codec='libsvtav1'
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
print("part1_result", part1_result)
|
| 442 |
+
return part1_result
|
| 443 |
+
|
| 444 |
+
|
| 445 |
+
def _display_preprocessing_results(part1_result: dict, part1_time: float):
|
| 446 |
+
"""Display Part 1 results."""
|
| 447 |
+
print(f"\n✅ Part 1 completed in {part1_time:.1f}s:")
|
| 448 |
+
print(f" 📁 Video: {os.path.basename(part1_result['path'])}")
|
| 449 |
+
print(f" 🎥 Codec: {part1_result['codec']} (original: {part1_result['original_codec']})")
|
| 450 |
+
print(f" ⏱️ Duration: {part1_result['duration']:.1f}s")
|
| 451 |
+
print(f" 🔄 Reencoded: {part1_result['was_reencoded']}")
|
| 452 |
+
print(f" 🎯 Target VMAF: {part1_result['target_vmaf']} ({part1_result['target_quality']})")
|
| 453 |
+
|
| 454 |
+
if part1_result['was_reencoded']:
|
| 455 |
+
print(f" 🔄 Lossless conversion: {part1_result['processing_info']['original_format']} → {part1_result['processing_info']['standardized_format']}")
|
| 456 |
+
print(f" ⏱️ Encoding time: {part1_result['encoding_time']:.1f}s")
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def _display_scene_detection_results(scenes_metadata: list, part2_time: float):
|
| 460 |
+
"""Display Part 2 results."""
|
| 461 |
+
print(f"\n✅ Part 2 completed in {part2_time:.1f}s: {len(scenes_metadata)} scenes detected")
|
| 462 |
+
|
| 463 |
+
# Display scene information
|
| 464 |
+
total_scene_size = 0
|
| 465 |
+
for scene in scenes_metadata:
|
| 466 |
+
scene_size = scene.get('file_size_mb', 0)
|
| 467 |
+
total_scene_size += scene_size
|
| 468 |
+
print(f" Scene {scene['scene_number']}: {scene['start_time']:.1f}s - {scene['end_time']:.1f}s "
|
| 469 |
+
f"(duration: {scene['duration']:.1f}s)")
|
| 470 |
+
if scene_size > 0:
|
| 471 |
+
print(f" 📁 File: {os.path.basename(scene['path'])} ({scene_size:.1f} MB)")
|
| 472 |
+
else:
|
| 473 |
+
print(f" 📁 File: {os.path.basename(scene['path'])}")
|
| 474 |
+
|
| 475 |
+
if total_scene_size > 0:
|
| 476 |
+
print(f" 📊 Total scene files: {total_scene_size:.1f} MB")
|
| 477 |
+
|
| 478 |
+
|
| 479 |
+
def _execute_ai_encoding(scenes_metadata: list, config: dict, target_quality: str) -> Optional[dict]:
|
| 480 |
+
"""Execute Part 3: AI Encoding."""
|
| 481 |
+
print(f"\n🧠 === Part 3: AI Encoding ===")
|
| 482 |
+
part3_start_time = time.time()
|
| 483 |
+
|
| 484 |
+
print(f" 🔧 Loading AI models and resources...")
|
| 485 |
+
print(f" 📋 Using quality-based CQ lookup tables for {target_quality} quality")
|
| 486 |
+
print(f" 🎯 Target Quality Level: {target_quality}")
|
| 487 |
+
|
| 488 |
+
# Display CQ ranges for selected quality level
|
| 489 |
+
quality_info = {
|
| 490 |
+
'High': {'vmaf': 95, 'cq_range': '16-22'},
|
| 491 |
+
'Medium': {'vmaf': 93, 'cq_range': '19-25'},
|
| 492 |
+
'Low': {'vmaf': 90, 'cq_range': '22-28'}
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
if target_quality in quality_info:
|
| 496 |
+
info = quality_info[target_quality]
|
| 497 |
+
print(f" 🎚️ CQ Range for {target_quality}: {info['cq_range']} (Target VMAF: {info['vmaf']})")
|
| 498 |
+
|
| 499 |
+
print(f" 🔧 Loading AI models and resources...")
|
| 500 |
+
|
| 501 |
+
try:
|
| 502 |
+
resources = load_encoding_resources(config, logging_enabled=True)
|
| 503 |
+
print(f" ✅ AI resources loaded successfully")
|
| 504 |
+
print(f" 🧠 Mode: Scene classification + CQ lookup table")
|
| 505 |
+
except Exception as e:
|
| 506 |
+
print(f" ❌ Failed to load GGG AI resources: {e}")
|
| 507 |
+
return None
|
| 508 |
+
|
| 509 |
+
# Process each scene individually
|
| 510 |
+
encoded_scenes_data = []
|
| 511 |
+
successful_encodings = 0
|
| 512 |
+
failed_encodings = 0
|
| 513 |
+
total_input_size = 0
|
| 514 |
+
total_output_size = 0
|
| 515 |
+
|
| 516 |
+
print(f"\n 📊 Processing {len(scenes_metadata)} scenes with AI approach...")
|
| 517 |
+
|
| 518 |
+
for i, scene_metadata in enumerate(scenes_metadata):
|
| 519 |
+
scene_result = _process_single_scene(
|
| 520 |
+
scene_metadata, i, len(scenes_metadata), config, resources, target_quality
|
| 521 |
+
)
|
| 522 |
+
|
| 523 |
+
if scene_result['success']:
|
| 524 |
+
successful_encodings += 1
|
| 525 |
+
total_input_size += scene_result['input_size_mb']
|
| 526 |
+
total_output_size += scene_result['output_size_mb']
|
| 527 |
+
else:
|
| 528 |
+
failed_encodings += 1
|
| 529 |
+
|
| 530 |
+
encoded_scenes_data.append(scene_result['scene_data'])
|
| 531 |
+
|
| 532 |
+
part3_time = time.time() - part3_start_time
|
| 533 |
+
|
| 534 |
+
# Display Part 3 summary
|
| 535 |
+
_display_ai_encoding_summary(
|
| 536 |
+
successful_encodings, failed_encodings, len(scenes_metadata),
|
| 537 |
+
part3_time, target_quality, total_input_size, total_output_size
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
if successful_encodings == 0:
|
| 541 |
+
print("❌ Part 3 failed completely. No scenes were encoded. Pipeline terminated.")
|
| 542 |
+
return None
|
| 543 |
+
|
| 544 |
+
return {
|
| 545 |
+
'encoded_scenes_data': encoded_scenes_data,
|
| 546 |
+
'successful_encodings': successful_encodings,
|
| 547 |
+
'failed_encodings': failed_encodings,
|
| 548 |
+
'processing_time': part3_time,
|
| 549 |
+
'total_input_size': total_input_size,
|
| 550 |
+
'total_output_size': total_output_size
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
|
| 554 |
+
def _process_single_scene(scene_metadata: dict, scene_index: int, total_scenes: int,
|
| 555 |
+
config: dict, resources: dict, target_quality: str) -> dict:
|
| 556 |
+
"""Process a single scene for AI encoding."""
|
| 557 |
+
scene_number = scene_metadata['scene_number']
|
| 558 |
+
scene_path = scene_metadata['path']
|
| 559 |
+
scene_duration = scene_metadata['duration']
|
| 560 |
+
|
| 561 |
+
print(f"\n 🎬 Scene {scene_number}/{total_scenes}: {os.path.basename(scene_path)}")
|
| 562 |
+
print(f" ⏱️ Duration: {scene_duration:.1f}s")
|
| 563 |
+
|
| 564 |
+
indicative_vmaf = scene_metadata['original_video_metadata'].get('target_vmaf')
|
| 565 |
+
print(f" 🎯 Target Quality: {target_quality}" + (f" (VMAF≈{indicative_vmaf})" if indicative_vmaf else ""))
|
| 566 |
+
print(f" 🧠 Method: scene classification + CQ lookup")
|
| 567 |
+
|
| 568 |
+
scene_start_time = time.time()
|
| 569 |
+
|
| 570 |
+
try:
|
| 571 |
+
encoded_path, scene_data = ai_encoding(
|
| 572 |
+
scene_metadata=scene_metadata,
|
| 573 |
+
config=config,
|
| 574 |
+
resources=resources,
|
| 575 |
+
target_vmaf=None,
|
| 576 |
+
target_quality_level=target_quality,
|
| 577 |
+
logging_enabled=True
|
| 578 |
+
)
|
| 579 |
+
|
| 580 |
+
scene_processing_time = time.time() - scene_start_time
|
| 581 |
+
|
| 582 |
+
if encoded_path and scene_data.get('encoding_success', False):
|
| 583 |
+
size_mb = scene_data.get('encoded_file_size_mb', 0)
|
| 584 |
+
input_size_mb = scene_data.get('input_size_mb', 0)
|
| 585 |
+
compression = scene_data.get('compression_ratio', 0)
|
| 586 |
+
|
| 587 |
+
print(f" ✅ Scene {scene_number} encoded successfully")
|
| 588 |
+
print(f" 📁 Output: {os.path.basename(encoded_path)}")
|
| 589 |
+
print(f" 📊 Size: {input_size_mb:.1f} MB → {size_mb:.1f} MB ({compression:+.1f}% compression)")
|
| 590 |
+
print(f" 🎭 Scene type: {scene_data.get('scene_type', 'unknown')}")
|
| 591 |
+
print(f" 🎯 Quality: {scene_data.get('target_quality_level', target_quality)}")
|
| 592 |
+
print(f" 🎚️ CQ used: {scene_data.get('base_cq_for_quality', 'N/A')} → {scene_data.get('final_adjusted_cq', 'unknown')} (after adjustment)")
|
| 593 |
+
print(f" 📋 Method: Quality-based lookup table CQ selection")
|
| 594 |
+
print(f" ⏱️ Processing: {scene_processing_time:.1f}s")
|
| 595 |
+
|
| 596 |
+
# Update scene metadata
|
| 597 |
+
scene_metadata['encoded_path'] = encoded_path
|
| 598 |
+
scene_metadata['encoding_data'] = scene_data
|
| 599 |
+
|
| 600 |
+
return {
|
| 601 |
+
'success': True,
|
| 602 |
+
'scene_data': scene_data,
|
| 603 |
+
'input_size_mb': input_size_mb,
|
| 604 |
+
'output_size_mb': size_mb
|
| 605 |
+
}
|
| 606 |
+
else:
|
| 607 |
+
error_reason = scene_data.get('error_reason', 'Unknown error')
|
| 608 |
+
print(f" ❌ Scene {scene_number} encoding failed: {error_reason}")
|
| 609 |
+
print(f" ⏱️ Processing: {scene_processing_time:.1f}s")
|
| 610 |
+
|
| 611 |
+
scene_metadata['encoded_path'] = None
|
| 612 |
+
scene_metadata['encoding_data'] = scene_data
|
| 613 |
+
|
| 614 |
+
return {
|
| 615 |
+
'success': False,
|
| 616 |
+
'scene_data': scene_data,
|
| 617 |
+
'input_size_mb': 0,
|
| 618 |
+
'output_size_mb': 0
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
except Exception as e:
|
| 622 |
+
scene_processing_time = time.time() - scene_start_time
|
| 623 |
+
print(f" ❌ Scene {scene_number} processing failed with exception: {e}")
|
| 624 |
+
print(f" ⏱️ Processing: {scene_processing_time:.1f}s")
|
| 625 |
+
|
| 626 |
+
error_scene_data = {
|
| 627 |
+
'scene_number': scene_number,
|
| 628 |
+
'encoding_success': False,
|
| 629 |
+
'error_reason': f'Exception: {str(e)}',
|
| 630 |
+
'processing_time_seconds': scene_processing_time,
|
| 631 |
+
'encoded_path': None,
|
| 632 |
+
'original_video_metadata': scene_metadata['original_video_metadata']
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
scene_metadata['encoded_path'] = None
|
| 636 |
+
scene_metadata['encoding_data'] = error_scene_data
|
| 637 |
+
|
| 638 |
+
return {
|
| 639 |
+
'success': False,
|
| 640 |
+
'scene_data': error_scene_data,
|
| 641 |
+
'input_size_mb': 0,
|
| 642 |
+
'output_size_mb': 0
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
def _display_ai_encoding_summary(successful_encodings: int, failed_encodings: int, total_scenes: int,
|
| 647 |
+
part3_time: float, target_quality: str, total_input_size: float, total_output_size: float):
|
| 648 |
+
"""Display Part 3 summary."""
|
| 649 |
+
print(f"\n 📊 Part 3 Processing Summary:")
|
| 650 |
+
print(f" ✅ Successful encodings: {successful_encodings}")
|
| 651 |
+
print(f" ❌ Failed encodings: {failed_encodings}")
|
| 652 |
+
print(f" 📈 Success rate: {successful_encodings/total_scenes*100:.1f}%")
|
| 653 |
+
print(f" ⏱️ Total processing time: {part3_time:.1f}s")
|
| 654 |
+
print(f" 🎯 Quality Level: {target_quality}")
|
| 655 |
+
print(f" 🧠 AI Method: Scene classification + quality-based CQ lookup")
|
| 656 |
+
|
| 657 |
+
if total_input_size > 0 and total_output_size > 0:
|
| 658 |
+
overall_compression = (1 - total_output_size / total_input_size) * 100
|
| 659 |
+
print(f" 🗜️ Overall compression: {overall_compression:+.1f}%")
|
| 660 |
+
print(f" 📊 Total size: {total_input_size:.1f} MB → {total_output_size:.1f} MB")
|
| 661 |
+
|
| 662 |
+
print(f"✅ Part 3 completed with {successful_encodings} successful encodings")
|
| 663 |
+
|
| 664 |
+
|
| 665 |
+
def _execute_validation_and_merging(part1_result: dict, encoded_scenes_data: list, config: dict) -> Optional[dict]:
|
| 666 |
+
"""Execute Part 4: Validation and Merging."""
|
| 667 |
+
part4_start_time = time.time()
|
| 668 |
+
|
| 669 |
+
try:
|
| 670 |
+
final_video_path, final_vmaf, comprehensive_report = validation_and_merging(
|
| 671 |
+
original_video_path=part1_result['path'],
|
| 672 |
+
encoded_scenes_data=encoded_scenes_data,
|
| 673 |
+
config=config,
|
| 674 |
+
logging_enabled=True
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
part4_time = time.time() - part4_start_time
|
| 678 |
+
|
| 679 |
+
if final_video_path and os.path.exists(final_video_path):
|
| 680 |
+
return {
|
| 681 |
+
'final_video_path': final_video_path,
|
| 682 |
+
'final_vmaf': final_vmaf,
|
| 683 |
+
'comprehensive_report': comprehensive_report,
|
| 684 |
+
'processing_time': part4_time
|
| 685 |
+
}
|
| 686 |
+
else:
|
| 687 |
+
print("❌ Part 4 failed. Could not create final video.")
|
| 688 |
+
return None
|
| 689 |
+
|
| 690 |
+
except Exception as e:
|
| 691 |
+
print(f"❌ Part 4 failed with exception: {e}")
|
| 692 |
+
return None
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
def _display_validation_results(part4_result: dict):
|
| 696 |
+
"""Display Part 4 results."""
|
| 697 |
+
final_video_path = part4_result['final_video_path']
|
| 698 |
+
final_vmaf = part4_result['final_vmaf']
|
| 699 |
+
comprehensive_report = part4_result['comprehensive_report']
|
| 700 |
+
part4_time = part4_result['processing_time']
|
| 701 |
+
|
| 702 |
+
print(f"✅ Part 4 completed successfully in {part4_time:.1f}s!")
|
| 703 |
+
print(f" 📁 Final video: {os.path.basename(final_video_path)}")
|
| 704 |
+
|
| 705 |
+
if final_vmaf:
|
| 706 |
+
print(f" 🎯 Final VMAF: {final_vmaf:.2f}")
|
| 707 |
+
|
| 708 |
+
if comprehensive_report:
|
| 709 |
+
compression_info = comprehensive_report.get('compression_metrics', {})
|
| 710 |
+
final_compression = compression_info.get('overall_compression_ratio_percent', 0)
|
| 711 |
+
final_size = compression_info.get('final_file_size_mb', 0)
|
| 712 |
+
|
| 713 |
+
print(f" 🗜️ Overall compression: {final_compression:+.1f}%")
|
| 714 |
+
print(f" 📊 Final file size: {final_size:.1f} MB")
|
| 715 |
+
|
| 716 |
+
|
| 717 |
+
def _display_pipeline_summary(input_file: str, final_video_path: str, part1_result: dict,
|
| 718 |
+
scenes_metadata: list, successful_encodings: int, output_dir: str,
|
| 719 |
+
pipeline_start_time: float, part1_time: float, part2_time: float,
|
| 720 |
+
part3_time: float, part4_time: float, total_pipeline_time: float,
|
| 721 |
+
final_vmaf: Optional[float], comprehensive_report: Optional[dict]):
|
| 722 |
+
"""Display complete pipeline summary."""
|
| 723 |
+
print(f"\n🎉 === Pipeline Completed Successfully ===")
|
| 724 |
+
print(f" 📁 Input video: {os.path.basename(input_file)}")
|
| 725 |
+
print(f" 📁 Final video: {os.path.basename(final_video_path)}")
|
| 726 |
+
print(f" 🎯 Target quality: {part1_result['target_quality']} (VMAF: {part1_result['target_vmaf']})")
|
| 727 |
+
print(f" 📊 Scenes processed: {len(scenes_metadata)} total, {successful_encodings} successful")
|
| 728 |
+
print(f" 📁 Output directory: {output_dir}")
|
| 729 |
+
print(f" 🕐 Completed: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
| 730 |
+
|
| 731 |
+
# Performance breakdown
|
| 732 |
+
print(f"\n ⏱️ Performance Breakdown:")
|
| 733 |
+
print(f" Part 1 (Pre-processing): {part1_time:.1f}s")
|
| 734 |
+
print(f" Part 2 (Scene Detection): {part2_time:.1f}s")
|
| 735 |
+
print(f" Part 3 (AI Encoding): {part3_time:.1f}s")
|
| 736 |
+
print(f" Part 4 (Validation & Merging): {part4_time:.1f}s")
|
| 737 |
+
print(f" Total Pipeline Time: {total_pipeline_time:.1f}s")
|
| 738 |
+
|
| 739 |
+
# Final file size comparison
|
| 740 |
+
input_file_path = Path(input_file)
|
| 741 |
+
final_video_path_obj = Path(final_video_path)
|
| 742 |
+
if input_file_path.exists() and final_video_path_obj.exists():
|
| 743 |
+
input_size = input_file_path.stat().st_size / (1024 * 1024)
|
| 744 |
+
output_size = final_video_path_obj.stat().st_size / (1024 * 1024)
|
| 745 |
+
final_compression = (1 - output_size / input_size) * 100
|
| 746 |
+
|
| 747 |
+
print(f"\n 📊 Final Size Comparison:")
|
| 748 |
+
print(f" Input: {input_size:.1f} MB")
|
| 749 |
+
print(f" Output: {output_size:.1f} MB")
|
| 750 |
+
print(f" Compression: {final_compression:+.1f}%")
|
| 751 |
+
|
| 752 |
+
if final_compression > 0:
|
| 753 |
+
print(f" 💾 Space saved: {input_size - output_size:.1f} MB")
|
| 754 |
+
|
| 755 |
+
# Quality achievement summary
|
| 756 |
+
if final_vmaf and comprehensive_report:
|
| 757 |
+
quality_info = comprehensive_report.get('quality_metrics', {})
|
| 758 |
+
scenes_meeting_target = quality_info.get('scenes_meeting_target', 0)
|
| 759 |
+
avg_scene_vmaf = quality_info.get('average_scene_vmaf', 0)
|
| 760 |
+
|
| 761 |
+
print(f"\n 🎯 Quality Achievement:")
|
| 762 |
+
print(f" Final VMAF: {final_vmaf:.2f}")
|
| 763 |
+
print(f" Average Scene VMAF: {avg_scene_vmaf:.2f}")
|
| 764 |
+
print(f" Scenes meeting target: {scenes_meeting_target}/{len(scenes_metadata)}")
|
| 765 |
+
|
| 766 |
+
if 'prediction_accuracy_stats' in comprehensive_report.get('scene_analysis', {}):
|
| 767 |
+
pred_stats = comprehensive_report['scene_analysis']['prediction_accuracy_stats']
|
| 768 |
+
avg_error = pred_stats.get('average_prediction_error')
|
| 769 |
+
if avg_error:
|
| 770 |
+
print(f" AI prediction accuracy: ±{avg_error:.1f} VMAF points")
|
| 771 |
+
|
| 772 |
+
# Report file locations
|
| 773 |
+
if comprehensive_report:
|
| 774 |
+
print(f"\n 📄 Reports Generated:")
|
| 775 |
+
print(f" 📁 Output directory: {output_dir}")
|
| 776 |
+
print(f" 📊 Comprehensive report: comprehensive_processing_report_*.json")
|
| 777 |
+
print(f" 📄 Individual scene reports: scene_reports/scene_*_report.json")
|
| 778 |
+
|
| 779 |
+
print(f"\n 🎉 Pipeline completed successfully!")
|
| 780 |
+
print(f" 🚀 Ready for playback: {final_video_path}")
|
| 781 |
+
|
| 782 |
+
|
| 783 |
+
# ============================================================================
|
| 784 |
+
# Main Execution
|
| 785 |
+
# ============================================================================
|
| 786 |
+
|
| 787 |
+
if __name__ == "__main__":
|
| 788 |
+
# import uvicorn
|
| 789 |
+
|
| 790 |
+
# logger.info("Starting video compressor server")
|
| 791 |
+
# logger.info(f"Video compressor server running on http://{CONFIG.video_compressor.host}:{CONFIG.video_compressor.port}")
|
| 792 |
+
|
| 793 |
+
# uvicorn.run(app, host=CONFIG.video_compressor.host, port=CONFIG.video_compressor.port)
|
| 794 |
+
|
| 795 |
+
result = test_video_compression('/home/85/test_video/0cb873d6-4c3a-4ced-84e9-87939b08565d.mp4')
|
| 796 |
+
print(result)
|
| 797 |
+
|
| 798 |
+
#python services/compress/server.py
|
services/compress/utils/__init__.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .video_utils import (
|
| 2 |
+
get_video_duration,
|
| 3 |
+
)
|
| 4 |
+
|
| 5 |
+
from .processing_utils import (
|
| 6 |
+
analyze_input_compression,
|
| 7 |
+
encode_scene_with_size_check,
|
| 8 |
+
should_skip_encoding
|
| 9 |
+
)
|
| 10 |
+
from .fast_scene_detect import adaptive_scene_detection_check, fast_scene_detection_check, motion_based_scene_check
|
| 11 |
+
|
| 12 |
+
# Import preprocessing classes to make them available for pickle loading
|
| 13 |
+
from .data_preprocessing import (
|
| 14 |
+
ColumnDropper, VMAFScaler, TargetExtractor, CQScaler,
|
| 15 |
+
ResolutionTransformer, FeatureScaler, FrameRateTransformer
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
__all__ = [
|
| 19 |
+
'monitor_memory_usage', 'ProgressTracker',
|
| 20 |
+
'calculate_perceptual_contrast', 'get_video_duration',
|
| 21 |
+
'calculate_contrast_adjusted_cq', 'sort_scene_files_by_number',
|
| 22 |
+
'process_scene_analysis_and_cq',
|
| 23 |
+
'calculate_vmaf_for_scenes_df',
|
| 24 |
+
'analyze_input_compression',
|
| 25 |
+
'calculate_bitrate_aware_cq',
|
| 26 |
+
'encode_scene_with_size_check',
|
| 27 |
+
'should_skip_encoding',
|
| 28 |
+
'create_summary_and_save', 'cleanup_temporary_items',
|
| 29 |
+
'adaptive_scene_detection_check', 'fast_scene_detection_check', 'motion_based_scene_check',
|
| 30 |
+
'VideoProcessingLogger',
|
| 31 |
+
# Add preprocessing classes to make them available
|
| 32 |
+
'ColumnDropper', 'VMAFScaler', 'TargetExtractor', 'CQScaler',
|
| 33 |
+
'ResolutionTransformer', 'FeatureScaler', 'FrameRateTransformer'
|
| 34 |
+
]
|
services/compress/utils/analyze_video_fast.py
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import numpy as np
|
| 4 |
+
import cv2
|
| 5 |
+
import subprocess
|
| 6 |
+
|
| 7 |
+
def analyze_video_fast(video_path, max_frames=150,logging_enabled=True,include_quality_metrics=False):
|
| 8 |
+
"""
|
| 9 |
+
Enhanced video analysis with ONLY required features for efficient processing.
|
| 10 |
+
|
| 11 |
+
Args:
|
| 12 |
+
video_path: Path to video file
|
| 13 |
+
max_frames: Maximum frames to analyze
|
| 14 |
+
logging_enabled: Whether to log progress
|
| 15 |
+
"""
|
| 16 |
+
if not os.path.exists(video_path):
|
| 17 |
+
if logging_enabled:
|
| 18 |
+
print(f"❌ Video file not found: {video_path}")
|
| 19 |
+
return None
|
| 20 |
+
|
| 21 |
+
features = {}
|
| 22 |
+
try:
|
| 23 |
+
# Step 1: Get basic video properties using ffprobe
|
| 24 |
+
basic_features = extract_basic_video_properties(video_path, logging_enabled)
|
| 25 |
+
if not basic_features:
|
| 26 |
+
return None
|
| 27 |
+
|
| 28 |
+
features.update(basic_features)
|
| 29 |
+
|
| 30 |
+
# Step 2: Extract comprehensive motion and complexity metrics using OpenCV
|
| 31 |
+
advanced_features = extract_comprehensive_video_metrics(video_path, max_frames, logging_enabled)
|
| 32 |
+
features.update(advanced_features)
|
| 33 |
+
|
| 34 |
+
# Step 3: Add quality metrics for preprocessing if requested
|
| 35 |
+
if include_quality_metrics:
|
| 36 |
+
if logging_enabled:
|
| 37 |
+
print(" 🔍 Analyzing video quality for preprocessing recommendations...")
|
| 38 |
+
quality_metrics = analyze_video_quality_metrics(video_path, num_frames=20)
|
| 39 |
+
if quality_metrics:
|
| 40 |
+
# Add quality metrics with prefix to avoid conflicts
|
| 41 |
+
for key, value in quality_metrics.items():
|
| 42 |
+
features[f'quality_{key}'] = value
|
| 43 |
+
|
| 44 |
+
if logging_enabled:
|
| 45 |
+
print(f"✅ Video analysis completed:")
|
| 46 |
+
|
| 47 |
+
return features
|
| 48 |
+
|
| 49 |
+
except Exception as e:
|
| 50 |
+
if logging_enabled:
|
| 51 |
+
print(f"❌ Video analysis failed: {e}")
|
| 52 |
+
return None
|
| 53 |
+
|
| 54 |
+
def extract_basic_video_properties(video_path, logging_enabled=True):
|
| 55 |
+
"""Extract basic video properties using ffprobe."""
|
| 56 |
+
try:
|
| 57 |
+
ffprobe_cmd = [
|
| 58 |
+
'ffprobe', '-v', 'quiet', '-print_format', 'json',
|
| 59 |
+
'-show_format', '-show_streams', video_path
|
| 60 |
+
]
|
| 61 |
+
|
| 62 |
+
result = subprocess.run(ffprobe_cmd, capture_output=True, text=True, timeout=30)
|
| 63 |
+
|
| 64 |
+
if result.returncode != 0:
|
| 65 |
+
if logging_enabled:
|
| 66 |
+
print(f"❌ ffprobe failed: {result.stderr}")
|
| 67 |
+
return None
|
| 68 |
+
|
| 69 |
+
probe_data = json.loads(result.stdout)
|
| 70 |
+
|
| 71 |
+
video_stream = None
|
| 72 |
+
for stream in probe_data.get('streams', []):
|
| 73 |
+
if stream.get('codec_type') == 'video':
|
| 74 |
+
video_stream = stream
|
| 75 |
+
break
|
| 76 |
+
|
| 77 |
+
if not video_stream:
|
| 78 |
+
if logging_enabled:
|
| 79 |
+
print("❌ No video stream found")
|
| 80 |
+
return None
|
| 81 |
+
|
| 82 |
+
features = {}
|
| 83 |
+
|
| 84 |
+
features['metrics_resolution_width'] = int(video_stream.get('width', 0))
|
| 85 |
+
features['metrics_resolution_height'] = int(video_stream.get('height', 0))
|
| 86 |
+
|
| 87 |
+
frame_rate_str = video_stream.get('r_frame_rate', '0/1')
|
| 88 |
+
if '/' in frame_rate_str:
|
| 89 |
+
num, den = frame_rate_str.split('/')
|
| 90 |
+
features['metrics_frame_rate'] = float(num) / float(den) if float(den) != 0 else 0
|
| 91 |
+
else:
|
| 92 |
+
features['metrics_frame_rate'] = float(frame_rate_str)
|
| 93 |
+
|
| 94 |
+
features['metrics_bit_depth'] = int(video_stream.get('bits_per_raw_sample', 8))
|
| 95 |
+
|
| 96 |
+
features['input_codec'] = video_stream.get('codec_name', 'unknown')
|
| 97 |
+
|
| 98 |
+
format_info = probe_data.get('format', {})
|
| 99 |
+
bitrate_bps = 0
|
| 100 |
+
|
| 101 |
+
if video_stream.get('bit_rate'):
|
| 102 |
+
bitrate_bps = int(video_stream['bit_rate'])
|
| 103 |
+
elif format_info.get('bit_rate'):
|
| 104 |
+
bitrate_bps = int(format_info['bit_rate'])
|
| 105 |
+
else:
|
| 106 |
+
file_size_bytes = int(format_info.get('size', 0))
|
| 107 |
+
duration_seconds = float(format_info.get('duration', 0))
|
| 108 |
+
if file_size_bytes > 0 and duration_seconds > 0:
|
| 109 |
+
bitrate_bps = int((file_size_bytes * 8) / duration_seconds)
|
| 110 |
+
|
| 111 |
+
features['input_bitrate_kbps'] = bitrate_bps / 1000 if bitrate_bps > 0 else 0
|
| 112 |
+
|
| 113 |
+
features['metrics_resolution'] = f"({features['metrics_resolution_width']}, {features['metrics_resolution_height']})"
|
| 114 |
+
|
| 115 |
+
if (features['metrics_resolution_width'] > 0 and
|
| 116 |
+
features['metrics_resolution_height'] > 0 and
|
| 117 |
+
features['metrics_frame_rate'] > 0 and
|
| 118 |
+
features['input_bitrate_kbps'] > 0):
|
| 119 |
+
|
| 120 |
+
pixels_per_second = (features['metrics_resolution_width'] *
|
| 121 |
+
features['metrics_resolution_height'] *
|
| 122 |
+
features['metrics_frame_rate'])
|
| 123 |
+
features['bits_per_pixel'] = (features['input_bitrate_kbps'] * 1000) / pixels_per_second
|
| 124 |
+
else:
|
| 125 |
+
features['bits_per_pixel'] = 0
|
| 126 |
+
|
| 127 |
+
return features
|
| 128 |
+
|
| 129 |
+
except Exception as e:
|
| 130 |
+
if logging_enabled:
|
| 131 |
+
print(f"❌ Basic video property extraction failed: {e}")
|
| 132 |
+
return None
|
| 133 |
+
|
| 134 |
+
def extract_comprehensive_video_metrics(video_path, max_frames=150, logging_enabled=True, use_middle_section=True):
|
| 135 |
+
"""
|
| 136 |
+
Extract ALL required video metrics using OpenCV analysis.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
use_middle_section (bool): If True, sample from middle 60% of video
|
| 140 |
+
"""
|
| 141 |
+
features = {
|
| 142 |
+
'metrics_avg_motion': 0,
|
| 143 |
+
'metrics_avg_edge_density': 0,
|
| 144 |
+
'metrics_avg_texture': 0,
|
| 145 |
+
'metrics_avg_color_complexity': 0,
|
| 146 |
+
'metrics_avg_motion_variance': 0,
|
| 147 |
+
'metrics_avg_grain_noise': 0
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
try:
|
| 151 |
+
cap = cv2.VideoCapture(video_path)
|
| 152 |
+
if not cap.isOpened():
|
| 153 |
+
if logging_enabled:
|
| 154 |
+
print(f"❌ Cannot open video: {video_path}")
|
| 155 |
+
return features
|
| 156 |
+
|
| 157 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 158 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 159 |
+
|
| 160 |
+
if use_middle_section and total_frames > max_frames * 2:
|
| 161 |
+
start_frame = int(total_frames * 0.2) # Skip first 20%
|
| 162 |
+
end_frame = int(total_frames * 0.8) # Skip last 20%
|
| 163 |
+
effective_total = end_frame - start_frame
|
| 164 |
+
else:
|
| 165 |
+
start_frame = min(int(fps * 1.0), total_frames // 10) # Skip first second or 10%
|
| 166 |
+
end_frame = total_frames
|
| 167 |
+
effective_total = end_frame - start_frame
|
| 168 |
+
|
| 169 |
+
if effective_total > max_frames:
|
| 170 |
+
frame_interval = effective_total // max_frames
|
| 171 |
+
else:
|
| 172 |
+
frame_interval = 1
|
| 173 |
+
|
| 174 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
| 175 |
+
|
| 176 |
+
edge_density_values = []
|
| 177 |
+
texture_values = []
|
| 178 |
+
color_complexity_values = []
|
| 179 |
+
spatial_info_values = []
|
| 180 |
+
temporal_info_values = []
|
| 181 |
+
grain_noise_values = []
|
| 182 |
+
motion_values = []
|
| 183 |
+
|
| 184 |
+
prev_frame = None
|
| 185 |
+
frame_count = 0
|
| 186 |
+
processed_frames = 0
|
| 187 |
+
current_frame_pos = start_frame
|
| 188 |
+
|
| 189 |
+
if logging_enabled:
|
| 190 |
+
print(f" 📊 Analyzing {min(max_frames, effective_total)} frames from middle section...")
|
| 191 |
+
print(f" 📍 Sampling range: frame {start_frame} to {end_frame} ({effective_total} frames)")
|
| 192 |
+
|
| 193 |
+
while processed_frames < max_frames:
|
| 194 |
+
ret, frame = cap.read()
|
| 195 |
+
if not ret:
|
| 196 |
+
break
|
| 197 |
+
|
| 198 |
+
if frame_count % frame_interval != 0:
|
| 199 |
+
frame_count += 1
|
| 200 |
+
current_frame_pos += 1
|
| 201 |
+
continue
|
| 202 |
+
|
| 203 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 204 |
+
|
| 205 |
+
edge_density = compute_edge_density(gray)
|
| 206 |
+
edge_density_values.append(edge_density)
|
| 207 |
+
|
| 208 |
+
texture_complexity = compute_texture_complexity(gray)
|
| 209 |
+
texture_values.append(texture_complexity)
|
| 210 |
+
|
| 211 |
+
color_complexity = compute_color_complexity(frame)
|
| 212 |
+
color_complexity_values.append(color_complexity)
|
| 213 |
+
|
| 214 |
+
spatial_info = compute_spatial_information(gray)
|
| 215 |
+
spatial_info_values.append(spatial_info)
|
| 216 |
+
|
| 217 |
+
grain_noise = compute_grain_noise_level(gray)
|
| 218 |
+
grain_noise_values.append(grain_noise)
|
| 219 |
+
|
| 220 |
+
if prev_frame is not None:
|
| 221 |
+
motion = compute_motion_metric(prev_frame, gray)
|
| 222 |
+
motion_values.append(motion)
|
| 223 |
+
temporal_info = compute_temporal_information(prev_frame, gray)
|
| 224 |
+
temporal_info_values.append(temporal_info)
|
| 225 |
+
|
| 226 |
+
prev_frame = gray.copy()
|
| 227 |
+
processed_frames += 1
|
| 228 |
+
frame_count += 1
|
| 229 |
+
current_frame_pos += 1
|
| 230 |
+
|
| 231 |
+
cap.release()
|
| 232 |
+
|
| 233 |
+
if edge_density_values:
|
| 234 |
+
features['metrics_avg_edge_density'] = np.mean(edge_density_values)
|
| 235 |
+
|
| 236 |
+
if texture_values:
|
| 237 |
+
features['metrics_avg_texture'] = np.mean(texture_values)
|
| 238 |
+
|
| 239 |
+
if color_complexity_values:
|
| 240 |
+
features['metrics_avg_color_complexity'] = np.mean(color_complexity_values)
|
| 241 |
+
|
| 242 |
+
if spatial_info_values:
|
| 243 |
+
features['metrics_avg_spatial_information'] = np.mean(spatial_info_values)
|
| 244 |
+
|
| 245 |
+
if grain_noise_values:
|
| 246 |
+
features['metrics_avg_grain_noise'] = np.mean(grain_noise_values)
|
| 247 |
+
|
| 248 |
+
if motion_values:
|
| 249 |
+
features['metrics_avg_motion'] = np.mean(motion_values)
|
| 250 |
+
features['metrics_avg_motion_variance'] = np.var(motion_values)
|
| 251 |
+
|
| 252 |
+
if temporal_info_values:
|
| 253 |
+
features['metrics_avg_temporal_information'] = np.mean(temporal_info_values)
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
if logging_enabled:
|
| 257 |
+
print(f" ✅ Processed {processed_frames} frames")
|
| 258 |
+
return features
|
| 259 |
+
|
| 260 |
+
except Exception as e:
|
| 261 |
+
if logging_enabled:
|
| 262 |
+
print(f"⚠️ Comprehensive video analysis failed: {e}")
|
| 263 |
+
|
| 264 |
+
return {
|
| 265 |
+
'metrics_avg_motion': 0.1,
|
| 266 |
+
'metrics_avg_edge_density': 0.05,
|
| 267 |
+
'metrics_avg_texture': 4.0,
|
| 268 |
+
'metrics_avg_color_complexity': 3.0,
|
| 269 |
+
'metrics_avg_motion_variance': 1.0,
|
| 270 |
+
'metrics_avg_grain_noise': 5.0
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
def compute_edge_density(gray_frame, threshold1=100, threshold2=200):
|
| 274 |
+
"""Compute edge density using Canny edge detection."""
|
| 275 |
+
edges = cv2.Canny(gray_frame, threshold1, threshold2)
|
| 276 |
+
edge_pixels = np.count_nonzero(edges)
|
| 277 |
+
total_pixels = edges.size
|
| 278 |
+
return edge_pixels / total_pixels
|
| 279 |
+
|
| 280 |
+
def compute_texture_complexity(gray_frame):
|
| 281 |
+
"""Compute texture complexity using histogram entropy."""
|
| 282 |
+
hist = cv2.calcHist([gray_frame], [0], None, [256], [0, 256]).ravel()
|
| 283 |
+
hist_norm = hist / hist.sum() # normalize histogram
|
| 284 |
+
entropy = -np.sum([p * np.log2(p) for p in hist_norm if p > 0])
|
| 285 |
+
return entropy
|
| 286 |
+
|
| 287 |
+
def compute_color_complexity(frame):
|
| 288 |
+
"""Compute color complexity as average entropy across color channels."""
|
| 289 |
+
channels = cv2.split(frame)
|
| 290 |
+
entropies = []
|
| 291 |
+
for channel in channels:
|
| 292 |
+
hist = cv2.calcHist([channel], [0], None, [256], [0, 256]).ravel()
|
| 293 |
+
hist_sum = np.sum(hist)
|
| 294 |
+
if hist_sum > 0:
|
| 295 |
+
hist_norm = hist / hist_sum
|
| 296 |
+
entropy = -np.sum([p * np.log2(p) for p in hist_norm if p > 0])
|
| 297 |
+
else:
|
| 298 |
+
entropy = 0
|
| 299 |
+
entropies.append(entropy)
|
| 300 |
+
return np.mean(entropies)
|
| 301 |
+
|
| 302 |
+
def compute_spatial_information(gray_frame):
|
| 303 |
+
"""Compute spatial information using Sobel gradients."""
|
| 304 |
+
sobelx = cv2.Sobel(gray_frame, cv2.CV_64F, 1, 0, ksize=3)
|
| 305 |
+
sobely = cv2.Sobel(gray_frame, cv2.CV_64F, 0, 1, ksize=3)
|
| 306 |
+
gradient_magnitude = np.sqrt(sobelx**2 + sobely**2)
|
| 307 |
+
return np.std(gradient_magnitude)
|
| 308 |
+
|
| 309 |
+
def compute_temporal_information(prev_gray, curr_gray):
|
| 310 |
+
"""Compute temporal information as standard deviation of frame difference."""
|
| 311 |
+
diff = cv2.absdiff(prev_gray, curr_gray)
|
| 312 |
+
return np.std(diff)
|
| 313 |
+
|
| 314 |
+
def compute_motion_metric(prev_gray, curr_gray):
|
| 315 |
+
"""Compute motion metric as normalized frame difference."""
|
| 316 |
+
diff = cv2.absdiff(prev_gray, curr_gray)
|
| 317 |
+
motion_value = np.mean(diff) / 255.0 # Normalize to 0-1
|
| 318 |
+
return motion_value
|
| 319 |
+
|
| 320 |
+
def compute_grain_noise_level(gray_frame, kernel_size=3):
|
| 321 |
+
"""Compute grain/noise level using high-pass filtering."""
|
| 322 |
+
blurred = cv2.GaussianBlur(gray_frame, (kernel_size, kernel_size), 0)
|
| 323 |
+
noise = gray_frame.astype(np.float32) - blurred.astype(np.float32)
|
| 324 |
+
return np.std(noise)
|
| 325 |
+
|
| 326 |
+
def analyze_video_quality_metrics(video_path, num_frames=30):
|
| 327 |
+
"""
|
| 328 |
+
Analyze video to determine which preprocessing filters would be beneficial.
|
| 329 |
+
Returns a dictionary of quality metrics and recommendations.
|
| 330 |
+
"""
|
| 331 |
+
cap = cv2.VideoCapture(video_path)
|
| 332 |
+
|
| 333 |
+
if not cap.isOpened():
|
| 334 |
+
return None
|
| 335 |
+
|
| 336 |
+
metrics = {
|
| 337 |
+
'noise_level': 0,
|
| 338 |
+
'sharpness': 0,
|
| 339 |
+
'contrast': 0,
|
| 340 |
+
'brightness': 0,
|
| 341 |
+
'color_saturation': 0,
|
| 342 |
+
'motion_blur': 0,
|
| 343 |
+
'compression_artifacts': 0,
|
| 344 |
+
'text_content': 0,
|
| 345 |
+
'edge_density': 0,
|
| 346 |
+
'temporal_consistency': 0
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
+
frames = []
|
| 350 |
+
frame_count = 0
|
| 351 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 352 |
+
step = max(1, total_frames // num_frames)
|
| 353 |
+
|
| 354 |
+
while frame_count < num_frames:
|
| 355 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_count * step)
|
| 356 |
+
ret, frame = cap.read()
|
| 357 |
+
if not ret:
|
| 358 |
+
break
|
| 359 |
+
frames.append(frame)
|
| 360 |
+
frame_count += 1
|
| 361 |
+
|
| 362 |
+
cap.release()
|
| 363 |
+
|
| 364 |
+
if not frames:
|
| 365 |
+
return None
|
| 366 |
+
|
| 367 |
+
# Analyze each metric
|
| 368 |
+
metrics.update({
|
| 369 |
+
'noise_level': _analyze_noise_level(frames),
|
| 370 |
+
'sharpness': _analyze_sharpness(frames),
|
| 371 |
+
'contrast': _analyze_contrast(frames),
|
| 372 |
+
'brightness': _analyze_brightness(frames),
|
| 373 |
+
'color_saturation': _analyze_color_saturation(frames),
|
| 374 |
+
'motion_blur': _analyze_motion_blur(frames),
|
| 375 |
+
'compression_artifacts': _analyze_compression_artifacts(frames),
|
| 376 |
+
'text_content': _analyze_text_content(frames),
|
| 377 |
+
'edge_density': _analyze_edge_density(frames),
|
| 378 |
+
'temporal_consistency': _analyze_temporal_consistency(frames)
|
| 379 |
+
})
|
| 380 |
+
|
| 381 |
+
return metrics
|
| 382 |
+
|
| 383 |
+
def _analyze_noise_level(frames):
|
| 384 |
+
"""Estimate noise level in video frames."""
|
| 385 |
+
noise_scores = []
|
| 386 |
+
|
| 387 |
+
for frame in frames:
|
| 388 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 389 |
+
|
| 390 |
+
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
|
| 391 |
+
noise_var = laplacian.var()
|
| 392 |
+
|
| 393 |
+
f_transform = np.fft.fft2(gray)
|
| 394 |
+
f_shift = np.fft.fftshift(f_transform)
|
| 395 |
+
magnitude = np.abs(f_shift)
|
| 396 |
+
|
| 397 |
+
h, w = magnitude.shape
|
| 398 |
+
center_h, center_w = h // 2, w // 2
|
| 399 |
+
high_freq_mask = np.zeros_like(magnitude)
|
| 400 |
+
high_freq_mask[center_h-h//8:center_h+h//8, center_w-w//8:center_w+w//8] = 1
|
| 401 |
+
high_freq_energy = np.sum(magnitude * (1 - high_freq_mask)) / np.sum(magnitude)
|
| 402 |
+
|
| 403 |
+
noise_scores.append(min(noise_var / 1000, high_freq_energy * 2))
|
| 404 |
+
|
| 405 |
+
return np.mean(noise_scores)
|
| 406 |
+
|
| 407 |
+
def _analyze_sharpness(frames):
|
| 408 |
+
"""Analyze image sharpness using multiple methods."""
|
| 409 |
+
sharpness_scores = []
|
| 410 |
+
|
| 411 |
+
for frame in frames:
|
| 412 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 413 |
+
|
| 414 |
+
laplacian_var = cv2.Laplacian(gray, cv2.CV_64F).var()
|
| 415 |
+
|
| 416 |
+
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
|
| 417 |
+
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
|
| 418 |
+
tenengrad = np.mean(sobelx**2 + sobely**2)
|
| 419 |
+
|
| 420 |
+
combined_sharpness = (laplacian_var / 2000 + tenengrad / 50000) / 2
|
| 421 |
+
sharpness_scores.append(min(combined_sharpness, 1.0))
|
| 422 |
+
|
| 423 |
+
return np.mean(sharpness_scores)
|
| 424 |
+
|
| 425 |
+
def _analyze_contrast(frames):
|
| 426 |
+
"""Analyze contrast using RMS contrast and histogram analysis."""
|
| 427 |
+
contrast_scores = []
|
| 428 |
+
|
| 429 |
+
for frame in frames:
|
| 430 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 431 |
+
|
| 432 |
+
rms_contrast = np.std(gray) / np.mean(gray) if np.mean(gray) > 0 else 0
|
| 433 |
+
|
| 434 |
+
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
|
| 435 |
+
hist_norm = hist / hist.sum()
|
| 436 |
+
|
| 437 |
+
pixels_in_range = []
|
| 438 |
+
for i in range(0, 256, 32):
|
| 439 |
+
pixels_in_range.append(np.sum(hist_norm[i:i+32]))
|
| 440 |
+
|
| 441 |
+
hist_spread = np.std(pixels_in_range)
|
| 442 |
+
|
| 443 |
+
combined_contrast = (rms_contrast / 100 + hist_spread * 3) / 2
|
| 444 |
+
contrast_scores.append(min(combined_contrast, 1.0))
|
| 445 |
+
|
| 446 |
+
return np.mean(contrast_scores)
|
| 447 |
+
|
| 448 |
+
def _analyze_brightness(frames):
|
| 449 |
+
"""Analyze brightness distribution."""
|
| 450 |
+
brightness_scores = []
|
| 451 |
+
|
| 452 |
+
for frame in frames:
|
| 453 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 454 |
+
mean_brightness = np.mean(gray) / 255.0
|
| 455 |
+
brightness_scores.append(mean_brightness)
|
| 456 |
+
|
| 457 |
+
return np.mean(brightness_scores)
|
| 458 |
+
|
| 459 |
+
def _analyze_color_saturation(frames):
|
| 460 |
+
"""Analyze color saturation levels."""
|
| 461 |
+
saturation_scores = []
|
| 462 |
+
|
| 463 |
+
for frame in frames:
|
| 464 |
+
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
|
| 465 |
+
saturation = hsv[:, :, 1]
|
| 466 |
+
mean_saturation = np.mean(saturation) / 255.0
|
| 467 |
+
saturation_scores.append(mean_saturation)
|
| 468 |
+
|
| 469 |
+
return np.mean(saturation_scores)
|
| 470 |
+
|
| 471 |
+
def _analyze_motion_blur(frames):
|
| 472 |
+
"""Detect motion blur using edge analysis."""
|
| 473 |
+
if len(frames) < 2:
|
| 474 |
+
return 0
|
| 475 |
+
|
| 476 |
+
blur_scores = []
|
| 477 |
+
|
| 478 |
+
for i in range(len(frames) - 1):
|
| 479 |
+
gray1 = cv2.cvtColor(frames[i], cv2.COLOR_BGR2GRAY)
|
| 480 |
+
gray2 = cv2.cvtColor(frames[i + 1], cv2.COLOR_BGR2GRAY)
|
| 481 |
+
|
| 482 |
+
edges1 = cv2.Canny(gray1, 50, 150)
|
| 483 |
+
edges2 = cv2.Canny(gray2, 50, 150)
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
edge_diff = np.sum(np.abs(edges1.astype(float) - edges2.astype(float)))
|
| 487 |
+
frame_size = gray1.shape[0] * gray1.shape[1]
|
| 488 |
+
blur_score = edge_diff / frame_size / 255.0
|
| 489 |
+
|
| 490 |
+
blur_scores.append(blur_score)
|
| 491 |
+
|
| 492 |
+
return np.mean(blur_scores)
|
| 493 |
+
|
| 494 |
+
def _analyze_compression_artifacts(frames):
|
| 495 |
+
"""Detect compression artifacts using blockiness detection."""
|
| 496 |
+
artifact_scores = []
|
| 497 |
+
|
| 498 |
+
for frame in frames:
|
| 499 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 500 |
+
|
| 501 |
+
block_diffs = []
|
| 502 |
+
h, w = gray.shape
|
| 503 |
+
|
| 504 |
+
for y in range(0, h - 8, 8):
|
| 505 |
+
for x in range(0, w - 8, 8):
|
| 506 |
+
block = gray[y:y+8, x:x+8]
|
| 507 |
+
|
| 508 |
+
if x + 8 < w:
|
| 509 |
+
right_block = gray[y:y+8, x+8:x+16] if x+16 <= w else None
|
| 510 |
+
if right_block is not None:
|
| 511 |
+
edge_diff = np.mean(np.abs(block[:, -1].astype(float) - right_block[:, 0].astype(float)))
|
| 512 |
+
block_diffs.append(edge_diff)
|
| 513 |
+
|
| 514 |
+
if block_diffs:
|
| 515 |
+
artifact_score = np.mean(block_diffs) / 255.0
|
| 516 |
+
artifact_scores.append(artifact_score)
|
| 517 |
+
|
| 518 |
+
return np.mean(artifact_scores) if artifact_scores else 0
|
| 519 |
+
|
| 520 |
+
def _analyze_text_content(frames):
|
| 521 |
+
"""Detect text/high-contrast content."""
|
| 522 |
+
text_scores = []
|
| 523 |
+
|
| 524 |
+
for frame in frames:
|
| 525 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 526 |
+
|
| 527 |
+
edges = cv2.Canny(gray, 100, 200)
|
| 528 |
+
edge_density = np.sum(edges > 0) / (edges.shape[0] * edges.shape[1])
|
| 529 |
+
|
| 530 |
+
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 531 |
+
contrast_regions = np.sum(binary > 0) / (binary.shape[0] * binary.shape[1])
|
| 532 |
+
|
| 533 |
+
text_score = (edge_density * 2 + abs(contrast_regions - 0.5) * 2)
|
| 534 |
+
text_scores.append(min(text_score, 1.0))
|
| 535 |
+
|
| 536 |
+
return np.mean(text_scores)
|
| 537 |
+
|
| 538 |
+
def _analyze_edge_density(frames):
|
| 539 |
+
"""Analyze edge density for detail preservation."""
|
| 540 |
+
edge_scores = []
|
| 541 |
+
|
| 542 |
+
for frame in frames:
|
| 543 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 544 |
+
edges = cv2.Canny(gray, 50, 150)
|
| 545 |
+
edge_density = np.sum(edges > 0) / (edges.shape[0] * edges.shape[1])
|
| 546 |
+
edge_scores.append(edge_density)
|
| 547 |
+
|
| 548 |
+
return np.mean(edge_scores)
|
| 549 |
+
|
| 550 |
+
def _analyze_temporal_consistency(frames):
|
| 551 |
+
"""Analyze temporal consistency between frames."""
|
| 552 |
+
if len(frames) < 3:
|
| 553 |
+
return 1.0
|
| 554 |
+
|
| 555 |
+
consistency_scores = []
|
| 556 |
+
|
| 557 |
+
for i in range(1, len(frames) - 1):
|
| 558 |
+
prev_gray = cv2.cvtColor(frames[i-1], cv2.COLOR_BGR2GRAY)
|
| 559 |
+
curr_gray = cv2.cvtColor(frames[i], cv2.COLOR_BGR2GRAY)
|
| 560 |
+
next_gray = cv2.cvtColor(frames[i+1], cv2.COLOR_BGR2GRAY)
|
| 561 |
+
|
| 562 |
+
diff1 = np.mean(np.abs(curr_gray.astype(float) - prev_gray.astype(float)))
|
| 563 |
+
diff2 = np.mean(np.abs(next_gray.astype(float) - curr_gray.astype(float)))
|
| 564 |
+
|
| 565 |
+
consistency = 1.0 - min(abs(diff1 - diff2) / 255.0, 1.0)
|
| 566 |
+
consistency_scores.append(consistency)
|
| 567 |
+
|
| 568 |
+
return np.mean(consistency_scores)
|
services/compress/utils/calculate_vmaf_adv.py
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
import json
|
| 4 |
+
import tempfile
|
| 5 |
+
import subprocess
|
| 6 |
+
import concurrent.futures
|
| 7 |
+
from ffmpeg_quality_metrics import FfmpegQualityMetrics
|
| 8 |
+
|
| 9 |
+
def _extract_clip_optimized(input_file, output_file, start_time, duration, logging_enabled=False):
|
| 10 |
+
"""Extract clip with keyframe-aware seeking for better quality."""
|
| 11 |
+
try:
|
| 12 |
+
input_ext = os.path.splitext(input_file)[1].lower()
|
| 13 |
+
raw_formats = ['.y4m', '.yuv', '.raw', '.rgb']
|
| 14 |
+
is_raw_format = input_ext in raw_formats
|
| 15 |
+
|
| 16 |
+
if is_raw_format:
|
| 17 |
+
cmd = [
|
| 18 |
+
'ffmpeg', '-y', '-ss', str(start_time), '-i', input_file,
|
| 19 |
+
'-t', str(duration),
|
| 20 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-qp', '0',
|
| 21 |
+
'-pix_fmt', 'yuv420p', '-an', output_file
|
| 22 |
+
]
|
| 23 |
+
else:
|
| 24 |
+
cmd = [
|
| 25 |
+
'ffmpeg', '-y',
|
| 26 |
+
'-ss', str(max(0, start_time - 1)),
|
| 27 |
+
'-i', input_file,
|
| 28 |
+
'-ss', '1',
|
| 29 |
+
'-t', str(duration),
|
| 30 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '10',
|
| 31 |
+
'-pix_fmt', 'yuv420p', '-an', output_file
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
| 35 |
+
success = result.returncode == 0 and os.path.exists(output_file) and os.path.getsize(output_file) > 0
|
| 36 |
+
|
| 37 |
+
if logging_enabled:
|
| 38 |
+
method = "lossless encoding" if is_raw_format else "keyframe-aligned encoding"
|
| 39 |
+
status = "✅" if success else "❌"
|
| 40 |
+
print(f" {status} Clip extraction ({method}): {start_time}s")
|
| 41 |
+
|
| 42 |
+
return success
|
| 43 |
+
|
| 44 |
+
except Exception as e:
|
| 45 |
+
if logging_enabled:
|
| 46 |
+
print(f" ❌ Clip extraction error: {e}")
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
def extract_vmaf_clips_with_keyframe_detection(input_file, encoded_file, num_clips=5, clip_duration=3, logging_enabled=True):
|
| 50 |
+
"""Enhanced clip extraction with proper keyframe alignment."""
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
cmd = [
|
| 54 |
+
'ffprobe', '-v', 'quiet', '-show_entries', 'format=duration',
|
| 55 |
+
'-of', 'json', input_file
|
| 56 |
+
]
|
| 57 |
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
| 58 |
+
data = json.loads(result.stdout)
|
| 59 |
+
duration = float(data['format']['duration'])
|
| 60 |
+
|
| 61 |
+
if duration < clip_duration * num_clips:
|
| 62 |
+
if logging_enabled:
|
| 63 |
+
print(f" ⚠️ Video too short ({duration:.1f}s) for {num_clips} clips")
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
except Exception as e:
|
| 67 |
+
if logging_enabled:
|
| 68 |
+
print(f" ❌ Could not determine video duration: {e}")
|
| 69 |
+
return None
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
cmd = [
|
| 73 |
+
'ffprobe', '-v', 'quiet', '-select_streams', 'v:0',
|
| 74 |
+
'-show_frames', '-show_entries', 'frame=pts_time,key_frame',
|
| 75 |
+
'-of', 'csv=p=0', input_file
|
| 76 |
+
]
|
| 77 |
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
| 78 |
+
|
| 79 |
+
keyframes = []
|
| 80 |
+
for line in result.stdout.strip().split('\n'):
|
| 81 |
+
if line:
|
| 82 |
+
parts = line.split(',')
|
| 83 |
+
if len(parts) >= 2 and parts[1] == '1': # key_frame=1
|
| 84 |
+
keyframes.append(float(parts[0]))
|
| 85 |
+
|
| 86 |
+
if logging_enabled:
|
| 87 |
+
print(f" 🔑 Found {len(keyframes)} keyframes")
|
| 88 |
+
|
| 89 |
+
except Exception as e:
|
| 90 |
+
if logging_enabled:
|
| 91 |
+
print(f" ⚠️ Keyframe detection failed: {e}, using standard timing")
|
| 92 |
+
keyframes = []
|
| 93 |
+
|
| 94 |
+
clip_positions = []
|
| 95 |
+
|
| 96 |
+
target_positions = []
|
| 97 |
+
for i in range(num_clips):
|
| 98 |
+
position = 0.15 + (i * (0.85 - 0.15) / (num_clips - 1)) if num_clips > 1 else 0.5
|
| 99 |
+
target_positions.append(duration * position)
|
| 100 |
+
|
| 101 |
+
for target_pos in target_positions:
|
| 102 |
+
if keyframes:
|
| 103 |
+
nearest_keyframe = min(keyframes, key=lambda x: abs(x - target_pos))
|
| 104 |
+
if nearest_keyframe + clip_duration <= duration:
|
| 105 |
+
clip_positions.append(nearest_keyframe)
|
| 106 |
+
else:
|
| 107 |
+
valid_keyframes = [kf for kf in keyframes if kf + clip_duration <= duration]
|
| 108 |
+
if valid_keyframes:
|
| 109 |
+
clip_positions.append(max(valid_keyframes))
|
| 110 |
+
else:
|
| 111 |
+
clip_positions.append(max(0, duration - clip_duration))
|
| 112 |
+
else:
|
| 113 |
+
clip_positions.append(max(0, min(target_pos, duration - clip_duration)))
|
| 114 |
+
|
| 115 |
+
if logging_enabled:
|
| 116 |
+
print(f" 📍 Clip positions: {[f'{pos:.2f}s' for pos in clip_positions]}")
|
| 117 |
+
|
| 118 |
+
vmaf_scores = []
|
| 119 |
+
|
| 120 |
+
for i, start_time in enumerate(clip_positions):
|
| 121 |
+
if logging_enabled:
|
| 122 |
+
print(f" 🎬 Extracting clip {i+1} at {start_time:.2f}s...")
|
| 123 |
+
|
| 124 |
+
temp_dir = tempfile.mkdtemp()
|
| 125 |
+
ref_clip = os.path.join(temp_dir, f"ref_clip_{i+1}.mp4")
|
| 126 |
+
enc_clip = os.path.join(temp_dir, f"enc_clip_{i+1}.mp4")
|
| 127 |
+
|
| 128 |
+
try:
|
| 129 |
+
ref_cmd = [
|
| 130 |
+
'ffmpeg', '-y', '-ss', str(start_time), '-i', input_file,
|
| 131 |
+
'-t', str(clip_duration), '-c:v', 'libx264', '-preset', 'ultrafast',
|
| 132 |
+
'-crf', '10', '-force_key_frames', 'expr:gte(t,0)',
|
| 133 |
+
'-pix_fmt', 'yuv420p', '-avoid_negative_ts', 'make_zero', ref_clip
|
| 134 |
+
]
|
| 135 |
+
|
| 136 |
+
enc_cmd = [
|
| 137 |
+
'ffmpeg', '-y', '-ss', str(start_time), '-i', encoded_file,
|
| 138 |
+
'-t', str(clip_duration), '-c:v', 'libx264', '-preset', 'ultrafast',
|
| 139 |
+
'-crf', '15', '-pix_fmt', 'yuv420p', '-avoid_negative_ts', 'make_zero', enc_clip
|
| 140 |
+
]
|
| 141 |
+
|
| 142 |
+
ref_result = subprocess.run(ref_cmd, capture_output=True, text=True, timeout=60)
|
| 143 |
+
enc_result = subprocess.run(enc_cmd, capture_output=True, text=True, timeout=60)
|
| 144 |
+
|
| 145 |
+
if ref_result.returncode == 0 and enc_result.returncode == 0:
|
| 146 |
+
from ffmpeg_quality_metrics import FfmpegQualityMetrics
|
| 147 |
+
|
| 148 |
+
ffqm = FfmpegQualityMetrics(ref_clip, enc_clip)
|
| 149 |
+
metrics = ffqm.calculate(["vmaf"])
|
| 150 |
+
|
| 151 |
+
if 'vmaf' in metrics and metrics['vmaf']:
|
| 152 |
+
clip_vmaf = sum([frame["vmaf"] for frame in metrics["vmaf"]]) / len(metrics["vmaf"])
|
| 153 |
+
vmaf_scores.append(round(clip_vmaf, 2))
|
| 154 |
+
|
| 155 |
+
if logging_enabled:
|
| 156 |
+
print(f" ✅ Clip {i+1} VMAF: {clip_vmaf:.2f}")
|
| 157 |
+
else:
|
| 158 |
+
if logging_enabled:
|
| 159 |
+
print(f" ❌ Clip {i+1} VMAF calculation failed")
|
| 160 |
+
else:
|
| 161 |
+
if logging_enabled:
|
| 162 |
+
print(f" ❌ Clip {i+1} extraction failed")
|
| 163 |
+
|
| 164 |
+
except Exception as e:
|
| 165 |
+
if logging_enabled:
|
| 166 |
+
print(f" ❌ Clip {i+1} processing failed: {e}")
|
| 167 |
+
finally:
|
| 168 |
+
# Cleanup
|
| 169 |
+
for temp_file in [ref_clip, enc_clip]:
|
| 170 |
+
if os.path.exists(temp_file):
|
| 171 |
+
try:
|
| 172 |
+
os.remove(temp_file)
|
| 173 |
+
except:
|
| 174 |
+
pass
|
| 175 |
+
try:
|
| 176 |
+
os.rmdir(temp_dir)
|
| 177 |
+
except:
|
| 178 |
+
pass
|
| 179 |
+
|
| 180 |
+
if vmaf_scores:
|
| 181 |
+
if len(vmaf_scores) >= 3:
|
| 182 |
+
sorted_scores = sorted(vmaf_scores)
|
| 183 |
+
median_score = sorted_scores[len(sorted_scores)//2]
|
| 184 |
+
min_score = sorted_scores[0]
|
| 185 |
+
max_score = sorted_scores[-1]
|
| 186 |
+
|
| 187 |
+
outliers = [s for s in vmaf_scores if abs(s - median_score) > 15]
|
| 188 |
+
|
| 189 |
+
if outliers and logging_enabled:
|
| 190 |
+
print(f" ⚠️ Detected outlier scores: {outliers}")
|
| 191 |
+
print(f" 📊 Score distribution: min={min_score:.1f}, median={median_score:.1f}, max={max_score:.1f}")
|
| 192 |
+
|
| 193 |
+
filtered_scores = [s for s in vmaf_scores if abs(s - median_score) <= 15]
|
| 194 |
+
if len(filtered_scores) >= 2:
|
| 195 |
+
final_vmaf = sum(filtered_scores) / len(filtered_scores)
|
| 196 |
+
print(f" 🔧 Using filtered scores (removed {len(outliers)} outliers): {final_vmaf:.2f}")
|
| 197 |
+
return final_vmaf
|
| 198 |
+
|
| 199 |
+
final_vmaf = sum(vmaf_scores) / len(vmaf_scores)
|
| 200 |
+
if logging_enabled:
|
| 201 |
+
print(f" 📊 Individual scores: {[f'{s:.2f}' for s in vmaf_scores]}")
|
| 202 |
+
print(f" 📊 Average VMAF: {final_vmaf:.2f}")
|
| 203 |
+
return final_vmaf
|
| 204 |
+
else:
|
| 205 |
+
return None
|
| 206 |
+
|
| 207 |
+
def calculate_vmaf_advanced(input_file, encoded_file,
|
| 208 |
+
use_sampling=True, num_clips=3, clip_duration=2,
|
| 209 |
+
use_downscaling=False, scale_factor=0.5,
|
| 210 |
+
use_parallel=False, # This parameter is for the caller of parallel, not used inside this single func
|
| 211 |
+
use_vmafneg=False,
|
| 212 |
+
default_vmaf_model_path_config: str = None,
|
| 213 |
+
vmafneg_model_path_config: str = None,
|
| 214 |
+
use_frame_rate_scaling=False,
|
| 215 |
+
target_fps=15.0,
|
| 216 |
+
frame_rate_scaling_method='uniform',
|
| 217 |
+
logger=None,logging_enabled=True):
|
| 218 |
+
"""
|
| 219 |
+
Calculate VMAF with multiple sampling methods
|
| 220 |
+
|
| 221 |
+
Args:
|
| 222 |
+
input_file: Path to the reference video file
|
| 223 |
+
encoded_file: Path to the encoded video file
|
| 224 |
+
use_sampling: Whether to use sampling for VMAF calculation
|
| 225 |
+
num_clips: Number of clips to sample for VMAF calculation
|
| 226 |
+
clip_duration: Duration of each clip in seconds
|
| 227 |
+
use_downscaling: Whether to downscale videos for faster VMAF calculation
|
| 228 |
+
scale_factor: Scale factor for downscaling (0.5 = 50% resolution)
|
| 229 |
+
use_vmafneg: Whether to use VMAFNEG model
|
| 230 |
+
default_vmaf_model_path_config: Full path to the default VMAF model.
|
| 231 |
+
vmafneg_model_path_config: Full path to the VMAFNEG model.
|
| 232 |
+
logger: Optional logger instance for logging messages
|
| 233 |
+
Returns:
|
| 234 |
+
VMAF score as a float, or None if calculation fails
|
| 235 |
+
|
| 236 |
+
"""
|
| 237 |
+
def log_message(message, level="info"):
|
| 238 |
+
if logger:
|
| 239 |
+
if level == "error": logger.error(message)
|
| 240 |
+
elif level == "warning": logger.warning(message)
|
| 241 |
+
elif level == "debug" and hasattr(logger, 'debug'): logger.debug(message)
|
| 242 |
+
else: logger.info(message)
|
| 243 |
+
else:
|
| 244 |
+
print(message) # Fallback if no logger
|
| 245 |
+
|
| 246 |
+
if not os.path.exists(input_file):
|
| 247 |
+
log_message(f"Error: Input file '{input_file}' does not exist", "error")
|
| 248 |
+
return None
|
| 249 |
+
|
| 250 |
+
if not os.path.exists(encoded_file):
|
| 251 |
+
log_message(f"Error: Encoded file '{encoded_file}' does not exist", "error")
|
| 252 |
+
return None
|
| 253 |
+
|
| 254 |
+
if use_frame_rate_scaling:
|
| 255 |
+
log_message(f"Using frame rate scaling: {target_fps} FPS with {frame_rate_scaling_method} method")
|
| 256 |
+
|
| 257 |
+
# Determine which VMAF model path to use directly from parameters
|
| 258 |
+
target_model_path = None
|
| 259 |
+
model_type_for_log = "libvmaf_default" # Default assumption
|
| 260 |
+
|
| 261 |
+
if use_vmafneg:
|
| 262 |
+
log_message(f"VMAFNEG flag is True. Using VMAFNEG model path: '{vmafneg_model_path_config}'", "debug")
|
| 263 |
+
target_model_path = vmafneg_model_path_config
|
| 264 |
+
model_type_for_log = "VMAFNEG"
|
| 265 |
+
else:
|
| 266 |
+
log_message(f"VMAFNEG flag is False. Using Default VMAF model path: '{default_vmaf_model_path_config}'", "debug")
|
| 267 |
+
target_model_path = default_vmaf_model_path_config
|
| 268 |
+
model_type_for_log = "Default VMAF"
|
| 269 |
+
|
| 270 |
+
vmaf_options_dict = None
|
| 271 |
+
|
| 272 |
+
if target_model_path and os.path.exists(target_model_path):
|
| 273 |
+
log_message(f"Using VMAF model ({model_type_for_log}): {target_model_path}")
|
| 274 |
+
vmaf_options_dict = {"model_path": target_model_path}
|
| 275 |
+
elif target_model_path: # Path provided but not found
|
| 276 |
+
log_message(f"Warning: Specified {model_type_for_log} model file not found at '{target_model_path}'. "
|
| 277 |
+
"ffmpeg-quality-metrics will use libvmaf's default model.", "warning")
|
| 278 |
+
model_type_for_log = "libvmaf_default (model file not found)" # Update log type
|
| 279 |
+
else: # No path provided for the selected option
|
| 280 |
+
log_message(f"Warning: No model path provided for {model_type_for_log} configuration. "
|
| 281 |
+
"ffmpeg-quality-metrics will use libvmaf's default model.", "warning")
|
| 282 |
+
model_type_for_log = "libvmaf_default (no model path provided)" # Update log type
|
| 283 |
+
|
| 284 |
+
log_message(f"Effective VMAF model for this calculation: {model_type_for_log}", "debug")
|
| 285 |
+
|
| 286 |
+
temp_dir = tempfile.mkdtemp()
|
| 287 |
+
temp_files = []
|
| 288 |
+
|
| 289 |
+
def extract_vmaf_score(metrics):
|
| 290 |
+
"""Extract VMAF score from metrics dictionary"""
|
| 291 |
+
try:
|
| 292 |
+
if 'vmaf' in metrics and metrics['vmaf']:
|
| 293 |
+
# Calculate average of frame VMAF scores
|
| 294 |
+
vmaf_score = sum([frame["vmaf"] for frame in metrics["vmaf"]]) / len(metrics["vmaf"])
|
| 295 |
+
return round(vmaf_score, 2)
|
| 296 |
+
else:
|
| 297 |
+
print("No VMAF data found in metrics")
|
| 298 |
+
return None
|
| 299 |
+
except Exception as e:
|
| 300 |
+
print(f"Error extracting VMAF score: {e}")
|
| 301 |
+
return None
|
| 302 |
+
|
| 303 |
+
try:
|
| 304 |
+
# ---------- Handle downscaling if enabled ----------
|
| 305 |
+
if use_downscaling:
|
| 306 |
+
if logging_enabled:
|
| 307 |
+
print(f"Downscaling videos to {int(scale_factor * 100)}% for faster VMAF calculation")
|
| 308 |
+
|
| 309 |
+
# ✅ Check if input is raw/uncompressed format
|
| 310 |
+
input_ext = os.path.splitext(input_file)[1].lower()
|
| 311 |
+
encoded_ext = os.path.splitext(encoded_file)[1].lower()
|
| 312 |
+
|
| 313 |
+
# Raw formats that cannot use stream copy
|
| 314 |
+
raw_formats = ['.y4m', '.yuv', '.raw', '.rgb']
|
| 315 |
+
|
| 316 |
+
is_input_raw = input_ext in raw_formats
|
| 317 |
+
is_encoded_raw = encoded_ext in raw_formats
|
| 318 |
+
|
| 319 |
+
scaled_ref = os.path.join(temp_dir, "ref_scaled.mp4")
|
| 320 |
+
scaled_enc = os.path.join(temp_dir, "enc_scaled.mp4")
|
| 321 |
+
temp_files.extend([scaled_ref, scaled_enc])
|
| 322 |
+
|
| 323 |
+
# Get video dimensions
|
| 324 |
+
cmd = [
|
| 325 |
+
'ffprobe', '-v', 'error', '-select_streams', 'v:0',
|
| 326 |
+
'-show_entries', 'stream=width,height', '-of', 'json', input_file
|
| 327 |
+
]
|
| 328 |
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
| 329 |
+
data = json.loads(result.stdout)
|
| 330 |
+
|
| 331 |
+
try:
|
| 332 |
+
width = int(int(data['streams'][0]['width']) * scale_factor)
|
| 333 |
+
height = int(int(data['streams'][0]['height']) * scale_factor)
|
| 334 |
+
|
| 335 |
+
# Must be even numbers for YUV formats
|
| 336 |
+
width = width - (width % 2)
|
| 337 |
+
height = height - (height % 2)
|
| 338 |
+
|
| 339 |
+
# ✅ Smart codec selection for reference video
|
| 340 |
+
if is_input_raw:
|
| 341 |
+
# Raw format - must encode (use lossless to preserve quality)
|
| 342 |
+
if logging_enabled:
|
| 343 |
+
print(f" 📹 Raw format detected ({input_ext}) - using lossless encoding")
|
| 344 |
+
ref_cmd = [
|
| 345 |
+
'ffmpeg', '-y', '-i', input_file,
|
| 346 |
+
'-vf', f'scale={width}:{height}',
|
| 347 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-qp', '0', # Lossless
|
| 348 |
+
'-pix_fmt', 'yuv420p', scaled_ref
|
| 349 |
+
]
|
| 350 |
+
else:
|
| 351 |
+
# Already encoded - try to use stream copy with scaling
|
| 352 |
+
if logging_enabled:
|
| 353 |
+
print(f" 📼 Encoded format detected ({input_ext}) - trying stream copy with scaling")
|
| 354 |
+
ref_cmd = [
|
| 355 |
+
'ffmpeg', '-y', '-i', input_file,
|
| 356 |
+
'-vf', f'scale={width}:{height}',
|
| 357 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '10', # Very high quality fallback
|
| 358 |
+
'-pix_fmt', 'yuv420p', scaled_ref
|
| 359 |
+
]
|
| 360 |
+
|
| 361 |
+
if is_encoded_raw:
|
| 362 |
+
# Raw format - must encode
|
| 363 |
+
enc_cmd = [
|
| 364 |
+
'ffmpeg', '-y', '-i', encoded_file,
|
| 365 |
+
'-vf', f'scale={width}:{height}',
|
| 366 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '15', # High quality
|
| 367 |
+
'-pix_fmt', 'yuv420p', scaled_enc
|
| 368 |
+
]
|
| 369 |
+
else:
|
| 370 |
+
# Already encoded - use moderate quality
|
| 371 |
+
enc_cmd = [
|
| 372 |
+
'ffmpeg', '-y', '-i', encoded_file,
|
| 373 |
+
'-vf', f'scale={width}:{height}',
|
| 374 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '18', # Standard quality
|
| 375 |
+
'-pix_fmt', 'yuv420p', scaled_enc
|
| 376 |
+
]
|
| 377 |
+
|
| 378 |
+
# Execute commands
|
| 379 |
+
ref_result = subprocess.run(ref_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 380 |
+
enc_result = subprocess.run(enc_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
| 381 |
+
|
| 382 |
+
if ref_result.returncode == 0 and enc_result.returncode == 0:
|
| 383 |
+
# Replace original paths with scaled versions
|
| 384 |
+
input_file = scaled_ref
|
| 385 |
+
encoded_file = scaled_enc
|
| 386 |
+
if logging_enabled:
|
| 387 |
+
quality_note = "lossless reference" if is_input_raw else "high quality reference"
|
| 388 |
+
print(f" ✅ Videos downscaled to {width}x{height} ({quality_note})")
|
| 389 |
+
else:
|
| 390 |
+
if logging_enabled:
|
| 391 |
+
print(f" ❌ Error downscaling videos, using original resolution")
|
| 392 |
+
if ref_result.returncode != 0:
|
| 393 |
+
print(f" Reference error: {ref_result.stderr.decode()}")
|
| 394 |
+
if enc_result.returncode != 0:
|
| 395 |
+
print(f" Encoded error: {enc_result.stderr.decode()}")
|
| 396 |
+
|
| 397 |
+
except (KeyError, ValueError, subprocess.SubprocessError) as e:
|
| 398 |
+
if logging_enabled:
|
| 399 |
+
print(f" ❌ Error downscaling videos, using original resolution: {e}")
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
if use_frame_rate_scaling:
|
| 403 |
+
if logging_enabled:
|
| 404 |
+
print(f"Scaling frame rate to {target_fps} FPS for faster VMAF calculation")
|
| 405 |
+
|
| 406 |
+
# Apply frame rate scaling to both input and encoded videos
|
| 407 |
+
fps_scaled_ref, fps_scaled_enc = apply_frame_rate_scaling(
|
| 408 |
+
input_file, encoded_file, target_fps, frame_rate_scaling_method, temp_dir, logging_enabled
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
if fps_scaled_ref and fps_scaled_enc:
|
| 412 |
+
temp_files.extend([fps_scaled_ref, fps_scaled_enc])
|
| 413 |
+
# Update file paths to use frame rate scaled versions
|
| 414 |
+
input_file = fps_scaled_ref
|
| 415 |
+
encoded_file = fps_scaled_enc
|
| 416 |
+
if logging_enabled:
|
| 417 |
+
print(f"Videos scaled to {target_fps} FPS for VMAF calculation")
|
| 418 |
+
else:
|
| 419 |
+
log_message(f"Frame rate scaling failed, using original frame rate", "warning")
|
| 420 |
+
|
| 421 |
+
# ---------- Handle clip sampling if enabled ----------
|
| 422 |
+
if use_sampling:
|
| 423 |
+
vmaf_score = extract_vmaf_clips_with_keyframe_detection(
|
| 424 |
+
input_file=input_file,
|
| 425 |
+
encoded_file=encoded_file,
|
| 426 |
+
num_clips=num_clips,
|
| 427 |
+
clip_duration=clip_duration,
|
| 428 |
+
logging_enabled=logging_enabled
|
| 429 |
+
)
|
| 430 |
+
|
| 431 |
+
if vmaf_score is not None:
|
| 432 |
+
return vmaf_score
|
| 433 |
+
else:
|
| 434 |
+
if logging_enabled:
|
| 435 |
+
print("Enhanced sampling failed, falling back to original method...")
|
| 436 |
+
|
| 437 |
+
# Get video duration
|
| 438 |
+
cmd = [
|
| 439 |
+
'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
|
| 440 |
+
'-of', 'json', encoded_file
|
| 441 |
+
]
|
| 442 |
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
| 443 |
+
data = json.loads(result.stdout)
|
| 444 |
+
|
| 445 |
+
try:
|
| 446 |
+
duration = float(data['format']['duration'])
|
| 447 |
+
print(f"Video duration: {duration}s")
|
| 448 |
+
except (KeyError, ValueError) as e:
|
| 449 |
+
print(f"Could not determine video duration: {e}, falling back to full calculation")
|
| 450 |
+
# Calculate VMAF on full videos
|
| 451 |
+
ffqm = FfmpegQualityMetrics(input_file, encoded_file)
|
| 452 |
+
metrics = ffqm.calculate(["vmaf"], vmaf_options=vmaf_options_dict) # Pass model options
|
| 453 |
+
return extract_vmaf_score(metrics)
|
| 454 |
+
|
| 455 |
+
# Skip sampling for short videos
|
| 456 |
+
if duration <= num_clips * clip_duration * 2:
|
| 457 |
+
log_message(f"Video too short ({duration}s), calculating full VMAF")
|
| 458 |
+
ffqm = FfmpegQualityMetrics(input_file, encoded_file)
|
| 459 |
+
metrics = ffqm.calculate(["vmaf"], vmaf_options=vmaf_options_dict) # Pass model options
|
| 460 |
+
return extract_vmaf_score(metrics)
|
| 461 |
+
|
| 462 |
+
# Calculate strategic sample points (beginning, middle, end)
|
| 463 |
+
sample_points = []
|
| 464 |
+
|
| 465 |
+
# Beginning segment (first 30%)
|
| 466 |
+
if num_clips >= 1:
|
| 467 |
+
begin_point = duration * 0.1 # 10% into the video
|
| 468 |
+
sample_points.append(begin_point)
|
| 469 |
+
|
| 470 |
+
# Middle segment (middle 40%)
|
| 471 |
+
if num_clips >= 2:
|
| 472 |
+
middle_point = duration * 0.5 # 50% into the video
|
| 473 |
+
sample_points.append(middle_point)
|
| 474 |
+
|
| 475 |
+
# End segment (last 30%)
|
| 476 |
+
if num_clips >= 3:
|
| 477 |
+
end_point = duration * 0.9 # 90% into the video
|
| 478 |
+
sample_points.append(end_point)
|
| 479 |
+
|
| 480 |
+
# Add more evenly distributed points if requested
|
| 481 |
+
if num_clips > 3:
|
| 482 |
+
for i in range(4, num_clips + 1):
|
| 483 |
+
pos = duration * (i - 0.5) / (num_clips + 1)
|
| 484 |
+
sample_points.append(pos)
|
| 485 |
+
|
| 486 |
+
print(f"Using {len(sample_points)} clips for VMAF calculation")
|
| 487 |
+
|
| 488 |
+
# ---------- Define clip processing function for parallel/sequential use ----------
|
| 489 |
+
def process_clip(start_time):
|
| 490 |
+
"""Process a single clip for VMAF calculation"""
|
| 491 |
+
# Adjust start time to ensure we don't go beyond video duration
|
| 492 |
+
start_time = max(0, min(start_time, duration - clip_duration))
|
| 493 |
+
|
| 494 |
+
# Create temp files for this clip
|
| 495 |
+
ref_clip = os.path.join(temp_dir, f"ref_clip_{start_time:.2f}.mp4")
|
| 496 |
+
enc_clip = os.path.join(temp_dir, f"enc_clip_{start_time:.2f}.mp4")
|
| 497 |
+
|
| 498 |
+
# ✅ Use optimized extraction
|
| 499 |
+
ref_success = _extract_clip_optimized(input_file, ref_clip, start_time, clip_duration, logging_enabled)
|
| 500 |
+
if not ref_success:
|
| 501 |
+
print(f"Failed to extract reference clip at {start_time}s")
|
| 502 |
+
return None
|
| 503 |
+
|
| 504 |
+
enc_success = _extract_clip_optimized(encoded_file, enc_clip, start_time, clip_duration, logging_enabled)
|
| 505 |
+
if not enc_success:
|
| 506 |
+
print(f"Failed to extract encoded clip at {start_time}s")
|
| 507 |
+
return None
|
| 508 |
+
|
| 509 |
+
temp_files.extend([ref_clip, enc_clip])
|
| 510 |
+
|
| 511 |
+
# Calculate VMAF for this clip
|
| 512 |
+
try:
|
| 513 |
+
ffqm = FfmpegQualityMetrics(ref_clip, enc_clip)
|
| 514 |
+
metrics = ffqm.calculate(["vmaf"], vmaf_options=vmaf_options_dict) # Pass model options
|
| 515 |
+
|
| 516 |
+
# Extract VMAF score using our helper function
|
| 517 |
+
clip_vmaf = extract_vmaf_score(metrics)
|
| 518 |
+
if clip_vmaf is not None:
|
| 519 |
+
print(f"Clip at {start_time:.2f}s VMAF: {clip_vmaf}")
|
| 520 |
+
return clip_vmaf
|
| 521 |
+
except Exception as e:
|
| 522 |
+
print(f"Error calculating VMAF for clip at {start_time:.2f}s: {e}")
|
| 523 |
+
return None
|
| 524 |
+
|
| 525 |
+
# ---------- Process clips in parallel or sequentially ----------
|
| 526 |
+
vmaf_scores = []
|
| 527 |
+
|
| 528 |
+
if use_parallel:
|
| 529 |
+
# Process clips in parallel
|
| 530 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=min(num_clips, os.cpu_count())) as executor:
|
| 531 |
+
futures = [executor.submit(process_clip, start_time) for start_time in sample_points]
|
| 532 |
+
|
| 533 |
+
for future in concurrent.futures.as_completed(futures):
|
| 534 |
+
try:
|
| 535 |
+
result = future.result()
|
| 536 |
+
if result is not None:
|
| 537 |
+
vmaf_scores.append(result)
|
| 538 |
+
except Exception as e:
|
| 539 |
+
print(f"Error processing clip: {e}")
|
| 540 |
+
else:
|
| 541 |
+
# Process clips sequentially
|
| 542 |
+
for start_time in sample_points:
|
| 543 |
+
result = process_clip(start_time)
|
| 544 |
+
if result is not None:
|
| 545 |
+
vmaf_scores.append(result)
|
| 546 |
+
|
| 547 |
+
# Average the scores
|
| 548 |
+
if vmaf_scores:
|
| 549 |
+
avg_vmaf = sum(vmaf_scores) / len(vmaf_scores)
|
| 550 |
+
return round(avg_vmaf, 2)
|
| 551 |
+
else:
|
| 552 |
+
print("No valid VMAF scores calculated from clips, trying direct calculation")
|
| 553 |
+
# Fall back to original calculation
|
| 554 |
+
try:
|
| 555 |
+
ffqm = FfmpegQualityMetrics(input_file, encoded_file)
|
| 556 |
+
metrics = ffqm.calculate(["vmaf"], vmaf_options=vmaf_options_dict) # Pass model options
|
| 557 |
+
return extract_vmaf_score(metrics)
|
| 558 |
+
except Exception as e:
|
| 559 |
+
print(f"Direct VMAF calculation failed: {e}")
|
| 560 |
+
return None
|
| 561 |
+
else:
|
| 562 |
+
# No sampling, calculate VMAF on entire video
|
| 563 |
+
try:
|
| 564 |
+
print("Calculating full VMAF (no sampling)...")
|
| 565 |
+
ffqm = FfmpegQualityMetrics(input_file, encoded_file)
|
| 566 |
+
metrics = ffqm.calculate(["vmaf"], vmaf_options=vmaf_options_dict) # Pass model options
|
| 567 |
+
return extract_vmaf_score(metrics)
|
| 568 |
+
except Exception as e:
|
| 569 |
+
print(f"Full VMAF calculation failed: {e}")
|
| 570 |
+
return None
|
| 571 |
+
|
| 572 |
+
except Exception as e:
|
| 573 |
+
log_message(f"Error in VMAF calculation: {e}", "error")
|
| 574 |
+
# Fall back to direct FFmpeg VMAF calculation
|
| 575 |
+
try:
|
| 576 |
+
log_message("Attempting direct FFmpeg VMAF calculation...")
|
| 577 |
+
libvmaf_options_list = ["log_fmt=json"]
|
| 578 |
+
if target_model_path and os.path.exists(target_model_path): # Check target_model_path directly
|
| 579 |
+
model_path_for_cli = target_model_path.replace("\\", "/")
|
| 580 |
+
libvmaf_options_list.append(f"model_path='{model_path_for_cli}'")
|
| 581 |
+
libvmaf_filter_options = ":".join(libvmaf_options_list)
|
| 582 |
+
cmd = [
|
| 583 |
+
'ffmpeg', '-i', input_file, '-i', encoded_file,
|
| 584 |
+
'-filter_complex', f'[0:v]setpts=PTS-STARTPTS[reference];[1:v]setpts=PTS-STARTPTS[distorted];[reference][distorted]libvmaf={libvmaf_filter_options}',
|
| 585 |
+
'-f', 'null', '-'
|
| 586 |
+
]
|
| 587 |
+
|
| 588 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 589 |
+
|
| 590 |
+
# Extract VMAF score from output
|
| 591 |
+
for line in result.stderr.splitlines():
|
| 592 |
+
if "VMAF score:" in line:
|
| 593 |
+
score = float(line.split("VMAF score:")[1].strip())
|
| 594 |
+
print(f"Direct FFmpeg VMAF score: {score}")
|
| 595 |
+
return round(score, 2)
|
| 596 |
+
|
| 597 |
+
print("Could not extract VMAF score from FFmpeg output")
|
| 598 |
+
return None
|
| 599 |
+
except Exception as e2:
|
| 600 |
+
print(f"Fatal error calculating VMAF: {e2}")
|
| 601 |
+
return None
|
| 602 |
+
|
| 603 |
+
finally:
|
| 604 |
+
# Clean up temp files and directory
|
| 605 |
+
for file in temp_files:
|
| 606 |
+
try:
|
| 607 |
+
if os.path.exists(file):
|
| 608 |
+
os.unlink(file)
|
| 609 |
+
except Exception as e:
|
| 610 |
+
print(f"Error cleaning up temp file {file}: {e}")
|
| 611 |
+
|
| 612 |
+
try:
|
| 613 |
+
os.rmdir(temp_dir)
|
| 614 |
+
except Exception as e:
|
| 615 |
+
print(f"Error removing temp directory: {e}")
|
| 616 |
+
|
| 617 |
+
def apply_frame_rate_scaling(input_file, encoded_file, target_fps, scaling_method, temp_dir, logging_enabled=True):
|
| 618 |
+
"""Apply frame rate scaling while preserving reference quality."""
|
| 619 |
+
try:
|
| 620 |
+
import subprocess
|
| 621 |
+
import json
|
| 622 |
+
|
| 623 |
+
# Get original video properties
|
| 624 |
+
def get_video_info(video_path):
|
| 625 |
+
cmd = [
|
| 626 |
+
'ffprobe', '-v', 'quiet', '-print_format', 'json',
|
| 627 |
+
'-show_streams', '-select_streams', 'v:0', video_path
|
| 628 |
+
]
|
| 629 |
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
| 630 |
+
if result.returncode == 0:
|
| 631 |
+
data = json.loads(result.stdout)
|
| 632 |
+
stream = data['streams'][0]
|
| 633 |
+
fps_str = stream.get('r_frame_rate', '30/1')
|
| 634 |
+
if '/' in fps_str:
|
| 635 |
+
num, den = fps_str.split('/')
|
| 636 |
+
fps = float(num) / float(den) if float(den) != 0 else 30
|
| 637 |
+
else:
|
| 638 |
+
fps = float(fps_str)
|
| 639 |
+
return fps
|
| 640 |
+
return None
|
| 641 |
+
|
| 642 |
+
# Get original frame rates
|
| 643 |
+
ref_fps = get_video_info(input_file)
|
| 644 |
+
enc_fps = get_video_info(encoded_file)
|
| 645 |
+
|
| 646 |
+
if not ref_fps or not enc_fps:
|
| 647 |
+
if logging_enabled:
|
| 648 |
+
print("Could not determine original frame rates")
|
| 649 |
+
return None, None
|
| 650 |
+
|
| 651 |
+
# Check if scaling is beneficial
|
| 652 |
+
min_original_fps = min(ref_fps, enc_fps)
|
| 653 |
+
if target_fps >= min_original_fps:
|
| 654 |
+
if logging_enabled:
|
| 655 |
+
print(f"Target FPS ({target_fps}) >= original FPS ({min_original_fps}), skipping frame rate scaling")
|
| 656 |
+
return input_file, encoded_file # Return original files
|
| 657 |
+
|
| 658 |
+
# Create output paths
|
| 659 |
+
scaled_ref = os.path.join(temp_dir, "ref_fps_scaled.mp4")
|
| 660 |
+
scaled_enc = os.path.join(temp_dir, "enc_fps_scaled.mp4")
|
| 661 |
+
|
| 662 |
+
# Build FFmpeg filter based on scaling method
|
| 663 |
+
if scaling_method == 'uniform':
|
| 664 |
+
filter_complex = f'fps={target_fps}'
|
| 665 |
+
elif scaling_method == 'smart':
|
| 666 |
+
interval = max(1, int(min_original_fps / target_fps))
|
| 667 |
+
filter_complex = f'select=not(mod(n\\,{interval})),setpts=N/FRAME_RATE/TB'
|
| 668 |
+
else:
|
| 669 |
+
filter_complex = f'fps={target_fps}'
|
| 670 |
+
|
| 671 |
+
# ✅ Smart codec selection based on input format
|
| 672 |
+
input_ext = os.path.splitext(input_file)[1].lower()
|
| 673 |
+
encoded_ext = os.path.splitext(encoded_file)[1].lower()
|
| 674 |
+
|
| 675 |
+
raw_formats = ['.y4m', '.yuv', '.raw', '.rgb']
|
| 676 |
+
is_input_raw = input_ext in raw_formats
|
| 677 |
+
is_encoded_raw = encoded_ext in raw_formats
|
| 678 |
+
|
| 679 |
+
# Scale reference video with appropriate quality
|
| 680 |
+
if is_input_raw:
|
| 681 |
+
# Raw format - use lossless encoding
|
| 682 |
+
ref_cmd = [
|
| 683 |
+
'ffmpeg', '-y', '-i', input_file,
|
| 684 |
+
'-vf', filter_complex,
|
| 685 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-qp', '0', # Lossless
|
| 686 |
+
'-pix_fmt', 'yuv420p', scaled_ref
|
| 687 |
+
]
|
| 688 |
+
else:
|
| 689 |
+
# Already encoded - use very high quality
|
| 690 |
+
ref_cmd = [
|
| 691 |
+
'ffmpeg', '-y', '-i', input_file,
|
| 692 |
+
'-vf', filter_complex,
|
| 693 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '10', # Very high quality
|
| 694 |
+
'-pix_fmt', 'yuv420p', scaled_ref
|
| 695 |
+
]
|
| 696 |
+
|
| 697 |
+
# Scale encoded video (can use standard quality)
|
| 698 |
+
enc_cmd = [
|
| 699 |
+
'ffmpeg', '-y', '-i', encoded_file,
|
| 700 |
+
'-vf', filter_complex,
|
| 701 |
+
'-c:v', 'libx264', '-preset', 'ultrafast', '-crf', '18',
|
| 702 |
+
'-pix_fmt', 'yuv420p', scaled_enc
|
| 703 |
+
]
|
| 704 |
+
|
| 705 |
+
# Execute commands
|
| 706 |
+
ref_result = subprocess.run(ref_cmd, capture_output=True, text=True)
|
| 707 |
+
if ref_result.returncode != 0:
|
| 708 |
+
if logging_enabled:
|
| 709 |
+
print(f"Reference frame rate scaling failed: {ref_result.stderr}")
|
| 710 |
+
return None, None
|
| 711 |
+
|
| 712 |
+
enc_result = subprocess.run(enc_cmd, capture_output=True, text=True)
|
| 713 |
+
if enc_result.returncode != 0:
|
| 714 |
+
if logging_enabled:
|
| 715 |
+
print(f"Encoded frame rate scaling failed: {enc_result.stderr}")
|
| 716 |
+
return None, None
|
| 717 |
+
|
| 718 |
+
# Verify scaled files exist and have content
|
| 719 |
+
if (os.path.exists(scaled_ref) and os.path.getsize(scaled_ref) > 0 and
|
| 720 |
+
os.path.exists(scaled_enc) and os.path.getsize(scaled_enc) > 0):
|
| 721 |
+
|
| 722 |
+
if logging_enabled:
|
| 723 |
+
quality_note = "lossless" if is_input_raw else "high quality"
|
| 724 |
+
print(f"Frame rate scaling successful: {min_original_fps:.1f} → {target_fps} FPS ({quality_note} reference)")
|
| 725 |
+
return scaled_ref, scaled_enc
|
| 726 |
+
else:
|
| 727 |
+
if logging_enabled:
|
| 728 |
+
print("Frame rate scaling produced empty files")
|
| 729 |
+
return None, None
|
| 730 |
+
|
| 731 |
+
except Exception as e:
|
| 732 |
+
if logging_enabled:
|
| 733 |
+
print(f"Frame rate scaling error: {e}")
|
| 734 |
+
return None, None
|