Skip to content

Commit

Permalink
- implement film list statistics
Browse files Browse the repository at this point in the history
- implement film duplicate search
- implement lucene index key for duplicate search
- implement duplicate details dialog which will display all related films
- code cleanup
- simplify film tab cell renderer and blend different background colors for better readability
  • Loading branch information
derreisende77 committed Oct 22, 2024
1 parent 669bda0 commit ac16488
Show file tree
Hide file tree
Showing 26 changed files with 956 additions and 36 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
- **FEATURE:** Im Tab Beschreibung und im FilmInfo-Dialog können jeweils selektierte Texte der Beschreibung per Kontextmenü in die Zwischenablage kopiert werden.
- **FEATURE(Linux):** Für exotische Window Manager können nun die FlatLaf-Dekorationen mittels `-dfd` oder `--disable-flatlaf-decorations` Parametern deaktiviert werden.
- **FEATURE:** Info-Datei kann nun per Kontextmenü für jeden Eintrag manuell erzeugt werden.
- **FEATURE:** Die Filmliste wird nun beim Laden zusätzlich auf Duplikate untersucht. Hierbei werden die Mediatheken der ARD und ZDF erst am Ende berücksichtigt um das Angebot der "kleineren" Sender nicht zu benachteiligen.
- **FEATURE:** Duplikate werden im Tab `Filme` farblich hervorgehoben. Die Farbe kann in den Einstellungen modifiziert werden.
- **FEATURE:** Im Tab `Filme` können über das Kontextmenü `Zusammengehörige Filme anzeigen...` bei einem markierten Duplikat alle zusammenghörigen Filme angezeigt werden.
- **FEATURE:** Mittels `Ansicht/Filmstatistik anzeigen` können nun für die vorhandenen Sender Informationen bzgl. Anzahl der Filme und der Duplikate angezeigt werden. Es wird hier nur die gesamte Filmliste ohne jegliche Filter abzüglich Livestreams berücksichtigt, so dass es zu Abweichungen zur Anzeige in der Statuszeile kommen kann.
- **FEATURE:** Mit der Lucene-Suche können mittels des `duplicate`-Boolean Parameters Filmduplikate berücksichtigt werden.

# **14.1.0**
- JDK 21 wird nun mitgeliefert. Behebt primär Darstellungsfehler von Java Apps unter Windows.
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/mediathek/config/Daten.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package mediathek.config;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import com.google.common.util.concurrent.*;
import mediathek.Main;
import mediathek.SplashScreen;
Expand All @@ -10,6 +12,7 @@
import mediathek.daten.*;
import mediathek.daten.blacklist.ListeBlacklist;
import mediathek.filmlisten.FilmeLaden;
import mediathek.gui.duplicates.FilmStatistics;
import mediathek.javafx.bookmark.BookmarkDataList;
import mediathek.tool.ReplaceList;
import mediathek.tool.notification.INotificationCenter;
Expand Down Expand Up @@ -45,6 +48,8 @@ public class Daten {
public static ListePset listePset;
// flags
private static boolean reset; // Programm auf Starteinstellungen zurücksetzen
private final EventList<FilmStatistics> duplicateStatisticsEventList = new BasicEventList<>();
private final EventList<FilmStatistics> commonStatisticsEventList = new BasicEventList<>();
private final FilmeLaden filmeLaden; // erledigt das updaten der Filmliste
/**
* "source" list of all entries, contains everything
Expand Down Expand Up @@ -120,6 +125,10 @@ private static List<Path> getMediathekXmlCopyFilePath() {
return xmlFilePath;
}

public EventList<FilmStatistics> getDuplicateStatistics() { return duplicateStatisticsEventList; }

public EventList<FilmStatistics> getCommonStatistics() { return commonStatisticsEventList; }

public StarterClass getStarterClass() {
return starterClass;
}
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/mediathek/config/MVColor.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class MVColor {
// Tabelle Filme
public static final MVC FILM_HISTORY = new MVC(MVConfig.Configs.FARBE__FILM_HISTORY, new Color(225, 225, 225), "Filme, gesehen");
public static final MVC FILM_BOOKMARKED = new MVC(MVConfig.Configs.FARBE__FILM_BOOKMARKED, new Color(204, 238, 255), "Filme, gemerkt");
public static final MVC FILM_DUPLICATE = new MVC(MVConfig.Configs.FARBE__FILM_DUPLICATE, new Color(255, 209, 220), "Film, Duplikat");

// Tabelle Downloads
public static final MVC DOWNLOAD_IST_ABO = new MVC(MVConfig.Configs.FARBE__DOWNLOAD_IST_ABO, new Color(138, 67, 0), "Download ist ein Abo");
Expand Down Expand Up @@ -59,6 +60,7 @@ public class MVColor {
public MVColor() {
liste.add(FILM_HISTORY);
liste.add(FILM_BOOKMARKED);
liste.add(FILM_DUPLICATE);
liste.add(DOWNLOAD_IST_ABO);
liste.add(DOWNLOAD_IST_DIREKTER_DOWNLOAD);
liste.add(DOWNLOAD_WAIT);
Expand Down Expand Up @@ -130,17 +132,17 @@ public static Color getAlternatingRowColor() {
}

public final void load() {
liste.stream().filter(mvc -> !MVConfig.get(mvc.configs).isEmpty()).forEach(mvc -> {
liste.stream().filter(mvc -> !MVConfig.get(mvc.getConfig()).isEmpty()).forEach(mvc -> {
try {
mvc.color = new Color(Integer.parseInt(MVConfig.get(mvc.configs)));
mvc.color = new Color(Integer.parseInt(MVConfig.get(mvc.getConfig())));
} catch (Exception ignored) {
}
});
}

public final void save() {
for (MVC mvc : liste) {
MVConfig.add(mvc.configs, String.valueOf(mvc.color.getRGB()));
MVConfig.add(mvc.getConfig(), String.valueOf(mvc.color.getRGB()));
}
}

Expand Down
1 change: 1 addition & 0 deletions src/main/java/mediathek/config/MVConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public enum Configs {
FARBE__FILM_GEOBLOCK_BACKGROUND("FARBE_FILM_GEOBLOCK_BACKGROUND"),
FARBE__FILM_GEOBLOCK_BACKGROUND_SEL("FARBE_FILM_GEOBLOCK_BACKGROUND_SEL"),
FARBE__FILM_BOOKMARKED("FARBE_FILM_BOOKMARKED"),
FARBE__FILM_DUPLICATE("FARBE_FILM_DUPLICATE"),
FARBE__DOWNLOAD_IST_ABO("FARBE_DOWNLOAD_IST_ABO"),
FARBE__DOWNLOAD_IST_DIREKTER_DOWNLOAD("FARBE_DOWNLOAD_IST_DIREKTER_DOWNLOAD"),
FARBE__DOWNLOAD_ANSEHEN("FARBE_DOWNLOAD_ANSEHEN"),
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/mediathek/daten/DatenFilm.java
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,27 @@ public boolean isTrailerTeaser() {
return flags.contains(DatenFilmFlags.TRAILER_TEASER);
}

/**
* Returns whether this film entry was already seen before and is hence a duplicate entry.
* @return true if it was seen before, false otherwise.
*/
public boolean isDuplicate() {
return flags.contains(DatenFilmFlags.DUPLICATE);
}

/**
* Set if this film entry has a URL that was seen before.
* @param duplicate are we a duplicate?
*/
public void setDuplicate(boolean duplicate) {
if (duplicate) {
flags.add(DatenFilmFlags.DUPLICATE);
}
else {
flags.remove(DatenFilmFlags.DUPLICATE);
}
}

public void setTrailerTeaser(boolean val) {
if (val) {
flags.add(DatenFilmFlags.TRAILER_TEASER);
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/mediathek/daten/DatenFilmFlags.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ public enum DatenFilmFlags {
/**
* Indicates that film contains a .m3u8 URL.
*/
PLAYLIST
PLAYLIST,
/**
* Indicate that this film's URL is already existing hence this entry is a duplicate.
*/
DUPLICATE
}
4 changes: 3 additions & 1 deletion src/main/java/mediathek/filmlisten/FilmeLaden.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import mediathek.filmeSuchen.ListenerFilmeLaden;
import mediathek.filmeSuchen.ListenerFilmeLadenEvent;
import mediathek.filmlisten.reader.FilmListReader;
import mediathek.gui.duplicates.FilmDuplicateEvaluationTask;
import mediathek.gui.messages.FilmListReadStopEvent;
import mediathek.gui.tasks.BlacklistFilterWorker;
import mediathek.gui.tasks.FilmlistWriterWorker;
Expand Down Expand Up @@ -316,7 +317,8 @@ private void undEnde(ListenerFilmeLadenEvent event) {
throw new RuntimeException(e);
}
var workerTask = CompletableFuture.runAsync(new RefreshAboWorker(progLabel, progressBar))
.thenRun(new BlacklistFilterWorker(progLabel, progressBar));
.thenRun(new BlacklistFilterWorker(progLabel, progressBar))
.thenRun(new FilmDuplicateEvaluationTask());

if (writeFilmList) {
workerTask = workerTask.thenRun(new FilmlistWriterWorker(progLabel, progressBar));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ private void readData(JsonParser jp, ListeFilme listeFilme) throws IOException {
//just initialize the film object, rest will be done in one of the filters
datenFilm.init();

// this will add the film to the filmlist if it passes...
dateFilter.filter(datenFilm);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package mediathek.gui.actions;

import mediathek.gui.duplicates.statistics.DuplicateStatisticsDialog;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;

public class ShowDuplicateStatisticsAction extends AbstractAction {
private final Frame owner;

public ShowDuplicateStatisticsAction(@NotNull Frame owner) {
this.owner = owner;
putValue(Action.NAME, "Film-Statistik anzeigen");
}

@Override
public void actionPerformed(ActionEvent e) {
var dlg = new DuplicateStatisticsDialog(owner, this);
dlg.setVisible(true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ private TableModel getModel() {
tModel.setRowCount(0);
for (MVC mvc : Daten.mVColor.liste) {
object = new Object[MVC_MAX];
object[MVC_TEXT] = mvc.text;
object[MVC_TEXT] = mvc.getText();
object[MVC_COLOR] = mvc;
tModel.addRow(object);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package mediathek.gui.duplicates;

import ca.odell.glazedlists.TransactionList;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Sets;
import mediathek.config.Daten;
import mediathek.daten.DatenFilm;
import mediathek.daten.ListeFilme;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class FilmDuplicateEvaluationTask implements Runnable {
private static final Logger logger = LogManager.getLogger();
private final ListeFilme listeFilme;

public FilmDuplicateEvaluationTask() {
this.listeFilme = Daten.getInstance().getListeFilme();
}

private void printDuplicateStatistics() {
Stopwatch watch = Stopwatch.createStarted();
final var duplicates = listeFilme.parallelStream()
.filter(DatenFilm::isDuplicate)
.toList();

var statisticsEventList = Daten.getInstance().getDuplicateStatistics();

Map<String, Long> statisticsMap = duplicates.parallelStream().collect(Collectors.groupingBy(DatenFilm::getSender, Collectors.counting()));
TransactionList<FilmStatistics> tList = new TransactionList<>(statisticsEventList);
tList.getReadWriteLock().writeLock().lock();
tList.clear();
for (var sender : statisticsMap.keySet()) {
tList.add(new FilmStatistics(sender, statisticsMap.get(sender)));
}
tList.getReadWriteLock().writeLock().unlock();
watch.stop();

logger.trace("Duplicate stream filter took: {}", watch);
logger.trace("Number of duplicates: {}", duplicates.size());
}

private void checkDuplicates() {
logger.trace("Start Duplicate URL search");
final Set<String> urlCache = Sets.newConcurrentHashSet();

Stopwatch watch = Stopwatch.createStarted();
listeFilme.stream()
.filter(f -> !f.isLivestream())
.sorted(new BigSenderPenaltyComparator())
.forEach(film -> {
final var url = film.getUrlNormalQuality();
film.setDuplicate(urlCache.contains(url));
urlCache.add(url);
});
watch.stop();
logger.trace("Duplicate URL search took: {}", watch);
urlCache.clear();
}

private void calculateCommonStats() {
Stopwatch watch = Stopwatch.createStarted();
final var films = listeFilme.parallelStream()
.filter(f -> !f.isLivestream())
.toList();

var statisticsList = Daten.getInstance().getCommonStatistics();

Map<String, Long> statisticsMap = films.parallelStream()
.collect(Collectors.groupingBy(DatenFilm::getSender, Collectors.counting()));
TransactionList<FilmStatistics> tList = new TransactionList<>(statisticsList);
tList.getReadWriteLock().writeLock().lock();
tList.clear();
for (var sender : statisticsMap.keySet()) {
tList.add(new FilmStatistics(sender, statisticsMap.get(sender)));
}
tList.getReadWriteLock().writeLock().unlock();
watch.stop();

logger.trace("common stats calculation took: {}", watch);
logger.trace("Number of films: {}", films.size());
}

@Override
public void run() {
calculateCommonStats();
checkDuplicates();
printDuplicateStatistics();
}

private static class BigSenderPenaltyComparator implements Comparator<DatenFilm> {
@Override
public int compare(DatenFilm s1, DatenFilm s2) {
// "ARD" und "ZDF" immer am Ende um die kleineren Mediatheken nicht zu benachteiligen
final var s1_sender = s1.getSender();
final var s2_sender = s2.getSender();
if (s1_sender.equals("ARD") || s1_sender.equals("ZDF")) {
return 1;
}
if (s2_sender.equals("ARD") || s2_sender.equals("ZDF")) {
return -1;
}
// Alphabetisch sortieren für alle anderen
return s1.compareTo(s2);
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/mediathek/gui/duplicates/FilmStatistics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package mediathek.gui.duplicates;

public record FilmStatistics(String sender, long count) {
}
Loading

0 comments on commit ac16488

Please sign in to comment.