android video captions srt

Buscando un ejemplo funcional de addTimedTextSource para agregar subtítulos a un video de un archivo.srt en Android 4.1



captions (3)

A las almas perdidas perdiendo tiempo en esto en> 2015:

Pasé dos días mirando a la fuente de Android tratando de resolver todos los errores que causaba este Framework de TimedText.

Mi recomendación es omitir su implementación por completo. Es incompleto e inconsistente. En versiones anteriores, una gran parte de la sincronización de texto se realiza en el reproductor de medios nativo, por lo que es propenso a errores de estado.

Mi alternativa es usar una subclase 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; } } }

Uso:

//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);

En su archivo xml de diseño, simplemente cree un textView como desea que se muestren los subtítulos, luego cambie la clase a 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"/>

Buena suerte.

He intentado usar un archivo .srt para una fuente de texto cronometrada (solo disponible en Android 4.1+ http://developer.android.com/about/versions/android-4.1.html#Multimedia ). El primer problema tiene que ver con obtener un descriptor de archivo para el archivo .srt (en la carpeta de activos, ¿cómo lo incluiría en su aplicación?). El archivo se comprime automáticamente, por lo que ni siquiera podrá ver el archivo sin cambiar la configuración de compilación o crear una compilación personalizada. La solución más fácil fue cambiar el nombre del archivo .srt a .jpg para que no se comprimiera y el método openFD aún funcione. Ahora estoy agregando el TimedTextSource con:

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

Ahora el archivo se carga correctamente y usando myMP.getTrackInfo () para obtener una lista de pistas, puede ver que después de agregar la fuente de texto cronometrada, la sexta pista tiene el tipo "3", que es el tipo de pista de texto programado. He utilizado selectTrack para elegir esta pista como se dice en la documentación de google, pero después de hacerlo no aparece ningún título y en mi TimedTextListener:

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

Dispara solo una vez (tengo como 20 eventos de texto cronometrados en el archivo) pero el parámetro de texto siempre es nulo. He hecho búsquedas y no puedo encontrar un solo código de trabajo que ejemplifique el uso de timeText y no aparece en ningún proyecto de muestra, literalmente no hay documentación más que los documentos de API de Google, pero hasta donde puedo decir, nadie ha publicado un ejemplo de trabajo todavía. Estoy probando esto en un Google Nexus actualizado a Android 4.2


Para que funcione con archivos .mp3, llame a player.start (); Inmediatamente después de declarar un nuevo reproductor multimedia y antes del código de texto adicional. Justo después de la línea de abajo

MediaPlayer player = MediaPlayer.create(this, R.raw.video);


Pude hacer que esto funcionara y dado que todavía es una pregunta abierta, incluiré la solución completa aquí.

Aunque la idea de cambiar la extensión de archivo para evitar la compresión es agradable, pero prefiero copiar el archivo srt de los recursos al directorio local de la aplicación en el dispositivo, pero de todos modos, para completar, hay una lista de extensiones que ganó no estar comprimido

".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"

Los pasos de la solución son simples:

  1. Cree una instancia de MediaPlayer y prepárela llamando a MediaPlayer.create() o player.setDataSource() luego player.prepare()

  2. Si los archivos de subtítulos aún no existen en el dispositivo Android, cópielo de la carpeta de recursos al dispositivo

  3. Llame a player.addTimedTextSource() con el primer argumento, una String que contiene la ruta completa del archivo de subtítulos en el dispositivo y MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP como segundo argumento

  4. Seleccione la pista de player.selectTrack() llamando a player.selectTrack() y pase the index of timedTextType buscando en TrackInfo[] devuelto por player.getTrackInfo() (generalmente lo encuentro 2 )

  5. Configura un oyente con player.setOnTimedTextListener() y luego comienza a reproducir el archivo de medios player.start()

Aquí está la clase completa:

Para ejecutar esta clase exacta, necesitarás dos archivos en la carpeta res/raw sub.srt y video.mp4 (o las extensiones). A continuación, defina una vista de TextView con la id txtDisplay . Finalmente su proyecto / dispositivo / emulador debe ser compatible con 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); } }

Y aquí está el archivo de subtitle que estoy usando como ejemplo:

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

Aquí hay algunas capturas de pantalla de la aplicación de prueba que muestran que TextView está cambiando automáticamente (es decir, leyendo desde el archivo de subtítulos) a medida que avanza el archivo de medios

Editar:

Aquí está el código para un proyecto de ejemplo