web-dev-qa-db-ja.com

Android 4.1で.srtファイルからビデオに字幕を追加するためのaddTimedTextSourceの実用的な例を探しています

時限テキストソースに.srtファイルを使用しようとしています(Android 4.1+ http://developer.Android.com/about/versions/Androidでのみ利用可能) -4.1.html#Multimedia )。最初の問題は、.srtファイルのファイル記述子を取得することに関するものです(assetsフォルダー内で、他にどのようにアプリにバンドルしますか?)ファイルは自動的に圧縮されますそのため、コンパイル設定を変更したり、カスタムビルドを行わないと、ファイルを表示することもできません。最も簡単な解決策は、.srtファイルの名前を.jpgに変更して、圧縮されず、openFDメソッドが機能するようにすることです。 TimedTextSourceを次のように追加しています:

_myMP.addTimedTextSource(getAssets().openFd("captions.jpg").getFileDescriptor(),   MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);

これで、ファイルが正しく読み込まれ、myMP.getTrackInfo()を使用してトラックのリストが取得されます。タイミングテキストソースを追加すると、6番目のトラックのタイプは「3」であり、これはタイミングテキストトラックタイプです。私はselectTrackを使用して、Googleドキュメントで述べられているようにこのトラックを選択しましたが、選択した後、キャプションがTimedTextListenerに表示されなくなりました。

 _myMP.setOnTimedTextListener(new OnTimedTextListener(){
        @Override
        public void onTimedText(MediaPlayer mp, TimedText text) {
                if (text!=null)
                   Log.d("TimedText", text.getText());  
            }       
        });

1回だけ起動します(ファイル内に20個の時限テキストイベントがあるようです)が、テキストパラメータは常にnullです。私は検索を実行しましたが、timeTextを使用する単一の動作するコード例を見つけることができず、サンプルプロジェクトには表示されません。Googleからのapi docs以外のドキュメントはありませんが、私が知る限り、誰も投稿していませんまだそれの実例。 Android 4.2に更新されたGoogle Nexusでこれをテストしています

32
user1489039

私はこれを機能させることができました、そしてそれはまだ未解決の質問なので、ここに完全な解決策を含めます。

圧縮を防ぐためにファイル拡張子を変更するという考えはいいですが、srtファイルをリソースからデバイスのアプリのローカルディレクトリにコピーすることを好みますが、いずれにしても、ここでは完全にするために圧縮されない拡張子のリスト。

「.jpg」、「。jpeg」、「。png」、「。gif」、「。wav」、「。mp2」、「。mp3」、「。ogg」、「。aac」、「。mpg」、 「.mpeg」、「。mid」、「。midi」、「。smf」、「。jet」、「。rtttl」、「。imy」、「。xmf」、「。mp4」、「。m4a」、 「.m4v」、「。3gp」、「。3gpp」、「。3g2」、「。3gpp2」、「。amr」、「。awb」、「。wma」、「。wmv」

解決策の手順は簡単です:

  1. MediaPlayerインスタンスを作成し、MediaPlayer.create()またはplayer.setDataSource()を呼び出してからplayer.prepare()を呼び出して準備します。

  2. Androidデバイスに字幕ファイルがまだ存在しない場合は、リソースフォルダーからデバイスにコピーします

  3. 最初の引数Stringを使用してplayer.addTimedTextSource()を呼び出します。これには、デバイス上の字幕ファイルの完全パスを含み、_MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP_ as第二引数

  4. player.selectTrack()を呼び出してTimedTextトラックを選択し、player.getTrackInfo()から返された_the index of timedTextType_を検索して_TrackInfo[]_を渡します(通常は_2_)

  5. player.setOnTimedTextListener()でリスナーを設定し、メディアファイルの再生を開始しますplayer.start()

これが完全なクラスです:

この正確なクラスを実行するには、_res/raw_フォルダー_sub.srt_および_video.mp4_(または任意の拡張子)の下に2つのファイルが必要です。次に、TextViewをID txtDisplayで定義します。最後に、プロジェクト/デバイス/エミュレーターは_API 16_をサポートする必要があります

_public class MainActivity extends Activity implements OnTimedTextListener {
    private static final String TAG = "TimedTextTest";
    private TextView txtDisplay;
    private static Handler handler = new Handler();

    @Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txtDisplay = (TextView) findViewById(R.id.txtDisplay);
    MediaPlayer player = MediaPlayer.create(this, R.raw.video);
    try {
        player.addTimedTextSource(getSubtitleFile(R.raw.sub),
                MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
        int textTrackIndex = findTrackIndexFor(
                TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT, player.getTrackInfo());
        if (textTrackIndex >= 0) {
            player.selectTrack(textTrackIndex);
        } else {
            Log.w(TAG, "Cannot find text track!");
        }
        player.setOnTimedTextListener(this);
        player.start();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private int findTrackIndexFor(int mediaTrackType, TrackInfo[] trackInfo) {
    int index = -1;
    for (int i = 0; i < trackInfo.length; i++) {
        if (trackInfo[i].getTrackType() == mediaTrackType) {
            return i;
        }
    }
    return index;
}

private String getSubtitleFile(int resId) {
    String fileName = getResources().getResourceEntryName(resId);
    File subtitleFile = getFileStreamPath(fileName);
    if (subtitleFile.exists()) {
        Log.d(TAG, "Subtitle already exists");
        return subtitleFile.getAbsolutePath();
    }
    Log.d(TAG, "Subtitle does not exists, copy it from res/raw");

    // Copy the file from the res/raw folder to your app folder on the
    // device
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
        inputStream = getResources().openRawResource(resId);
        outputStream = new FileOutputStream(subtitleFile, false);
        copyFile(inputStream, outputStream);
        return subtitleFile.getAbsolutePath();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        closeStreams(inputStream, outputStream);
    }
    return "";
}

private void copyFile(InputStream inputStream, OutputStream outputStream)
        throws IOException {
    final int BUFFER_SIZE = 1024;
    byte[] buffer = new byte[BUFFER_SIZE];
    int length = -1;
    while ((length = inputStream.read(buffer)) != -1) {
        outputStream.write(buffer, 0, length);
    }
}

// A handy method I use to close all the streams
private void closeStreams(Closeable... closeables) {
    if (closeables != null) {
        for (Closeable stream : closeables) {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

@Override
public void onTimedText(final MediaPlayer mp, final TimedText text) {
    if (text != null) {
        handler.post(new Runnable() {
            @Override
            public void run() {
                int seconds = mp.getCurrentPosition() / 1000;

                txtDisplay.setText("[" + secondsToDuration(seconds) + "] "
                        + text.getText());
            }
        });
    }
}

// To display the seconds in the duration format 00:00:00
public String secondsToDuration(int seconds) {
    return String.format("%02d:%02d:%02d", seconds / 3600,
            (seconds % 3600) / 60, (seconds % 60), Locale.US);
}
}
_

そして、ここに私が例として使用しているsubtitleファイルがあります:

_1
00:00:00,220 --> 00:00:01,215
First Text Example

2
00:00:03,148 --> 00:00:05,053
Second Text Example

3
00:00:08,004 --> 00:00:09,884
Third Text Example

4
00:00:11,300 --> 00:00:12,900
Fourth Text Example

5
00:00:15,500 --> 00:00:16,700
Fifth Text Example

6
00:00:18,434 --> 00:00:20,434
Sixth Text Example

7
00:00:22,600 --> 00:00:23,700
Last Text Example
_

メディアファイルの進行に合わせてTextViewが自動的に変更される(つまり、字幕ファイルから読み取られる)ことを示すテストアプリのスクリーンショットの一部を次に示します

TimedText Example

編集:

これが サンプルプロジェクト のコードです。

28
iTech

編集:私はここ数年で、AndroidのKitKat後のバージョンがアプリを使用するAndroidデバイスの市場シェアのほとんどになりました。以下の実装は、古いデバイスとの互換性を橋渡しするための試みでした。現時点では、カスタムソリューションに大きなメンテナンスコストがかかる可能性があるため、AndroidによってリリースされたTimedTextフレームワーク(KitKatでは正常に機能しました)または新しい代替手段を使用することをお勧めします。


このTimedText Frameworkが引き起こしていたすべてのバグを解決しようとするAndroidソースを見て、2日間かけてみました。

私の推奨は、それらの実装を完全にスキップすることです。それは不完全で矛盾しています。以前のバージョンでは、テキストの同期の多くはネイティブメディアプレーヤーで行われるため、状態エラーが発生する傾向があります。

私の代わりは、Textviewサブクラスを使用することです:

package ca.yourpackage.yourapp;

import Android.content.Context;
import Android.media.MediaPlayer;
import Android.util.AttributeSet;
import Android.util.Log;
import Android.widget.TextView;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import Java.io.LineNumberReader;
import Java.util.Locale;
import Java.util.Map;
import Java.util.TreeMap;

/**
 * Created by MHDante on 2015-07-26.
 */
public class SubtitleView extends TextView implements Runnable{
    private static final String TAG = "SubtitleView";
    private static final boolean DEBUG = false;
    private static final int UPDATE_INTERVAL = 300;
    private MediaPlayer player;
    private TreeMap<Long, Line> track;

    public SubtitleView(Context context) {
        super(context);
    }


    public SubtitleView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public SubtitleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public void run() {
        if (player !=null && track!= null){
            int seconds = player.getCurrentPosition() / 1000;
            setText((DEBUG?"[" + secondsToDuration(seconds) + "] ":"")
                    + getTimedText(player.getCurrentPosition()));
        }
        postDelayed(this, UPDATE_INTERVAL);
    }

    private String getTimedText(long currentPosition) {
        String result = "";
        for(Map.Entry<Long, Line> entry: track.entrySet()){
            if (currentPosition < entry.getKey()) break;
            if (currentPosition < entry.getValue().to) result = entry.getValue().text;
        }
        return result;
    }

    // To display the seconds in the duration format 00:00:00
    public String secondsToDuration(int seconds) {
        return String.format("%02d:%02d:%02d", seconds / 3600,
                (seconds % 3600) / 60, (seconds % 60), Locale.US);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        postDelayed(this, 300);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks(this);
    }
    public void setPlayer(MediaPlayer player) {
        this.player = player;
    }

    public void setSubSource(int ResID, String mime){
        if(mime.equals(MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP))
            track = getSubtitleFile(ResID);
        else
            throw new UnsupportedOperationException("Parser only built for SRT subs");
    }

    /////////////Utility Methods:
    //Based on https://github.com/sannies/mp4parser/
    //Apache 2.0 Licence at: https://github.com/sannies/mp4parser/blob/master/LICENSE

    public static TreeMap<Long, Line> parse(InputStream is) throws IOException {
        LineNumberReader r = new LineNumberReader(new InputStreamReader(is, "UTF-8"));
        TreeMap<Long, Line> track = new TreeMap<>();
        while ((r.readLine()) != null) /*Read cue number*/{
            String timeString = r.readLine();
            String lineString = "";
            String s;
            while (!((s = r.readLine()) == null || s.trim().equals(""))) {
                lineString += s + "\n";
            }
            long startTime = parse(timeString.split("-->")[0]);
            long endTime = parse(timeString.split("-->")[1]);
            track.put(startTime, new Line(startTime, endTime, lineString));
        }
        return track;
    }

    private static long parse(String in) {
        long hours = Long.parseLong(in.split(":")[0].trim());
        long minutes = Long.parseLong(in.split(":")[1].trim());
        long seconds = Long.parseLong(in.split(":")[2].split(",")[0].trim());
        long millies = Long.parseLong(in.split(":")[2].split(",")[1].trim());

        return hours * 60 * 60 * 1000 + minutes * 60 * 1000 + seconds * 1000 + millies;

    }

    private TreeMap<Long, Line> getSubtitleFile(int resId) {
        InputStream inputStream = null;
        try {
            inputStream = getResources().openRawResource(resId);
            return parse(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    public static class Line {
        long from;
        long to;
        String text;


        public Line(long from, long to, String text) {
            this.from = from;
            this.to = to;
            this.text = text;
        }
    }
}

使用法:

//I used and reccomend asyncPrepare()
MediaPlayer mp = MediaPlayer.create(context, R.raw.video);
SubtitleView subView = (SubtitleView) getViewbyId(R.id.subs_box);
subView.setPlayer(mp);
subView.setSubSource(R.raw.subs_intro, MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);

レイアウトxmlファイルで、字幕を表示したいテキストビューを作成し、クラスをca.yourpagckage.yourapp.SubtitleViewに変更します。

<ca.yourpagckage.yourapp.SubtitleView
    Android:layout_width="300dp"
    Android:layout_height="300dp"
    Android:text="Subtitles go Here"
    Android:id="@+id/subs_box"/>

幸運を。

11
MHDante