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 @Override 038 public int compare(File f1, File f2) { 039 // use primary video file size for media characteristics (i.e. make companion files like subtitles follow the corresponding primary video file) 040 File vf1 = findPrimaryFile(f1, VIDEO_FILES).orElse(f1); 041 File vf2 = findPrimaryFile(f2, VIDEO_FILES).orElse(f2); 042 043 MediaCharacteristics mi1 = getMediaCharacteristics(vf1).orElse(null); 044 MediaCharacteristics mi2 = getMediaCharacteristics(vf2).orElse(null); 045 046 // compare media characteristics if possible 047 if (mi1 != null && mi2 != null) { 048 return quality.compare(mi1, mi2); 049 } 050 051 // compare file size if necessary 052 return FILE_SIZE_ORDER.compare(vf1, vf2); 053 } 054 055 private int getRepackScore(MediaCharacteristics mi) { 056 return find(mi.getFileName(), repack) ? 1 : 0; 057 } 058 059 private int getFormatScore(MediaCharacteristics mi) { 060 Integer w = mi.getWidth(); 061 Integer h = mi.getHeight(); 062 063 if (w != null && h != null) { 064 try { 065 return format.guessFormat(w, h); 066 } catch (Exception e) { 067 debug.warning(cause(e)); 068 } 069 } 070 071 // invalid media file 072 return 0; 073 } 074 075 private double getBitrateScore(MediaCharacteristics mi) { 076 // use primary video file when checking video resolution of subtitle files or disk folders 077 Integer w = mi.getWidth(); 078 Integer h = mi.getHeight(); 079 Double br = mi.getBitRate(); 080 081 if (w != null && h != null && br != null) { 082 return w * h * br; 083 } 084 085 // invalid media file 086 return 0; 087 } 088 089 public enum VideoCodec { 090 091 MPEG, AVC, HEVC, AV1; 092 093 public static VideoCodec get(MediaCharacteristics mi) { 094 String vc = mi.getVideoCodec(); 095 096 if (vc != null) { 097 if (vc.contains("AV1")) 098 return AV1; 099 if (vc.contains("HEVC")) 100 return HEVC; 101 if (vc.contains("AVC")) 102 return AVC; 103 if (vc.contains("MPEG-4")) 104 return MPEG; 105 } 106 107 // print missing codec name 108 throw new IllegalArgumentException("Unknown Video Codec: " + vc); 109 } 110 111 public static int compare(MediaCharacteristics mi1, MediaCharacteristics mi2) { 112 try { 113 VideoCodec vc1 = VideoCodec.get(mi1); 114 VideoCodec vc2 = VideoCodec.get(mi2); 115 return vc1.compareTo(vc2); 116 } catch (Exception e) { 117 debug.warning(cause(e)); 118 } 119 120 // invalid media file 121 return 0; 122 } 123 124 } 125 126}