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 |
|
||||
| :-------- | :-----------: | :--------: | :--------: | :------: | :-------------: |
|
||||
| bilibili.com | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||
| dailymotion | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| instagram posts & stories | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||
| instagram reels | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||
| ok video | ✅ | ❌ | ❌ | ✅ | ✅ |
|
||||
|
|
|
@ -24,6 +24,7 @@ import pinterest from "./services/pinterest.js";
|
|||
import streamable from "./services/streamable.js";
|
||||
import twitch from "./services/twitch.js";
|
||||
import rutube from "./services/rutube.js";
|
||||
import dailymotion from "./services/dailymotion.js";
|
||||
|
||||
export default async function(host, patternMatch, url, lang, obj) {
|
||||
assert(url instanceof URL);
|
||||
|
@ -156,6 +157,9 @@ export default async function(host, patternMatch, url, lang, obj) {
|
|||
isAudioOnly: isAudioOnly
|
||||
});
|
||||
break;
|
||||
case "dailymotion":
|
||||
r = await dailymotion(patternMatch);
|
||||
break;
|
||||
default:
|
||||
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",
|
||||
"patterns": ["video/:id", "play/embed/:id"],
|
||||
"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.tvId?.length <= 24,
|
||||
|
||||
"dailymotion": (patternMatch) => patternMatch.id?.length <= 32,
|
||||
|
||||
"instagram": (patternMatch) =>
|
||||
patternMatch.postId?.length <= 12
|
||||
|| (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]}`)
|
||||
}
|
||||
break;
|
||||
|
||||
case "dai":
|
||||
if (url.hostname === 'dai.ly' && parts.length === 2) {
|
||||
url = new URL(`https://dailymotion.com/video/${parts[1]}`)
|
||||
}
|
||||
}
|
||||
|
||||
return url
|
||||
|
|
|
@ -215,7 +215,7 @@ export function streamVideoOnly(streamInfo, res) {
|
|||
args.push('-an')
|
||||
}
|
||||
|
||||
if (['vimeo', 'rutube'].includes(streamInfo.service)) {
|
||||
if (["vimeo", "rutube", "dailymotion"].includes(streamInfo.service)) {
|
||||
args.push('-bsf:a', 'aac_adtstoasc')
|
||||
}
|
||||
|
||||
|
|
|
@ -1197,5 +1197,30 @@
|
|||
"code": 200,
|
||||
"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