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}