Merge pull request 'support for collections' (#28) from collections into stable
Reviewed-on: #28
This commit is contained in:
commit
57da060e73
47
README.md
47
README.md
|
@ -74,7 +74,8 @@ Options:
|
|||
--name-sep=<sep> field separator [default: - ]
|
||||
--name-seq-pfx=<pfx> sequence counter prefix [default: - ]
|
||||
--name-seq-no-pad disable sequence zero-padding
|
||||
--name-add-resolution add resolution tag
|
||||
--name-add-rendition add rendition code
|
||||
--name-add-variant add variant code
|
||||
```
|
||||
|
||||
🔧 How it works
|
||||
|
@ -82,33 +83,15 @@ Options:
|
|||
|
||||
## 🏗️ The streaming infrastructure
|
||||
|
||||
Every video program have a _program identifier_ visible in their web page URL:
|
||||
|
||||
```
|
||||
https://www.arte.tv/es/videos/110139-000-A/fromental-halevy-la-tempesta/
|
||||
https://www.arte.tv/fr/videos/100204-001-A/esprit-d-hiver-1-3/
|
||||
https://www.arte.tv/en/videos/104001-000-A/clint-eastwood/
|
||||
```
|
||||
|
||||
That _program identifier_ enables us to query an API for the program's information.
|
||||
We support both _single program pages_ and _program collection pages_. Every page is shipped with some embedded JSON data, example of such data can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/www/). From that we extract metadata for each programs. In particular, we extract a _site language_ and a _program ID_. These enables us to query the config API
|
||||
|
||||
### The _config_ API
|
||||
|
||||
For the last example the API call is as such:
|
||||
|
||||
```
|
||||
https://api.arte.tv/api/player/v2/config/en/104001-000-A
|
||||
```
|
||||
|
||||
The response is a JSON object, a sample of which can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/api/config-105612-000-A.json):
|
||||
|
||||
Information about the program is detailed in `$.data.attributes.metadata` and a list of available audio/subtitles combinations in `$.data.attributes.streams`. In our code such a combination is referred to as a _rendition_ (or _version_ in the CLI).
|
||||
|
||||
Every such _rendition_ has a reference to a _program index_ file in `.streams[i].url`
|
||||
This API returns a `ConfigPlayer` JSON object, a sample of which can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/api/). A list of available audio/subtitles combinations in `$.data.attributes.streams`. In our code such a combination is referred to as a _rendition_. Every such _rendition_ has a reference to a _program index_ file in `.streams[i].url`
|
||||
|
||||
### The _program index_ file
|
||||
|
||||
As defined in [HTTP Live Streaming](https://www.rfc-editor.org/rfc/rfc8216) (sample file can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/program-105612-000-A_VOF-STMF_XQ.m3u8) or [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/program-105612-000-A_VA-STA_XQ.m3u8)). This file show the a list of video _variants_ URIs (one per video resolution). Each of them has
|
||||
As defined in [HTTP Live Streaming](https://www.rfc-editor.org/rfc/rfc8216) (sample files can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/)). This file show the a list of video _variants_ URIs (one per video resolution). Each of them has
|
||||
- exactly one video _track index_ reference
|
||||
- exactly one audio _track index_ reference
|
||||
- at most one subtitles _track index_ reference
|
||||
|
@ -120,23 +103,21 @@ Audio and subtitles tracks reference also include:
|
|||
|
||||
### The video and audio _track index_ file
|
||||
|
||||
As defined in [HTTP Live Streaming](https://www.rfc-editor.org/rfc/rfc8216) (a sample file can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/audio-105612-000-A_aud_VA.m3u8) or [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/video-105612-000-A_v1080.m3u8)). This file is basically a list of _segments_ (http ranges) the client is supposed to download in sequence.
|
||||
As defined in [HTTP Live Streaming](https://www.rfc-editor.org/rfc/rfc8216) (sample files can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/). This file is basically a list of _segments_ (http ranges) the client is supposed to download in sequence.
|
||||
|
||||
### The subtitles _track index_ file
|
||||
|
||||
As defined in [HTTP Live Streaming](https://www.rfc-editor.org/rfc/rfc8216) (a sample file can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/subtitles-105612-000-A_st_VA-ALL.m3u8)). This file references the actual file containing the subtitles [VTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API) data.
|
||||
As defined in [HTTP Live Streaming](https://www.rfc-editor.org/rfc/rfc8216) (sample files can be found [here](https://git.afpy.org/fcode/delarte/src/branch/stable/samples/hls/)). This file references the actual file containing the subtitles [VTT](https://developer.mozilla.org/en-US/docs/Web/API/WebVTT_API) data.
|
||||
|
||||
## ⚙️The process
|
||||
|
||||
1. Figure out available _sources_ by:
|
||||
- fetching the _config_ API object for the _program identifier_
|
||||
- fetching all referenced _program index_.
|
||||
2. Select the desired _target_ based on _renditions_ and _variants_ codes.
|
||||
|
||||
3. Download video, audio and subtitles tracks content.
|
||||
- convert `VTT` subtitles to styled `SRT`
|
||||
|
||||
4. Feed the all the tracks to `ffmpeg` for multiplexing (or _muxing_)
|
||||
1. Fetch _program sources_ form the page pointed by the given URL
|
||||
2. Fetch _rendition sources_ from _config API_
|
||||
3. Filter _renditions_
|
||||
4. Fetch _variant sources_ from _HLS_ _program index_ files.
|
||||
5. Filter _variants_
|
||||
6. Fetch final target information and figure out output naming
|
||||
7. Download data streams (convert VTT subtitles to formatted SRT subtitles) and mux them with FFMPEG
|
||||
|
||||
## 📽️ FFMPEG
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,893 @@
|
|||
{
|
||||
"props": {
|
||||
"pageProps": {
|
||||
"geoblocking": null,
|
||||
"initialPage": {
|
||||
"tag": "Ok",
|
||||
"value": {
|
||||
"code": "RC-022923",
|
||||
"language": "fr",
|
||||
"support": "web",
|
||||
"type": "collection",
|
||||
"level": 3,
|
||||
"parent": {
|
||||
"type": "category",
|
||||
"page": "SER",
|
||||
"label": "Séries et fictions",
|
||||
"url": "/fr/videos/series-et-fictions/",
|
||||
"deeplink": "arte://emac/SER",
|
||||
"id": "SER_fr_web",
|
||||
"slug": "series-et-fictions",
|
||||
"parent": null
|
||||
},
|
||||
"alternativeLanguages": [
|
||||
{
|
||||
"code": "fr",
|
||||
"label": "Français",
|
||||
"page": "RC-022923",
|
||||
"url": "/fr/videos/RC-022923/cry-wolf/",
|
||||
"title": "Cry Wolf"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"label": "Deutsch",
|
||||
"page": "RC-022923",
|
||||
"url": "/de/videos/RC-022923/cry-wolf/",
|
||||
"title": "Cry Wolf"
|
||||
}
|
||||
],
|
||||
"url": "/fr/videos/RC-022923/cry-wolf/",
|
||||
"deeplink": "arte://collection/RC-022923",
|
||||
"slug": "cry-wolf",
|
||||
"zones": [
|
||||
{
|
||||
"id": "51ba8da1-5389-43c4-af08-cca614192d77_RC-022923",
|
||||
"code": "collection_content_RC-022923",
|
||||
"title": "Cry Wolf",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "single-collectionContent",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "RC-022923",
|
||||
"type": "collection",
|
||||
"kind": {
|
||||
"code": "TV_SERIES",
|
||||
"label": "Série",
|
||||
"isCollection": true
|
||||
},
|
||||
"url": "/fr/videos/RC-022923/cry-wolf/",
|
||||
"deeplink": "arte://collection/RC-022923",
|
||||
"title": "Cry Wolf",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Mais ses parents nient l’accusation. Un assistant social atypique est chargé de démêler l’affaire mais Holly subit les remontrances de sa mère qui l’accuse de mentir. Drame familial perturbant, la série danoise \"Cry Wolf\" plonge dans l’intimité d’une famille apparemment bien sous tous rapports.",
|
||||
"mainImage": {
|
||||
"caption": "Série Cry Wolf",
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/eFsiE6z6z3LXnBZj9A6SQ9/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "COLLECTION",
|
||||
"label": "COLLECTION"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&language=fr&pageid=collection&position=1&support=web&teaserid=RC-022923&teasertitle=Cry%20Wolf&zoneCode=collection_content_RC-022923&zoneIndexInPage=0&zoneTemplate=single_collectionContent&zoneid=collection_content&zonename=Cry%20Wolf",
|
||||
"trailer": {
|
||||
"config": "https://api.arte.tv/api/player/v2/trailer/fr/RC-022923"
|
||||
},
|
||||
"partners": null,
|
||||
"description": "Holly, 14 ans, accuse son beau-père de violences envers elle. Mais ses parents nient l’accusation. Un assistant social atypique est chargé de démêler l’affaire mais Holly subit les remontrances de sa mère qui l’accuse de mentir. Drame familial perturbant, la série danoise \"Cry Wolf\" plonge dans l’intimité d’une famille apparemment bien sous tous rapports.",
|
||||
"video": null,
|
||||
"availability": null
|
||||
}
|
||||
],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "d3bad9c8-b1c0-43db-87fb-fb95cc68766d_RC-022923",
|
||||
"code": "collection_videos_RC-022923",
|
||||
"title": "Toutes les vidéos",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "e0dfcff6-67bc-4d83-8055-8edf45687aea_RC-022923_RC-022924",
|
||||
"code": "collection_subcollection_RC-022923_RC-022924",
|
||||
"title": "Cry Wolf",
|
||||
"slug": "cry-wolf",
|
||||
"description": "Une série choc danoise sur l’enfance maltraitée. Un drame familial remuant aux accents de thriller.",
|
||||
"displayOptions": {
|
||||
"template": "verticalFirstHighlighted-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "100998-001-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-001-A/cry-wolf-1-8/",
|
||||
"deeplink": "arte://program/100998-001-A",
|
||||
"title": "Cry Wolf (1/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire... Un drame familial remuant aux accents de thriller, tout en tension et en non-dits.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/NnBniMoe7qfMqSo2r38YFE/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-001-A&language=fr&pageid=collection&position=1&support=web&teaserid=100998-001-A_fr&teasertitle=Cry%20Wolf%20%281%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-001-A",
|
||||
"teaserText": "Un drame familial remuant aux accents de thriller, tout en tension et en non-dits.",
|
||||
"duration": 3328,
|
||||
"durationLabel": "56 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "100998-002-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-002-A/cry-wolf-2-8/",
|
||||
"deeplink": "arte://program/100998-002-A",
|
||||
"title": "Cry Wolf (2/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire… Un drame familial remuant aux accents de thriller, tout en tension et en non-dits. Deuxième épisode : l’ambiance est assommante dans la maison familiale. Dea interroge Simon : a-t-il oui ou non frappé Holly ?",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/VTefZdwQPweXAvxuqaeGcc/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-002-A&language=fr&pageid=collection&position=2&support=web&teaserid=100998-002-A_fr&teasertitle=Cry%20Wolf%20%282%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-002-A",
|
||||
"teaserText": "L’ambiance est assommante dans la maison familiale. Dea interroge Simon : a-t-il oui ou non frappé Holly ?",
|
||||
"duration": 3071,
|
||||
"durationLabel": "52 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "100998-003-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-003-A/cry-wolf-3-8/",
|
||||
"deeplink": "arte://program/100998-003-A",
|
||||
"title": "Cry Wolf (3/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire… Un drame familial remuant aux accents de thriller, tout en tension et en non-dits. Troisième épisode : Simon se rend compte qu’il risque de tout perdre et s’affole..",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/oyDB5vM2RpNKRCvTrP5bVS/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-003-A&language=fr&pageid=collection&position=3&support=web&teaserid=100998-003-A_fr&teasertitle=Cry%20Wolf%20%283%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-003-A",
|
||||
"teaserText": "Simon se rend compte qu’il risque de tout perdre et s’affole.",
|
||||
"duration": 3300,
|
||||
"durationLabel": "55 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "100998-008-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-008-A/cry-wolf-4-8/",
|
||||
"deeplink": "arte://program/100998-008-A",
|
||||
"title": "Cry Wolf (4/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire… Un drame familial remuant aux accents de thriller, tout en tension et en non-dits. Quatrième épisode : Theo a disparu. En réalité, il est parti à la recherche de son père pour son anniversaire.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/CV3pnaC9yg6bKqArzToEYo/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-008-A&language=fr&pageid=collection&position=4&support=web&teaserid=100998-008-A_fr&teasertitle=Cry%20Wolf%20%284%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-008-A",
|
||||
"teaserText": "Theo a disparu. En réalité, il est parti à la recherche de son père pour son anniversaire...",
|
||||
"duration": 2861,
|
||||
"durationLabel": "48 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "100998-005-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-005-A/cry-wolf-5-8/",
|
||||
"deeplink": "arte://program/100998-005-A",
|
||||
"title": "Cry Wolf (5/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire… Cinquième épisode : une vidéo partagée sur les réseaux sociaux fragilise grandement Lars.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/wHzjfqVZ3PaLC9fGiQyJkF/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-005-A&language=fr&pageid=collection&position=5&support=web&teaserid=100998-005-A_fr&teasertitle=Cry%20Wolf%20%285%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-005-A",
|
||||
"teaserText": "Une vidéo partagée sur les réseaux sociaux fragilise grandement Lars.",
|
||||
"duration": 2640,
|
||||
"durationLabel": "44 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "100998-006-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-006-A/cry-wolf-6-8/",
|
||||
"deeplink": "arte://program/100998-006-A",
|
||||
"title": "Cry Wolf (6/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire… Sixième épisode : alors qu’une nouvelle réunion de la commission de l’enfance approche, l’avocate de Simon et Dea leur propose une solution radicale.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/6Zvp8ms34Sqre6sn8k6xL3/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-006-A&language=fr&pageid=collection&position=6&support=web&teaserid=100998-006-A_fr&teasertitle=Cry%20Wolf%20%286%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-006-A",
|
||||
"teaserText": "Alors qu’une nouvelle réunion de la commission de l’enfance approche, l’avocate de Simon et Dea leur propose une solution radicale.",
|
||||
"duration": 3010,
|
||||
"durationLabel": "51 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "100998-007-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-007-A/cry-wolf-7-8/",
|
||||
"deeplink": "arte://program/100998-007-A",
|
||||
"title": "Cry Wolf (7/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire… Septième épisode : la réunion de la commission de l’enfance se prépare. Toujours plus proche de Jonatan, Holly est déstabilisée.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/HWRodkStBtsVJZZmpA7usX/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-007-A&language=fr&pageid=collection&position=7&support=web&teaserid=100998-007-A_fr&teasertitle=Cry%20Wolf%20%287%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-007-A",
|
||||
"teaserText": "La réunion de la commission de l’enfance se prépare. Toujours plus proche de Jonatan, Holly est déstabilisée...",
|
||||
"duration": 3011,
|
||||
"durationLabel": "51 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "100998-004-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100998-004-A/cry-wolf-8-8/",
|
||||
"deeplink": "arte://program/100998-004-A",
|
||||
"title": "Cry Wolf (8/8)",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Holly, 14 ans, accuse son beau-père de violences envers elle. Qu’en est-il vraiment ? Un assistant social atypique, Lars Madsen, est chargé de démêler l’affaire… Dernier épisode : un changement radical attend la famille. L’atmosphère est plus sombre que jamais ; la tension, permanente.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/HAEoSZ67qzafC9LFwg8tJX/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=B&em=100998-004-A&language=fr&pageid=collection&position=8&support=web&teaserid=100998-004-A_fr&teasertitle=Cry%20Wolf%20%288%2F8%29&zoneCode=collection_subcollection_RC-022923_RC-022924&zoneIndexInPage=2&zoneTemplate=horizontal_landscape&zoneid=collection_subcollection&zonename=Cry%20Wolf",
|
||||
"programId": "100998-004-A",
|
||||
"teaserText": "Un changement radical attend la famille. L’atmosphère est plus sombre que jamais ; la tension, permanente.",
|
||||
"duration": 3208,
|
||||
"durationLabel": "54 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-12T04:00:00Z",
|
||||
"end": "2023-03-27T03:00:00Z",
|
||||
"upcomingDate": "2023-01-12T04:00:00Z",
|
||||
"label": "Disponible du 12/01/2023 au 26/03/2023"
|
||||
},
|
||||
"ageRating": 16,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"totalCount": 8,
|
||||
"links": {
|
||||
"first": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/e0dfcff6-67bc-4d83-8055-8edf45687aea/content?abv=B&collectionId=RC-022923&page=1&pageId=collection&subCollectionId=RC-022924&type=collection&zoneIndexInPage=2",
|
||||
"next": null,
|
||||
"last": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/e0dfcff6-67bc-4d83-8055-8edf45687aea/content?abv=B&collectionId=RC-022923&page=1&pageId=collection&subCollectionId=RC-022924&type=collection&zoneIndexInPage=2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5c5ab0fc-a32c-493c-bfa1-7d4eddca8834_RC-022923",
|
||||
"code": "collection_upcoming_RC-022923",
|
||||
"title": "Collection Upcomings",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3e4b7126-60ee-4740-9aa6-e0a92af2bfe1_RC-022923",
|
||||
"code": "collection_article_RC-022923",
|
||||
"title": "Collection Articles",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "125ee988-ab24-4bdf-a62c-703717d02164_RC-022923",
|
||||
"code": "collection_associated_RC-022923",
|
||||
"title": "Sur le même thème",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "371699f8-dcfb-4abb-933a-83cc956a5d7d_RC-022923",
|
||||
"code": "collection_partner_RC-022923",
|
||||
"title": "Collection Partners",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"xiti": {
|
||||
"page_name": "Home_RC-022923_cry-wolf",
|
||||
"chapter1": "SER_series-et-fictions",
|
||||
"chapter2": "Série",
|
||||
"chapter3": "RC-022923_cry-wolf",
|
||||
"x1": "fr",
|
||||
"x2": "Collection",
|
||||
"s2": 1,
|
||||
"siteId": "582046",
|
||||
"env_work": "prod",
|
||||
"search_keywords": null
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"title": "Cry Wolf",
|
||||
"description": "Holly, 14 ans, accuse son beau-père de violences envers elle. Mais ses parents nient l’accusation. Un assistant social atypique est chargé de démêler l’affaire mais Holly subit les remontrances de sa mère qui l’accuse de mentir. Drame familial perturbant, la série danoise \"Cry Wolf\" plonge dans l’intimité d’une famille apparemment bien sous tous rapports.",
|
||||
"seo": {
|
||||
"title": "Cry Wolf - Séries et fictions | ARTE",
|
||||
"description": "Holly, 14 ans, accuse son beau-père de violences envers elle. Mais ses parents nient l’accusation. Un assistant social atypique est chargé de démêler l’affaire mais Holly subit les remontrances de sa mère qui l’accuse de mentir. Drame familial perturbant, la série danoise \"Cry Wolf\" plonge dans l’intimité d’une famille apparemment bien sous tous rapports.",
|
||||
"canonical": "/fr/videos/RC-022923/cry-wolf/"
|
||||
},
|
||||
"og": {
|
||||
"image": {
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/eFsiE6z6z3LXnBZj9A6SQ9/1920x1080?type=TEXT&watermark=true",
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
}
|
||||
},
|
||||
"twitter": {
|
||||
"image": {
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/eFsiE6z6z3LXnBZj9A6SQ9/1920x1080?type=TEXT&watermark=true"
|
||||
},
|
||||
"site": "@ARTEfr"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"type": "collections",
|
||||
"redirect": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"initialType": "collections",
|
||||
"mamiBaseUrl": "https://api-cdn.arte.tv/api/mami/v1/",
|
||||
"locale": "fr",
|
||||
"tcStartFrom": null,
|
||||
"abvGroups": "B",
|
||||
"emacVersion": "v4"
|
||||
},
|
||||
"locale": "fr",
|
||||
"footerProps": {
|
||||
"main": [
|
||||
{
|
||||
"label": "Sites",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "ARTE VOD/DVD",
|
||||
"kind": "internal",
|
||||
"href": "https://boutique.arte.tv/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE Radio",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arteradio.com/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "Coups de cœur culture",
|
||||
"kind": "internal",
|
||||
"href": "https://my.arte.tv/index.php?lang=fr&page=eventsFavorite",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "Programmes en UHD ",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/videos/RC-022710/nos-programmes-en-uhd/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE Info Plus - Décryptez l'actualité",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/videos/RC-022628/arte-info-plus/",
|
||||
"rel": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Entreprise",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Tout sur ARTE",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Emplois et stages",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/particuliers-professionnels/#offres-demploi-et-de-stages",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Appels d'offres",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/appels-doffres/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Aide & contact",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/nous-repondons-a-vos-questions/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Streamer responsable",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/streaming-responsable/",
|
||||
"rel": "nofollow"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Infos légales",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Gérer les cookies",
|
||||
"kind": "cookie",
|
||||
"href": null,
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE et vos données personnelles",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/donnees-personnelles/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Mentions légales et crédits",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/credits/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "CGU",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/conditions-generales-dutilisation-cgu/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/accessibilite/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Plan du site",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/sitemap/",
|
||||
"rel": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Réseaux sociaux",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Facebook",
|
||||
"kind": "external",
|
||||
"href": "https://www.facebook.com/artetv",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Instagram",
|
||||
"kind": "external",
|
||||
"href": "https://www.instagram.com/artefr",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Youtube",
|
||||
"kind": "external",
|
||||
"href": "https://www.youtube.com/arteplus7",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Twitter",
|
||||
"kind": "external",
|
||||
"href": "https://www.twitter.com/artefr",
|
||||
"rel": "nofollow"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"geoblocking": "DE_FR",
|
||||
"serverTime": "2023-01-23T21:29:41.265Z",
|
||||
"__N_SSP": true
|
||||
},
|
||||
"page": "/videos/[...identifiers]",
|
||||
"query": {
|
||||
"identifiers": [
|
||||
"RC-022923",
|
||||
"cry-wolf"
|
||||
]
|
||||
},
|
||||
"buildId": "230117085533",
|
||||
"assetPrefix": "https://static-cdn.arte.tv/replay",
|
||||
"runtimeConfig": {
|
||||
"emacVersion": "v4",
|
||||
"ebuBoxUrl": "https://reco.ebu.io/news-reco-arte.js",
|
||||
"ffABTesting": true,
|
||||
"ffDirectPlayerAutoPlay": "true",
|
||||
"ffEbuBox": false,
|
||||
"ffImagePlaceholder": false,
|
||||
"ffLivePage": true,
|
||||
"ffMeta": true,
|
||||
"ffNewGuideTv": true,
|
||||
"ffNewsletterZoneWithTeaserImage": false,
|
||||
"ffPlayerAutoPlay": "if_available",
|
||||
"ffProgramTrailer": false,
|
||||
"ffProfileMenu": false,
|
||||
"ffSettingsMenuTargetedComms": false,
|
||||
"ffSidaction": false,
|
||||
"ffThemeSwitch": false,
|
||||
"ffAtInternetSrcForce": true,
|
||||
"ffSettingsMenuVideoQuality": false,
|
||||
"ffSliderMetaInfoButtonSeeMore": false,
|
||||
"newsletterSubscribeUrl": "https://api.arte.tv/api/sso/v3/newsletter/subscribe",
|
||||
"tagCommanderUrl": "https://cdn.tagcommander.com/3445"
|
||||
},
|
||||
"isFallback": false,
|
||||
"gssp": true,
|
||||
"customServer": true,
|
||||
"appGip": true,
|
||||
"locale": "fr",
|
||||
"locales": [
|
||||
"fr",
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"pl",
|
||||
"it"
|
||||
],
|
||||
"defaultLocale": "fr",
|
||||
"scriptLoader": []
|
||||
}
|
|
@ -0,0 +1,703 @@
|
|||
{
|
||||
"props": {
|
||||
"pageProps": {
|
||||
"geoblocking": null,
|
||||
"initialPage": {
|
||||
"tag": "Ok",
|
||||
"value": {
|
||||
"code": "RC-023013",
|
||||
"language": "fr",
|
||||
"support": "web",
|
||||
"type": "collection",
|
||||
"level": 3,
|
||||
"parent": {
|
||||
"type": "category",
|
||||
"page": "HIS",
|
||||
"label": "Histoire",
|
||||
"url": "/fr/videos/histoire/",
|
||||
"deeplink": "arte://emac/HIS",
|
||||
"id": "HIS_fr_web",
|
||||
"slug": "histoire",
|
||||
"parent": null
|
||||
},
|
||||
"alternativeLanguages": [
|
||||
{
|
||||
"code": "fr",
|
||||
"label": "Français",
|
||||
"page": "RC-023013",
|
||||
"url": "/fr/videos/RC-023013/l-incroyable-periple-de-magellan/",
|
||||
"title": "L'incroyable périple de Magellan"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"label": "Deutsch",
|
||||
"page": "RC-023013",
|
||||
"url": "/de/videos/RC-023013/die-abenteuerliche-weltreise-des-magellan/",
|
||||
"title": "Die abenteuerliche Weltreise des Magellan"
|
||||
}
|
||||
],
|
||||
"url": "/fr/videos/RC-023013/l-incroyable-periple-de-magellan/",
|
||||
"deeplink": "arte://collection/RC-023013",
|
||||
"slug": "l-incroyable-periple-de-magellan",
|
||||
"zones": [
|
||||
{
|
||||
"id": "51ba8da1-5389-43c4-af08-cca614192d77_RC-023013",
|
||||
"code": "collection_content_RC-023013",
|
||||
"title": "L'incroyable périple de Magellan",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "single-collectionContent",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "RC-023013",
|
||||
"type": "collection",
|
||||
"kind": {
|
||||
"code": "TV_SERIES",
|
||||
"label": "Série",
|
||||
"isCollection": true
|
||||
},
|
||||
"url": "/fr/videos/RC-023013/l-incroyable-periple-de-magellan/",
|
||||
"deeplink": "arte://collection/RC-023013",
|
||||
"title": "L'incroyable périple de Magellan",
|
||||
"subtitle": null,
|
||||
"shortDescription": "De 1519 à 1522, le navigateur portugais Fernand de Magellan et sa flotte réalisent le tout premier tour du monde par voie maritime. Une épopée hors du commun, empreinte d'erreurs et de trahisons mais aussi de rencontres. Cette série documentaire en quatre volets retrace un exploit maritime digne des plus grands romans d’aventure.",
|
||||
"mainImage": {
|
||||
"caption": "Magellan",
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/yKtLtmVraMDiGhCYFv8bnU/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "COLLECTION",
|
||||
"label": "COLLECTION"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&language=fr&pageid=collection&position=1&support=web&teaserid=RC-023013&teasertitle=L%27incroyable%20p%C3%A9riple%20de%20Magellan&zoneCode=collection_content_RC-023013&zoneIndexInPage=0&zoneTemplate=single_collectionContent&zoneid=collection_content&zonename=L%27incroyable%20p%C3%A9riple%20de%20Magellan",
|
||||
"trailer": null,
|
||||
"partners": null,
|
||||
"description": "De 1519 à 1522, le navigateur portugais Fernand de Magellan et sa flotte réalisent le tout premier tour du monde par voie maritime. Une épopée hors du commun, empreinte d'erreurs et de trahisons mais aussi de rencontres. Cette série documentaire en quatre volets retrace un exploit maritime digne des plus grands romans d’aventure.",
|
||||
"video": null,
|
||||
"availability": null
|
||||
}
|
||||
],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "d3bad9c8-b1c0-43db-87fb-fb95cc68766d_RC-023013",
|
||||
"code": "collection_videos_RC-023013",
|
||||
"title": "Toutes les vidéos",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "verticalFirstHighlighted-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "093644-001-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/093644-001-A/l-incroyable-periple-de-magellan-1-4/",
|
||||
"deeplink": "arte://program/093644-001-A",
|
||||
"title": "L'incroyable périple de Magellan (1/4)",
|
||||
"subtitle": "Le partage du monde",
|
||||
"shortDescription": "En 1519, entré au service du roi d’Espagne, le navigateur portugais Fernand de Magellan dirige la flotte qui bouclera, sans l’avoir décidé au départ, le premier tour du monde maritime de l’histoire. En quatre volets, le formidable récit d’une expédition historique, marquée par les drames.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/8irEnBpEuNCBCzr3vW5nZT/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=093644-001-A&language=fr&pageid=collection&position=1&support=web&teaserid=093644-001-A_fr&teasertitle=L%27incroyable%20p%C3%A9riple%20de%20Magellan%20%281%2F4%29&zoneCode=collection_videos_RC-023013&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "093644-001-A",
|
||||
"teaserText": "Entre 1519 et 1522, la flotte du navigateur portugais Fernand de Magellan réalisa le premier tour du monde de l’histoire.",
|
||||
"duration": 3153,
|
||||
"durationLabel": "53 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2022-11-12T04:00:00Z",
|
||||
"end": "2023-01-28T04:00:00Z",
|
||||
"upcomingDate": "2022-11-12T04:00:00Z",
|
||||
"label": "Disponible du 12/11/2022 au 27/01/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "093644-002-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/093644-002-A/l-incroyable-periple-de-magellan-2-4/",
|
||||
"deeplink": "arte://program/093644-002-A",
|
||||
"title": "L'incroyable périple de Magellan (2/4)",
|
||||
"subtitle": "Voyage au bord du monde",
|
||||
"shortDescription": "Entre 1519 et 1522, la flotte du navigateur portugais Fernand de Magellan réalisa le premier tour du monde de l’histoire. Deuxième volet du récit de cette expédition : pour rejoindre l’Orient par l’Occident, Magellan promet qu’il trouvera passage à travers l’Amérique et qu’il réussira à rejoindre les Indes, là où Christophe Colomb avait échoué.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/4Ki6dV3KtNtVk624X2qcMK/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=093644-002-A&language=fr&pageid=collection&position=2&support=web&teaserid=093644-002-A_fr&teasertitle=L%27incroyable%20p%C3%A9riple%20de%20Magellan%20%282%2F4%29&zoneCode=collection_videos_RC-023013&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "093644-002-A",
|
||||
"teaserText": "Pour rejoindre l’Orient par l’Occident, Magellan promet qu’il réussira à rejoindre les Indes, là où Christophe Colomb avait échoué.",
|
||||
"duration": 3282,
|
||||
"durationLabel": "55 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2022-11-12T04:00:00Z",
|
||||
"end": "2023-01-28T04:00:00Z",
|
||||
"upcomingDate": "2022-11-12T04:00:00Z",
|
||||
"label": "Disponible du 12/11/2022 au 27/01/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "093644-003-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/093644-003-A/l-incroyable-periple-de-magellan-3-4/",
|
||||
"deeplink": "arte://program/093644-003-A",
|
||||
"title": "L'incroyable périple de Magellan (3/4)",
|
||||
"subtitle": "Le royaume de Magellan",
|
||||
"shortDescription": "Entre 1519 et 1522, la flotte du navigateur portugais Fernand de Magellan réalisa le premier tour du monde de l’histoire. Troisième volet du récit de cette expédition : longeant la côte sud-américaine au-delà du Brésil, Magellan découvre au sud de l’Argentine un passage qui lui permet de s'enfoncer dans le continent américain.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/aF8bDFGRTD6pm9ocsR7LRG/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=093644-003-A&language=fr&pageid=collection&position=3&support=web&teaserid=093644-003-A_fr&teasertitle=L%27incroyable%20p%C3%A9riple%20de%20Magellan%20%283%2F4%29&zoneCode=collection_videos_RC-023013&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "093644-003-A",
|
||||
"teaserText": "Longeant la côte au-delà du Brésil, Magellan découvre un passage qui lui permet de s'enfoncer dans le continent américain.",
|
||||
"duration": 3089,
|
||||
"durationLabel": "52 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2022-11-12T04:00:00Z",
|
||||
"end": "2023-01-28T04:00:00Z",
|
||||
"upcomingDate": "2022-11-12T04:00:00Z",
|
||||
"label": "Disponible du 12/11/2022 au 27/01/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "093644-004-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/093644-004-A/l-incroyable-periple-de-magellan-4-4/",
|
||||
"deeplink": "arte://program/093644-004-A",
|
||||
"title": "L'incroyable périple de Magellan (4/4)",
|
||||
"subtitle": "Le premier tour du monde",
|
||||
"shortDescription": "Entre 1519 et 1522, la flotte du navigateur portugais Fernand de Magellan réalisa le premier tour du monde de l’histoire. Dernier volet : privé de Magellan, qui a succombé aux Philippines le 27 avril 1521, ainsi que de leurs meilleurs officiers, assassinés lors du piège tendu par le chef Lapu-Lapu, les équipages des deux navires restants poursuivent leur route.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/Qc5BebADZ6mrCvdnZaoxE8/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=093644-004-A&language=fr&pageid=collection&position=4&support=web&teaserid=093644-004-A_fr&teasertitle=L%27incroyable%20p%C3%A9riple%20de%20Magellan%20%284%2F4%29&zoneCode=collection_videos_RC-023013&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "093644-004-A",
|
||||
"teaserText": "Privé de Magellan, qui a succombé aux Philippines le 27 avril 1521, les équipages des deux navires restants poursuivent leur route.",
|
||||
"duration": 3197,
|
||||
"durationLabel": "54 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2022-11-12T04:00:00Z",
|
||||
"end": "2023-01-28T04:00:00Z",
|
||||
"upcomingDate": "2022-11-12T04:00:00Z",
|
||||
"label": "Disponible du 12/11/2022 au 27/01/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"totalCount": 4,
|
||||
"links": {
|
||||
"first": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/d3bad9c8-b1c0-43db-87fb-fb95cc68766d/content?abv=A&collectionId=RC-023013&page=1&pageId=collection&type=collection&zoneIndexInPage=1",
|
||||
"next": null,
|
||||
"last": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/d3bad9c8-b1c0-43db-87fb-fb95cc68766d/content?abv=A&collectionId=RC-023013&page=1&pageId=collection&type=collection&zoneIndexInPage=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5c5ab0fc-a32c-493c-bfa1-7d4eddca8834_RC-023013",
|
||||
"code": "collection_upcoming_RC-023013",
|
||||
"title": "Collection Upcomings",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3e4b7126-60ee-4740-9aa6-e0a92af2bfe1_RC-023013",
|
||||
"code": "collection_article_RC-023013",
|
||||
"title": "Collection Articles",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "125ee988-ab24-4bdf-a62c-703717d02164_RC-023013",
|
||||
"code": "collection_associated_RC-023013",
|
||||
"title": "Sur le même thème",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "RC-020554",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "TOPIC",
|
||||
"label": "Collection",
|
||||
"isCollection": true
|
||||
},
|
||||
"url": "/fr/videos/RC-020554/les-series-documentaires-d-histoire/",
|
||||
"deeplink": "arte://collection/RC-020554",
|
||||
"title": "Les séries documentaires d'histoire",
|
||||
"subtitle": null,
|
||||
"shortDescription": null,
|
||||
"mainImage": {
|
||||
"caption": "Séries Doc",
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/nzpxHm9yHWryG3TfPQMnSR/__SIZE__"
|
||||
},
|
||||
"stickers": null,
|
||||
"trackingPixel": "/ct/?abv=A&em=RC-020554&language=fr&pageid=collection&position=1&support=web&teaserid=RC-020554&teasertitle=Les%20s%C3%A9ries%20documentaires%20d%27histoire&zoneCode=collection_associated_RC-023013&zoneIndexInPage=4&zoneTemplate=horizontal_landscape&zoneid=collection_associated&zonename=Sur%20le%20m%C3%AAme%20th%C3%A8me",
|
||||
"programId": "RC-020554",
|
||||
"teaserText": null,
|
||||
"duration": null,
|
||||
"durationLabel": "Collection",
|
||||
"geoblocking": null,
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": null,
|
||||
"ageRating": null,
|
||||
"callToAction": "Watch",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"totalCount": 1,
|
||||
"links": {
|
||||
"first": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/125ee988-ab24-4bdf-a62c-703717d02164/content?abv=A&collectionId=RC-023013&page=1&pageId=collection&type=collection&zoneIndexInPage=4",
|
||||
"next": null,
|
||||
"last": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/125ee988-ab24-4bdf-a62c-703717d02164/content?abv=A&collectionId=RC-023013&page=1&pageId=collection&type=collection&zoneIndexInPage=4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "371699f8-dcfb-4abb-933a-83cc956a5d7d_RC-023013",
|
||||
"code": "collection_partner_RC-023013",
|
||||
"title": "Collection Partners",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"xiti": {
|
||||
"page_name": "Home_RC-023013_l-incroyable-periple-de-magellan",
|
||||
"chapter1": "HIS_histoire",
|
||||
"chapter2": "Série",
|
||||
"chapter3": "RC-023013_l-incroyable-periple-de-magellan",
|
||||
"x1": "fr",
|
||||
"x2": "Collection",
|
||||
"s2": 1,
|
||||
"siteId": "582046",
|
||||
"env_work": "prod",
|
||||
"search_keywords": null
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"title": "L'incroyable périple de Magellan",
|
||||
"description": "De 1519 à 1522, le navigateur portugais Fernand de Magellan et sa flotte réalisent le tout premier tour du monde par voie maritime. Une épopée hors du commun, empreinte d'erreurs et de trahisons mais aussi de rencontres. Cette série documentaire en quatre volets retrace un exploit maritime digne des plus grands romans d’aventure.",
|
||||
"seo": {
|
||||
"title": "L'incroyable périple de Magellan - Histoire | ARTE",
|
||||
"description": "De 1519 à 1522, le navigateur portugais Fernand de Magellan et sa flotte réalisent le tout premier tour du monde par voie maritime. Une épopée hors du commun, empreinte d'erreurs et de trahisons mais aussi de rencontres. Cette série documentaire en quatre volets retrace un exploit maritime digne des plus grands romans d’aventure.",
|
||||
"canonical": "/fr/videos/RC-023013/l-incroyable-periple-de-magellan/"
|
||||
},
|
||||
"og": {
|
||||
"image": {
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/yKtLtmVraMDiGhCYFv8bnU/1920x1080?type=TEXT&watermark=true",
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
}
|
||||
},
|
||||
"twitter": {
|
||||
"image": {
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/yKtLtmVraMDiGhCYFv8bnU/1920x1080?type=TEXT&watermark=true"
|
||||
},
|
||||
"site": "@ARTEfr"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"type": "collections",
|
||||
"redirect": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"initialType": "collections",
|
||||
"mamiBaseUrl": "https://api-cdn.arte.tv/api/mami/v1/",
|
||||
"locale": "fr",
|
||||
"tcStartFrom": null,
|
||||
"abvGroups": "A",
|
||||
"emacVersion": "v4"
|
||||
},
|
||||
"locale": "fr",
|
||||
"footerProps": {
|
||||
"main": [
|
||||
{
|
||||
"label": "Sites",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "ARTE VOD/DVD",
|
||||
"kind": "internal",
|
||||
"href": "https://boutique.arte.tv/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE Radio",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arteradio.com/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "Coups de cœur culture",
|
||||
"kind": "internal",
|
||||
"href": "https://my.arte.tv/index.php?lang=fr&page=eventsFavorite",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "Programmes en UHD ",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/videos/RC-022710/nos-programmes-en-uhd/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE Info Plus - Décryptez l'actualité",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/videos/RC-022628/arte-info-plus/",
|
||||
"rel": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Entreprise",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Tout sur ARTE",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Emplois et stages",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/particuliers-professionnels/#offres-demploi-et-de-stages",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Appels d'offres",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/appels-doffres/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Aide & contact",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/nous-repondons-a-vos-questions/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Streamer responsable",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/streaming-responsable/",
|
||||
"rel": "nofollow"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Infos légales",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Gérer les cookies",
|
||||
"kind": "cookie",
|
||||
"href": null,
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE et vos données personnelles",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/donnees-personnelles/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Mentions légales et crédits",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/credits/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "CGU",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/conditions-generales-dutilisation-cgu/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/accessibilite/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Plan du site",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/sitemap/",
|
||||
"rel": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Réseaux sociaux",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Facebook",
|
||||
"kind": "external",
|
||||
"href": "https://www.facebook.com/artetv",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Instagram",
|
||||
"kind": "external",
|
||||
"href": "https://www.instagram.com/artefr",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Youtube",
|
||||
"kind": "external",
|
||||
"href": "https://www.youtube.com/arteplus7",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Twitter",
|
||||
"kind": "external",
|
||||
"href": "https://www.twitter.com/artefr",
|
||||
"rel": "nofollow"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"geoblocking": "DE_FR",
|
||||
"serverTime": "2023-01-24T09:06:38.559Z",
|
||||
"__N_SSP": true
|
||||
},
|
||||
"page": "/videos/[...identifiers]",
|
||||
"query": {
|
||||
"identifiers": [
|
||||
"RC-023013",
|
||||
"l-incroyable-periple-de-magellan"
|
||||
]
|
||||
},
|
||||
"buildId": "230117085533",
|
||||
"assetPrefix": "https://static-cdn.arte.tv/replay",
|
||||
"runtimeConfig": {
|
||||
"emacVersion": "v4",
|
||||
"ebuBoxUrl": "https://reco.ebu.io/news-reco-arte.js",
|
||||
"ffABTesting": true,
|
||||
"ffDirectPlayerAutoPlay": "true",
|
||||
"ffEbuBox": false,
|
||||
"ffImagePlaceholder": false,
|
||||
"ffLivePage": true,
|
||||
"ffMeta": true,
|
||||
"ffNewGuideTv": true,
|
||||
"ffNewsletterZoneWithTeaserImage": false,
|
||||
"ffPlayerAutoPlay": "if_available",
|
||||
"ffProgramTrailer": false,
|
||||
"ffProfileMenu": false,
|
||||
"ffSettingsMenuTargetedComms": false,
|
||||
"ffSidaction": false,
|
||||
"ffThemeSwitch": false,
|
||||
"ffAtInternetSrcForce": true,
|
||||
"ffSettingsMenuVideoQuality": false,
|
||||
"ffSliderMetaInfoButtonSeeMore": false,
|
||||
"newsletterSubscribeUrl": "https://api.arte.tv/api/sso/v3/newsletter/subscribe",
|
||||
"tagCommanderUrl": "https://cdn.tagcommander.com/3445"
|
||||
},
|
||||
"isFallback": false,
|
||||
"gssp": true,
|
||||
"customServer": true,
|
||||
"appGip": true,
|
||||
"locale": "fr",
|
||||
"locales": [
|
||||
"fr",
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"pl",
|
||||
"it"
|
||||
],
|
||||
"defaultLocale": "fr",
|
||||
"scriptLoader": []
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,948 @@
|
|||
{
|
||||
"props": {
|
||||
"pageProps": {
|
||||
"geoblocking": null,
|
||||
"initialPage": {
|
||||
"tag": "Ok",
|
||||
"value": {
|
||||
"code": "RC-023242",
|
||||
"language": "fr",
|
||||
"support": "web",
|
||||
"type": "collection",
|
||||
"level": 3,
|
||||
"parent": {
|
||||
"type": "category",
|
||||
"page": "HIS",
|
||||
"label": "Histoire",
|
||||
"url": "/fr/videos/histoire/",
|
||||
"deeplink": "arte://emac/HIS",
|
||||
"id": "HIS_fr_web",
|
||||
"slug": "histoire",
|
||||
"parent": null
|
||||
},
|
||||
"alternativeLanguages": [
|
||||
{
|
||||
"code": "fr",
|
||||
"label": "Français",
|
||||
"page": "RC-023242",
|
||||
"url": "/fr/videos/RC-023242/bandes-de-pirates/",
|
||||
"title": "Bandes de pirates !"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"label": "Deutsch",
|
||||
"page": "RC-023242",
|
||||
"url": "/de/videos/RC-023242/die-schrecken-der-meere/",
|
||||
"title": "Die Schrecken der Meere"
|
||||
}
|
||||
],
|
||||
"url": "/fr/videos/RC-023242/bandes-de-pirates/",
|
||||
"deeplink": "arte://collection/RC-023242",
|
||||
"slug": "bandes-de-pirates",
|
||||
"zones": [
|
||||
{
|
||||
"id": "51ba8da1-5389-43c4-af08-cca614192d77_RC-023242",
|
||||
"code": "collection_content_RC-023242",
|
||||
"title": "Bandes de pirates !",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "single-collectionContent",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "RC-023242",
|
||||
"type": "collection",
|
||||
"kind": {
|
||||
"code": "TOPIC",
|
||||
"label": "Collection",
|
||||
"isCollection": true
|
||||
},
|
||||
"url": "/fr/videos/RC-023242/bandes-de-pirates/",
|
||||
"deeplink": "arte://collection/RC-023242",
|
||||
"title": "Bandes de pirates !",
|
||||
"subtitle": "Les aventuriers des océans",
|
||||
"shortDescription": "Aux XVII et XVIIIe siècles, les pirates et les corsaires sillonent les océans en quête d'aventures. Ils sont redoutables et intriguants et, du Capitaine Horatio Hornblower aux corsaires napoléonniens en passant par Francis Drake, ils ont alimenté nos imaginaires et contribuent encore à de fameuses légendes.",
|
||||
"mainImage": {
|
||||
"caption": "Pirates & Co.",
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/Jhjhuk8piMeNtdBsdM8Tjf/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "COLLECTION",
|
||||
"label": "COLLECTION"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&language=fr&pageid=collection&position=1&support=web&teaserid=RC-023242&teasertitle=Bandes%20de%20pirates%20%21&zoneCode=collection_content_RC-023242&zoneIndexInPage=0&zoneTemplate=single_collectionContent&zoneid=collection_content&zonename=Bandes%20de%20pirates%20%21",
|
||||
"trailer": null,
|
||||
"partners": null,
|
||||
"description": "Aux XVII et XVIIIe siècles, les pirates et les corsaires sillonent les océans en quête d'aventures. Ils sont redoutables et intriguants et, du Capitaine Horatio Hornblower aux corsaires napoléonniens en passant par Francis Drake, ils ont alimenté nos imaginaires et contribuent encore à de fameuses légendes.",
|
||||
"video": null,
|
||||
"availability": null
|
||||
}
|
||||
],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "d3bad9c8-b1c0-43db-87fb-fb95cc68766d_RC-023242",
|
||||
"code": "collection_videos_RC-023242",
|
||||
"title": "Toutes les vidéos",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "verticalFirstHighlighted-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "100814-000-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/100814-000-A/la-veritable-histoire-des-pirates/",
|
||||
"deeplink": "arte://program/100814-000-A",
|
||||
"title": "La véritable histoire des pirates",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Figures populaires de la littérature et du cinéma, les pirates ont écumé les mers aux XVIIe et XVIIIe siècles. Dans le sillage de deux campagnes de fouilles dirigées par l’archéologue Jean Soulat, Stéphane Bégoin nous entraîne sur leurs traces à l’île Maurice et à Madagascar.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/9fzDtM4epMJC4iJ3XoAFdT/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=100814-000-A&language=fr&pageid=collection&position=1&support=web&teaserid=100814-000-A_fr&teasertitle=La%20v%C3%A9ritable%20histoire%20des%20pirates&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "100814-000-A",
|
||||
"teaserText": "Qui étaient ces flibustiers qui sillonnaient les mers aux XVIIe et XVIIIe siècles pour s’emparer des cargaisons des navires marchands ?",
|
||||
"duration": 5523,
|
||||
"durationLabel": "93 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2022-12-31T04:00:00Z",
|
||||
"end": "2023-03-08T04:00:00Z",
|
||||
"upcomingDate": "2022-12-31T04:00:00Z",
|
||||
"label": "Disponible du 31/12/2022 au 07/03/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "063615-000-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/063615-000-A/a-la-decouverte-des-caraibes/",
|
||||
"deeplink": "arte://program/063615-000-A",
|
||||
"title": "À la découverte des Caraïbes",
|
||||
"subtitle": null,
|
||||
"shortDescription": "La biodiversité des îles caribéennes recèle de nombreux trésors, malheureusement les Caraïbes ne sont épargnées ni par les séismes ni par les cyclones aux effets dévastateurs. Explications.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/GKijReRUWQmpeMGPRQ6CVR/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=063615-000-A&language=fr&pageid=collection&position=2&support=web&teaserid=063615-000-A_fr&teasertitle=%C3%80%20la%20d%C3%A9couverte%20des%20Cara%C3%AFbes&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "063615-000-A",
|
||||
"teaserText": "Une riche biodiversité se maintient dans cette région frappée par les séismes et les cyclones.",
|
||||
"duration": 3092,
|
||||
"durationLabel": "52 min",
|
||||
"geoblocking": {
|
||||
"code": "EUR_DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-06T04:00:00Z",
|
||||
"end": "2023-02-06T04:00:00Z",
|
||||
"upcomingDate": "2023-01-06T04:00:00Z",
|
||||
"label": "Disponible du 06/01/2023 au 05/02/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "056769-002-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/056769-002-A/les-corsaires-barbaresques/",
|
||||
"deeplink": "arte://program/056769-002-A",
|
||||
"title": "Les corsaires barbaresques",
|
||||
"subtitle": null,
|
||||
"shortDescription": "Au XVIIIe siècle, l'Europe est terrorisée par les corsaires barbaresques. Ces derniers partent en quête de \"l'or blanc\" d'alors, c’est-à-dire des Européens des deux sexes, à la peau claire, qui seront vendus comme esclaves en Afrique du Nord et en Orient.",
|
||||
"mainImage": {
|
||||
"caption": "Pirates",
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/7VkG7xfCTvTwJc4amGZXnZ/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=056769-002-A&language=fr&pageid=collection&position=3&support=web&teaserid=056769-002-A_fr&teasertitle=Les%20corsaires%20barbaresques&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "056769-002-A",
|
||||
"teaserText": "Qui sont les corsaires barbaresques qui terrorisèrent l'Europe au XVIIIe siècle ?",
|
||||
"duration": 3000,
|
||||
"durationLabel": "50 min",
|
||||
"geoblocking": {
|
||||
"code": "DE_FR",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-06T04:00:00Z",
|
||||
"end": "2023-02-06T04:00:00Z",
|
||||
"upcomingDate": "2023-01-06T04:00:00Z",
|
||||
"label": "Disponible du 06/01/2023 au 05/02/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "056769-001-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/056769-001-A/francis-drake-corsaire-de-sa-majeste/",
|
||||
"deeplink": "arte://program/056769-001-A",
|
||||
"title": "Francis Drake, corsaire de Sa Majesté",
|
||||
"subtitle": null,
|
||||
"shortDescription": "XVIe siècle. Après la découverte de l'Amérique, le monde est dominé par l’empire espagnol du très catholique Philippe II. La jeune souveraine anglicane Élisabeth Ire s'en inquiète et veut que son royaume devienne une grande puissance maritime. Pour cela, elle fait appel au fascinant Francis Drake – qui avait commencé comme simple mousse.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/xS2y6wbE52WidzWpAYy6fW/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=056769-001-A&language=fr&pageid=collection&position=4&support=web&teaserid=056769-001-A_fr&teasertitle=Francis%20Drake%2C%20corsaire%20de%20Sa%20Majest%C3%A9&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "056769-001-A",
|
||||
"teaserText": "Voulant que son royaume devienne une grande puissance maritime, Élisabeth Ire fait appel au fascinant corsaire Francis Drake.",
|
||||
"duration": 3136,
|
||||
"durationLabel": "53 min",
|
||||
"geoblocking": {
|
||||
"code": "ALL",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-06T04:00:00Z",
|
||||
"end": "2023-02-06T04:00:00Z",
|
||||
"upcomingDate": "2023-01-06T04:00:00Z",
|
||||
"label": "Disponible du 06/01/2023 au 05/02/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "094484-002-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/094484-002-A/faire-l-histoire/",
|
||||
"deeplink": "arte://program/094484-002-A",
|
||||
"title": "Faire l'histoire",
|
||||
"subtitle": "Le drapeau pirate, contre les nations",
|
||||
"shortDescription": "Pièce maitresse du déguisement des enfants, le drapeau pirate est pourtant loin d'être un objet anecdotique. Guillaume Calafat nous explique qu'à travers l'histoire de cet étendard familier se joue une partie décisive pour les Etats-nations qui, à partir du XVIe siècle, se forgent autant sur les mers que par leurs frontières terrestres. Le drapeau pirate devient ainsi le révélateur indirect d'une histoire de la souveraineté.",
|
||||
"mainImage": {
|
||||
"caption": "Le drapeau pirate",
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/tHRt5eBbkC4LEpTjKiRiiU/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=094484-002-A&language=fr&pageid=collection&position=5&support=web&teaserid=094484-002-A_fr&teasertitle=Faire%20l%27histoire&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "094484-002-A",
|
||||
"teaserText": "Quand le drapeau pirate devient le révélateur indirect d'une histoire de la souveraineté.",
|
||||
"duration": 1028,
|
||||
"durationLabel": "18 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2021-09-16T09:10:00Z",
|
||||
"end": "2024-08-12T21:59:00Z",
|
||||
"upcomingDate": "2021-09-16T09:10:00Z",
|
||||
"label": "Disponible du 16/09/2021 au 12/08/2024"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "047323-001-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/047323-001-A/iles-de-beautes/",
|
||||
"deeplink": "arte://program/047323-001-A",
|
||||
"title": "Îles de beautés",
|
||||
"subtitle": "Zanzibar",
|
||||
"shortDescription": "Zanzibar se situe au large des côtes africaines, dans l'océan Indien. La jungle, vierge de présence humaine, reste le domaine de rongeurs géants, de singes mangeurs de charbon et de chauves-souris frugivores.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/rYc6LmUEYCrPTi5LxhkxQW/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=047323-001-A&language=fr&pageid=collection&position=6&support=web&teaserid=047323-001-A_fr&teasertitle=%C3%8Eles%20de%20beaut%C3%A9s&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "047323-001-A",
|
||||
"teaserText": "Au large des côtes africaines, dans l'océan Indien, Zanzibar a gardé un écosystème et une nature vierge.",
|
||||
"duration": 2583,
|
||||
"durationLabel": "44 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-06T04:00:00Z",
|
||||
"end": "2023-04-07T03:00:00Z",
|
||||
"upcomingDate": "2023-01-06T04:00:00Z",
|
||||
"label": "Disponible du 06/01/2023 au 06/04/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "047323-003-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/047323-003-A/iles-de-beautes/",
|
||||
"deeplink": "arte://program/047323-003-A",
|
||||
"title": "Îles de beautés",
|
||||
"subtitle": "Sri Lanka",
|
||||
"shortDescription": "À la découverte des faunes étranges qui peuplent le Sri Lanka. Située à quelques degrés de latitude nord, l'île émerge de l'océan Indien. Ses plaines vivent au rythme de la mousson. Inondées deux fois l'an, en mai et en octobre, elles souffrent de la sécheresse le reste de l'année.",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/9xHhLrauwcoq7VGCRb82PV/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=047323-003-A&language=fr&pageid=collection&position=7&support=web&teaserid=047323-003-A_fr&teasertitle=%C3%8Eles%20de%20beaut%C3%A9s&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "047323-003-A",
|
||||
"teaserText": "Inondées par la mousson en mai et en octobre, les plaines du Sri Lanka souffrent de la sécheresse le reste de l'année.",
|
||||
"duration": 2590,
|
||||
"durationLabel": "44 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-06T04:00:00Z",
|
||||
"end": "2023-04-07T03:00:00Z",
|
||||
"upcomingDate": "2023-01-06T04:00:00Z",
|
||||
"label": "Disponible du 06/01/2023 au 06/04/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "047323-004-A_fr",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "SHOW",
|
||||
"label": "Programme",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "/fr/videos/047323-004-A/iles-de-beautes/",
|
||||
"deeplink": "arte://program/047323-004-A",
|
||||
"title": "Îles de beautés",
|
||||
"subtitle": "Les Caraïbes",
|
||||
"shortDescription": "À la découverte des faunes étranges qui peuplent les Caraïbes, véritables laboratoires de l'évolution. Les plages de Trinidad sont un des lieux de ponte favoris des tortues-luth, qui sillonnaient déjà les mers du temps des dinosaures. Autre emblème des Caraïbes, le minuscule colibri, l'oiseau au métabolisme le plus rapide du monde",
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/mG773a698ovUhpEBU2ZBf8/__SIZE__"
|
||||
},
|
||||
"stickers": [
|
||||
{
|
||||
"code": "PLAYABLE",
|
||||
"label": "PLAYABLE"
|
||||
},
|
||||
{
|
||||
"code": "FULL_VIDEO",
|
||||
"label": "Voir le programme"
|
||||
}
|
||||
],
|
||||
"trackingPixel": "/ct/?abv=A&em=047323-004-A&language=fr&pageid=collection&position=8&support=web&teaserid=047323-004-A_fr&teasertitle=%C3%8Eles%20de%20beaut%C3%A9s&zoneCode=collection_videos_RC-023242&zoneIndexInPage=1&zoneTemplate=horizontal_landscape&zoneid=collection_videos&zonename=Toutes%20les%20vid%C3%A9os",
|
||||
"programId": "047323-004-A",
|
||||
"teaserText": "Tortues-luth, colibris : les Caraïbes sont peuplées d'une faune étrange, véritable laboratoire de l'évolution.",
|
||||
"duration": 2572,
|
||||
"durationLabel": "43 min",
|
||||
"geoblocking": {
|
||||
"code": "SAT",
|
||||
"label": "",
|
||||
"inclusion": [],
|
||||
"exclusion": []
|
||||
},
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": {
|
||||
"type": "VOD",
|
||||
"start": "2023-01-06T04:00:00Z",
|
||||
"end": "2023-04-07T03:00:00Z",
|
||||
"upcomingDate": "2023-01-06T04:00:00Z",
|
||||
"label": "Disponible du 06/01/2023 au 06/04/2023"
|
||||
},
|
||||
"ageRating": 0,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"totalCount": 8,
|
||||
"links": {
|
||||
"first": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/d3bad9c8-b1c0-43db-87fb-fb95cc68766d/content?abv=A&collectionId=RC-023242&page=1&pageId=collection&type=collection&zoneIndexInPage=1",
|
||||
"next": null,
|
||||
"last": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/d3bad9c8-b1c0-43db-87fb-fb95cc68766d/content?abv=A&collectionId=RC-023242&page=1&pageId=collection&type=collection&zoneIndexInPage=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5c5ab0fc-a32c-493c-bfa1-7d4eddca8834_RC-023242",
|
||||
"code": "collection_upcoming_RC-023242",
|
||||
"title": "Prochainement",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3e4b7126-60ee-4740-9aa6-e0a92af2bfe1_RC-023242",
|
||||
"code": "collection_article_RC-023242",
|
||||
"title": "Collection Articles",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "125ee988-ab24-4bdf-a62c-703717d02164_RC-023242",
|
||||
"code": "collection_associated_RC-023242",
|
||||
"title": "Sur le même thème",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [
|
||||
{
|
||||
"id": "https://www.arteradio.com/serie/la_derniere_nuit_d_anne_bonny/2393",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "EXTERNAL",
|
||||
"label": "Lien web",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "https://www.arteradio.com/serie/la_derniere_nuit_d_anne_bonny/2393",
|
||||
"deeplink": null,
|
||||
"title": "La dernière nuit d'Anne Bonny - Un podcast ARTE Radio",
|
||||
"subtitle": null,
|
||||
"shortDescription": null,
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/Jhjhuk8piMeNtdBsdM8Tjf/__SIZE__?photoId=32ce3f9271c297efb7a5dca15ba210c1603c998f"
|
||||
},
|
||||
"stickers": [],
|
||||
"trackingPixel": "/ct/?abv=A&language=fr&pageid=collection&position=1&support=web&teaserid=https%3A%2F%2Fwww.arteradio.com%2Fserie%2Fla_derniere_nuit_d_anne_bonny%2F2393&teasertitle=La%20derni%C3%A8re%20nuit%20d%27Anne%20Bonny%20-%20Un%20podcast%20ARTE%20Radio&zoneCode=collection_associated_RC-023242&zoneIndexInPage=4&zoneTemplate=horizontal_landscape&zoneid=collection_associated&zonename=Sur%20le%20m%C3%AAme%20th%C3%A8me",
|
||||
"programId": null,
|
||||
"teaserText": null,
|
||||
"duration": null,
|
||||
"durationLabel": null,
|
||||
"geoblocking": null,
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": null,
|
||||
"ageRating": null,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
},
|
||||
{
|
||||
"id": "https://www.arteradio.com/son/61675284/anne_bonny_la_reine_des_pirates_1_6",
|
||||
"type": "teaser",
|
||||
"kind": {
|
||||
"code": "EXTERNAL",
|
||||
"label": "Lien web",
|
||||
"isCollection": false
|
||||
},
|
||||
"url": "https://www.arteradio.com/son/61675284/anne_bonny_la_reine_des_pirates_1_6",
|
||||
"deeplink": null,
|
||||
"title": "Anne Bonny, la reine des pirates - Une fiction jeune public sur ARTE Radio",
|
||||
"subtitle": null,
|
||||
"shortDescription": null,
|
||||
"mainImage": {
|
||||
"caption": null,
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/Jhjhuk8piMeNtdBsdM8Tjf/__SIZE__?photoId=663343f621223cc6d55eed38146aa0162e9875b9"
|
||||
},
|
||||
"stickers": [],
|
||||
"trackingPixel": "/ct/?abv=A&language=fr&pageid=collection&position=2&support=web&teaserid=https%3A%2F%2Fwww.arteradio.com%2Fson%2F61675284%2Fanne_bonny_la_reine_des_pirates_1_6&teasertitle=Anne%20Bonny%2C%20la%20reine%20des%20pirates%20-%20Une%20fiction%20jeune%20public%20sur%20ARTE%20Radio&zoneCode=collection_associated_RC-023242&zoneIndexInPage=4&zoneTemplate=horizontal_landscape&zoneid=collection_associated&zonename=Sur%20le%20m%C3%AAme%20th%C3%A8me",
|
||||
"programId": null,
|
||||
"teaserText": null,
|
||||
"duration": null,
|
||||
"durationLabel": null,
|
||||
"geoblocking": null,
|
||||
"genre": null,
|
||||
"audioVersions": [],
|
||||
"availability": null,
|
||||
"ageRating": null,
|
||||
"callToAction": "Regarder",
|
||||
"clip": null,
|
||||
"trailer": null,
|
||||
"childrenCount": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"totalCount": 2,
|
||||
"links": {
|
||||
"first": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/125ee988-ab24-4bdf-a62c-703717d02164/content?abv=A&collectionId=RC-023242&page=1&pageId=collection&type=collection&zoneIndexInPage=4",
|
||||
"next": null,
|
||||
"last": "https://api-internal.arte.tv/api/emac/v4/fr/web/zones/125ee988-ab24-4bdf-a62c-703717d02164/content?abv=A&collectionId=RC-023242&page=1&pageId=collection&type=collection&zoneIndexInPage=4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "371699f8-dcfb-4abb-933a-83cc956a5d7d_RC-023242",
|
||||
"code": "collection_partner_RC-023242",
|
||||
"title": "Collection Partners",
|
||||
"slug": null,
|
||||
"description": null,
|
||||
"displayOptions": {
|
||||
"template": "horizontal-landscape",
|
||||
"theme": null,
|
||||
"showZoneTitle": true,
|
||||
"showItemTitle": true
|
||||
},
|
||||
"link": null,
|
||||
"authenticatedContent": null,
|
||||
"content": {
|
||||
"data": [],
|
||||
"pagination": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"stats": {
|
||||
"xiti": {
|
||||
"page_name": "Home_RC-023242_bandes-de-pirates",
|
||||
"chapter1": "HIS_histoire",
|
||||
"chapter2": "Collection",
|
||||
"chapter3": "RC-023242_bandes-de-pirates",
|
||||
"x1": "fr",
|
||||
"x2": "Collection",
|
||||
"s2": 1,
|
||||
"siteId": "582046",
|
||||
"env_work": "prod",
|
||||
"search_keywords": null
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"title": "Bandes de pirates !",
|
||||
"description": "Aux XVII et XVIIIe siècles, les pirates et les corsaires sillonent les océans en quête d'aventures. Ils sont redoutables et intriguants et, du Capitaine Horatio Hornblower aux corsaires napoléonniens en passant par Francis Drake, ils ont alimenté nos imaginaires et contribuent encore à de fameuses légendes.",
|
||||
"seo": {
|
||||
"title": "Bandes de pirates ! - Histoire | ARTE",
|
||||
"description": "Aux XVII et XVIIIe siècles, les pirates et les corsaires sillonent les océans en quête d'aventures. Ils sont redoutables et intriguants et, du Capitaine Horatio Hornblower aux corsaires napoléonniens en passant par Francis Drake, ils ont alimenté nos imaginaires et contribuent encore à de fameuses légendes.",
|
||||
"canonical": "/fr/videos/RC-023242/bandes-de-pirates/"
|
||||
},
|
||||
"og": {
|
||||
"image": {
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/Jhjhuk8piMeNtdBsdM8Tjf/1920x1080?type=TEXT&watermark=true",
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
}
|
||||
},
|
||||
"twitter": {
|
||||
"image": {
|
||||
"url": "https://api-cdn.arte.tv/img/v2/image/Jhjhuk8piMeNtdBsdM8Tjf/1920x1080?type=TEXT&watermark=true"
|
||||
},
|
||||
"site": "@ARTEfr"
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"type": "collections",
|
||||
"redirect": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"initialType": "collections",
|
||||
"mamiBaseUrl": "https://api-cdn.arte.tv/api/mami/v1/",
|
||||
"locale": "fr",
|
||||
"tcStartFrom": null,
|
||||
"abvGroups": "A",
|
||||
"emacVersion": "v4"
|
||||
},
|
||||
"locale": "fr",
|
||||
"footerProps": {
|
||||
"main": [
|
||||
{
|
||||
"label": "Sites",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "ARTE VOD/DVD",
|
||||
"kind": "internal",
|
||||
"href": "https://boutique.arte.tv/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE Radio",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arteradio.com/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "Coups de cœur culture",
|
||||
"kind": "internal",
|
||||
"href": "https://my.arte.tv/index.php?lang=fr&page=eventsFavorite",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "Programmes en UHD ",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/videos/RC-022710/nos-programmes-en-uhd/",
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE Info Plus - Décryptez l'actualité",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/videos/RC-022628/arte-info-plus/",
|
||||
"rel": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Entreprise",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Tout sur ARTE",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Emplois et stages",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/particuliers-professionnels/#offres-demploi-et-de-stages",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Appels d'offres",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/appels-doffres/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Aide & contact",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/nous-repondons-a-vos-questions/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Streamer responsable",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/streaming-responsable/",
|
||||
"rel": "nofollow"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Infos légales",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Gérer les cookies",
|
||||
"kind": "cookie",
|
||||
"href": null,
|
||||
"rel": null
|
||||
},
|
||||
{
|
||||
"label": "ARTE et vos données personnelles",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/donnees-personnelles/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Mentions légales et crédits",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/credits/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "CGU",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/conditions-generales-dutilisation-cgu/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Accessibilité",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/sites/corporate/accessibilite/",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Plan du site",
|
||||
"kind": "internal",
|
||||
"href": "https://www.arte.tv/fr/sitemap/",
|
||||
"rel": null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Réseaux sociaux",
|
||||
"href": null,
|
||||
"links": [
|
||||
{
|
||||
"label": "Facebook",
|
||||
"kind": "external",
|
||||
"href": "https://www.facebook.com/artetv",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Instagram",
|
||||
"kind": "external",
|
||||
"href": "https://www.instagram.com/artefr",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Youtube",
|
||||
"kind": "external",
|
||||
"href": "https://www.youtube.com/arteplus7",
|
||||
"rel": "nofollow"
|
||||
},
|
||||
{
|
||||
"label": "Twitter",
|
||||
"kind": "external",
|
||||
"href": "https://www.twitter.com/artefr",
|
||||
"rel": "nofollow"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"geoblocking": "DE_FR",
|
||||
"serverTime": "2023-01-24T08:43:36.718Z",
|
||||
"__N_SSP": true
|
||||
},
|
||||
"page": "/videos/[...identifiers]",
|
||||
"query": {
|
||||
"identifiers": [
|
||||
"RC-023242",
|
||||
"bandes-de-pirates"
|
||||
]
|
||||
},
|
||||
"buildId": "230117085533",
|
||||
"assetPrefix": "https://static-cdn.arte.tv/replay",
|
||||
"runtimeConfig": {
|
||||
"emacVersion": "v4",
|
||||
"ebuBoxUrl": "https://reco.ebu.io/news-reco-arte.js",
|
||||
"ffABTesting": true,
|
||||
"ffDirectPlayerAutoPlay": "true",
|
||||
"ffEbuBox": false,
|
||||
"ffImagePlaceholder": false,
|
||||
"ffLivePage": true,
|
||||
"ffMeta": true,
|
||||
"ffNewGuideTv": true,
|
||||
"ffNewsletterZoneWithTeaserImage": false,
|
||||
"ffPlayerAutoPlay": "if_available",
|
||||
"ffProgramTrailer": false,
|
||||
"ffProfileMenu": false,
|
||||
"ffSettingsMenuTargetedComms": false,
|
||||
"ffSidaction": false,
|
||||
"ffThemeSwitch": false,
|
||||
"ffAtInternetSrcForce": true,
|
||||
"ffSettingsMenuVideoQuality": false,
|
||||
"ffSliderMetaInfoButtonSeeMore": false,
|
||||
"newsletterSubscribeUrl": "https://api.arte.tv/api/sso/v3/newsletter/subscribe",
|
||||
"tagCommanderUrl": "https://cdn.tagcommander.com/3445"
|
||||
},
|
||||
"isFallback": false,
|
||||
"gssp": true,
|
||||
"customServer": true,
|
||||
"appGip": true,
|
||||
"locale": "fr",
|
||||
"locales": [
|
||||
"fr",
|
||||
"de",
|
||||
"en",
|
||||
"es",
|
||||
"pl",
|
||||
"it"
|
||||
],
|
||||
"defaultLocale": "fr",
|
||||
"scriptLoader": []
|
||||
}
|
|
@ -9,155 +9,165 @@ from .error import *
|
|||
from .model import *
|
||||
|
||||
|
||||
def fetch_sources(http_session, url):
|
||||
"""Fetch sources at a given ArteTV page URL."""
|
||||
from .api import fetch_program_info
|
||||
from .hls import fetch_program_tracks
|
||||
from .www import parse_url
|
||||
def fetch_program_sources(url, http_session):
|
||||
"""Fetch program sources listed on given ArteTV page."""
|
||||
from .www import iter_programs
|
||||
|
||||
site, program_id, slug = parse_url(url)
|
||||
|
||||
variants = dict()
|
||||
renditions = dict()
|
||||
|
||||
p_meta, program_index_urls = fetch_program_info(http_session, site, program_id)
|
||||
|
||||
program = Program(program_id, slug, p_meta)
|
||||
|
||||
for program_index_url in program_index_urls:
|
||||
v_tracks, a_track, s_track = fetch_program_tracks(
|
||||
http_session, program_index_url
|
||||
return [
|
||||
ProgramSource(
|
||||
program,
|
||||
player_config_url,
|
||||
)
|
||||
for v_meta, v_url in v_tracks:
|
||||
if v_meta not in variants:
|
||||
variants[v_meta] = v_url
|
||||
elif variants[v_meta] != v_url:
|
||||
raise ValueError
|
||||
for program, player_config_url in iter_programs(url, http_session)
|
||||
]
|
||||
|
||||
a_meta, a_url = a_track
|
||||
s_meta, s_url = s_track or (None, None)
|
||||
|
||||
if (a_meta, s_meta) not in renditions:
|
||||
renditions[(a_meta, s_meta)] = (a_url, s_url)
|
||||
elif renditions[(a_meta, s_meta)] != (a_url, s_url):
|
||||
raise ValueError
|
||||
def fetch_rendition_sources(program_sources, http_session):
|
||||
"""Fetch renditions for given programs."""
|
||||
from itertools import groupby
|
||||
|
||||
return Sources(
|
||||
program,
|
||||
[Variant(key, source) for key, source in variants.items()],
|
||||
[Rendition(key, source) for key, source in renditions.items()],
|
||||
from .api import iter_renditions
|
||||
|
||||
sources = [
|
||||
RenditionSource(
|
||||
program,
|
||||
rendition,
|
||||
protocol,
|
||||
program_index_url,
|
||||
)
|
||||
for program, player_config_url in program_sources
|
||||
for rendition, protocol, program_index_url in iter_renditions(
|
||||
program.id,
|
||||
player_config_url,
|
||||
http_session,
|
||||
)
|
||||
]
|
||||
|
||||
descriptors = list({(s.rendition.code, s.rendition.label) for s in sources})
|
||||
|
||||
descriptors.sort()
|
||||
for code, group in groupby(descriptors, key=lambda t: t[0]):
|
||||
labels_for_code = [t[1] for t in group]
|
||||
if len(labels_for_code) != 1:
|
||||
raise UnexpectedError("MULTIPLE_RENDITION_LABELS", code, labels_for_code)
|
||||
|
||||
return sources
|
||||
|
||||
|
||||
def fetch_variant_sources(renditions_sources, http_session):
|
||||
"""Fetch variants for given renditions."""
|
||||
from itertools import groupby
|
||||
|
||||
from .hls import iter_variants
|
||||
|
||||
sources = [
|
||||
VariantSource(
|
||||
program,
|
||||
rendition,
|
||||
variant,
|
||||
VariantSource.VideoMedia(*video),
|
||||
VariantSource.AudioMedia(*audio),
|
||||
VariantSource.SubtitlesMedia(*subtitles) if subtitles else None,
|
||||
)
|
||||
for program, rendition, protocol, program_index_url in renditions_sources
|
||||
for variant, video, audio, subtitles in iter_variants(
|
||||
protocol, program_index_url, http_session
|
||||
)
|
||||
]
|
||||
|
||||
descriptors = list(
|
||||
{(s.variant.code, s.video_media.track.frame_rate) for s in sources}
|
||||
)
|
||||
|
||||
descriptors.sort()
|
||||
for code, group in groupby(descriptors, key=lambda t: t[0]):
|
||||
frame_rates_for_code = [t[1] for t in group]
|
||||
if len(frame_rates_for_code) != 1:
|
||||
raise UnexpectedError(
|
||||
"MULTIPLE_RENDITION_FRAME_RATES", code, frame_rates_for_code
|
||||
)
|
||||
|
||||
def iter_renditions(sources):
|
||||
"""Iterate over renditions (code, key) of the given sources."""
|
||||
keys = [r.key for r in sources.renditions]
|
||||
|
||||
keys.sort(
|
||||
key=lambda k: (
|
||||
not k[0].is_original,
|
||||
k[0].language,
|
||||
k[0].is_descriptive,
|
||||
k[1].language if k[1] else "",
|
||||
k[1].is_descriptive if k[1] else False,
|
||||
)
|
||||
)
|
||||
|
||||
for (a_meta, s_meta) in keys:
|
||||
code = a_meta.language
|
||||
|
||||
if a_meta.is_descriptive:
|
||||
code += "[AD]"
|
||||
|
||||
if s_meta:
|
||||
if s_meta.is_descriptive:
|
||||
code += f"-{s_meta.language}[CC]"
|
||||
elif s_meta.language != a_meta.language:
|
||||
code += f"-{s_meta.language}"
|
||||
|
||||
yield code, (a_meta, s_meta)
|
||||
return sources
|
||||
|
||||
|
||||
def select_rendition(sources, key):
|
||||
"""Reject all other renditions from the given sources."""
|
||||
renditions = [r for r in sources.renditions if r.key == key]
|
||||
match len(renditions):
|
||||
case 0:
|
||||
raise ValueError("rendition not found")
|
||||
case 1:
|
||||
pass
|
||||
case _:
|
||||
raise ValueError("non unique rendition")
|
||||
|
||||
sources.renditions[:] = renditions
|
||||
|
||||
|
||||
def iter_variants(sources):
|
||||
"""Iterate over variants (code, key) of the given sources."""
|
||||
import itertools
|
||||
|
||||
keys = [v.key for v in sources.variants]
|
||||
|
||||
keys.sort(key=lambda k: (k.height, k.frame_rate), reverse=True)
|
||||
|
||||
for height, group in itertools.groupby(keys, lambda m: m.height):
|
||||
group = list(group)
|
||||
if len(group) == 1:
|
||||
yield f"{height}p", group[0]
|
||||
else:
|
||||
for m in group:
|
||||
yield f"{height}p@{m.frame_rate}", m
|
||||
|
||||
|
||||
def select_variant(sources, key):
|
||||
"""Reject all other variants from the given sources."""
|
||||
variants = [v for v in sources.variants if v.key == key]
|
||||
match len(variants):
|
||||
case 0:
|
||||
raise ValueError("variant not found")
|
||||
case 1:
|
||||
pass
|
||||
case _:
|
||||
raise ValueError("non unique variant")
|
||||
|
||||
sources.variants[:] = variants
|
||||
|
||||
|
||||
def compile_sources(sources, **naming_options):
|
||||
"""Return target from the given sources."""
|
||||
def fetch_targets(variant_sources, http_session, **naming_options):
|
||||
"""Compile download targets for given variants."""
|
||||
from .hls import fetch_mp4_media, fetch_vtt_media
|
||||
from .naming import file_name_builder
|
||||
|
||||
match len(sources.variants):
|
||||
case 0:
|
||||
raise ValueError("no variants")
|
||||
case 1:
|
||||
v_meta, v_url = sources.variants[0]
|
||||
case _:
|
||||
raise ValueError("multiple variants")
|
||||
build_file_name = file_name_builder(**naming_options)
|
||||
|
||||
match len(sources.renditions):
|
||||
case 0:
|
||||
raise ValueError("no renditions")
|
||||
case 1:
|
||||
(a_meta, s_meta), (a_url, s_url) = sources.renditions[0]
|
||||
case _:
|
||||
raise ValueError("multiple renditions")
|
||||
targets = [
|
||||
Target(
|
||||
Target.VideoInput(
|
||||
video_media.track,
|
||||
fetch_mp4_media(video_media.track_index_url, http_session),
|
||||
),
|
||||
Target.AudioInput(
|
||||
audio_media.track,
|
||||
fetch_mp4_media(audio_media.track_index_url, http_session),
|
||||
),
|
||||
(
|
||||
Target.SubtitlesInput(
|
||||
subtitles_media.track,
|
||||
fetch_vtt_media(subtitles_media.track_index_url, http_session),
|
||||
)
|
||||
if subtitles_media
|
||||
else None
|
||||
),
|
||||
(program.title, program.subtitle) if program.subtitle else program.title,
|
||||
build_file_name(program, rendition, variant),
|
||||
)
|
||||
for program, rendition, variant, video_media, audio_media, subtitles_media in variant_sources
|
||||
]
|
||||
|
||||
build_file_name = file_name_builder(v_meta, a_meta, s_meta, **naming_options)
|
||||
|
||||
return Target(
|
||||
sources.program.meta,
|
||||
VideoTrack(v_meta, v_url),
|
||||
AudioTrack(a_meta, a_url),
|
||||
SubtitlesTrack(s_meta, s_url) if s_meta else None,
|
||||
build_file_name(sources.program),
|
||||
)
|
||||
return targets
|
||||
|
||||
|
||||
def download_target(http_session, target, progress):
|
||||
"""Download the given target."""
|
||||
from .hls import download_target_tracks
|
||||
def download_targets(targets, http_session, on_progress):
|
||||
"""Download given target."""
|
||||
import os
|
||||
|
||||
from .download import download_mp4_media, download_vtt_media
|
||||
from .muxing import mux_target
|
||||
|
||||
with download_target_tracks(http_session, target, progress) as local_target:
|
||||
mux_target(local_target, progress)
|
||||
for target in targets:
|
||||
|
||||
video_path = target.output + ".video.mp4"
|
||||
audio_path = target.output + ".audio.mp4"
|
||||
subtitles_path = target.output + ".srt"
|
||||
|
||||
download_mp4_media(
|
||||
target.video_input.url, video_path, http_session, on_progress
|
||||
)
|
||||
|
||||
download_mp4_media(
|
||||
target.audio_input.url, audio_path, http_session, on_progress
|
||||
)
|
||||
|
||||
if target.subtitles_input:
|
||||
download_vtt_media(
|
||||
target.subtitles_input.url, subtitles_path, http_session, on_progress
|
||||
)
|
||||
|
||||
mux_target(
|
||||
target._replace(
|
||||
video_input=target.video_input._replace(url=video_path),
|
||||
audio_input=target.audio_input._replace(url=audio_path),
|
||||
subtitles_input=(
|
||||
target.subtitles_input._replace(url=subtitles_path)
|
||||
if target.subtitles_input
|
||||
else None
|
||||
),
|
||||
),
|
||||
on_progress,
|
||||
)
|
||||
|
||||
if os.path.isfile(subtitles_path):
|
||||
os.unlink(subtitles_path)
|
||||
|
||||
if os.path.isfile(audio_path):
|
||||
os.unlink(audio_path)
|
||||
|
||||
if os.path.isfile(video_path):
|
||||
os.unlink(video_path)
|
||||
|
|
|
@ -23,13 +23,14 @@ Options:
|
|||
--version print current version of the program
|
||||
--debug on error, print debugging information
|
||||
--name-use-id use the program ID
|
||||
--name-use-slug use the URL slug
|
||||
--name-sep=<sep> field separator [default: - ]
|
||||
--name-seq-pfx=<pfx> sequence counter prefix [default: - ]
|
||||
--name-seq-no-pad disable sequence zero-padding
|
||||
--name-add-resolution add resolution tag
|
||||
--name-add-rendition add rendition code
|
||||
--name-add-variant add variant code
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
@ -37,16 +38,15 @@ import docopt
|
|||
import requests
|
||||
|
||||
from . import (
|
||||
ModuleError,
|
||||
UnexpectedError,
|
||||
__version__,
|
||||
compile_sources,
|
||||
download_target,
|
||||
fetch_sources,
|
||||
iter_renditions,
|
||||
iter_variants,
|
||||
select_rendition,
|
||||
select_variant,
|
||||
download_targets,
|
||||
fetch_program_sources,
|
||||
fetch_rendition_sources,
|
||||
fetch_targets,
|
||||
fetch_variant_sources,
|
||||
)
|
||||
from .error import ModuleError, UnexpectedError
|
||||
|
||||
|
||||
class Abort(ModuleError):
|
||||
|
@ -57,131 +57,104 @@ class Fail(UnexpectedError):
|
|||
"""Unexpected error."""
|
||||
|
||||
|
||||
_LANGUAGES = {
|
||||
"de": "German",
|
||||
"en": "English",
|
||||
"es": "Spanish",
|
||||
"fr": "French",
|
||||
"it": "Italian",
|
||||
"mul": "multiple language",
|
||||
"no": "Norwegian",
|
||||
"pt": "Portuguese",
|
||||
}
|
||||
def _create_progress():
|
||||
# create a progress handler for input downloads
|
||||
state = {}
|
||||
|
||||
|
||||
def _language_name_for_code(code):
|
||||
return _LANGUAGES.get(code, f"[{code}]")
|
||||
|
||||
|
||||
def _language_name(meta):
|
||||
return _language_name_for_code(meta.language)
|
||||
|
||||
|
||||
def _print_renditions(renditions):
|
||||
has_original = False
|
||||
for code, (a_meta, s_meta) in renditions:
|
||||
label = _language_name(a_meta)
|
||||
if a_meta.is_original:
|
||||
has_original = True
|
||||
label = "original " + label
|
||||
elif a_meta.is_descriptive:
|
||||
label += " audio description"
|
||||
elif has_original:
|
||||
label += " dubbed"
|
||||
|
||||
if s_meta:
|
||||
if s_meta.is_descriptive:
|
||||
label += f" ({_language_name(s_meta)} closed captions)"
|
||||
elif s_meta.language != a_meta.language:
|
||||
label += f" ({_language_name(s_meta)} subtitles)"
|
||||
|
||||
print(f"\t{code:>6} - {label}")
|
||||
|
||||
|
||||
def _validate_rendition(renditions, code):
|
||||
for code_, rendition in renditions:
|
||||
if code_ == code:
|
||||
break
|
||||
else:
|
||||
print(f"{code!r} is not a valid rendition code, possible values are:")
|
||||
_print_renditions(renditions)
|
||||
raise Abort()
|
||||
|
||||
return rendition
|
||||
|
||||
|
||||
def _print_variants(variants):
|
||||
for code, _ in variants:
|
||||
print(f"\t{code}")
|
||||
|
||||
|
||||
def _validate_variant(variants, code):
|
||||
for code_, variant in variants:
|
||||
if code_ == code:
|
||||
break
|
||||
else:
|
||||
print(f"{code!r} is not a valid variant code, possible values are:")
|
||||
_print_variants(variants)
|
||||
raise Abort()
|
||||
|
||||
return variant
|
||||
|
||||
|
||||
def create_progress():
|
||||
"""Create a progress handler for input downloads."""
|
||||
state = {
|
||||
"last_update_time": 0,
|
||||
"last_channel": None,
|
||||
}
|
||||
|
||||
def progress(channel, current, total):
|
||||
def on_progress(file, current, total):
|
||||
now = time.time()
|
||||
|
||||
if current == total:
|
||||
print(f"\rDownloading {channel}: 100.0%")
|
||||
state["last_update_time"] = now
|
||||
elif channel != state["last_channel"]:
|
||||
print(f"Downloading {channel}: 0.0%", end="")
|
||||
state["last_update_time"] = now
|
||||
state["last_channel"] = channel
|
||||
elif now - state["last_update_time"] > 1:
|
||||
if current == 0:
|
||||
print(f"Downloading {file!r}: 0.0%", end="")
|
||||
state["start_time"] = now
|
||||
state["last_time"] = now
|
||||
state["last_count"] = 0
|
||||
|
||||
elif current == total:
|
||||
elapsed_time = now - state["start_time"]
|
||||
rate = int(total / elapsed_time) if elapsed_time else "NaN"
|
||||
print(f"\rDownloading {file!r}: 100.0% [{rate}]")
|
||||
state.clear()
|
||||
|
||||
elif now - state["last_time"] > 1:
|
||||
elapsed_time1 = now - state["start_time"]
|
||||
elapsed_time2 = now - state["last_time"]
|
||||
progress = int(1000.0 * current / total) / 10.0
|
||||
rate1 = int(current / elapsed_time1) if elapsed_time1 else "NaN"
|
||||
rate2 = (
|
||||
int((current - state["last_count"]) / elapsed_time2)
|
||||
if elapsed_time2
|
||||
else "NaN"
|
||||
)
|
||||
print(
|
||||
f"\rDownloading {channel}: {int(1000.0 * current / total) / 10.0}%",
|
||||
f"\rDownloading {file!r}: {progress}% [{rate1}, {rate2}]",
|
||||
end="",
|
||||
)
|
||||
state["last_update_time"] = now
|
||||
state["last_time"] = now
|
||||
state["last_count"] = current
|
||||
|
||||
return progress
|
||||
return on_progress
|
||||
|
||||
|
||||
def _select_rendition_sources(rendition_code, rendition_sources):
|
||||
if rendition_code:
|
||||
filtered = [s for s in rendition_sources if s.rendition.code == rendition_code]
|
||||
if filtered:
|
||||
return filtered
|
||||
print(
|
||||
f"{rendition_code!r} is not a valid rendition code. Available values are:"
|
||||
)
|
||||
else:
|
||||
print("Available renditions:")
|
||||
|
||||
key = lambda s: (s.rendition.label, s.rendition.code)
|
||||
|
||||
rendition_sources.sort(key=key)
|
||||
for (label, code), _ in itertools.groupby(rendition_sources, key=key):
|
||||
print(f"{code:>12} : {label}")
|
||||
|
||||
raise Abort()
|
||||
|
||||
|
||||
def _select_variant_sources(variant_code, variant_sources):
|
||||
if variant_code:
|
||||
filtered = [s for s in variant_sources if s.variant.code == variant_code]
|
||||
if filtered:
|
||||
return filtered
|
||||
print(f"{variant_code!r} is not a valid variant code. Available values are:")
|
||||
else:
|
||||
print("Available variants:")
|
||||
|
||||
variant_sources.sort(key=lambda s: s.video_media.track.height, reverse=True)
|
||||
for code, _ in itertools.groupby(variant_sources, key=lambda s: s.variant.code):
|
||||
print(f"{code:>12}")
|
||||
|
||||
raise Abort()
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI command."""
|
||||
args = docopt.docopt(__doc__, sys.argv[1:], version=__version__)
|
||||
|
||||
http_session = requests.sessions.Session()
|
||||
|
||||
try:
|
||||
|
||||
http_session = requests.sessions.Session()
|
||||
program_sources = fetch_program_sources(args["URL"], http_session)
|
||||
|
||||
sources = fetch_sources(http_session, args["URL"])
|
||||
rendition_sources = _select_rendition_sources(
|
||||
args["RENDITION"],
|
||||
fetch_rendition_sources(program_sources, http_session),
|
||||
)
|
||||
|
||||
renditions = list(iter_renditions(sources))
|
||||
if not args["RENDITION"]:
|
||||
print(f"Available renditions:")
|
||||
_print_renditions(renditions)
|
||||
return 0
|
||||
variant_sources = _select_variant_sources(
|
||||
args["VARIANT"],
|
||||
fetch_variant_sources(rendition_sources, http_session),
|
||||
)
|
||||
|
||||
select_rendition(sources, _validate_rendition(renditions, args["RENDITION"]))
|
||||
|
||||
variants = list(iter_variants(sources))
|
||||
if not args["VARIANT"]:
|
||||
print(f"Available variants:")
|
||||
_print_variants(variants)
|
||||
return 0
|
||||
|
||||
select_variant(sources, _validate_variant(variants, args["VARIANT"]))
|
||||
|
||||
target = compile_sources(
|
||||
sources,
|
||||
targets = fetch_targets(
|
||||
variant_sources,
|
||||
http_session,
|
||||
**{
|
||||
k[7:].replace("-", "_"): v
|
||||
for k, v in args.items()
|
||||
|
@ -189,9 +162,7 @@ def main():
|
|||
},
|
||||
)
|
||||
|
||||
progress = create_progress()
|
||||
|
||||
download_target(http_session, target, progress)
|
||||
download_targets(targets, http_session, _create_progress())
|
||||
|
||||
except UnexpectedError as e:
|
||||
print(str(e))
|
||||
|
|
|
@ -3,62 +3,67 @@
|
|||
|
||||
"""Provide ArteTV JSON API utilities."""
|
||||
|
||||
from .error import UnexpectedAPIResponse, UnsupportedHLSProtocol
|
||||
from .model import ProgramMeta
|
||||
from .error import UnexpectedAPIResponse
|
||||
from .model import Rendition
|
||||
|
||||
MIME_TYPE = "application/vnd.api+json; charset=utf-8"
|
||||
|
||||
|
||||
def _fetch_api_data(http_session, path, object_type):
|
||||
def _fetch_api_object(http_session, url, object_type):
|
||||
# Fetch an API object.
|
||||
url = "https://api.arte.tv/api/player/v2/" + path
|
||||
|
||||
r = http_session.get(url)
|
||||
r.raise_for_status()
|
||||
|
||||
if (_ := r.headers["content-type"]) != MIME_TYPE:
|
||||
raise UnexpectedAPIResponse("MIME_TYPE", path, MIME_TYPE, _)
|
||||
mime_type = r.headers["content-type"]
|
||||
if mime_type != MIME_TYPE:
|
||||
raise UnexpectedAPIResponse("MIME_TYPE", url, MIME_TYPE, mime_type)
|
||||
|
||||
obj = r.json()["data"]
|
||||
obj = r.json()
|
||||
|
||||
if (_ := obj["type"]) != object_type:
|
||||
raise UnexpectedAPIResponse("OBJECT_TYPE", path, object_type, _)
|
||||
try:
|
||||
data_type = obj["data"]["type"]
|
||||
if data_type != object_type:
|
||||
raise UnexpectedAPIResponse("OBJECT_TYPE", url, object_type, data_type)
|
||||
|
||||
return obj["attributes"]
|
||||
return obj["data"]["attributes"]
|
||||
|
||||
except (KeyError, IndexError, ValueError) as e:
|
||||
raise UnexpectedAPIResponse("SCHEMA", url) from e
|
||||
|
||||
|
||||
def fetch_program_info(http_session, site, program_id):
|
||||
"""Fetch the given program metadata and indexes."""
|
||||
obj = _fetch_api_data(http_session, f"config/{site}/{program_id}", "ConfigPlayer")
|
||||
def iter_renditions(program_id, player_config_url, http_session):
|
||||
"""Iterate over renditions for the given program."""
|
||||
obj = _fetch_api_object(http_session, player_config_url, "ConfigPlayer")
|
||||
|
||||
if (_ := obj["metadata"]["providerId"]) != program_id:
|
||||
raise UnexpectedAPIResponse(
|
||||
"PROGRAM_ID_MISMATCH",
|
||||
site,
|
||||
program_id,
|
||||
_,
|
||||
)
|
||||
|
||||
program_meta = ProgramMeta(
|
||||
obj["metadata"]["title"],
|
||||
obj["metadata"]["subtitle"],
|
||||
obj["metadata"]["description"],
|
||||
)
|
||||
|
||||
program_index_urls = set()
|
||||
|
||||
for s in obj["streams"]:
|
||||
if (_ := s["protocol"]) != "HLS_NG":
|
||||
raise UnsupportedHLSProtocol(site, program_id, _)
|
||||
|
||||
if (program_index_url := s["url"]) in program_index_urls:
|
||||
codes = set()
|
||||
try:
|
||||
provider_id = obj["metadata"]["providerId"]
|
||||
if provider_id != program_id:
|
||||
raise UnexpectedAPIResponse(
|
||||
"DUPLICATE_PROGRAM_INDEX_URL",
|
||||
site,
|
||||
program_id,
|
||||
program_index_url,
|
||||
"PROVIDER_ID_MISMATCH", player_config_url, provider_id
|
||||
)
|
||||
|
||||
program_index_urls.add(program_index_url)
|
||||
for s in obj["streams"]:
|
||||
code = s["versions"][0]["eStat"]["ml5"]
|
||||
|
||||
return program_meta, program_index_urls
|
||||
if code in codes:
|
||||
raise UnexpectedAPIResponse(
|
||||
"DUPLICATE_RENDITION_CODE", player_config_url, code
|
||||
)
|
||||
codes.add(code)
|
||||
|
||||
yield (
|
||||
Rendition(
|
||||
s["versions"][0]["eStat"]["ml5"],
|
||||
s["versions"][0]["label"],
|
||||
),
|
||||
s["protocol"],
|
||||
s["url"],
|
||||
)
|
||||
|
||||
except (KeyError, IndexError, ValueError) as e:
|
||||
raise UnexpectedAPIResponse("SCHEMA", player_config_url) from e
|
||||
|
||||
if not codes:
|
||||
raise UnexpectedAPIResponse("NO_RENDITIONS", player_config_url)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
# License: GNU AGPL v3: http://www.gnu.org/licenses/
|
||||
# This file is part of `delarte` (https://git.afpy.org/fcode/delarte.git)
|
||||
|
||||
"""Provide download utilities."""
|
||||
import os
|
||||
|
||||
from . import subtitles
|
||||
|
||||
_CHUNK = 64 * 1024
|
||||
|
||||
|
||||
def download_mp4_media(url, file_name, http_session, on_progress):
|
||||
"""Download a MP4 (video or audio) to given file."""
|
||||
on_progress(file_name, 0, 0)
|
||||
|
||||
if os.path.isfile(file_name):
|
||||
on_progress(file_name, 1, 1)
|
||||
return
|
||||
|
||||
temp_file = f"{file_name}.tmp"
|
||||
|
||||
with open(temp_file, "w+b") as f:
|
||||
r = http_session.get(url, timeout=5, stream=True)
|
||||
r.raise_for_status()
|
||||
total = int(r.headers["content-length"])
|
||||
|
||||
for content in r.iter_content(_CHUNK):
|
||||
f.write(content)
|
||||
on_progress(file_name, f.tell(), total)
|
||||
|
||||
os.rename(temp_file, file_name)
|
||||
|
||||
|
||||
def download_vtt_media(url, file_name, http_session, on_progress):
|
||||
"""Download a VTT and SRT-convert it to to given file."""
|
||||
on_progress(file_name, 0, 0)
|
||||
|
||||
if os.path.isfile(file_name):
|
||||
on_progress(file_name, 1, 1)
|
||||
return
|
||||
|
||||
temp_file = f"{file_name}.tmp"
|
||||
|
||||
with open(temp_file, "w", encoding="utf-8") as f:
|
||||
r = http_session.get(url, timeout=5)
|
||||
r.raise_for_status()
|
||||
r.encoding = "utf-8"
|
||||
|
||||
subtitles.convert(r.text, f)
|
||||
on_progress(file_name, f.tell(), f.tell())
|
||||
|
||||
os.rename(temp_file, file_name)
|
|
@ -16,18 +16,39 @@ class ModuleError(Exception):
|
|||
return f"{self.__class__}{self.args!r}"
|
||||
|
||||
|
||||
class ExpectedError(ModuleError):
|
||||
"""A feature limitation to submit as an enhancement to developers."""
|
||||
|
||||
|
||||
class UnexpectedError(ModuleError):
|
||||
"""An error to report to developers."""
|
||||
|
||||
|
||||
class InvalidUrl(ModuleError):
|
||||
"""Invalid ArteTV URL."""
|
||||
#
|
||||
# www
|
||||
#
|
||||
class PageNotFound(ModuleError):
|
||||
"""Page not found at ArteTV."""
|
||||
|
||||
|
||||
class PageNotSupported(ExpectedError):
|
||||
"""The page you are trying to download from is not (yet) supported."""
|
||||
|
||||
|
||||
class InvalidPage(UnexpectedError):
|
||||
"""Invalid ArteTV page."""
|
||||
|
||||
|
||||
#
|
||||
# api
|
||||
#
|
||||
class UnexpectedAPIResponse(UnexpectedError):
|
||||
"""Unexpected response from ArteTV."""
|
||||
|
||||
|
||||
#
|
||||
# hls
|
||||
#
|
||||
class UnexpectedHLSResponse(UnexpectedError):
|
||||
"""Unexpected response from ArteTV."""
|
||||
|
||||
|
@ -36,5 +57,8 @@ class UnsupportedHLSProtocol(ModuleError):
|
|||
"""Program type not supported."""
|
||||
|
||||
|
||||
#
|
||||
# subtitles
|
||||
#
|
||||
class WebVTTError(UnexpectedError):
|
||||
"""Unexpected WebVTT data."""
|
||||
|
|
|
@ -4,23 +4,10 @@
|
|||
"""Provide HLS protocol utilities."""
|
||||
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
import m3u8
|
||||
|
||||
from . import subtitles
|
||||
from .error import UnexpectedHLSResponse
|
||||
from .model import (
|
||||
AudioMeta,
|
||||
AudioTrack,
|
||||
SubtitlesMeta,
|
||||
SubtitlesTrack,
|
||||
VideoMeta,
|
||||
VideoTrack,
|
||||
Target,
|
||||
)
|
||||
from .error import UnexpectedHLSResponse, UnsupportedHLSProtocol
|
||||
from .model import AudioTrack, SubtitlesTrack, Variant, VideoTrack
|
||||
|
||||
#
|
||||
# WARNING !
|
||||
|
@ -40,7 +27,7 @@ from .model import (
|
|||
MIME_TYPE = "application/x-mpegURL"
|
||||
|
||||
|
||||
def _fetch_index(http_session, url):
|
||||
def _fetch_index(url, http_session):
|
||||
# Fetch a M3U8 playlist
|
||||
r = http_session.get(url)
|
||||
r.raise_for_status()
|
||||
|
@ -53,9 +40,12 @@ def _fetch_index(http_session, url):
|
|||
return m3u8.loads(r.text, url)
|
||||
|
||||
|
||||
def fetch_program_tracks(http_session, program_index_url):
|
||||
"""Fetch video, audio and subtitles tracks for the given program index."""
|
||||
program_index = _fetch_index(http_session, program_index_url)
|
||||
def iter_variants(protocol, program_index_url, http_session):
|
||||
"""Iterate over variants for the given rendition."""
|
||||
if protocol != "HLS_NG":
|
||||
raise UnsupportedHLSProtocol(protocol, program_index_url)
|
||||
|
||||
program_index = _fetch_index(program_index_url, http_session)
|
||||
|
||||
audio_media = None
|
||||
subtitles_media = None
|
||||
|
@ -78,8 +68,9 @@ def fetch_program_tracks(http_session, program_index_url):
|
|||
if not audio_media:
|
||||
raise UnexpectedHLSResponse("NO_AUDIO_MEDIA", program_index_url)
|
||||
|
||||
audio_track = AudioTrack(
|
||||
AudioMeta(
|
||||
audio = (
|
||||
AudioTrack(
|
||||
audio_media.name,
|
||||
audio_media.language,
|
||||
audio_media.name.startswith("VO"),
|
||||
(
|
||||
|
@ -90,9 +81,10 @@ def fetch_program_tracks(http_session, program_index_url):
|
|||
audio_media.absolute_uri,
|
||||
)
|
||||
|
||||
subtitles_track = (
|
||||
SubtitlesTrack(
|
||||
SubtitlesMeta(
|
||||
subtitles = (
|
||||
(
|
||||
SubtitlesTrack(
|
||||
subtitles_media.name,
|
||||
subtitles_media.language,
|
||||
(
|
||||
subtitles_media.characteristics is not None
|
||||
|
@ -105,7 +97,7 @@ def fetch_program_tracks(http_session, program_index_url):
|
|||
else None
|
||||
)
|
||||
|
||||
video_tracks = set()
|
||||
codes = set()
|
||||
|
||||
for video_media in program_index.playlists:
|
||||
stream_info = video_media.stream_info
|
||||
|
@ -117,33 +109,39 @@ def fetch_program_tracks(http_session, program_index_url):
|
|||
if subtitles_media:
|
||||
if stream_info.subtitles != subtitles_media.group_id:
|
||||
raise UnexpectedHLSResponse(
|
||||
"INVALID_SUBTITLES_MEDIA",
|
||||
program_index_url,
|
||||
stream_info.subtitles,
|
||||
"INVALID_SUBTITLES_MEDIA", program_index_url, stream_info.subtitles
|
||||
)
|
||||
elif stream_info.subtitles:
|
||||
raise UnexpectedHLSResponse(
|
||||
"INVALID_SUBTITLES_MEDIA",
|
||||
program_index_url,
|
||||
stream_info.subtitles,
|
||||
"INVALID_SUBTITLES_MEDIA", program_index_url, stream_info.subtitles
|
||||
)
|
||||
|
||||
video_track = VideoTrack(
|
||||
VideoMeta(
|
||||
stream_info.resolution[0],
|
||||
stream_info.resolution[1],
|
||||
stream_info.frame_rate,
|
||||
code = f"{stream_info.resolution[1]}p"
|
||||
if code in codes:
|
||||
raise UnexpectedHLSResponse(
|
||||
"DUPLICATE_STREAM_CODE", program_index_url, code
|
||||
)
|
||||
codes.add(code)
|
||||
|
||||
yield (
|
||||
Variant(
|
||||
code,
|
||||
stream_info.average_bandwidth,
|
||||
),
|
||||
video_media.absolute_uri,
|
||||
(
|
||||
VideoTrack(
|
||||
stream_info.resolution[0],
|
||||
stream_info.resolution[1],
|
||||
stream_info.frame_rate,
|
||||
),
|
||||
video_media.absolute_uri,
|
||||
),
|
||||
audio,
|
||||
subtitles,
|
||||
)
|
||||
|
||||
if video_track in video_tracks:
|
||||
raise UnexpectedHLSResponse(
|
||||
"DUPLICATE_VIDEO_TRACK", program_index_url, video_track
|
||||
)
|
||||
video_tracks.add(video_track)
|
||||
|
||||
return video_tracks, audio_track, subtitles_track
|
||||
if not codes:
|
||||
raise UnexpectedHLSResponse("NO_VARIANTS", program_index_url)
|
||||
|
||||
|
||||
def _convert_byterange(obj):
|
||||
|
@ -154,18 +152,16 @@ def _convert_byterange(obj):
|
|||
return offset, offset + count - 1
|
||||
|
||||
|
||||
def _fetch_av_index(http_session, track_index_url):
|
||||
# Fetch an audio or video track index.
|
||||
# Return a tuple:
|
||||
# - the media file url
|
||||
# - the media file's ranges
|
||||
track_index = _fetch_index(http_session, track_index_url)
|
||||
def fetch_mp4_media(track_index_url, http_session):
|
||||
"""Fetch an audio or video media."""
|
||||
track_index = _fetch_index(track_index_url, http_session)
|
||||
|
||||
file_name = track_index.segment_map[0].uri
|
||||
start, end = _convert_byterange(track_index.segment_map[0])
|
||||
if start != 0:
|
||||
raise UnexpectedHLSResponse("INVALID_AV_INDEX_FRAGMENT_START", track_index_url)
|
||||
ranges = [(start, end)]
|
||||
|
||||
# ranges = [(start, end)]
|
||||
next_start = end + 1
|
||||
|
||||
for segment in track_index.segments:
|
||||
|
@ -178,16 +174,15 @@ def _fetch_av_index(http_session, track_index_url):
|
|||
"DISCONTINUOUS_AV_INDEX_FRAGMENT", track_index_url
|
||||
)
|
||||
|
||||
ranges.append((start, end))
|
||||
# ranges.append((start, end))
|
||||
next_start = end + 1
|
||||
|
||||
return track_index.segment_map[0].absolute_uri, ranges
|
||||
return track_index.segment_map[0].absolute_uri
|
||||
|
||||
|
||||
def _fetch_s_index(http_session, track_index_url):
|
||||
# Fetch subtitles index.
|
||||
# Return the subtitle file url.
|
||||
track_index = _fetch_index(http_session, track_index_url)
|
||||
def fetch_vtt_media(track_index_url, http_session):
|
||||
"""Fetch an audio or video media."""
|
||||
track_index = _fetch_index(track_index_url, http_session)
|
||||
urls = [s.absolute_uri for s in track_index.segments]
|
||||
|
||||
if not urls:
|
||||
|
@ -197,112 +192,3 @@ def _fetch_s_index(http_session, track_index_url):
|
|||
raise UnexpectedHLSResponse("MULTIPLE_S_INDEX_FILES", track_index_url)
|
||||
|
||||
return urls[0]
|
||||
|
||||
|
||||
def _download_av_track(http_session, track_index_url, progress):
|
||||
# Download an audio or video data to temporary file.
|
||||
# Return the temporary file path.
|
||||
url, ranges = _fetch_av_index(http_session, track_index_url)
|
||||
total = ranges[-1][1]
|
||||
|
||||
with (
|
||||
NamedTemporaryFile(
|
||||
mode="w+b", delete=False, prefix="delarte.", suffix=".mp4"
|
||||
) as f
|
||||
):
|
||||
for range_start, range_end in ranges:
|
||||
r = http_session.get(
|
||||
url,
|
||||
headers={
|
||||
"Range": f"bytes={range_start}-{range_end}",
|
||||
},
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
r.raise_for_status()
|
||||
|
||||
if r.status_code != 206:
|
||||
raise UnexpectedHLSResponse(
|
||||
"UNEXPECTED_AV_TRACK_HTTP_STATUS",
|
||||
track_index_url,
|
||||
r.request.headers,
|
||||
r.status,
|
||||
)
|
||||
|
||||
if len(r.content) != range_end - range_start + 1:
|
||||
raise UnexpectedHLSResponse(
|
||||
"INVALID_AV_TRACK_FRAGMENT_LENGTH", track_index_url
|
||||
)
|
||||
f.write(r.content)
|
||||
|
||||
progress(range_end, total)
|
||||
|
||||
return f.name
|
||||
|
||||
|
||||
def _download_s_track(http_session, track_index_url, progress):
|
||||
# Download a subtitle file (converted from VTT to SRT format) into a temporary file.
|
||||
# Return the temporary file path.
|
||||
url = _fetch_s_index(http_session, track_index_url)
|
||||
|
||||
progress(0, 2)
|
||||
r = http_session.get(url)
|
||||
r.raise_for_status()
|
||||
r.encoding = "utf-8"
|
||||
progress(1, 2)
|
||||
|
||||
with NamedTemporaryFile(
|
||||
"w", delete=False, prefix="delarte.", suffix=".srt", encoding="utf8"
|
||||
) as f:
|
||||
subtitles.convert(r.text, f)
|
||||
progress(2, 2)
|
||||
return f.name
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def download_target_tracks(http_session, target, progress):
|
||||
"""Download target tracks to temporary files.
|
||||
|
||||
Returns a context manager that will delete the temporary files on exit.
|
||||
The context expression is a local version of the given target.
|
||||
"""
|
||||
v_path, (v_meta, v_url) = None, target.video_track
|
||||
a_path, (a_meta, a_url) = None, target.audio_track
|
||||
s_path, (s_meta, s_url) = None, target.subtitles_track or (None, None)
|
||||
|
||||
try:
|
||||
s_path = (
|
||||
_download_s_track(
|
||||
http_session,
|
||||
s_url,
|
||||
lambda i, n: progress("subtitles", i, n),
|
||||
)
|
||||
if s_meta
|
||||
else None
|
||||
)
|
||||
|
||||
a_path = _download_av_track(
|
||||
http_session, a_url, lambda i, n: progress("audio", i, n)
|
||||
)
|
||||
|
||||
v_path = _download_av_track(
|
||||
http_session, v_url, lambda i, n: progress("video", i, n)
|
||||
)
|
||||
|
||||
yield Target(
|
||||
target.program,
|
||||
VideoTrack(v_meta, v_path),
|
||||
AudioTrack(a_meta, a_path),
|
||||
SubtitlesTrack(s_meta, s_path) if s_meta else None,
|
||||
target.file_name,
|
||||
)
|
||||
|
||||
finally:
|
||||
if v_path and os.path.isfile(v_path):
|
||||
os.unlink(v_path)
|
||||
|
||||
if a_path and os.path.isfile(a_path):
|
||||
os.unlink(a_path)
|
||||
|
||||
if s_path and os.path.isfile(s_path):
|
||||
os.unlink(s_path)
|
||||
|
|
|
@ -7,111 +7,131 @@
|
|||
from typing import NamedTuple, Optional
|
||||
|
||||
|
||||
class ProgramMeta(NamedTuple):
|
||||
#
|
||||
# Metadata objects
|
||||
#
|
||||
class Program(NamedTuple):
|
||||
"""A program metadata."""
|
||||
|
||||
id: str
|
||||
language: str
|
||||
title: str
|
||||
"""The title."""
|
||||
|
||||
subtitle: str
|
||||
"""The subtitle or secondary title."""
|
||||
|
||||
description: str
|
||||
"""The description."""
|
||||
|
||||
|
||||
class VideoMeta(NamedTuple):
|
||||
"""A video track metadata."""
|
||||
class Rendition(NamedTuple):
|
||||
"""A program rendition metadata."""
|
||||
|
||||
width: int
|
||||
"""Horizontal part of the resolution."""
|
||||
|
||||
height: int
|
||||
"""Vertical part of the resolution."""
|
||||
|
||||
frame_rate: float
|
||||
"""Frame rate per seconds."""
|
||||
code: str
|
||||
label: str
|
||||
|
||||
|
||||
class SubtitlesMeta(NamedTuple):
|
||||
"""A subtitles track metadata."""
|
||||
class Variant(NamedTuple):
|
||||
"""A program variant metadata."""
|
||||
|
||||
language: str
|
||||
"""ISO 639-1 two-letter language codes."""
|
||||
|
||||
is_descriptive: bool
|
||||
"""Whether provides a textual description (closed captions)."""
|
||||
|
||||
|
||||
class AudioMeta(NamedTuple):
|
||||
"""A audio track metadata."""
|
||||
|
||||
language: str
|
||||
"""ISO 639-1 two-letter language codes, or "mul" for multiple languages."""
|
||||
|
||||
is_original: bool
|
||||
"""Whether audio track is original (no audio description or dubbing)."""
|
||||
|
||||
is_descriptive: bool
|
||||
"""Whether provides an audio description."""
|
||||
code: str
|
||||
average_bandwidth: int
|
||||
|
||||
|
||||
#
|
||||
# Track objects
|
||||
#
|
||||
class VideoTrack(NamedTuple):
|
||||
"""A video track."""
|
||||
|
||||
meta: VideoMeta
|
||||
url: str
|
||||
width: int
|
||||
height: int
|
||||
frame_rate: float
|
||||
|
||||
|
||||
class AudioTrack(NamedTuple):
|
||||
"""An audio track."""
|
||||
|
||||
name: str
|
||||
language: str
|
||||
original: bool
|
||||
visual_impaired: bool
|
||||
|
||||
|
||||
class SubtitlesTrack(NamedTuple):
|
||||
"""A subtitles track."""
|
||||
|
||||
meta: SubtitlesMeta
|
||||
url: str
|
||||
name: str
|
||||
language: str
|
||||
hearing_impaired: bool
|
||||
|
||||
|
||||
class AudioTrack(NamedTuple):
|
||||
"""A audio track."""
|
||||
|
||||
meta: AudioMeta
|
||||
url: str
|
||||
|
||||
|
||||
class Variant(NamedTuple):
|
||||
"""A program variant."""
|
||||
|
||||
key: VideoMeta
|
||||
source: str
|
||||
|
||||
|
||||
class Rendition(NamedTuple):
|
||||
"""A program rendition."""
|
||||
|
||||
key: tuple[AudioMeta, Optional[SubtitlesMeta]]
|
||||
source: tuple[str, Optional[str]]
|
||||
|
||||
|
||||
class Program(NamedTuple):
|
||||
"""A program representation."""
|
||||
|
||||
id: str
|
||||
slug: str
|
||||
meta: ProgramMeta
|
||||
|
||||
|
||||
class Sources(NamedTuple):
|
||||
"""A program's sources."""
|
||||
#
|
||||
# Source objects
|
||||
#
|
||||
class ProgramSource(NamedTuple):
|
||||
"""A program source item."""
|
||||
|
||||
program: Program
|
||||
variants: list[Variant]
|
||||
renditions: list[Rendition]
|
||||
player_config_url: str
|
||||
|
||||
|
||||
class RenditionSource(NamedTuple):
|
||||
"""A rendition source item."""
|
||||
|
||||
program: Program
|
||||
rendition: Rendition
|
||||
protocol: str
|
||||
program_index_url: Program
|
||||
|
||||
|
||||
class VariantSource(NamedTuple):
|
||||
"""A variant source item."""
|
||||
|
||||
class VideoMedia(NamedTuple):
|
||||
"""A video media."""
|
||||
|
||||
track: VideoTrack
|
||||
track_index_url: str
|
||||
|
||||
class AudioMedia(NamedTuple):
|
||||
"""An audio media."""
|
||||
|
||||
track: AudioTrack
|
||||
track_index_url: str
|
||||
|
||||
class SubtitlesMedia(NamedTuple):
|
||||
"""A subtitles media."""
|
||||
|
||||
track: SubtitlesTrack
|
||||
track_index_url: str
|
||||
|
||||
program: Program
|
||||
rendition: Rendition
|
||||
variant: Variant
|
||||
video_media: VideoMedia
|
||||
audio_media: AudioMedia
|
||||
subtitles_media: Optional[SubtitlesMedia]
|
||||
|
||||
|
||||
class Target(NamedTuple):
|
||||
"""A download target."""
|
||||
"""A download target item."""
|
||||
|
||||
program: ProgramMeta
|
||||
video_track: VideoTrack
|
||||
audio_track: AudioTrack
|
||||
subtitles_track: Optional[SubtitlesTrack]
|
||||
file_name: str
|
||||
class VideoInput(NamedTuple):
|
||||
"""A video input."""
|
||||
|
||||
track: VideoTrack
|
||||
url: str
|
||||
|
||||
class AudioInput(NamedTuple):
|
||||
"""An audio input."""
|
||||
|
||||
track: AudioTrack
|
||||
url: str
|
||||
|
||||
class SubtitlesInput(NamedTuple):
|
||||
"""A subtitles input."""
|
||||
|
||||
track: SubtitlesTrack
|
||||
url: str
|
||||
|
||||
video_input: VideoInput
|
||||
audio_input: AudioInput
|
||||
subtitles_input: Optional[SubtitlesInput]
|
||||
title: str | tuple[str, str]
|
||||
output: str
|
||||
|
|
|
@ -1,33 +1,74 @@
|
|||
# License: GNU AGPL v3: http://www.gnu.org/licenses/
|
||||
# This file is part of `delarte` (https://git.afpy.org/fcode/delarte.git)
|
||||
|
||||
"""Provide tracks muxing utilities."""
|
||||
"""Provide target muxing utilities."""
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def mux_target(target, _progress):
|
||||
"""Multiplexes tracks into a single file."""
|
||||
"""Multiplexes target into a single file."""
|
||||
cmd = ["ffmpeg", "-hide_banner"]
|
||||
cmd.extend(["-i", target.video_track.url])
|
||||
cmd.extend(["-i", target.audio_track.url])
|
||||
if target.subtitles_track:
|
||||
cmd.extend(["-i", target.subtitles_track.url])
|
||||
|
||||
# inputs
|
||||
cmd.extend(["-i", target.video_input.url])
|
||||
cmd.extend(["-i", target.audio_input.url])
|
||||
if target.subtitles_input:
|
||||
cmd.extend(["-i", target.subtitles_input.url])
|
||||
|
||||
# codecs
|
||||
cmd.extend(["-c:v", "copy"])
|
||||
cmd.extend(["-c:a", "copy"])
|
||||
if target.subtitles_track:
|
||||
if target.subtitles_input:
|
||||
cmd.extend(["-c:s", "copy"])
|
||||
|
||||
cmd.extend(["-bsf:a", "aac_adtstoasc"])
|
||||
cmd.extend(["-metadata:s:a:0", f"language={target.audio_track.meta.language}"])
|
||||
|
||||
if target.subtitles_track:
|
||||
# stream metadata & disposition
|
||||
# cmd.extend(["-metadata:s:v:0", f"name={target.video.name!r}"])
|
||||
# cmd.extend(["-metadata:s:v:0", f"language={target.video.language!r}"])
|
||||
|
||||
cmd.extend(["-metadata:s:a:0", f"name={target.audio_input.track.name}"])
|
||||
cmd.extend(["-metadata:s:a:0", f"language={target.audio_input.track.language}"])
|
||||
|
||||
a_disposition = "default"
|
||||
if target.audio_input.track.original:
|
||||
a_disposition += "+original"
|
||||
else:
|
||||
a_disposition += "-original"
|
||||
|
||||
if target.audio_input.track.visual_impaired:
|
||||
a_disposition += "+visual_impaired"
|
||||
else:
|
||||
a_disposition += "-visual_impaired"
|
||||
|
||||
cmd.extend(["-disposition:a:0", a_disposition])
|
||||
|
||||
if target.subtitles_input:
|
||||
cmd.extend(["-metadata:s:s:0", f"name={target.subtitles_input.track.name}"])
|
||||
cmd.extend(
|
||||
["-metadata:s:s:0", f"language={target.subtitles_track.meta.language}"]
|
||||
["-metadata:s:s:0", f"language={target.subtitles_input.track.language}"]
|
||||
)
|
||||
cmd.extend(["-disposition:s:0", "default"])
|
||||
|
||||
cmd.append(f"{target.file_name}.mkv")
|
||||
s_disposition = "default"
|
||||
|
||||
if target.subtitles_input.track.hearing_impaired:
|
||||
s_disposition += "+hearing_impaired+descriptions"
|
||||
else:
|
||||
s_disposition += "-hearing_impaired-descriptions"
|
||||
|
||||
cmd.extend(["-disposition:s:0", s_disposition])
|
||||
|
||||
# file metadata
|
||||
if isinstance(target.title, tuple):
|
||||
cmd.extend(["-metadata", f"title={target.title[0]}"])
|
||||
cmd.extend(["-metadata", f"subtitle={target.title[1]}"])
|
||||
else:
|
||||
cmd.extend(["-metadata", f"title={target.title}"])
|
||||
|
||||
# output
|
||||
cmd.append(f"{target.output}.mkv")
|
||||
|
||||
print(cmd)
|
||||
|
||||
subprocess.run(cmd)
|
||||
|
|
|
@ -4,23 +4,17 @@
|
|||
"""Provide contextualized based file naming utility."""
|
||||
import re
|
||||
|
||||
from typing import Optional
|
||||
from .model import Program, VideoMeta, AudioMeta, SubtitlesMeta
|
||||
|
||||
|
||||
def file_name_builder(
|
||||
v_meta: VideoMeta,
|
||||
a_meta: AudioMeta,
|
||||
s_meta: Optional[SubtitlesMeta],
|
||||
*,
|
||||
use_id=False,
|
||||
use_slug=False,
|
||||
sep=" - ",
|
||||
seq_pfx=" - ",
|
||||
seq_no_pad=False,
|
||||
add_resolution=False,
|
||||
add_rendition=False,
|
||||
add_variant=False
|
||||
):
|
||||
"""Create a file namer from context."""
|
||||
"""Create a file namer."""
|
||||
|
||||
def sub_sequence_counter(match):
|
||||
index = match[1]
|
||||
|
@ -32,20 +26,20 @@ def file_name_builder(
|
|||
def replace_sequence_counter(s: str) -> str:
|
||||
return re.sub(r"\s+\((\d+)/(\d+)\)", sub_sequence_counter, s)
|
||||
|
||||
def build_file_name(program: Program) -> str:
|
||||
"""Create a file name for given program."""
|
||||
def build_file_name(program, rendition, variant):
|
||||
"""Create a file name."""
|
||||
if use_id:
|
||||
return program.id
|
||||
|
||||
if use_slug:
|
||||
return program.slug
|
||||
fields = [replace_sequence_counter(program.title)]
|
||||
if program.subtitle:
|
||||
fields.append(replace_sequence_counter(program.subtitle))
|
||||
|
||||
fields = [replace_sequence_counter(program.meta.title)]
|
||||
if program.meta.subtitle:
|
||||
fields.add(replace_sequence_counter(program.meta.subtitles))
|
||||
if add_rendition:
|
||||
fields.append(rendition.code)
|
||||
|
||||
if add_resolution:
|
||||
fields.append(f"{v_meta.height}p")
|
||||
if add_variant:
|
||||
fields.append(variant.code)
|
||||
|
||||
name = sep.join(fields)
|
||||
name = re.sub(r'[/:<>"\\|?*]', "", name)
|
||||
|
|
|
@ -7,7 +7,6 @@ import re
|
|||
|
||||
from .error import WebVTTError
|
||||
|
||||
|
||||
RE_CUE_START = r"^((?:\d\d:)\d\d:\d\d)\.(\d\d\d) --> ((?:\d\d:)\d\d:\d\d)\.(\d\d\d)"
|
||||
RE_STYLED_CUE = r"^<c\.(\w+)\.bg_(?:\w+)>(.*)</c>$"
|
||||
|
||||
|
|
|
@ -3,28 +3,135 @@
|
|||
|
||||
"""Provide ArteTV website utilities."""
|
||||
|
||||
from .error import InvalidUrl
|
||||
import json
|
||||
|
||||
BASE = "https://www.arte.tv/"
|
||||
SITES = ["fr", "de", "en", "es", "pl", "it"]
|
||||
from .error import InvalidPage, PageNotFound, PageNotSupported
|
||||
from .model import Program
|
||||
|
||||
_DATA_MARK = '<script id="__NEXT_DATA__" type="application/json">'
|
||||
|
||||
|
||||
def parse_url(url):
|
||||
"""Parse ArteTV web URL into target ID and web UI language."""
|
||||
if not url.startswith(BASE):
|
||||
raise InvalidUrl("BASE", url)
|
||||
def _process_programs_page(page_value):
|
||||
|
||||
path = url[len(BASE) :].split("/")
|
||||
language = page_value["language"]
|
||||
|
||||
site = path.pop(0)
|
||||
zone_found = False
|
||||
program_found = False
|
||||
|
||||
if site not in SITES:
|
||||
raise InvalidUrl("SITE", url, site)
|
||||
for zone in page_value["zones"]:
|
||||
if zone["code"].startswith("program_content_"):
|
||||
if zone_found:
|
||||
raise InvalidPage("PROGRAMS_CONTENT_ZONES_COUNT")
|
||||
zone_found = True
|
||||
else:
|
||||
continue
|
||||
|
||||
if (_ := path.pop(0)) != "videos":
|
||||
raise InvalidUrl("PATH", url, _)
|
||||
for data_item in zone["content"]["data"]:
|
||||
if data_item["type"] == "program":
|
||||
if program_found:
|
||||
raise InvalidPage("PROGRAMS_CONTENT_PROGRAM_COUNT")
|
||||
program_found = True
|
||||
else:
|
||||
raise InvalidPage("PROGRAMS_CONTENT_PROGRAM_TYPE")
|
||||
|
||||
id = path.pop(0)
|
||||
slug = path.pop(0)
|
||||
yield (
|
||||
Program(
|
||||
data_item["programId"],
|
||||
language,
|
||||
data_item["title"],
|
||||
data_item["subtitle"],
|
||||
),
|
||||
data_item["player"]["config"],
|
||||
)
|
||||
|
||||
return site, id, slug
|
||||
if not zone_found:
|
||||
raise InvalidPage("PROGRAMS_CONTENT_ZONES_COUNT")
|
||||
|
||||
if not program_found:
|
||||
raise InvalidPage("PROGRAMS_CONTENT_PROGRAM_COUNT")
|
||||
|
||||
|
||||
def _process_collections_page(page_value):
|
||||
|
||||
language = page_value["language"]
|
||||
|
||||
main_zone_found = False
|
||||
sub_zone_found = False
|
||||
program_found = False
|
||||
|
||||
for zone in page_value["zones"]:
|
||||
if zone["code"].startswith("collection_videos_"):
|
||||
if main_zone_found:
|
||||
raise InvalidPage("COLLECTIONS_MAIN_ZONE_COUNT")
|
||||
if program_found:
|
||||
raise InvalidPage("COLLECTIONS_MIXED_ZONES")
|
||||
main_zone_found = True
|
||||
elif zone["code"].startswith("collection_subcollection_"):
|
||||
if program_found and not sub_zone_found:
|
||||
raise InvalidPage("COLLECTIONS_MIXED_ZONES")
|
||||
sub_zone_found = True
|
||||
else:
|
||||
continue
|
||||
|
||||
for data_item in zone["content"]["data"]:
|
||||
if (_ := data_item["type"]) == "teaser":
|
||||
program_found = True
|
||||
else:
|
||||
raise InvalidPage("COLLECTIONS_INVALID_CONTENT_DATA_ITEM", _)
|
||||
|
||||
yield (
|
||||
Program(
|
||||
data_item["programId"],
|
||||
language,
|
||||
data_item["title"],
|
||||
data_item["subtitle"],
|
||||
),
|
||||
f"https://api.arte.tv/api/player/v2/config/{language}/{data_item['programId']}",
|
||||
)
|
||||
|
||||
if not main_zone_found:
|
||||
raise InvalidPage("COLLECTIONS_MAIN_ZONE_COUNT")
|
||||
|
||||
if not program_found:
|
||||
raise InvalidPage("COLLECTIONS_PROGRAMS_COUNT")
|
||||
|
||||
|
||||
def iter_programs(page_url, http_session):
|
||||
"""Iterate over programs listed on given ArteTV page."""
|
||||
r = http_session.get(page_url)
|
||||
|
||||
# special handling of 404
|
||||
if r.status_code == 404:
|
||||
raise PageNotFound(page_url)
|
||||
r.raise_for_status()
|
||||
|
||||
# no HTML parsing required, whe just find the mark
|
||||
html = r.text
|
||||
start = html.find(_DATA_MARK)
|
||||
if start < 0:
|
||||
raise InvalidPage("DATA_MARK_NOT_FOUND", page_url)
|
||||
start += len(_DATA_MARK)
|
||||
end = html.index("</script>", start)
|
||||
|
||||
try:
|
||||
next_js_data = json.loads(html[start:end].strip())
|
||||
except json.JSONDecodeError:
|
||||
raise InvalidPage("INVALID_JSON_DATA", page_url)
|
||||
|
||||
try:
|
||||
initial_page_value = next_js_data["props"]["pageProps"]["initialPage"]["value"]
|
||||
initial_type = next_js_data["props"]["pageProps"]["initialType"]
|
||||
|
||||
match initial_type:
|
||||
case "programs":
|
||||
yield from _process_programs_page(initial_page_value)
|
||||
case "collections":
|
||||
yield from _process_collections_page(initial_page_value)
|
||||
case _:
|
||||
raise PageNotSupported(page_url, initial_type)
|
||||
|
||||
except (KeyError, IndexError, ValueError) as e:
|
||||
raise InvalidPage("SCHEMA", page_url) from e
|
||||
|
||||
except InvalidPage as e:
|
||||
raise InvalidPage(e.args[0], page_url) from e
|
||||
|
|
Loading…
Reference in New Issue