Skip to content

Commit

Permalink
Implement audio download
Browse files Browse the repository at this point in the history
  • Loading branch information
kdid committed Jun 13, 2024
1 parent 414f47b commit 5e59f64
Show file tree
Hide file tree
Showing 7 changed files with 9,613 additions and 1,251 deletions.
10,713 changes: 9,477 additions & 1,236 deletions lambdas/package-lock.json

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion lambdas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
"@aws-sdk/client-mediaconvert": "^3.410.0",
"@aws-sdk/client-s3": "^3.410.0",
"@aws-sdk/s3-request-presigner": "^3.410.0",
"@aws-sdk/signature-v4": "^3.130.0"
"@aws-sdk/signature-v4": "^3.130.0",
"aws-sdk": "^2.1640.0"
},
"dependencies": {
"axios": "^1.7.2",
"fluent-ffmpeg": "^2.1.3"
}
}
55 changes: 55 additions & 0 deletions lambdas/start-audio-transcode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const AWS = require('aws-sdk');
const ffmpeg = require('fluent-ffmpeg');
const stream = require('stream');

module.exports.handler = async (event) => {

console.log("start-audio-transcode.js: event: ", event);

const s3 = new AWS.S3();
const url = event.streamingUrl;
const referer = event.referer || null;
const inputOptions = referer ? ["-referer", referer] : [];
const BUCKET = event.destinationBucket;
const KEY = event.destinationKey;
const pass = new stream.PassThrough();

pass.on('close', () => {
console.log('PassThrough stream closed');
});


return new Promise((resolve, reject) => {
ffmpeg()
.input(url)
.inputOptions(inputOptions)
.format('mp3')
.output(pass, { end: true })
.on('start', () => {
console.log('ffmpeg process started');
s3.upload({
Bucket: BUCKET,
Key: KEY,
Body: pass,
}, (error, data) => {
if (error) {
console.error('upload failed', error);
reject(error);
} else {
console.log('upload completed');
resolve({ success: true });
}
});
})
.on('error', (error) => {
console.error('ffmpeg error', error);
reject(error);
})
.on('end', () => {
console.log('ffmpeg process ended');
})
.on('progress', (p) => console.log('progress', p))
.run();
});
}
38 changes: 27 additions & 11 deletions node/src/handlers/get-file-set-download.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ const path = require("path");
* Handler for download file set endpoint
*/
exports.handler = wrap(async (event) => {
console.log(event)
const id = event.pathParameters.id;
const email = event.queryStringParameters?.email;
const referer = event.headers?.referer;

const allowPrivate =
event.userToken.isSuperUser() ||
Expand All @@ -32,14 +34,14 @@ exports.handler = wrap(async (event) => {

if (esResponse.statusCode == "200") {
const doc = JSON.parse(esResponse.body);
if (isVideoDownload(doc)) {
if (isAVDownload(doc)) {
if (!email) {
return invalidRequest(400, "Query string must include email address");
}
if (!event.userToken.isSuperUser()) {
return invalidRequest(401, "Unauthorized");
}
return await processAVDownload(doc, email);
return await processAVDownload(doc, email, referer);
} else if (isImageDownload(doc)) {
return await IIIFImageRequest(doc);
} else if (isAltFileDownload(doc)) {
Expand Down Expand Up @@ -70,14 +72,12 @@ function isAltFileDownload(doc) {
);
}

function isVideoDownload(doc) {
// Note - audio is not currently implemented due to an issue with AWS
// & MediaConvert and our .m3u8 files
function isAVDownload(doc) {
return (
doc.found &&
doc._source.role === "Access" &&
doc._source.mime_type != null &&
["video"].includes(doc._source.mime_type.split("/")[0]) &&
["audio", "video"].includes(doc._source.mime_type.split("/")[0]) &&
doc._source.streaming_url != null
);
}
Expand All @@ -91,6 +91,10 @@ function isImageDownload(doc) {
);
}

function isAudio(doc) {
return (["audio"].includes(doc._source.mime_type.split("/")[0]));
}

function derivativeKey(doc) {
const id = doc._id;
let prefix =
Expand Down Expand Up @@ -169,12 +173,15 @@ const IIIFImageRequest = async (doc) => {
};
};

async function processAVDownload(doc, email) {

async function processAVDownload(doc, email, referer) {

const stepFunctionConfig = process.env.STEP_FUNCTION_ENDPOINT
? { endpoint: process.env.STEP_FUNCTION_ENDPOINT }
: {};
const client = new SFNClient(stepFunctionConfig);

const audio = isAudio(doc);

Check failure on line 184 in node/src/handlers/get-file-set-download.js

View workflow job for this annotation

GitHub Actions / test

'audio' is assigned a value but never used
const fileSet = doc._source;
const url = new URL(fileSet.streaming_url);

Expand All @@ -184,26 +191,33 @@ async function processAVDownload(doc, email) {
const fileSetLabel = fileSet.label;
const workId = fileSet.work_id;
const fileType = fileSet.mime_type.split("/")[0];
const destinationKey = `av-downloads/${fileSetId}.mp4`; //TODO - account for audio
const destinationLocation = `s3://${destinationBucket}/av-downloads/${fileSetId}`; // TODO - account for audio
const settings = videoTranscodeSettings(sourceLocation, destinationLocation); // TODO - account for audio
const destinationKey = isAudio(doc) ? `av-downloads/${fileSetId}.mp3` : `av-downloads/${fileSetId}.mp4`;
const destinationLocation = `s3://${destinationBucket}/av-downloads/${fileSetId}`;
const settings = isAudio(doc) ? {} : videoTranscodeSettings(sourceLocation, destinationLocation);
const filename = isAudio(doc) ? `${fileSetId}.mp3` : `${fileSetId}.mp4`;

var params = {
stateMachineArn: process.env.AV_DOWNLOAD_STATE_MACHINE_ARN,
input: JSON.stringify({
configuration: {
startAudioTranscodeFunction: process.env.START_AUDIO_TRANSCODE_FUNCTION,
startTranscodeFunction: process.env.START_TRANSCODE_FUNCTION,
transcodeStatusFunction: process.env.TRANSCODE_STATUS_FUNCTION,
getDownloadLinkFunction: process.env.GET_DOWNLOAD_LINK_FUNCTION,
sendTemplatedEmailFunction: process.env.SEND_TEMPLATED_EMAIL_FUNCTION,
},
transcodeInput: {
settings: settings,
type: fileType,
streamingUrl: fileSet.streaming_url,
referer: referer,
destinationBucket: destinationBucket,
destinationKey: destinationKey,
},
presignedUrlInput: {
bucket: destinationBucket,
key: destinationKey,
disposition: `${fileSetId}.mp4`,
disposition: filename,
},
sendEmailInput: {
to: email,
Expand All @@ -224,6 +238,8 @@ async function processAVDownload(doc, email) {
const command = new StartExecutionCommand(params);
await client.send(command);

console.log("HERE")

return {
statusCode: 200,
headers: { "content-type": "text/plain" },
Expand Down
4 changes: 2 additions & 2 deletions node/src/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 23 additions & 1 deletion state_machines/av_download.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
{
"Comment": "HLS stiching and save as file in s3 and email download link",
"StartAt": "startTranscode",
"StartAt": "audioOrVideo",
"States": {
"audioOrVideo": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.transcodeInput.type",
"StringEquals": "audio",
"Next": "startAudioTranscode"
}
],
"Default": "startTranscode"
},
"startAudioTranscode": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
"Parameters": {
"Payload.$": "$.transcodeInput",
"FunctionName.$": "$.configuration.startAudioTranscodeFunction"
},
"Next": "getDownloadLink",
"InputPath": "$",
"ResultPath": "$.audioTranscodeOutput"
},
"startTranscode": {
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke",
Expand Down
23 changes: 23 additions & 0 deletions template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ Resources:
MEDIA_CONVERT_ROLE_ARN: !Ref MediaConvertRoleArn
PYRAMID_BUCKET: !Ref PyramidBucket
REPOSITORY_EMAIL: !Ref RepositoryEmail
START_AUDIO_TRANSCODE_FUNCTION: !GetAtt startAudioTranscodeFunction.Arn
START_TRANSCODE_FUNCTION: !GetAtt startTranscodeFunction.Arn
TRANSCODE_STATUS_FUNCTION: !GetAtt transcodeStatusFunction.Arn
GET_DOWNLOAD_LINK_FUNCTION: !GetAtt getDownloadLinkFunction.Arn
Expand Down Expand Up @@ -718,6 +719,7 @@ Resources:
Action:
- lambda:InvokeFunction
Resource:
- !GetAtt startAudioTranscodeFunction.Arn
- !GetAtt startTranscodeFunction.Arn
- !GetAtt transcodeStatusFunction.Arn
- !GetAtt getDownloadLinkFunction.Arn
Expand Down Expand Up @@ -917,6 +919,27 @@ Resources:
<!--[if gte mso 15]></td></tr></table><![endif]-->
</body>
</html>
startAudioTranscodeFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs16.x
CodeUri: ./lambdas
Handler: start-audio-transcode.handler
Description: Starts audio transcode job with ffmpeg
Timeout: 300
Layers:
- arn:aws:lambda:us-east-1:625046682746:layer:ffmpeg:10
Policies:
Version: 2012-10-17
Statement:
- Sid: BucketAccess
Effect: Allow
Action:
- s3:PutObject
Resource: !Sub "arn:aws:s3:::${MediaConvertDestinationBucket}/*"
Environment:
Variables:
MEDIA_CONVERT_DESTINATION_BUCKET: !Ref MediaConvertDestinationBucket
startTranscodeFunction:
Type: AWS::Serverless::Function
Properties:
Expand Down

0 comments on commit 5e59f64

Please sign in to comment.