Skip to content

Commit dfb97bd

Browse files
committed
srf website url
verbessertes Zuschneiden der Beschreibungstexte ard: search compilations
2 parents 056a285 + 32120f0 commit dfb97bd

14 files changed

Lines changed: 430 additions & 43 deletions

‎build.gradle‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ sourceCompatibility = JavaVersion.VERSION_17
2727
targetCompatibility = JavaVersion.VERSION_17
2828
group = 'de.mediathekview'
2929
archivesBaseName = "MServer"
30-
version = '3.1.284'
30+
version = '3.1.285'
3131

3232
def jarName = 'MServer.jar'
3333
def mainClass = 'mServer.Main'

‎src/main/java/de/mediathekview/mlib/Const.java‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class Const {
3939
public static final int ALTER_FILMLISTE_SEKUNDEN_FUER_AUTOUPDATE = 3 * 60 * 60; // beim Start des Programms wir die Liste geladen wenn sie älter ist als ..
4040
public static final String TIME_MAX_AGE_FOR_DIFF = "09"; // Uhrzeit ab der die Diffliste alle Änderungen abdeckt, die Filmliste darf also nicht vor xx erstellt worden sein
4141
public static final int MAX_BESCHREIBUNG = 400; // max länge der Beschreibung in Zeichen -> mehr gibts aber jetzt nicht mehr!
42+
public static final int MAX_BESCHREIBUNG_MARGIN_LENGTH = 40;
4243

4344
public static final String DREISAT = "3Sat";
4445
public static final String ARD = "ARD";

‎src/main/java/de/mediathekview/mlib/daten/DatenFilm.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public static String cleanDescription(String description) {
201201
description = description.replace("\\\"", "\"");
202202
}
203203
if (description.length() > Const.MAX_BESCHREIBUNG) {
204-
return description.substring(0, Const.MAX_BESCHREIBUNG) + "\n.....";
204+
return SentenceShortener.shorten(description, Const.MAX_BESCHREIBUNG, Const.MAX_BESCHREIBUNG_MARGIN_LENGTH);
205205
} else {
206206
return description;
207207
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package de.mediathekview.mlib.tool;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.regex.Pattern;
6+
7+
public final class SentenceShortener {
8+
private static final Pattern WHITESPACE_REGEX = Pattern.compile("\\s+");
9+
private static final Pattern SENTENCE_BOUNDARY_REGEX = Pattern.compile("(?<=[.!?])\\s+");
10+
private static final String SHORTENING_SUFFIX = "...";
11+
12+
private SentenceShortener() {
13+
}
14+
15+
/**
16+
* Shortens text to a target length while preserving complete sentences.
17+
*
18+
* <p>Whitespace is normalized before shortening. If the text is already within the target
19+
* length, the normalized text is returned unchanged. If adding the next sentence would exceed
20+
* {@code targetLength + marginLength}, that sentence is omitted and {@code ...} is appended to
21+
* the returned text. If the first sentence alone is already longer than the limit, it is kept
22+
* whole.
23+
*
24+
* @param text the text to shorten; may be {@code null}
25+
* @param targetLength the preferred target length; must be positive
26+
* @param marginLength the allowed soft overflow beyond the target; must not be negative
27+
* @return the shortened text, or {@code null} if the input is {@code null} or blank
28+
* @throws IllegalArgumentException if {@code targetLength <= 0} or {@code marginLength < 0}
29+
*/
30+
public static String shorten(String text, int targetLength, int marginLength) {
31+
if (targetLength <= 0) {
32+
throw new IllegalArgumentException("targetLength must be positive");
33+
}
34+
if (marginLength < 0) {
35+
throw new IllegalArgumentException("marginLength must not be negative");
36+
}
37+
38+
String normalized = normalize(text);
39+
if (normalized == null || normalized.length() <= targetLength) {
40+
return normalized;
41+
}
42+
43+
int softLimit = targetLength + marginLength;
44+
String[] rawSentences = SENTENCE_BOUNDARY_REGEX.split(normalized);
45+
List<String> sentences = new ArrayList<>();
46+
for (String rawSentence : rawSentences) {
47+
String sentence = rawSentence.trim();
48+
if (!sentence.isEmpty()) {
49+
sentences.add(sentence);
50+
}
51+
}
52+
if (sentences.isEmpty()) {
53+
return normalized;
54+
}
55+
56+
List<String> selectedSentences = new ArrayList<>();
57+
int currentLength = 0;
58+
for (String sentence : sentences) {
59+
int candidateLength = selectedSentences.isEmpty()
60+
? sentence.length()
61+
: currentLength + 1 + sentence.length();
62+
if (candidateLength > softLimit) {
63+
break;
64+
}
65+
selectedSentences.add(sentence);
66+
currentLength = candidateLength;
67+
}
68+
69+
if (!selectedSentences.isEmpty()) {
70+
String shortened = String.join(" ", selectedSentences);
71+
return selectedSentences.size() < sentences.size()
72+
? shortened + SHORTENING_SUFFIX
73+
: shortened;
74+
}
75+
return sentences.size() > 1
76+
? sentences.get(0) + SHORTENING_SUFFIX
77+
: sentences.get(0);
78+
}
79+
80+
/**
81+
* Shortens text to a target length without any soft overflow margin.
82+
*
83+
* @param text the text to shorten; may be {@code null}
84+
* @param targetLength the preferred target length; must be positive
85+
* @return the shortened text, or {@code null} if the input is {@code null} or blank
86+
* @throws IllegalArgumentException if {@code targetLength <= 0}
87+
*/
88+
public static String shorten(String text, int targetLength) {
89+
return shorten(text, targetLength, 0);
90+
}
91+
92+
/**
93+
* Normalizes whitespace and trims a text value.
94+
*
95+
* @param text the text to normalize; may be {@code null}
96+
* @return the normalized text, or {@code null} if the input is {@code null} or blank
97+
*/
98+
public static String normalize(String text) {
99+
if (text == null) {
100+
return null;
101+
}
102+
String normalized = WHITESPACE_REGEX.matcher(text).replaceAll(" ").trim();
103+
return normalized.isEmpty() ? null : normalized;
104+
}
105+
}

‎src/main/java/mServer/crawler/sender/ard/ArdCrawler.java‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.time.LocalDateTime;
1313
import java.time.format.DateTimeFormatter;
1414
import java.util.HashSet;
15+
import java.util.List;
1516
import java.util.Set;
1617
import java.util.concurrent.ConcurrentLinkedQueue;
1718
import java.util.concurrent.ExecutionException;
@@ -27,6 +28,7 @@ public class ArdCrawler extends MediathekCrawler {
2728
= DateTimeFormatter.ofPattern("yyyy-MM-dd");
2829

2930
public static final String[] MISSING_TOPIC_IDS = new String[]{
31+
// "Y3JpZDovL2JyLmRlL2Jyb2FkY2FzdFNlcmllcy9icm9hZGNhc3RTZXJpZXM6L2JyZGUvZmVybnNlaGVuL2JheWVyaXNjaGVzLWZlcm5zZWhlbi9zZW5kdW5nZW4vZGFob2FtLWlzLWRhaG9hbQ"
3032
};
3133

3234
public ArdCrawler(FilmeSuchen ssearch, int startPrio) {
@@ -131,6 +133,7 @@ private Set<ArdFilmInfoDto> getDaysEntries() throws InterruptedException, Execut
131133
}
132134

133135
private Set<ArdFilmInfoDto> getTopicsEntries() throws ExecutionException, InterruptedException {
136+
final Set<ArdFilmInfoDto> shows = new HashSet<>();
134137
Set<CrawlerUrlDTO> topics = new HashSet<>();
135138
topics.addAll(getTopicEntriesBySender(ArdConstants.DEFAULT_CLIENT));
136139
for (String client : ArdConstants.CLIENTS) {
@@ -149,9 +152,21 @@ private Set<ArdFilmInfoDto> getTopicsEntries() throws ExecutionException, Interr
149152
ConcurrentLinkedQueue<CrawlerUrlDTO> topicUrls = new ConcurrentLinkedQueue<>(assitUrls);
150153

151154
final ArdTopicPageTask topicTask = new ArdTopicPageTask(this, topicUrls);
152-
final Set<ArdFilmInfoDto> filmInfos = forkJoinPool.submit(topicTask).get();
153-
Log.sysLog("ard shows by topics: " + filmInfos.size());
154-
return filmInfos;
155+
final Set<ArdFilmInfoDto> ardFilmInfosWithCompilations = forkJoinPool.submit(topicTask).get();
156+
157+
// add filmInfos without compilation
158+
shows.addAll(ardFilmInfosWithCompilations.stream().filter(filmInfo -> !filmInfo.isCompilation()).toList());
159+
160+
// search compilations
161+
final List<ArdFilmInfoDto> compilations = ardFilmInfosWithCompilations.stream().filter(ArdFilmInfoDto::isCompilation).toList();
162+
final ArdTopicCompilationTask compilationTask = new ArdTopicCompilationTask(this, new ConcurrentLinkedQueue<>(compilations));
163+
final Set<ArdFilmInfoDto> ardCompilationEntries = forkJoinPool.submit(compilationTask).get();
164+
165+
final int sizeBefore = shows.size();
166+
shows.addAll(ardCompilationEntries.stream().filter(filmInfo -> !filmInfo.isCompilation()).toList());
167+
Log.sysLog("ard shows by topics compilation: " + (shows.size() - sizeBefore));
168+
Log.sysLog("ard shows by topics: " + shows.size());
169+
return shows;
155170
}
156171

157172
// temporary workaround for missing topics

‎src/main/java/mServer/crawler/sender/ard/ArdFilmInfoDto.java‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,24 @@ public class ArdFilmInfoDto extends CrawlerUrlDTO {
77

88
private final String id;
99
private final int numberOfClips;
10+
private final boolean isCompilation;
1011

11-
public ArdFilmInfoDto(String id, String aUrl, int numberOfClips) {
12+
public ArdFilmInfoDto(String id, String aUrl, int numberOfClips, boolean isCompilation) {
1213
super(aUrl);
1314

1415
this.id = id;
1516
this.numberOfClips = numberOfClips;
17+
this.isCompilation = isCompilation;
1618
}
1719

1820
public String getId() {
1921
return id;
2022
}
2123

24+
public boolean isCompilation() {
25+
return isCompilation;
26+
}
27+
2228
public int getNumberOfClips() {
2329
return numberOfClips;
2430
}
@@ -36,11 +42,12 @@ public boolean equals(Object o) {
3642
}
3743
ArdFilmInfoDto that = (ArdFilmInfoDto) o;
3844
return numberOfClips == that.numberOfClips
39-
&& Objects.equals(id, that.id);
45+
&& Objects.equals(id, that.id)
46+
&& isCompilation == that.isCompilation;
4047
}
4148

4249
@Override
4350
public int hashCode() {
44-
return Objects.hash(super.hashCode(), id, numberOfClips);
51+
return Objects.hash(super.hashCode(), id, numberOfClips, isCompilation);
4552
}
4653
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package mServer.crawler.sender.ard;
2+
3+
import de.mediathekview.mlib.tool.Log;
4+
import mServer.crawler.sender.base.Qualities;
5+
6+
import java.util.EnumMap;
7+
import java.util.Map;
8+
import java.util.regex.Matcher;
9+
import java.util.regex.Pattern;
10+
11+
/**
12+
* * Converts an m3u8 playlist URL (in the style shown in the example) * into a list of progressive
13+
* mp4 URLs. * * Default progressive base host: https://wdr-progressive.ard-mcdn.de * * Example: *
14+
* input: *
15+
* https://wdrvod-rwrtr.akamaized.net/i/,/media/.../ID_AVC-,270,360,540,720,1080,.mp4.csmil/index-...m3u8
16+
* * * produced outputs: * https://wdr-progressive.ard-mcdn.de/media/.../ID_AVC-270.mp4 * ...
17+
*/
18+
public class WdrM3U8ToMp4Converter {
19+
20+
// Looks for /media/...<codec>-<comma-list>.mp4.csmil
21+
// group(1) = /media/... (path before codec)
22+
// group(2) = _<CODEC>- (codec token + trailing '-')
23+
// group(3) = the comma separated tail that contains bitrate numbers (e.g. ",270,360,540,")
24+
private static final Pattern MEDIA_PATTERN =
25+
Pattern.compile(
26+
"(/media/.+?)(_[A-Za-z0-9]+-)([^/]+?)\\.mp4\\.csmil", Pattern.CASE_INSENSITIVE);
27+
28+
private final String progressiveBase; // no trailing slash
29+
30+
public WdrM3U8ToMp4Converter() {
31+
this("https://wdr-progressive.ard-mcdn.de");
32+
}
33+
34+
public WdrM3U8ToMp4Converter(String progressiveBase) {
35+
if (progressiveBase == null)
36+
throw new IllegalArgumentException("progressiveBase must not be null");
37+
// ensure no trailing slash to make concatenation predictable
38+
this.progressiveBase = progressiveBase.replaceAll("/+$", "");
39+
}
40+
41+
/**
42+
* Convert an m3u8 url into a list of mp4 URLs.
43+
*
44+
* @param m3u8Url the source m3u8 url
45+
* @return map of mp4 URLs (in the same order as bitrates found)
46+
* @throws IllegalArgumentException if the url cannot be parsed or no bitrates found
47+
*/
48+
public Map<Qualities, String> convert(String m3u8Url) {
49+
if (m3u8Url == null) throw new IllegalArgumentException("m3u8Url must not be null");
50+
51+
Map<Qualities, String> result = new EnumMap<>(Qualities.class);
52+
53+
Matcher m = MEDIA_PATTERN.matcher(m3u8Url);
54+
if (!m.find()) {
55+
return result;
56+
}
57+
58+
String pathBeforeCodec = m.group(1); // includes leading /media/...
59+
String codecWithDash = m.group(2); // e.g. _AVC-
60+
String bitrateListPart = m.group(3); // e.g. ",270,360,540,720,1080," or "270,360"
61+
62+
// build the base path (starts with /media/...)
63+
String basePrefix = pathBeforeCodec + codecWithDash; // ends with '-'
64+
65+
// extract numeric tokens (bitrate values)
66+
Pattern digits = Pattern.compile("\\d+");
67+
Matcher mDigits = digits.matcher(bitrateListPart);
68+
69+
while (mDigits.find()) {
70+
String bitrate = mDigits.group();
71+
// join progressive base + basePrefix + bitrate + .mp4
72+
String mp4 = progressiveBase + basePrefix + bitrate + ".mp4";
73+
final Qualities resolution = getResolutionFromWidth(bitrate);
74+
result.put(resolution, mp4);
75+
}
76+
77+
if (result.isEmpty()) {
78+
throw new IllegalArgumentException("No numeric bitrate tokens found in m3u8 URL: " + m3u8Url);
79+
}
80+
81+
return result;
82+
}
83+
84+
private Qualities getResolutionFromWidth(String bitrate) {
85+
try {
86+
return switch (Integer.parseInt(bitrate)) {
87+
case 720 -> Qualities.NORMAL;
88+
case 1080 -> Qualities.HD;
89+
case 540, 360, 270 -> Qualities.SMALL;
90+
default -> {
91+
Log.sysLog("Unknown bitrate found in m3u8 URL: " + bitrate + ", defaulting to VERY_SMALL");
92+
yield Qualities.SMALL;
93+
}
94+
};
95+
} catch (NumberFormatException e) {
96+
Log.errorLog(165346373, e);
97+
return Qualities.SMALL;
98+
}
99+
}
100+
}

‎src/main/java/mServer/crawler/sender/ard/json/ArdDayPageDeserializer.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private Set<ArdFilmInfoDto> parseChannels(JsonArray channels) {
5050

5151
private ArdFilmInfoDto createFilmInfo(final String id, final int numberOfClips) {
5252
final String url = String.format(ArdConstants.ITEM_URL, id);
53-
return new ArdFilmInfoDto(id, url, numberOfClips);
53+
return new ArdFilmInfoDto(id, url, numberOfClips, false);
5454
}
5555

5656
private Optional<String> toId(final JsonObject teaserObject) {

0 commit comments

Comments
 (0)