parent
77df90412b
commit
a97733d257
8 changed files with 150 additions and 1 deletions
|
@ -14,6 +14,7 @@ this list is not final and keeps expanding over time. if support for a service y
|
||||||
| service | video + audio | only audio | only video | metadata | rich file names |
|
| service | video + audio | only audio | only video | metadata | rich file names |
|
||||||
| :-------- | :-----------: | :--------: | :--------: | :------: | :-------------: |
|
| :-------- | :-----------: | :--------: | :--------: | :------: | :-------------: |
|
||||||
| bilibili.com | ✅ | ✅ | ✅ | ➖ | ➖ |
|
| bilibili.com | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||||
|
| dailymotion | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| instagram posts & stories | ✅ | ✅ | ✅ | ➖ | ➖ |
|
| instagram posts & stories | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||||
| instagram reels | ✅ | ✅ | ✅ | ➖ | ➖ |
|
| instagram reels | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||||
| ok video | ✅ | ❌ | ❌ | ✅ | ✅ |
|
| ok video | ✅ | ❌ | ❌ | ✅ | ✅ |
|
||||||
|
|
|
@ -24,6 +24,7 @@ import pinterest from "./services/pinterest.js";
|
||||||
import streamable from "./services/streamable.js";
|
import streamable from "./services/streamable.js";
|
||||||
import twitch from "./services/twitch.js";
|
import twitch from "./services/twitch.js";
|
||||||
import rutube from "./services/rutube.js";
|
import rutube from "./services/rutube.js";
|
||||||
|
import dailymotion from "./services/dailymotion.js";
|
||||||
|
|
||||||
export default async function(host, patternMatch, url, lang, obj) {
|
export default async function(host, patternMatch, url, lang, obj) {
|
||||||
assert(url instanceof URL);
|
assert(url instanceof URL);
|
||||||
|
@ -156,6 +157,9 @@ export default async function(host, patternMatch, url, lang, obj) {
|
||||||
isAudioOnly: isAudioOnly
|
isAudioOnly: isAudioOnly
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "dailymotion":
|
||||||
|
r = await dailymotion(patternMatch);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return apiJSON(0, { t: errorUnsupported(lang) });
|
return apiJSON(0, { t: errorUnsupported(lang) });
|
||||||
}
|
}
|
||||||
|
|
107
src/modules/processing/services/dailymotion.js
Normal file
107
src/modules/processing/services/dailymotion.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
import HLSParser from 'hls-parser';
|
||||||
|
import { maxVideoDuration } from '../../config.js';
|
||||||
|
|
||||||
|
let _token;
|
||||||
|
|
||||||
|
function getExp(token) {
|
||||||
|
return JSON.parse(
|
||||||
|
Buffer.from(token.split('.')[1], 'base64')
|
||||||
|
).exp * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getToken = async () => {
|
||||||
|
if (_token && getExp(_token) > new Date().getTime()) {
|
||||||
|
return _token;
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = await fetch('https://graphql.api.dailymotion.com/oauth/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||||
|
'User-Agent': 'dailymotion/240213162706 CFNetwork/1492.0.1 Darwin/23.3.0',
|
||||||
|
'Authorization': 'Basic MGQyZDgyNjQwOWFmOWU3MmRiNWQ6ODcxNmJmYTVjYmEwMmUwMGJkYTVmYTg1NTliNDIwMzQ3NzIyYWMzYQ=='
|
||||||
|
},
|
||||||
|
body: 'traffic_segment=&grant_type=client_credentials'
|
||||||
|
}).then(r => r.json()).catch(() => {});
|
||||||
|
|
||||||
|
if (req.access_token) {
|
||||||
|
return _token = req.access_token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function({ id }) {
|
||||||
|
const token = await getToken();
|
||||||
|
if (!token) return { error: 'ErrorSomethingWentWrong' };
|
||||||
|
|
||||||
|
const req = await fetch('https://graphql.api.dailymotion.com/',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'dailymotion/240213162706 CFNetwork/1492.0.1 Darwin/23.3.0',
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-DM-AppInfo-Version': '7.16.0_240213162706',
|
||||||
|
'X-DM-AppInfo-Type': 'iosapp',
|
||||||
|
'X-DM-AppInfo-Id': 'com.dailymotion.dailymotion'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
operationName: "Media",
|
||||||
|
query: `
|
||||||
|
query Media($xid: String!, $password: String) {
|
||||||
|
media(xid: $xid, password: $password) {
|
||||||
|
__typename
|
||||||
|
... on Video {
|
||||||
|
xid
|
||||||
|
hlsURL
|
||||||
|
duration
|
||||||
|
title
|
||||||
|
channel {
|
||||||
|
displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: { xid: id }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).then(r => r.status === 200 && r.json()).catch(() => {});
|
||||||
|
|
||||||
|
const media = req?.data?.media;
|
||||||
|
|
||||||
|
if (media?.__typename !== 'Video' || !media.hlsURL) {
|
||||||
|
return { error: 'ErrorEmptyDownload' }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media.duration * 1000 > maxVideoDuration) {
|
||||||
|
return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifest = await fetch(media.hlsURL).then(r => r.text()).catch(() => {});
|
||||||
|
if (!manifest) return { error: 'ErrorSomethingWentWrong' };
|
||||||
|
|
||||||
|
const bestQuality = HLSParser.parse(manifest).variants
|
||||||
|
.filter(v => v.codecs.includes('avc1'))
|
||||||
|
.reduce((a, b) => a.bandwidth > b.bandwidth ? a : b);
|
||||||
|
if (!bestQuality) return { error: 'ErrorEmptyDownload' }
|
||||||
|
|
||||||
|
const fileMetadata = {
|
||||||
|
title: media.title,
|
||||||
|
artist: media.channel.displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
urls: bestQuality.uri,
|
||||||
|
isM3U8: true,
|
||||||
|
filenameAttributes: {
|
||||||
|
service: 'dailymotion',
|
||||||
|
id: media.xid,
|
||||||
|
title: fileMetadata.title,
|
||||||
|
author: fileMetadata.artist,
|
||||||
|
resolution: `${bestQuality.resolution.width}x${bestQuality.resolution.height}`,
|
||||||
|
qualityLabel: `${bestQuality.resolution.height}p`,
|
||||||
|
extension: 'mp4'
|
||||||
|
},
|
||||||
|
fileMetadata
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,6 +109,11 @@
|
||||||
"tld": "ru",
|
"tld": "ru",
|
||||||
"patterns": ["video/:id", "play/embed/:id"],
|
"patterns": ["video/:id", "play/embed/:id"],
|
||||||
"enabled": true
|
"enabled": true
|
||||||
|
},
|
||||||
|
"dailymotion": {
|
||||||
|
"alias": "dailymotion videos",
|
||||||
|
"patterns": ["video/:id"],
|
||||||
|
"enabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ export const testers = {
|
||||||
patternMatch.comId?.length <= 12 || patternMatch.comShortLink?.length <= 16
|
patternMatch.comId?.length <= 12 || patternMatch.comShortLink?.length <= 16
|
||||||
|| patternMatch.tvId?.length <= 24,
|
|| patternMatch.tvId?.length <= 24,
|
||||||
|
|
||||||
|
"dailymotion": (patternMatch) => patternMatch.id?.length <= 32,
|
||||||
|
|
||||||
"instagram": (patternMatch) =>
|
"instagram": (patternMatch) =>
|
||||||
patternMatch.postId?.length <= 12
|
patternMatch.postId?.length <= 12
|
||||||
|| (patternMatch.username?.length <= 30 && patternMatch.storyId?.length <= 24),
|
|| (patternMatch.username?.length <= 30 && patternMatch.storyId?.length <= 24),
|
||||||
|
|
|
@ -59,6 +59,11 @@ export function aliasURL(url) {
|
||||||
url = new URL(`https://bilibili.com/_shortLink/${parts[1]}`)
|
url = new URL(`https://bilibili.com/_shortLink/${parts[1]}`)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "dai":
|
||||||
|
if (url.hostname === 'dai.ly' && parts.length === 2) {
|
||||||
|
url = new URL(`https://dailymotion.com/video/${parts[1]}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
|
@ -215,7 +215,7 @@ export function streamVideoOnly(streamInfo, res) {
|
||||||
args.push('-an')
|
args.push('-an')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['vimeo', 'rutube'].includes(streamInfo.service)) {
|
if (["vimeo", "rutube", "dailymotion"].includes(streamInfo.service)) {
|
||||||
args.push('-bsf:a', 'aac_adtstoasc')
|
args.push('-bsf:a', 'aac_adtstoasc')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1197,5 +1197,30 @@
|
||||||
"code": 200,
|
"code": 200,
|
||||||
"status": "stream"
|
"status": "stream"
|
||||||
}
|
}
|
||||||
|
}],
|
||||||
|
"dailymotion": [{
|
||||||
|
"name": "regular video",
|
||||||
|
"url": "https://www.dailymotion.com/video/x8t1eho",
|
||||||
|
"params": {},
|
||||||
|
"expected": {
|
||||||
|
"code": 200,
|
||||||
|
"status": "stream"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "private video",
|
||||||
|
"url": "https://www.dailymotion.com/video/k41fZWpx2TaAORA2nok",
|
||||||
|
"params": {},
|
||||||
|
"expected": {
|
||||||
|
"code": 200,
|
||||||
|
"status": "stream"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
"name": "dai.ly shortened link",
|
||||||
|
"url": "https://dai.ly/k41fZWpx2TaAORA2nok",
|
||||||
|
"params": {},
|
||||||
|
"expected": {
|
||||||
|
"code": 200,
|
||||||
|
"status": "stream"
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue