Skip to content

Commit

Permalink
ffmpeg function to segment a video
Browse files Browse the repository at this point in the history
  • Loading branch information
timarney committed Sep 22, 2022
1 parent f7106d1 commit addcf91
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 5 deletions.
11 changes: 11 additions & 0 deletions includes/Experiments.php
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,17 @@ public function get_experiments(): array {
'description' => __( 'Enable detailed page advancement settings on a per-page basis', 'web-stories' ),
'group' => 'editor',
],
/**
* Author: @timarney
* Issue: #12164
* Creation date: 2022-09-19
*/
[
'name' => 'segmentVideo',
'label' => __( 'Segment video', 'web-stories' ),
'description' => __( 'Enable support for segmenting video files', 'web-stories' ),
'group' => 'editor',
],
];
}

Expand Down
2 changes: 2 additions & 0 deletions packages/design-system/src/utils/panelTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const SHAPE_STYLE = 'shapeStyle';
const TEXT_ACCESSIBILITY = 'textAccessibility';
const TEXT_STYLE = 'textStyle';
const VIDEO_OPTIONS = 'videoOptions';
const VIDEO_SEGMENT = 'videoSegment';
const VIDEO_ACCESSIBILITY = 'videoAccessibility';
const ELEMENT_ALIGNMENT = 'elementAlignment';
const PRODUCT = 'product';
Expand All @@ -47,6 +48,7 @@ const PanelTypes = {
BORDER,
ANIMATION,
VIDEO_OPTIONS,
VIDEO_SEGMENT,
CAPTIONS,
LINK,
IMAGE_ACCESSIBILITY,
Expand Down
1 change: 1 addition & 0 deletions packages/element-library/src/video/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const panels = [
PanelTypes.ELEMENT_ALIGNMENT,
...MEDIA_PANELS,
PanelTypes.VIDEO_OPTIONS,
PanelTypes.VIDEO_SEGMENT,
PanelTypes.VIDEO_ACCESSIBILITY,
PanelTypes.CAPTIONS,
];
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export default function useContextValueProvider(reducerState, reducerActions) {
muteExistingVideo,
cropExistingVideo,
trimExistingVideo,
segmentVideo,
} = useProcessMedia({
postProcessingResource,
uploadMedia,
Expand Down Expand Up @@ -344,6 +345,7 @@ export default function useContextValueProvider(reducerState, reducerActions) {
updateBaseColor,
updateBlurHash,
cropExistingVideo,
segmentVideo,
},
};
}
18 changes: 13 additions & 5 deletions packages/story-editor/src/app/media/utils/test/useProcessMedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import APIContext from '../../../api/context';
import StoryContext from '../../../story/context';
import useProcessMedia from '../useProcessMedia';
import useMediaInfo from '../useMediaInfo';
import { ConfigProvider } from '../../../config';

const fetchRemoteFileMock = (url, mimeType) => {
if (url === 'http://www.google.com/foo.mov') {
Expand Down Expand Up @@ -110,12 +111,19 @@ function setup() {
const storyContextValue = {
actions: { updateElementsByResourceId },
};
const configState = {
capabilities: {
hasUploadMediaAction: true,
},
};
const wrapper = ({ children }) => (
<APIContext.Provider value={apiContextValue}>
<StoryContext.Provider value={storyContextValue}>
{children}
</StoryContext.Provider>
</APIContext.Provider>
<ConfigProvider config={configState}>
<APIContext.Provider value={apiContextValue}>
<StoryContext.Provider value={storyContextValue}>
{children}
</StoryContext.Provider>
</APIContext.Provider>
</ConfigProvider>
);

const uploadVideoPoster = jest.fn();
Expand Down
61 changes: 61 additions & 0 deletions packages/story-editor/src/app/media/utils/useFFmpeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,65 @@ function useFFmpeg() {
[getFFmpegInstance]
);

/**
* Segment a video using FFmpeg.
*
* @param {File} file Original video file object.
* @param {string} segmentTime number of secs to split the video into.
* @return {Promise<File[]>} Segmented video files .
*/
const segmentVideo = useCallback(
async (file, segmentTime) => {
let ffmpeg;
try {
ffmpeg = await getFFmpegInstance(file);
const type = file?.type || MEDIA_TRANSCODED_MIME_TYPE;
const ext = getExtensionFromMimeType(type);
const outputFileName = getFileBasename(file) + '_%03d.' + ext;
await ffmpeg.run(
'-i',
file.name,
'-c',
'copy',
'-f',
'segment',
'-segment_time',
`${segmentTime}`,
'-reset_timestamps',
'1',
outputFileName
);

const files = [];

await ffmpeg
.FS('readdir', '/')
.filter(
(outputFile) =>
outputFile !== file.name && outputFile.endsWith(`.${ext}`)
)
.forEach(async (outputFile) => {
const data = await ffmpeg.FS('readFile', outputFile);
files.push(
blobToFile(new Blob([data.buffer], { type }), outputFile, type)
);
});
return files;
} catch (err) {
// eslint-disable-next-line no-console -- We want to surface this error.
console.error(err);
throw err;
} finally {
try {
ffmpeg.exit();
} catch {
// Not interested in errors here.
}
}
},
[getFFmpegInstance]
);

/**
* Trim Video using FFmpeg.
*
Expand Down Expand Up @@ -601,6 +660,7 @@ function useFFmpeg() {
convertToMp3,
trimVideo,
cropVideo,
segmentVideo,
}),
[
isTranscodingEnabled,
Expand All @@ -612,6 +672,7 @@ function useFFmpeg() {
convertToMp3,
trimVideo,
cropVideo,
segmentVideo,
]
);
}
Expand Down
30 changes: 30 additions & 0 deletions packages/story-editor/src/app/media/utils/useProcessMedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { trackError } from '@googleforcreators/tracking';
*/
import useAPI from '../../api/useAPI';
import useStory from '../../story/useStory';
import useFFmpeg from './useFFmpeg';
import useMediaInfo from './useMediaInfo';

function useProcessMedia({
Expand All @@ -41,6 +42,9 @@ function useProcessMedia({
const {
actions: { getOptimizedMediaById, getMutedMediaById },
} = useAPI();

const { segmentVideo: ffSegmentVideo } = useFFmpeg();

const { updateElementsByResourceId, updateElementById } = useStory(
(state) => ({
updateElementsByResourceId: state.actions.updateElementsByResourceId,
Expand Down Expand Up @@ -567,12 +571,38 @@ function useProcessMedia({
]
);

/**
* Segment video using FFmpeg.
*
* @param {import('@googleforcreators/media').Resource} resource Resource object.
* @param {Function} handleSegmented function to handle segmentedFile processing.
*/
const segmentVideo = useCallback(
async ({ resource: oldResource, segmentTime }, handleSegmented) => {
const { src: url, mimeType } = oldResource;
const segmentedFiles = await ffSegmentVideo(
await fetchRemoteFile(url, mimeType),
segmentTime
);

const addElement = handleSegmented(segmentedFiles);

for (const file of segmentedFiles) {
// eslint-disable-next-line no-await-in-loop -- need to await new new resource
await uploadMedia([file], { onUploadSuccess: addElement });
}
return segmentedFiles;
},
[uploadMedia, ffSegmentVideo]
);

return {
optimizeVideo,
optimizeGif,
muteExistingVideo,
trimExistingVideo,
cropExistingVideo,
segmentVideo,
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ export { default as TextAccessibilityPanel } from './textAccessibility';
export { default as TextStylePanel } from './textStyle';
export { default as VideoAccessibilityPanel } from './videoAccessibility';
export { default as VideoOptionsPanel } from './videoOptions';
export { default as VideoSegmentPanel } from './videoSegment';
export { default as PageBackgroundAudioPanel } from './pageBackgroundAudio';
export { default as ProductPanel } from './product';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2022 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { default } from './videoSegment';
Loading

0 comments on commit addcf91

Please sign in to comment.