Get started

Updated at:
Copy as MD

This topic describes how to use the IMS SDK to perform batch video production by calling the SubmitMediaProducingJob API.

Key concepts

Batch video production

Batch video production generates multiple unique videos by combining a small set of audio and video clips with different voiceover variations.

Use cases

Short video platforms often use deduplication algorithms to prevent content theft. To run a marketing campaign across multiple channels or accounts, you must create unique variations of your video ads. The batch video production feature automates this process. By customizing the timeline parameter in your SubmitMediaProducingJob API call, you can generate different video outputs to meet your needs.

Billing

Submitting an editing and production job may incur charges for multiple billable items. For more information, see intelligent production.

How it works

  • Combine video, audio, image, and subtitle clips into a finished video by submitting editing jobs with different timeline parameter configurations using the SubmitMediaProducingJob API.

  • A timeline is where you arrange clips and design effects for your video project. A timeline consists of three main objects: tracks, clips, and effects. For more information, see Timeline configuration.

  • The intelligent production feature supports editing and production, effect rendering, and template-based production for live streams, video-on-demand (VOD), and OSS clips. For more information, see Overview of intelligent production.

Before you begin

Step 1: Create an OSS bucket

Log on to the OSS console and click Buckets > Create Bucket. For the region, select China (Shanghai). Note the bucket name, as it is required for the sample code and will serve as the output destination for the edited videos.

For the creation mode, select Custom Creation.

Step 2: Run the batch video production code

  1. Set up your environment:

    • Operating system: Windows 8.1 or later, macOS, or Linux.

    • IntelliJ IDEA: 2020.1 or later.

    • JDK: 1.8 or later.

  2. Create a new Maven project to run the code.

  3. Import the required third-party dependencies.

    Note

    The server-side SDK version in the following sample code is for reference only. To get the latest version, see server-side SDK.

    <dependencies>
      <dependency>
        <groupId>com.aliyun</groupId>
        <artifactId>ice20201109</artifactId>
        <version>2.3.0</version>
      </dependency>
      <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.9</version>
      </dependency>
    </dependencies>
  4. Configure your AccessKey ID and AccessKey Secret. For more information, see Manage credentials. For other authentication methods, see Manage credentials.

    Note

    For more ways to manage your credentials, see Manage credentials.

  5. Create a new Java class and paste the following sample code.

    Expand to view sample code

    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import com.aliyun.ice20201109.Client;
    import com.aliyun.ice20201109.models.*;
    import com.aliyun.teaopenapi.models.Config;
    
    import java.util.*;
    
    // Sample URL of a successfully generated video
    // http://oushu-test-shanghai.oss-cn-shanghai.aliyuncs.com/ice_output/46b29eb5775f4f758846171ab79bfca7.mp4
    
    /**
     *  The following Maven dependencies are required:
     *   <dependency>
     *      <groupId>com.aliyun</groupId>
     *      <artifactId>ice20201109</artifactId>
     *      <version>2.1.0</version>
     *  </dependency>
     *  <dependency>
     *      <groupId>com.alibaba</groupId>
     *      <artifactId>fastjson</artifactId>
     *      <version>1.2.9</version>
     *  </dependency>
     */
    public class BatchProduceAlignment {
    
        static final String regionId = "cn-shanghai";
        // Replace with the name of the bucket you created in Step 1.
        static final String bucket = "your-bucket-name";
        private Client iceClient;
        
        public static void main(String[] args) {
            try {
                BatchProduceAlignment batchProduce = new BatchProduceAlignment();
                batchProduce.initClient();
                batchProduce.run();
            } catch (Exception e) {
                System.out.println("Produce failed. Exception: " + e.toString());
            }
        }
    
        public void initClient() throws Exception {
    
            // An AccessKey pair of an Alibaba Cloud account has permissions on all APIs. We recommend that you use a RAM user for API access or routine O&M.
            // This example shows how to store the AccessKey ID and AccessKey Secret in environment variables. For configuration instructions, see: https://www.alibabacloud.com/help/en/sdk/developer-reference/v2-manage-access-credentials
            com.aliyun.credentials.Client credentialClient = new com.aliyun.credentials.Client();
            Config config = new Config();
            config.setCredential(credentialClient);
    
            // If you want to hardcode your credentials, use the following code. However, we strongly recommend that you do not store your AccessKey ID and AccessKey Secret in your project code. Hardcoding credentials can lead to security risks.
            // config.accessKeyId = <your-accesskey-id>;
            // config.accessKeySecret = <your-accesskey-secret>;
            config.endpoint = "ice." + regionId + ".aliyuncs.com";
            config.regionId = regionId;
            iceClient = new Client(config);
        }
    
        public void run() throws Exception {
            // Text content for text-to-speech
            String text = "People describe life with mixed flavors because they know that taste is a sentiment cherished in every heart. In this era, everyone experiences much pain and joy. People tend to hide the bitterness and turn happiness into food, presenting it on the dining table throughout the seasons.";
            // Video clips
            String[] videoArray = new String[]{
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f1.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f2.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f3.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f4.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f5.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f6.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f7.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f8.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f9.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f10.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f11.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f12.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f13.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f14.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f15.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f16.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f17.mp4",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/food/f18.mp4"
            };
            // Background music clips
            String[] bgMusicArray = new String[]{
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m1.wav",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m2.wav",
                    "https://ice-document-materials.oss-cn-shanghai.aliyuncs.com/test_media/music/m3.wav"
            };
            // Video title
            String title = "A Bite of Delicacy";
            // Number of videos to generate
            int produceCount = 3;
    
            // Submit jobs in a batch
            List<String> jobIds = new ArrayList<String>();
            for (int i = 0; i < produceCount; i++) {
                String jobId = produceSingleVideo(title, text, videoArray, bgMusicArray);
                jobIds.add(jobId);
            }
            // Poll job status until all jobs are complete
            System.out.println("waiting job finished...");
            while (true) {
                Thread.sleep(3000);
                boolean allFinished = true;
                for (int i = 0; i < jobIds.size(); i++) {
                    String jobId = jobIds.get(i);
                    GetMediaProducingJobRequest req = new GetMediaProducingJobRequest();
                    req.setJobId(jobId);
                    GetMediaProducingJobResponse response = iceClient.getMediaProducingJob(req);
                    GetMediaProducingJobResponseBody.GetMediaProducingJobResponseBodyMediaProducingJob mediaProducingJob = response.getBody().getMediaProducingJob();
                    String status = mediaProducingJob.getStatus();
                    System.out.println("jobId: " + mediaProducingJob.getJobId() + ", status:" + status);
                    if ("Failed".equalsIgnoreCase(status)) {
                        System.out.println("jobfailed. jobInfo: " + JSONObject.toJSONString(mediaProducingJob));
                        throw new Exception("Produce failed. jobid: " + mediaProducingJob.getJobId());
                    }
                    if (!"Success".equalsIgnoreCase(status)) {
                        allFinished = false;
                        break;
                    }
                }
                if (allFinished) {
                    System.out.println("all job finished.");
                    break;
                }
            }
        }
    
        public String produceSingleVideo(String title, String text, String[] videoArray, String[] bgMusicArray) throws Exception {
            text = text.replace(",", "。");
            text = text.replace("\n", "。");
            String[] sentenceArray = text.split("。");
    
            JSONArray videoClipArray = new JSONArray();
            JSONArray audioClipArray = new JSONArray();
    
            List<String> videoList = Arrays.asList(videoArray);
            Collections.shuffle(videoList);
    
            for (int i = 0; i < sentenceArray.length; i++) {
                String sentence = sentenceArray[i];
                String clipId = "clip" + i;
                String videoUrl = videoList.get(i);
                String videoClip = "{\"MediaURL\":\""+videoUrl+"\",\"ReferenceClipId\":\""+clipId+"\",\"Effects\":[{\"Type\":\"Background\",\"SubType\":\"Blur\",\"Radius\":0.1}]}";
                videoClipArray.add(JSONObject.parseObject(videoClip));
                String audioClip = "{\"Type\":\"AI_TTS\",\"Content\":\"" + sentence + "\",\"Voice\":\"zhichu\",\"ClipId\":\""+clipId+"\",\"Effects\":[{\"Type\":\"AI_ASR\",\"Font\":\"Alibaba PuHuiTi\",\"Alignment\":\"TopCenter\",\"Y\":0.75,\"FontSize\":55,\"FontColor\":\"#ffffff\",\"AdaptMode\":\"AutoWrap\",\"TextWidth\":0.8,\"Outline\":2,\"OutlineColour\":\"#000000\"}]}";
                audioClipArray.add(JSONObject.parseObject(audioClip));
            }
    
            String subtitleTrack = "{\"SubtitleTrackClips\":[{\"Type\":\"Text\",\"Font\":\"HappyZcool-2016\",\"Content\":\""+title+"\",\"FontSize\":80,\"FontColor\":\"#ffffff\",\"Y\":0.15,\"Alignment\":\"TopCenter\",\"EffectColorStyle\":\"CS0004-000005\",\"FontFace\":{\"Bold\":true,\"Italic\":false,\"Underline\":false}}]}";
    
            int bgMusicIndex = (int)(Math.random() * bgMusicArray.length);
            String bgMusicUrl = bgMusicArray[bgMusicIndex];
            String timeline = "{\"VideoTracks\":[{\"VideoTrackClips\":"+videoClipArray.toJSONString()+"}],\"AudioTracks\":[{\"AudioTrackClips\":"+audioClipArray.toJSONString()+"},{\"AudioTrackClips\":[{\"MediaURL\":\""+bgMusicUrl+"\"}]}],\"SubtitleTracks\":[" + subtitleTrack + "]}";
    
            //
            String targetFileName = UUID.randomUUID().toString().replace("-", "");
            String outputMediaUrl = "http://" + bucket + ".oss-" + regionId + ".aliyuncs.com/ice_output/" + targetFileName + ".mp4";
            int width = 720;
            int height = 1280;
            String outputMediaConfig = "{\"MediaURL\":\"" + outputMediaUrl + "\",\"Width\":"+width+",\"Height\":"+height+"}";
    
            SubmitMediaProducingJobRequest request = new SubmitMediaProducingJobRequest();
            request.setTimeline(timeline);
            request.setOutputMediaConfig(outputMediaConfig);
            SubmitMediaProducingJobResponse response = iceClient.submitMediaProducingJob(request);
            System.out.println("start job. jobid: " + response.getBody().getJobId() + ", outputMediaUrl: " + outputMediaUrl);
            return response.getBody().getJobId();
        }
    
    }
    
  6. Replace the bucket placeholder in the code with the name of the bucket you created in Step 1.

  7. Run the code to generate the videos. The code performs the following workflow:

    Generate a timeline > submit a video production job > poll the job status > print logs to the console.

    public static void main(String[] args) throws Exception {
        BatchProduce batchProduce = new BatchProduce();
        batchProduce.initClient();
        batchProduce.run();
    }
    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: Defaulting to no-operation (NOP) logger implementation
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    start job. jobid: 150de5f285c942b0894f1150ad5e6d50, outputMediaUrl: http://oushu-test-shanghai.oss-cn-shanghai.aliyuncs.com/ice_output/e0852f92dd2041e19e0739f4a7cee064.mp4
    waiting job finished...
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Init
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Init
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Init
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Init
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Init
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Init
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Processing
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Processing
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Processing
    jobId: 150de5f285c942b0894f1150ad5e6d50, status:Success
    all job finished.
    Process finished with exit code 0

  8. When the job status changes to Success, the outputMediaUrl provides the URL of the generated video. Because the bucket is private, you cannot open this URL directly. Instead, you must copy a signed URL from the OSS console to preview the video.

    From the Buckets list, select your bucket. Choose File Management > File List and open the ice_output/ directory. Select the target .mp4 file. In the details panel, enable HTTPS, set an expiration time, and click Copy Object URL to get the signed URL.

  9. The produceCount variable specifies the number of videos to generate. Modify this produceCount value to output a different number of videos. You can also replace the sample text and video clips with your own content. For more information, see the Timeline configuration documentation.

References

Next steps