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.severe(e::getMessage); 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 // check for libmediainfo codec names and ffprobe codec names 097 if (vc != null) { 098 if (vc.contains("AV1") || vc.contains("av1")) 099 return AV1; 100 if (vc.contains("HEVC") || vc.contains("hevc")) 101 return HEVC; 102 if (vc.contains("AVC") || vc.contains("h264")) 103 return AVC; 104 if (vc.contains("MPEG") || vc.contains("mpeg")) // e.g. MPEG-4 or MPEG Video 105 return MPEG; 106 } 107 108 // print missing codec name 109 throw new IllegalArgumentException("Unknown Video Codec: " + vc); 110 } 111 112 public static int compare(MediaCharacteristics mi1, MediaCharacteristics mi2) { 113 try { 114 VideoCodec vc1 = VideoCodec.get(mi1); 115 VideoCodec vc2 = VideoCodec.get(mi2); 116 return vc1.compareTo(vc2); 117 } catch (Exception e) { 118 debug.severe(e::getMessage); 119 } 120 121 // invalid media file 122 return 0; 123 } 124 125 } 126 127}