-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a9da885
commit 9c48d9d
Showing
55 changed files
with
729 additions
and
19,722 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
Backend/src/main/java/lan/dk/podcastserver/manager/worker/downloader/SixPlayDownloader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package lan.dk.podcastserver.manager.worker.downloader; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.jayway.jsonpath.DocumentContext; | ||
import com.jayway.jsonpath.TypeRef; | ||
import javaslang.collection.HashSet; | ||
import javaslang.collection.Set; | ||
import javaslang.control.Option; | ||
import lan.dk.podcastserver.entity.Item; | ||
import lan.dk.podcastserver.repository.ItemRepository; | ||
import lan.dk.podcastserver.repository.PodcastRepository; | ||
import lan.dk.podcastserver.service.*; | ||
import lan.dk.podcastserver.service.properties.PodcastServerParameters; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.jsoup.nodes.Element; | ||
import org.jsoup.select.Elements; | ||
import org.springframework.messaging.simp.SimpMessagingTemplate; | ||
|
||
import static java.util.Objects.nonNull; | ||
|
||
/** | ||
* Created by kevin on 22/03/2017 for Podcast Server | ||
*/ | ||
public class SixPlayDownloader extends M3U8Downloader { | ||
|
||
private static final TypeRef<Set<M6PlayItem>> TYPE_ITEMS = new TypeRef<Set<M6PlayItem>>() {}; | ||
|
||
private final HtmlService htmlService; | ||
private final JsonService jsonService; | ||
|
||
String url = null; | ||
|
||
public SixPlayDownloader(ItemRepository itemRepository, PodcastRepository podcastRepository, PodcastServerParameters podcastServerParameters, SimpMessagingTemplate template, MimeTypeService mimeTypeService, UrlService urlService, M3U8Service m3U8Service, FfmpegService ffmpegService, ProcessService processService, HtmlService htmlService, JsonService jsonService) { | ||
super(itemRepository, podcastRepository, podcastServerParameters, template, mimeTypeService, urlService, m3U8Service, ffmpegService, processService); | ||
this.htmlService = htmlService; | ||
this.jsonService = jsonService; | ||
} | ||
|
||
@Override | ||
public String getItemUrl(Item item) { | ||
|
||
if (nonNull(this.item) && !this.item.equals(item)) { | ||
return item.getUrl(); | ||
} | ||
|
||
if (nonNull(url)) { | ||
return url; | ||
} | ||
|
||
url = htmlService.get(item.getUrl()) | ||
.map(d -> this.extractUrl(d.select("script"))) | ||
.getOrElseThrow(() -> new RuntimeException("Url not found for " + item.getUrl())); | ||
|
||
return url; | ||
} | ||
|
||
private String extractUrl(Elements script) { | ||
|
||
url = extractJson(script) | ||
.map(JsonService.to("mainStoreState.video.currentVideo.clips[*].assets[*]", TYPE_ITEMS)) | ||
.getOrElse(HashSet.empty()) | ||
.filter(i -> "usp_hls_h264".equals(i.getType())) | ||
.map(M6PlayItem::getFull_physical_path) | ||
.getOrElseThrow(() -> new RuntimeException("Stream \"usp_hls_h264\" not found for item " + this.item.getTitle())); | ||
|
||
return url; | ||
} | ||
|
||
private Option<DocumentContext> extractJson(Elements elements) { | ||
return HashSet.ofAll(elements) | ||
.find(s -> s.html().contains("root.__6play")) | ||
.map(Element::html) | ||
.map(s -> StringUtils.substringBetween(s, "root.__6play = ", "}(this));")) | ||
.map(jsonService::parse); | ||
} | ||
|
||
@Override | ||
public Integer compatibility(String url) { | ||
return nonNull(url) && url.startsWith("http://www.6play.fr/") ? 1 : Integer.MAX_VALUE; | ||
} | ||
|
||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
public static class M6PlayItem { | ||
@Setter @Getter private String full_physical_path; | ||
@Setter @Getter private String type; | ||
} | ||
} |
76 changes: 76 additions & 0 deletions
76
Backend/src/main/java/lan/dk/podcastserver/manager/worker/finder/SixPlayFinder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package lan.dk.podcastserver.manager.worker.finder; | ||
|
||
import javaslang.collection.HashSet; | ||
import lan.dk.podcastserver.entity.Cover; | ||
import lan.dk.podcastserver.entity.Podcast; | ||
import lan.dk.podcastserver.service.HtmlService; | ||
import lan.dk.podcastserver.service.ImageService; | ||
import lan.dk.podcastserver.service.JsonService; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import net.minidev.json.JSONArray; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.hibernate.validator.constraints.NotEmpty; | ||
import org.jsoup.nodes.Document; | ||
import org.jsoup.nodes.Element; | ||
import org.jsoup.select.Elements; | ||
import org.springframework.stereotype.Service; | ||
|
||
import static java.util.Objects.nonNull; | ||
|
||
/** | ||
* Created by kevin on 20/12/2016 for Podcast Server | ||
*/ | ||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class SixPlayFinder implements Finder { | ||
|
||
private final HtmlService htmlService; | ||
private final ImageService imageService; | ||
private final JsonService jsonService; | ||
|
||
@Override | ||
public Podcast find(String url) { | ||
return htmlService.get(url) | ||
.map(this::htmlToPodcast) | ||
.getOrElse(Podcast.DEFAULT_PODCAST); | ||
} | ||
|
||
private Podcast htmlToPodcast(Document document) { | ||
return Podcast.builder() | ||
.title(document.select("h1.tile-section__title").text()) | ||
.url(document.select("link[rel=canonical]").attr("href")) | ||
.description(getDescription(document.select("script"))) | ||
.cover(getCover(document.select("div.header-image__image").attr("style"))) | ||
.type("SixPlay") | ||
.build(); | ||
} | ||
|
||
private String getDescription(Elements script) { | ||
return HashSet | ||
.ofAll(script) | ||
.find(s -> s.html().contains("root.__6play")) | ||
.map(Element::html) | ||
.map(s -> StringUtils.substringBetween(s, "root.__6play = ", "}(this));")) | ||
.map(jsonService::parse) | ||
.map(d -> (JSONArray) d.read("context.dispatcher.stores.ProgramStore.programs.*.description")) | ||
.flatMap(r -> HashSet.ofAll(r).headOption()) | ||
.map(Object::toString) | ||
.getOrElse(() -> null); | ||
} | ||
|
||
private Cover getCover(String style) { | ||
return HashSet | ||
.of(style.split(";")) | ||
.find(s -> s.contains("background-image")) | ||
.map(s -> StringUtils.substringBetween(s, "(", ")")) | ||
.map(imageService::getCoverFromURL) | ||
.getOrElse(Cover.DEFAULT_COVER); | ||
} | ||
|
||
@Override | ||
public Integer compatibility(@NotEmpty String url) { | ||
return nonNull(url) && url.contains("www.6play.fr") ? 1 : Integer.MAX_VALUE; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
176 changes: 176 additions & 0 deletions
176
Backend/src/main/java/lan/dk/podcastserver/manager/worker/updater/SixPlayUpdater.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package lan.dk.podcastserver.manager.worker.updater; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.jayway.jsonpath.DocumentContext; | ||
import com.jayway.jsonpath.TypeRef; | ||
import javaslang.Value; | ||
import javaslang.collection.HashMap; | ||
import javaslang.collection.HashSet; | ||
import javaslang.collection.Set; | ||
import javaslang.control.Option; | ||
import lan.dk.podcastserver.entity.Item; | ||
import lan.dk.podcastserver.entity.Podcast; | ||
import lan.dk.podcastserver.service.HtmlService; | ||
import lan.dk.podcastserver.service.ImageService; | ||
import lan.dk.podcastserver.service.JsonService; | ||
import lan.dk.podcastserver.service.SignatureService; | ||
import lan.dk.podcastserver.service.properties.PodcastServerParameters; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
import lombok.ToString; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.codec.digest.DigestUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.jsoup.nodes.Element; | ||
import org.jsoup.select.Elements; | ||
import org.springframework.stereotype.Component; | ||
|
||
import javax.validation.Validator; | ||
import java.time.LocalDateTime; | ||
import java.time.ZoneId; | ||
import java.time.ZonedDateTime; | ||
import java.time.format.DateTimeFormatter; | ||
import java.util.Locale; | ||
|
||
import static java.util.Objects.nonNull; | ||
|
||
/** | ||
* Created by kevin on 20/12/2016 for Podcast Server | ||
*/ | ||
@Slf4j | ||
@Component("SixPlayUpdater") | ||
public class SixPlayUpdater extends AbstractUpdater { | ||
|
||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); | ||
private static final TypeRef<Set<SixPlayItem>> TYPE_ITEMS = new TypeRef<Set<SixPlayItem>>(){}; | ||
private static final TypeRef<HashMap<String, Object>> TYPE_KEYS = new TypeRef<HashMap<String, Object>>(){}; | ||
private static final TypeRef<Set<Integer>> TYPE_IDS = new TypeRef<Set<Integer>>(){}; | ||
|
||
private static final String VIDEOS_SELECTOR = "mainStoreState.video.programVideosBySubCategory.%d.%d"; | ||
private static final String URL_TEMPLATE_PODCAST = "http://www.6play.fr/%s-p_%d/"; | ||
private static final String SUB_CAT_SELECTOR = "context.dispatcher.stores.ProgramStore.subcats.%d[*].id"; | ||
private static final String PROGRAM_CODE_SELECTOR = "context.dispatcher.stores.ProgramStore.programs.%d.code"; | ||
private static final String PROGRAM_ID_SELECTOR = "context.dispatcher.stores.ProgramStore.programs"; | ||
|
||
private final HtmlService htmlService; | ||
private final JsonService jsonService; | ||
private final ImageService imageService; | ||
|
||
protected SixPlayUpdater(PodcastServerParameters podcastServerParameters, SignatureService signatureService, Validator validator, HtmlService htmlService, JsonService jsonService, ImageService imageService) { | ||
super(podcastServerParameters, signatureService, validator); | ||
this.htmlService = htmlService; | ||
this.jsonService = jsonService; | ||
this.imageService = imageService; | ||
} | ||
|
||
@Override | ||
public Set<Item> getItems(Podcast podcast) { | ||
return htmlService.get(podcast.getUrl()) | ||
.map(d -> this.extractItems(d.select("script"))) | ||
.getOrElse(HashSet::empty); | ||
} | ||
|
||
private Set<Item> extractItems(Elements script) { | ||
Option<DocumentContext> root6Play = extractJson(script); | ||
|
||
Integer programId = root6Play | ||
.map(JsonService.to(PROGRAM_ID_SELECTOR, TYPE_KEYS)) | ||
.map(HashMap::keySet) | ||
.flatMap(Value::getOption) | ||
.map(Integer::valueOf) | ||
.getOrElseThrow(() -> new RuntimeException("programId not found in root.__6play")); | ||
|
||
Set<Integer> subCatIds = root6Play | ||
.map(JsonService.to(String.format(SUB_CAT_SELECTOR, programId), TYPE_IDS)) | ||
.getOrElseThrow(() -> new RuntimeException("subcatId not found in root.__6play")); | ||
|
||
String programCode = root6Play | ||
.map(JsonService.to(String.format(PROGRAM_CODE_SELECTOR, programId), String.class)) | ||
.getOrElseThrow(() -> new RuntimeException("programCode not found in root.__6play")); | ||
|
||
String basePath = String.format(URL_TEMPLATE_PODCAST, programCode, programId); | ||
|
||
return subCatIds | ||
.flatMap(v -> root6Play.map(JsonService.to(String.format(VIDEOS_SELECTOR, programId, v), TYPE_ITEMS))) | ||
.map(c -> c.map(s -> this.convertToItem(s, basePath))) | ||
.getOrElseThrow(() -> new RuntimeException("Error during transformation of json to items")); | ||
} | ||
|
||
private Item convertToItem(SixPlayItem i, String basePath) { | ||
return Item.builder() | ||
.title(i.getTitle()) | ||
.pubDate(i.getLastDiffusion()) | ||
.length(i.getDuration()) | ||
.url(i.url(basePath)) | ||
.description(i.description) | ||
.cover(imageService.getCoverFromURL(i.cover())) | ||
.build(); | ||
} | ||
|
||
private Option<DocumentContext> extractJson(Elements elements) { | ||
return HashSet.ofAll(elements) | ||
.find(s -> s.html().contains("root.__6play")) | ||
.map(Element::html) | ||
.map(s -> StringUtils.substringBetween(s, "root.__6play = ", "}(this));")) | ||
.map(jsonService::parse); | ||
} | ||
|
||
@Override | ||
public String signatureOf(Podcast podcast) { | ||
return "foo"; | ||
} | ||
|
||
@Override | ||
public Type type() { | ||
return new Type("SixPlay", "6Play"); | ||
} | ||
|
||
@Override | ||
public Integer compatibility(String url) { | ||
return nonNull(url) && url.startsWith("http://www.6play.fr/") ? 1 : Integer.MAX_VALUE; | ||
} | ||
|
||
@ToString | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
private static class SixPlayItem{ | ||
|
||
@Getter @Setter Set<Image> images; | ||
@Getter @Setter String code; | ||
@Getter @Setter String description; | ||
@Getter @Setter String title; | ||
@Setter String lastDiffusion; /* 2016-12-18 11:20:00 */ | ||
@Getter @Setter Long duration; | ||
@Getter @Setter String id; | ||
|
||
ZonedDateTime getLastDiffusion() { | ||
return Option.of(lastDiffusion) | ||
.map(l -> ZonedDateTime.of(LocalDateTime.parse(l, DATE_FORMATTER), ZoneId.of("Europe/Paris"))) | ||
.getOrElse(() -> null); | ||
} | ||
|
||
String url(String basePath) { | ||
return basePath + code + "-c_" + StringUtils.substringAfter(id, "_"); | ||
} | ||
|
||
String cover() { | ||
return images.headOption() | ||
.map(Image::url) | ||
.getOrElse(() -> null); | ||
} | ||
|
||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
private static class Image { | ||
|
||
private static final String domain = "https://images.6play.fr"; | ||
private static final String path = "/v1/images/%s/raw?width=600&height=336&fit=max&quality=60&format=jpeg&interlace=1"; | ||
private static final String salt = "54b55408a530954b553ff79e98"; | ||
|
||
@Getter @Setter Integer external_key; | ||
|
||
public String url() { | ||
String path = String.format(Image.path, external_key); | ||
return domain + path + "&hash=" + DigestUtils.sha1Hex(path + salt); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.