001package net.filebot.media; 002 003import static java.util.Comparator.*; 004import static net.filebot.Logging.*; 005import static net.filebot.MediaTypes.*; 006import static net.filebot.media.CachedMediaCharacteristics.*; 007import static net.filebot.media.MediaDetection.*; 008import static net.filebot.media.MediaFileUtilities.*; 009import static net.filebot.util.StringUtilities.*; 010 011import java.io.File; 012import java.util.Comparator; 013import java.util.regex.Pattern; 014 015public class VideoQuality implements Comparator<File> { 016 017 public static final Comparator<File> DESCENDING_ORDER = new VideoQuality(VideoFormat.DEFAULT_GROUPS, releaseInfo.getRepackPattern()).reversed(); 018 019 public static boolean isBetter(File f1, File f2) { 020 return DESCENDING_ORDER.compare(f1, f2) < 0; 021 } 022 023 private final VideoFormat format; 024 private final Pattern repack; 025 026 public VideoQuality(VideoFormat format, Pattern repack) { 027 this.format = format; 028 this.repack = repack; 029 } 030 031 private final Comparator<MediaCharacteristics> quality = comparingInt(this::getFormatScore) 032 .thenComparing(VideoCodec::compare) 033 .thenComparingInt(this::getRepackScore) 034 .thenComparingDouble(this::getBitrateScore) 035 .thenComparingLong(MediaCharacteristics::getFileSize); 036 037 private final Comparator<File> size = comparingLong(File::length); 038 039 @Override 040 public int compare(File f1, File f2) { 041 // use primary video file size for media characteristics (i.e. make companion files like subtitles follow the corresponding primary video file) 042 File vf1 = findPrimaryFile(f1, VIDEO_FILES).orElse(f1); 043 File vf2 = findPrimaryFile(f2, VIDEO_FILES).orElse(f2); 044 045 MediaCharacteristics mi1 = getMediaCharacteristics(vf1).orElse(null); 046 MediaCharacteristics mi2 = getMediaCharacteristics(vf2).orElse(null); 047 048 // compare media characteristics if possible 049 if (mi1 != null && mi2 != null) { 050 return quality.compare(mi1, mi2); 051 } 052 053 // compare file size if necessary 054 return size.compare(vf1, vf2); 055 } 056 057 private int getRepackScore(MediaCharacteristics mi) { 058 return find(mi.getFileName(), repack) ? 1 : 0; 059 } 060 061 private int getFormatScore(MediaCharacteristics mi) { 062 Integer w = mi.getWidth(); 063 Integer h = mi.getHeight(); 064 065 if (w != null && h != null) { 066 try { 067 return format.guessFormat(w, h); 068 } catch (Exception e) { 069 debug.warning(cause(e)); 070 } 071 } 072 073 // invalid media file 074 return 0; 075 } 076 077 private double getBitrateScore(MediaCharacteristics mi) { 078 // use primary video file when checking video resolution of subtitle files or disk folders 079 Integer w = mi.getWidth(); 080 Integer h = mi.getHeight(); 081 Double br = mi.getBitRate(); 082 083 if (w != null && h != null && br != null) { 084 return w * h * br; 085 } 086 087 // invalid media file 088 return 0; 089 } 090 091 public enum VideoCodec { 092 093 MPEG, AVC, HEVC, AV1; 094 095 public static VideoCodec get(MediaCharacteristics mi) { 096 String vc = mi.getVideoCodec(); 097 098 if (vc != null) { 099 if (vc.contains("AV1")) 100 return AV1; 101 if (vc.contains("HEVC")) 102 return HEVC; 103 if (vc.contains("AVC")) 104 return AVC; 105 if (vc.contains("MPEG-4")) 106 return MPEG; 107 } 108 109 // print missing codec name 110 throw new IllegalArgumentException("Unknown Video Codec: " + vc); 111 } 112 113 public static int compare(MediaCharacteristics mi1, MediaCharacteristics mi2) { 114 try { 115 VideoCodec vc1 = VideoCodec.get(mi1); 116 VideoCodec vc2 = VideoCodec.get(mi2); 117 return vc1.compareTo(vc2); 118 } catch (Exception e) { 119 debug.warning(cause(e)); 120 } 121 122 // invalid media file 123 return 0; 124 } 125 126 } 127 128}