A gadget about m3u8

preface

     There are many small movies in UC because of the recent plan to change mobile phones. Cough (▰ ˘ ◡ ˘ ▰) no, it's the learning materials that are going to be transferred. I found that many videos are m3u8, which are composed of fragmented fragments, affecting the learning efficiency, so I have this article

1, m3u8 file

     I won't introduce the m3u8 document in detail (I also copied it online). Interested partners can take a look at this article article

Here is a simple quote:

M3U8 file refers to M3U file in UTF-8 encoding format (M3U is encoded by Latin-1 character set). M3U file is a plain text file that records the index. When it is opened, the playback software does not play it, but finds the network address of the corresponding audio and video file according to its index for online playback

The file format of m3u8 is as follows:

2, Magic tools

     Next, let's introduce the focus of this time FFmpeg This tool is described on the official website as follows:

A complete, cross platform solution to record, convert and stream audio and video

FFmpeg has many functions and is very powerful. We only need to use two functions here. One is to download videos according to the m3u8 website, and the other is to synthesize mp4 files according to the ts file list

3, Thought arrangement

1. Install FFmpeg

     Go straight to this FFmpeg official website Just download it. Find the corresponding version, extract and configure environment variables. For details, refer to here

2. Get m3u8 files

     When there is only one m3u8 file, just write the corresponding file path, but we often have a lot of data (cough). Therefore, you need to specify a folder and get all the m3u8 files under the folder

3. Parse m3u8 file

     After obtaining the m3u8 file, you need to extract the file information (mainly the path) of m3u8, and then convert the Android path into the path under Windows, as follows:

Here we borrow an open-m3u8.jar to parse. See the specific usage git introduction of open-m3u8.jar

4. Generate file list

     Before calling FFmpeg, you need to specify a file list to FFmpeg as a parameter for merging. The file format is generally as follows:

It's basically reading m3u8 file list and converting it into this format

5. Call FFmpeg

     After generating the list file, you need to call the FFmpeg command to synthesize the file. This is also simple. You only need to call the Runtime provided by java. The FFmpeg command is as follows

ffmpeg   -f concat -safe 0 -i Generated list file  -c copy  Video name

6. Others

     The above is implemented by merging ts. of course, it can also be downloaded directly through the network. The command is as follows:

ffmpeg -allowed_extensions ALL -protocol_whitelist "file,http,crypto,tcp" -i  m3u8 File path  -c copy Output video path

m3u8 files can be found in the ts directory, as follows:


It is obvious that the http protocol is not the file protocol

4, Code implementation

git address

The specific codes are as follows:

package com.hqd.test;

import com.iheartradio.m3u8.Encoding;
import com.iheartradio.m3u8.Format;
import com.iheartradio.m3u8.PlaylistParser;
import com.iheartradio.m3u8.data.Playlist;
import com.iheartradio.m3u8.data.TrackData;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class FileUtilsTest {

    public static String SUFFIX_NAME_MP4 = ".mp4";
    public static String SUFFIX_NAME_TS = ".ts";
    public static String SUFFIX_NAME_TXT = ".txt";
    public static String SUFFIX_NAME_M3U8 = ".m3u8";

    /**
     * E:\Ffmpeg\bin\ffmpeg.exe -allowed_extensions ALL -protocol_whitelist "file,http,crypto,tcp" -i  G:\FFOutput\fileList.txt  -c copy G:\FFOutput\test.mp4 Parse command containing key
     *
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {

        mergeAllTSFiles("E:\\Ffmpeg\\bin\\ffmpeg", "G:\\FFOutput", false);
    }

    public static void mergeAllTSFiles(String ffmpegPath, String rootPath, boolean isAddSuffix) throws IOException {
        if (StringUtils.isBlank(rootPath)) {
            return;
        }
        if (!rootPath.endsWith(File.separator)) {
            rootPath += File.separator;
        }
        List<File> m3u8Files = searchFileBySuffix(rootPath, SUFFIX_NAME_M3U8);
        PlaylistParser parser = null;
        for (File f : m3u8Files) {
            parser = new PlaylistParser(new FileInputStream(f), Format.EXT_M3U, Encoding.UTF_8);
            try {
                Playlist playlist = parser.parse();
                List<TrackData> tracks = playlist.getMediaPlaylist().getTracks();
                String tsName = getTsName(tracks);
                String tsFullPath = rootPath + tsName;
                if (isAddSuffix) {
                    addFileSuffix(new File(tsFullPath), SUFFIX_NAME_TS);
                }
                String listFilePath = writeFileListText(rootPath, tsName, tracks, isAddSuffix);
                String[] cmd = {"cmd", "/C",
                        String.format("start  %s  -f concat -safe 0 -i %s -c copy %s -y", ffmpegPath,
                                "\"" + listFilePath + "\"", "\"" + tsFullPath.substring(0, tsFullPath.lastIndexOf('.')) + SUFFIX_NAME_MP4 + "\"")
                };
                Runtime.getRuntime().exec(cmd);
            } catch (Exception e) {
                System.out.println(String.format("merge%s File exception:%s", f.getName(), e.fillInStackTrace()));
            }
        }

    }

    /**
     * Get ts file pathname
     *
     * @param trackDataList
     * @return
     */
    private static String getTsName(List<TrackData> trackDataList) {
        if (CollectionUtils.isNotEmpty(trackDataList)) {
            String uri = trackDataList.get(0).getUri();
            if (StringUtils.isNotBlank(uri) && uri.startsWith("file://")) {
                int end = uri.lastIndexOf('/');
                if (end != -1) {
                    int start = uri.substring(0, end).lastIndexOf('/');
                    if (start != -1) {
                        return uri.substring(start + 1, end);
                    }
                }
            }
        }
        return null;
    }

    private static String writeFileListText(String savePath, String name, List<TrackData> trackDataList, boolean isContainsSuffix) throws IOException {
        if (CollectionUtils.isNotEmpty(trackDataList) && StringUtils.isNotBlank(name)) {
            File file = new File(savePath);
            FileUtils.forceMkdir(file);
            File listFileText = new File(file, name.substring(0, name.lastIndexOf('.')) + "_list" + SUFFIX_NAME_TXT);
            StringBuilder sb = new StringBuilder();
            File tsPath = new File(savePath, name);
            if (tsPath.exists()) {
                trackDataList.stream().forEach((trackData) -> {
                    String uri = trackData.getUri();
                    if (StringUtils.isNotBlank(uri) && uri.startsWith("file://")) {
                        int index = uri.lastIndexOf('/');
                        if (index != -1) {
                            sb.append("file  ")
                                    .append("'")
                                    .append(tsPath.getAbsolutePath() + File.separator + uri.substring(index + 1));
                            if (isContainsSuffix) {
                                sb.append(SUFFIX_NAME_TS);
                            }
                            sb.append("'").append(System.lineSeparator());
                        }
                    }
                });
                try (BufferedWriter bw = new BufferedWriter(new FileWriter(listFileText))) {
                    bw.write(sb.toString());
                } catch (IOException e) {
                    throw e;
                }
                return listFileText.getAbsolutePath();
            }
        }
        return null;
    }


    private static void alterSuffix(File file, String suffix) {
        if (file != null) {
            if (file.getName().lastIndexOf(".") == -1) {
                String fileName = file.getAbsolutePath() + "/" + getFileName(file) + suffix;
                File newFile = new File(fileName);
                file.renameTo(newFile);
            }
        }
    }

    public static void addFileSuffix(File file, String suffix) {
        if (file != null && file.exists()) {
            if (file.isFile()) {
                alterSuffix(file, suffix);
            }
            File[] files = file.listFiles();
            for (File f : files) {
                if (f.isFile()) {
                    alterSuffix(f, suffix);
                }
            }
        }
    }

    /**
     * Get file name (excluding suffix)
     *
     * @param file
     * @return
     */
    private static String getFileName(File file) {
        int last = file.getName().lastIndexOf(".");
        if (last == -1)
            return file.getName();
        String fileName = file.getName().substring(0, last);
        return fileName;
    }

    /**
     * Search for files in the specified directory
     *
     * @param file        Find path
     * @param fileName    file name
     * @param isRecursion Recursive
     * @return
     */

    public static List<File> searchFile(File file, String fileName, boolean isRecursion) {
        List<File> fileList = new ArrayList<>();
        if (file == null || !file.exists() || StringUtils.isBlank(fileName)) {
            return fileList;
        }
        if (file.isFile()) {
            if (file.getName().toUpperCase().contains(fileName.toUpperCase())) {
                fileList.add(file);
            }
            return fileList;
        }
        for (File f : file.listFiles()) {
            if (f.isFile()) {
                if (f.getName().toUpperCase().contains(fileName.toUpperCase())) {
                    fileList.add(f);
                }
            } else {
                if (isRecursion) {
                    if (f.getName().startsWith(".")) {
                        continue;
                    }
                    fileList.addAll(searchFile(f, fileName, isRecursion));
                }
            }
        }
        return fileList;
    }

    public static List<File> searchFileBySuffix(String filePath, String suffix) {
        return searchFileBySuffix(filePath, suffix, false);
    }

    public static List<File> searchFileBySuffix(String filePath, String suffix, boolean isRecursion) {
        List<File> files = searchFile(new File(filePath), suffix, isRecursion);
        List<File> newFiles = new ArrayList<>(files.size());
        for (File f : files) {
            if (f.getName().endsWith(suffix)) {
                newFiles.add(f);
            }
        }
        return newFiles;
    }

}

summary

     Here is just a simple tool to merge ts. if there are mistakes, you are welcome to point them out (″ '▽')

Tags: Java

Posted on Tue, 02 Nov 2021 08:52:51 -0400 by Unforgiven