mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-29 22:32:59 +00:00 
			
		
		
		
	Merge branch 'dev' into dev
This commit is contained in:
		| @@ -13,7 +13,7 @@ | |||||||
| </p> | </p> | ||||||
| <hr> | <hr> | ||||||
| <p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#updates">Updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p> | <p align="center"><a href="#screenshots">Screenshots</a> • <a href="#description">Description</a> • <a href="#features">Features</a> • <a href="#updates">Updates</a> • <a href="#contribution">Contribution</a> • <a href="#donate">Donate</a> • <a href="#license">License</a></p> | ||||||
| <p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a>  • <a href="https://newpipe.schabi.org/press/">Press</a></p> | <p align="center"><a href="https://newpipe.schabi.org">Website</a> • <a href="https://newpipe.schabi.org/blog/">Blog</a> • <a href="https://newpipe.schabi.org/FAQ/">FAQ</a> • <a href="https://newpipe.schabi.org/press/">Press</a></p> | ||||||
| <hr> | <hr> | ||||||
|  |  | ||||||
| <b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b> | <b>WARNING: THIS IS A BETA VERSION, THEREFORE YOU MAY ENCOUNTER BUGS. IF YOU DO, OPEN AN ISSUE VIA OUR GITHUB REPOSITORY.</b> | ||||||
|   | |||||||
| @@ -832,7 +832,6 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck | |||||||
|                     psArgs = new String[]{ |                     psArgs = new String[]{ | ||||||
|                             selectedStream.getFormat().getSuffix(), |                             selectedStream.getFormat().getSuffix(), | ||||||
|                             "false",// ignore empty frames |                             "false",// ignore empty frames | ||||||
|                             "false",// detect youtube duplicate lines |  | ||||||
|                     }; |                     }; | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|   | |||||||
| @@ -79,6 +79,7 @@ import org.schabi.newpipe.util.Constants; | |||||||
| import org.schabi.newpipe.util.ExtractorHelper; | import org.schabi.newpipe.util.ExtractorHelper; | ||||||
| import org.schabi.newpipe.util.ImageDisplayConstants; | import org.schabi.newpipe.util.ImageDisplayConstants; | ||||||
| import org.schabi.newpipe.util.InfoCache; | import org.schabi.newpipe.util.InfoCache; | ||||||
|  | import org.schabi.newpipe.util.KoreUtil; | ||||||
| import org.schabi.newpipe.util.ListHelper; | import org.schabi.newpipe.util.ListHelper; | ||||||
| import org.schabi.newpipe.util.Localization; | import org.schabi.newpipe.util.Localization; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| @@ -624,7 +625,7 @@ public class VideoDetailFragment | |||||||
|                             url.replace("https", "http"))); |                             url.replace("https", "http"))); | ||||||
|                 } catch (Exception e) { |                 } catch (Exception e) { | ||||||
|                     if (DEBUG) Log.i(TAG, "Failed to start kore", e); |                     if (DEBUG) Log.i(TAG, "Failed to start kore", e); | ||||||
|                     showInstallKoreDialog(activity); |                     KoreUtil.showInstallKoreDialog(activity); | ||||||
|                 } |                 } | ||||||
|                 return true; |                 return true; | ||||||
|             default: |             default: | ||||||
| @@ -632,16 +633,6 @@ public class VideoDetailFragment | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static void showInstallKoreDialog(final Context context) { |  | ||||||
|         final AlertDialog.Builder builder = new AlertDialog.Builder(context); |  | ||||||
|         builder.setMessage(R.string.kore_not_found) |  | ||||||
|                 .setPositiveButton(R.string.install, (DialogInterface dialog, int which) -> |  | ||||||
|                         NavigationHelper.installKore(context)) |  | ||||||
|                 .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { |  | ||||||
|                 }); |  | ||||||
|         builder.create().show(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void setupActionBarOnError(final String url) { |     private void setupActionBarOnError(final String url) { | ||||||
|         if (DEBUG) Log.d(TAG, "setupActionBarHandlerOnError() called with: url = [" + url + "]"); |         if (DEBUG) Log.d(TAG, "setupActionBarHandlerOnError() called with: url = [" + url + "]"); | ||||||
|         Log.e("-----", "missing code"); |         Log.e("-----", "missing code"); | ||||||
|   | |||||||
| @@ -55,10 +55,7 @@ public final class BackgroundPlayerActivity extends ServicePlayerActivity { | |||||||
|                 return true; |                 return true; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             this.player.setRecovery(); |             return switchTo(PopupVideoPlayer.class); | ||||||
|             getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); |  | ||||||
|             getApplicationContext().startService(getSwitchIntent(PopupVideoPlayer.class)); |  | ||||||
|             return true; |  | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -150,6 +150,8 @@ public abstract class BasePlayer implements | |||||||
|     @NonNull |     @NonNull | ||||||
|     public static final String RESUME_PLAYBACK = "resume_playback"; |     public static final String RESUME_PLAYBACK = "resume_playback"; | ||||||
|     @NonNull |     @NonNull | ||||||
|  |     public static final String START_PAUSED = "start_paused"; | ||||||
|  |     @NonNull | ||||||
|     public static final String SELECT_ON_APPEND = "select_on_append"; |     public static final String SELECT_ON_APPEND = "select_on_append"; | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -304,7 +306,7 @@ public abstract class BasePlayer implements | |||||||
|         } |         } | ||||||
|         // Good to go... |         // Good to go... | ||||||
|         initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, |         initPlayback(queue, repeatMode, playbackSpeed, playbackPitch, playbackSkipSilence, | ||||||
|                 /*playOnInit=*/true); |                 /*playOnInit=*/!intent.getBooleanExtra(START_PAUSED, false)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected void initPlayback(@NonNull final PlayQueue queue, |     protected void initPlayback(@NonNull final PlayQueue queue, | ||||||
| @@ -944,10 +946,10 @@ public abstract class BasePlayer implements | |||||||
|     public void onPlayPause() { |     public void onPlayPause() { | ||||||
|         if (DEBUG) Log.d(TAG, "onPlayPause() called"); |         if (DEBUG) Log.d(TAG, "onPlayPause() called"); | ||||||
|  |  | ||||||
|         if (!isPlaying()) { |         if (isPlaying()) { | ||||||
|             onPlay(); |  | ||||||
|         } else { |  | ||||||
|             onPause(); |             onPause(); | ||||||
|  |         } else { | ||||||
|  |             onPlay(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ import android.database.ContentObserver; | |||||||
| import android.graphics.Color; | import android.graphics.Color; | ||||||
| import android.graphics.drawable.ColorDrawable; | import android.graphics.drawable.ColorDrawable; | ||||||
| import android.media.AudioManager; | import android.media.AudioManager; | ||||||
|  | import android.net.Uri; | ||||||
| import android.os.Build; | import android.os.Build; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| @@ -75,6 +76,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItemTouchCallback; | |||||||
| import org.schabi.newpipe.player.resolver.MediaSourceTag; | import org.schabi.newpipe.player.resolver.MediaSourceTag; | ||||||
| import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; | import org.schabi.newpipe.player.resolver.VideoPlaybackResolver; | ||||||
| import org.schabi.newpipe.util.AnimationUtils; | import org.schabi.newpipe.util.AnimationUtils; | ||||||
|  | import org.schabi.newpipe.util.KoreUtil; | ||||||
| import org.schabi.newpipe.util.ListHelper; | import org.schabi.newpipe.util.ListHelper; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| import org.schabi.newpipe.util.PermissionHelper; | import org.schabi.newpipe.util.PermissionHelper; | ||||||
| @@ -440,6 +442,7 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|         private boolean queueVisible; |         private boolean queueVisible; | ||||||
|  |  | ||||||
|         private ImageButton moreOptionsButton; |         private ImageButton moreOptionsButton; | ||||||
|  |         private ImageButton kodiButton; | ||||||
|         private ImageButton shareButton; |         private ImageButton shareButton; | ||||||
|         private ImageButton toggleOrientationButton; |         private ImageButton toggleOrientationButton; | ||||||
|         private ImageButton switchPopupButton; |         private ImageButton switchPopupButton; | ||||||
| @@ -476,6 +479,7 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|  |  | ||||||
|             this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton); |             this.moreOptionsButton = rootView.findViewById(R.id.moreOptionsButton); | ||||||
|             this.secondaryControls = rootView.findViewById(R.id.secondaryControls); |             this.secondaryControls = rootView.findViewById(R.id.secondaryControls); | ||||||
|  |             this.kodiButton = rootView.findViewById(R.id.kodi); | ||||||
|             this.shareButton = rootView.findViewById(R.id.share); |             this.shareButton = rootView.findViewById(R.id.share); | ||||||
|             this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation); |             this.toggleOrientationButton = rootView.findViewById(R.id.toggleOrientation); | ||||||
|             this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground); |             this.switchBackgroundButton = rootView.findViewById(R.id.switchBackground); | ||||||
| @@ -487,6 +491,9 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|  |  | ||||||
|             titleTextView.setSelected(true); |             titleTextView.setSelected(true); | ||||||
|             channelTextView.setSelected(true); |             channelTextView.setSelected(true); | ||||||
|  |             boolean showKodiButton = PreferenceManager.getDefaultSharedPreferences(this.context).getBoolean( | ||||||
|  | 					this.context.getString(R.string.show_play_with_kodi_key), false); | ||||||
|  |             kodiButton.setVisibility(showKodiButton ? View.VISIBLE : View.GONE); | ||||||
|  |  | ||||||
|             getRootView().setKeepScreenOn(true); |             getRootView().setKeepScreenOn(true); | ||||||
|         } |         } | ||||||
| @@ -523,6 +530,7 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|             closeButton.setOnClickListener(this); |             closeButton.setOnClickListener(this); | ||||||
|  |  | ||||||
|             moreOptionsButton.setOnClickListener(this); |             moreOptionsButton.setOnClickListener(this); | ||||||
|  |             kodiButton.setOnClickListener(this); | ||||||
|             shareButton.setOnClickListener(this); |             shareButton.setOnClickListener(this); | ||||||
|             toggleOrientationButton.setOnClickListener(this); |             toggleOrientationButton.setOnClickListener(this); | ||||||
|             switchBackgroundButton.setOnClickListener(this); |             switchBackgroundButton.setOnClickListener(this); | ||||||
| @@ -593,6 +601,17 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|             finish(); |             finish(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         public void onKodiShare() { | ||||||
|  |             onPause(); | ||||||
|  |             try { | ||||||
|  |                 NavigationHelper.playWithKore(this.context, Uri.parse( | ||||||
|  |                         playerImpl.getVideoUrl().replace("https", "http"))); | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 if (DEBUG) Log.i(TAG, "Failed to start kore", e); | ||||||
|  |                 KoreUtil.showInstallKoreDialog(this.context); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         /*////////////////////////////////////////////////////////////////////////// |         /*////////////////////////////////////////////////////////////////////////// | ||||||
|         // Player Overrides |         // Player Overrides | ||||||
|         //////////////////////////////////////////////////////////////////////////*/ |         //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -619,7 +638,8 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|                     this.getPlaybackPitch(), |                     this.getPlaybackPitch(), | ||||||
|                     this.getPlaybackSkipSilence(), |                     this.getPlaybackSkipSilence(), | ||||||
|                     this.getPlaybackQuality(), |                     this.getPlaybackQuality(), | ||||||
|                     false |                     false, | ||||||
|  |                     !isPlaying() | ||||||
|             ); |             ); | ||||||
|             context.startService(intent); |             context.startService(intent); | ||||||
|  |  | ||||||
| @@ -642,7 +662,8 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|                     this.getPlaybackPitch(), |                     this.getPlaybackPitch(), | ||||||
|                     this.getPlaybackSkipSilence(), |                     this.getPlaybackSkipSilence(), | ||||||
|                     this.getPlaybackQuality(), |                     this.getPlaybackQuality(), | ||||||
|                     false |                     false, | ||||||
|  |                     !isPlaying() | ||||||
|             ); |             ); | ||||||
|             context.startService(intent); |             context.startService(intent); | ||||||
|  |  | ||||||
| @@ -691,6 +712,8 @@ public final class MainVideoPlayer extends AppCompatActivity | |||||||
|             } else if (v.getId() == closeButton.getId()) { |             } else if (v.getId() == closeButton.getId()) { | ||||||
|                 onPlaybackShutdown(); |                 onPlaybackShutdown(); | ||||||
|                 return; |                 return; | ||||||
|  |             } else if (v.getId() == kodiButton.getId()) { | ||||||
|  |             	onKodiShare(); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (getCurrentState() != STATE_COMPLETED) { |             if (getCurrentState() != STATE_COMPLETED) { | ||||||
|   | |||||||
| @@ -571,7 +571,8 @@ public final class PopupVideoPlayer extends Service { | |||||||
|                     this.getPlaybackPitch(), |                     this.getPlaybackPitch(), | ||||||
|                     this.getPlaybackSkipSilence(), |                     this.getPlaybackSkipSilence(), | ||||||
|                     this.getPlaybackQuality(), |                     this.getPlaybackQuality(), | ||||||
|                     false |                     false, | ||||||
|  |                     !isPlaying() | ||||||
|             ); |             ); | ||||||
|             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||||
|             context.startActivity(intent); |             context.startActivity(intent); | ||||||
|   | |||||||
| @@ -48,10 +48,7 @@ public final class PopupVideoPlayerActivity extends ServicePlayerActivity { | |||||||
|     @Override |     @Override | ||||||
|     public boolean onPlayerOptionSelected(MenuItem item) { |     public boolean onPlayerOptionSelected(MenuItem item) { | ||||||
|         if (item.getItemId() == R.id.action_switch_background) { |         if (item.getItemId() == R.id.action_switch_background) { | ||||||
|             this.player.setRecovery(); |             return switchTo(BackgroundPlayer.class); | ||||||
|             getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); |  | ||||||
|             getApplicationContext().startService(getSwitchIntent(BackgroundPlayer.class)); |  | ||||||
|             return true; |  | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -164,10 +164,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity | |||||||
|                 startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS)); |                 startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS)); | ||||||
|                 return true; |                 return true; | ||||||
|             case R.id.action_switch_main: |             case R.id.action_switch_main: | ||||||
|                 this.player.setRecovery(); |                 return switchTo(MainVideoPlayer.class); | ||||||
|                 getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); |  | ||||||
|                 getApplicationContext().startActivity(getSwitchIntent(MainVideoPlayer.class)); |  | ||||||
|                 return true; |  | ||||||
|         } |         } | ||||||
|         return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item); |         return onPlayerOptionSelected(item) || super.onOptionsItemSelected(item); | ||||||
|     } |     } | ||||||
| @@ -188,8 +185,17 @@ public abstract class ServicePlayerActivity extends AppCompatActivity | |||||||
|                 this.player.getPlaybackPitch(), |                 this.player.getPlaybackPitch(), | ||||||
|                 this.player.getPlaybackSkipSilence(), |                 this.player.getPlaybackSkipSilence(), | ||||||
|                 null, |                 null, | ||||||
|  |                 false, | ||||||
|                 false |                 false | ||||||
|         ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |         ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | ||||||
|  |         .putExtra(BasePlayer.START_PAUSED, !this.player.isPlaying()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected boolean switchTo(final Class clazz) { | ||||||
|  |         this.player.setRecovery(); | ||||||
|  |         getApplicationContext().sendBroadcast(getPlayerShutdownIntent()); | ||||||
|  |         getApplicationContext().startActivity(getSwitchIntent(clazz)); | ||||||
|  |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     //////////////////////////////////////////////////////////////////////////// |     //////////////////////////////////////////////////////////////////////////// | ||||||
|   | |||||||
| @@ -0,0 +1,95 @@ | |||||||
|  | package org.schabi.newpipe.streams; | ||||||
|  |  | ||||||
|  | import org.jsoup.Jsoup; | ||||||
|  | import org.jsoup.nodes.Document; | ||||||
|  | import org.jsoup.nodes.Element; | ||||||
|  | import org.jsoup.nodes.Node; | ||||||
|  | import org.jsoup.nodes.TextNode; | ||||||
|  | import org.jsoup.parser.Parser; | ||||||
|  | import org.jsoup.select.Elements; | ||||||
|  | import org.schabi.newpipe.streams.io.SharpStream; | ||||||
|  |  | ||||||
|  | import java.io.ByteArrayInputStream; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.nio.charset.Charset; | ||||||
|  | import java.nio.charset.StandardCharsets; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @author kapodamy | ||||||
|  |  */ | ||||||
|  | public class SrtFromTtmlWriter { | ||||||
|  |     private static final String NEW_LINE = "\r\n"; | ||||||
|  |  | ||||||
|  |     private SharpStream out; | ||||||
|  |     private boolean ignoreEmptyFrames; | ||||||
|  |     private final Charset charset = StandardCharsets.UTF_8; | ||||||
|  |  | ||||||
|  |     private int frameIndex = 0; | ||||||
|  |  | ||||||
|  |     public SrtFromTtmlWriter(SharpStream out, boolean ignoreEmptyFrames) { | ||||||
|  |         this.out = out; | ||||||
|  |         this.ignoreEmptyFrames = ignoreEmptyFrames; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static String getTimestamp(Element frame, String attr) { | ||||||
|  |         return frame | ||||||
|  |                 .attr(attr) | ||||||
|  |                 .replace('.', ',');// SRT subtitles uses comma as decimal separator | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void writeFrame(String begin, String end, StringBuilder text) throws IOException { | ||||||
|  |         writeString(String.valueOf(frameIndex++)); | ||||||
|  |         writeString(NEW_LINE); | ||||||
|  |         writeString(begin); | ||||||
|  |         writeString(" --> "); | ||||||
|  |         writeString(end); | ||||||
|  |         writeString(NEW_LINE); | ||||||
|  |         writeString(text.toString()); | ||||||
|  |         writeString(NEW_LINE); | ||||||
|  |         writeString(NEW_LINE); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void writeString(String text) throws IOException { | ||||||
|  |         out.write(text.getBytes(charset)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void build(SharpStream ttml) throws IOException { | ||||||
|  |         /* | ||||||
|  |          * TTML parser with BASIC support | ||||||
|  |          * multiple CUE is not supported | ||||||
|  |          * styling is not supported | ||||||
|  |          * tag timestamps (in auto-generated subtitles) are not supported, maybe in the future | ||||||
|  |          * also TimestampTagOption enum is not applicable | ||||||
|  |          * Language parsing is not supported | ||||||
|  |          */ | ||||||
|  |  | ||||||
|  |         // parse XML | ||||||
|  |         byte[] buffer = new byte[(int) ttml.available()]; | ||||||
|  |         ttml.read(buffer); | ||||||
|  |         Document doc = Jsoup.parse(new ByteArrayInputStream(buffer), "UTF-8", "", Parser.xmlParser()); | ||||||
|  |  | ||||||
|  |         StringBuilder text = new StringBuilder(128); | ||||||
|  |         Elements paragraph_list = doc.select("body > div > p"); | ||||||
|  |  | ||||||
|  |         // check if has frames | ||||||
|  |         if (paragraph_list.size() < 1) return; | ||||||
|  |  | ||||||
|  |         for (Element paragraph : paragraph_list) { | ||||||
|  |             text.setLength(0); | ||||||
|  |  | ||||||
|  |             for (Node children : paragraph.childNodes()) { | ||||||
|  |                 if (children instanceof TextNode) | ||||||
|  |                     text.append(((TextNode) children).text()); | ||||||
|  |                 else if (children instanceof Element && ((Element) children).tagName().equalsIgnoreCase("br")) | ||||||
|  |                     text.append(NEW_LINE); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if (ignoreEmptyFrames && text.length() < 1) continue; | ||||||
|  |  | ||||||
|  |             String begin = getTimestamp(paragraph, "begin"); | ||||||
|  |             String end = getTimestamp(paragraph, "end"); | ||||||
|  |  | ||||||
|  |             writeFrame(begin, end, text); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,369 +0,0 @@ | |||||||
| package org.schabi.newpipe.streams; |  | ||||||
|  |  | ||||||
| import org.schabi.newpipe.streams.io.SharpStream; |  | ||||||
| import org.w3c.dom.Document; |  | ||||||
| import org.w3c.dom.Element; |  | ||||||
| import org.w3c.dom.Node; |  | ||||||
| import org.w3c.dom.NodeList; |  | ||||||
| import org.xml.sax.SAXException; |  | ||||||
|  |  | ||||||
| import java.io.ByteArrayInputStream; |  | ||||||
| import java.io.IOException; |  | ||||||
| import java.nio.charset.Charset; |  | ||||||
| import java.text.ParseException; |  | ||||||
| import java.util.Locale; |  | ||||||
|  |  | ||||||
| import javax.xml.parsers.DocumentBuilder; |  | ||||||
| import javax.xml.parsers.DocumentBuilderFactory; |  | ||||||
| import javax.xml.parsers.ParserConfigurationException; |  | ||||||
| import javax.xml.xpath.XPathExpressionException; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @author kapodamy |  | ||||||
|  */ |  | ||||||
| public class SubtitleConverter { |  | ||||||
|     private static final String NEW_LINE = "\r\n"; |  | ||||||
|  |  | ||||||
|     public void dumpTTML(SharpStream in, final SharpStream out, final boolean ignoreEmptyFrames, final boolean detectYoutubeDuplicateLines |  | ||||||
|     ) throws IOException, ParseException, SAXException, ParserConfigurationException, XPathExpressionException { |  | ||||||
|  |  | ||||||
|         final FrameWriter callback = new FrameWriter() { |  | ||||||
|             int frameIndex = 0; |  | ||||||
|             final Charset charset = Charset.forName("utf-8"); |  | ||||||
|  |  | ||||||
|             @Override |  | ||||||
|             public void yield(SubtitleFrame frame) throws IOException { |  | ||||||
|                 if (ignoreEmptyFrames && frame.isEmptyText()) { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 out.write(String.valueOf(frameIndex++).getBytes(charset)); |  | ||||||
|                 out.write(NEW_LINE.getBytes(charset)); |  | ||||||
|                 out.write(getTime(frame.start, true).getBytes(charset)); |  | ||||||
|                 out.write(" --> ".getBytes(charset)); |  | ||||||
|                 out.write(getTime(frame.end, true).getBytes(charset)); |  | ||||||
|                 out.write(NEW_LINE.getBytes(charset)); |  | ||||||
|                 out.write(frame.text.getBytes(charset)); |  | ||||||
|                 out.write(NEW_LINE.getBytes(charset)); |  | ||||||
|                 out.write(NEW_LINE.getBytes(charset)); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         read_xml_based(in, callback, detectYoutubeDuplicateLines, |  | ||||||
|                 "tt", "xmlns", "http://www.w3.org/ns/ttml", |  | ||||||
|                 new String[]{"timedtext", "head", "wp"}, |  | ||||||
|                 new String[]{"body", "div", "p"}, |  | ||||||
|                 "begin", "end", true |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void read_xml_based(SharpStream source, FrameWriter callback, boolean detectYoutubeDuplicateLines, |  | ||||||
|                                 String root, String formatAttr, String formatVersion, String[] cuePath, String[] framePath, |  | ||||||
|                                 String timeAttr, String durationAttr, boolean hasTimestamp |  | ||||||
|     ) throws IOException, ParseException, SAXException, ParserConfigurationException, XPathExpressionException { |  | ||||||
|         /* |  | ||||||
|          * XML based subtitles parser with BASIC support |  | ||||||
|          * multiple CUE is not supported |  | ||||||
|          * styling is not supported |  | ||||||
|          * tag timestamps (in auto-generated subtitles) are not supported, maybe in the future |  | ||||||
|          * also TimestampTagOption enum is not applicable |  | ||||||
|          * Language parsing is not supported |  | ||||||
|          */ |  | ||||||
|  |  | ||||||
|         byte[] buffer = new byte[(int) source.available()]; |  | ||||||
|         source.read(buffer); |  | ||||||
|  |  | ||||||
|         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |  | ||||||
|         factory.setNamespaceAware(true); |  | ||||||
|         DocumentBuilder builder = factory.newDocumentBuilder(); |  | ||||||
|         Document xml = builder.parse(new ByteArrayInputStream(buffer)); |  | ||||||
|  |  | ||||||
|         String attr; |  | ||||||
|  |  | ||||||
|         // get the format version or namespace |  | ||||||
|         Element node = xml.getDocumentElement(); |  | ||||||
|  |  | ||||||
|         if (node == null) { |  | ||||||
|             throw new ParseException("Can't get the format version. ¿wrong namespace?", -1); |  | ||||||
|         } else if (!node.getNodeName().equals(root)) { |  | ||||||
|             throw new ParseException("Invalid root", -1); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (formatAttr.equals("xmlns")) { |  | ||||||
|             if (!node.getNamespaceURI().equals(formatVersion)) { |  | ||||||
|                 throw new UnsupportedOperationException("Expected xml namespace: " + formatVersion); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             attr = node.getAttributeNS(formatVersion, formatAttr); |  | ||||||
|             if (attr == null) { |  | ||||||
|                 throw new ParseException("Can't get the format attribute", -1); |  | ||||||
|             } |  | ||||||
|             if (!attr.equals(formatVersion)) { |  | ||||||
|                 throw new ParseException("Invalid format version : " + attr, -1); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         NodeList node_list; |  | ||||||
|  |  | ||||||
|         int line_break = 0;// Maximum characters per line if present (valid for TranScript v3) |  | ||||||
|  |  | ||||||
|         if (!hasTimestamp) { |  | ||||||
|             node_list = selectNodes(xml, cuePath, formatVersion); |  | ||||||
|  |  | ||||||
|             if (node_list != null) { |  | ||||||
|                 // if the subtitle has multiple CUEs, use the highest value |  | ||||||
|                 for (int i = 0; i < node_list.getLength(); i++) { |  | ||||||
|                     try { |  | ||||||
|                         int tmp = Integer.parseInt(((Element) node_list.item(i)).getAttributeNS(formatVersion, "ah")); |  | ||||||
|                         if (tmp > line_break) { |  | ||||||
|                             line_break = tmp; |  | ||||||
|                         } |  | ||||||
|                     } catch (Exception err) { |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // parse every frame |  | ||||||
|         node_list = selectNodes(xml, framePath, formatVersion); |  | ||||||
|  |  | ||||||
|         if (node_list == null) { |  | ||||||
|             return;// no frames detected |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         int fs_ff = -1;// first timestamp of first frame |  | ||||||
|         boolean limit_lines = false; |  | ||||||
|  |  | ||||||
|         for (int i = 0; i < node_list.getLength(); i++) { |  | ||||||
|             Element elem = (Element) node_list.item(i); |  | ||||||
|             SubtitleFrame obj = new SubtitleFrame(); |  | ||||||
|             obj.text = elem.getTextContent(); |  | ||||||
|  |  | ||||||
|             attr = elem.getAttribute(timeAttr);// ¡this cant be null! |  | ||||||
|             obj.start = hasTimestamp ? parseTimestamp(attr) : Integer.parseInt(attr); |  | ||||||
|  |  | ||||||
|             attr = elem.getAttribute(durationAttr); |  | ||||||
|             if (obj.text == null || attr == null) { |  | ||||||
|                 continue;// normally is a blank line (on auto-generated subtitles) ignore |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (hasTimestamp) { |  | ||||||
|                 obj.end = parseTimestamp(attr); |  | ||||||
|  |  | ||||||
|                 if (detectYoutubeDuplicateLines) { |  | ||||||
|                     if (limit_lines) { |  | ||||||
|                         int swap = obj.end; |  | ||||||
|                         obj.end = fs_ff; |  | ||||||
|                         fs_ff = swap; |  | ||||||
|                     } else { |  | ||||||
|                         if (fs_ff < 0) { |  | ||||||
|                             fs_ff = obj.end; |  | ||||||
|                         } else { |  | ||||||
|                             if (fs_ff < obj.start) { |  | ||||||
|                                 limit_lines = true;// the subtitles has duplicated lines |  | ||||||
|                             } else { |  | ||||||
|                                 detectYoutubeDuplicateLines = false; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 obj.end = obj.start + Integer.parseInt(attr); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (/*node.getAttribute("w").equals("1") &&*/line_break > 1 && obj.text.length() > line_break) { |  | ||||||
|  |  | ||||||
|                 // implement auto line breaking (once) |  | ||||||
|                 StringBuilder text = new StringBuilder(obj.text); |  | ||||||
|                 obj.text = null; |  | ||||||
|  |  | ||||||
|                 switch (text.charAt(line_break)) { |  | ||||||
|                     case ' ': |  | ||||||
|                     case '\t': |  | ||||||
|                         putBreakAt(line_break, text); |  | ||||||
|                         break; |  | ||||||
|                     default:// find the word start position |  | ||||||
|                         for (int j = line_break - 1; j > 0; j--) { |  | ||||||
|                             switch (text.charAt(j)) { |  | ||||||
|                                 case ' ': |  | ||||||
|                                 case '\t': |  | ||||||
|                                     putBreakAt(j, text); |  | ||||||
|                                     j = -1; |  | ||||||
|                                     break; |  | ||||||
|                                 case '\r': |  | ||||||
|                                 case '\n': |  | ||||||
|                                     j = -1;// long word, just ignore |  | ||||||
|                                     break; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                         break; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 obj.text = text.toString();// set the processed text |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             callback.yield(obj); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static NodeList selectNodes(Document xml, String[] path, String namespaceUri) { |  | ||||||
|         Element ref = xml.getDocumentElement(); |  | ||||||
|  |  | ||||||
|         for (int i = 0; i < path.length - 1; i++) { |  | ||||||
|             NodeList nodes = ref.getChildNodes(); |  | ||||||
|             if (nodes.getLength() < 1) { |  | ||||||
|                 return null; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             Element elem; |  | ||||||
|             for (int j = 0; j < nodes.getLength(); j++) { |  | ||||||
|                 if (nodes.item(j).getNodeType() == Node.ELEMENT_NODE) { |  | ||||||
|                     elem = (Element) nodes.item(j); |  | ||||||
|                     if (elem.getNodeName().equals(path[i]) && elem.getNamespaceURI().equals(namespaceUri)) { |  | ||||||
|                         ref = elem; |  | ||||||
|                         break; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return ref.getElementsByTagNameNS(namespaceUri, path[path.length - 1]); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static int parseTimestamp(String multiImpl) throws NumberFormatException, ParseException { |  | ||||||
|         if (multiImpl.length() < 1) { |  | ||||||
|             return 0; |  | ||||||
|         } else if (multiImpl.length() == 1) { |  | ||||||
|             return Integer.parseInt(multiImpl) * 1000;// ¡this must be a number in seconds! |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // detect wallclock-time |  | ||||||
|         if (multiImpl.startsWith("wallclock(")) { |  | ||||||
|             throw new UnsupportedOperationException("Parsing wallclock timestamp is not implemented"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // detect offset-time |  | ||||||
|         if (multiImpl.indexOf(':') < 0) { |  | ||||||
|             int multiplier = 1000; |  | ||||||
|             char metric = multiImpl.charAt(multiImpl.length() - 1); |  | ||||||
|             switch (metric) { |  | ||||||
|                 case 'h': |  | ||||||
|                     multiplier *= 3600000; |  | ||||||
|                     break; |  | ||||||
|                 case 'm': |  | ||||||
|                     multiplier *= 60000; |  | ||||||
|                     break; |  | ||||||
|                 case 's': |  | ||||||
|                     if (multiImpl.charAt(multiImpl.length() - 2) == 'm') { |  | ||||||
|                         multiplier = 1;// ms |  | ||||||
|                     } |  | ||||||
|                     break; |  | ||||||
|                 default: |  | ||||||
|                     if (!Character.isDigit(metric)) { |  | ||||||
|                         throw new NumberFormatException("Invalid metric suffix found on : " + multiImpl); |  | ||||||
|                     } |  | ||||||
|                     metric = '\0'; |  | ||||||
|                     break; |  | ||||||
|             } |  | ||||||
|             try { |  | ||||||
|                 String offset_time = multiImpl; |  | ||||||
|  |  | ||||||
|                 if (multiplier == 1) { |  | ||||||
|                     offset_time = offset_time.substring(0, offset_time.length() - 2); |  | ||||||
|                 } else if (metric != '\0') { |  | ||||||
|                     offset_time = offset_time.substring(0, offset_time.length() - 1); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 double time_metric_based = Double.parseDouble(offset_time); |  | ||||||
|                 if (Math.abs(time_metric_based) <= Double.MAX_VALUE) { |  | ||||||
|                     return (int) (time_metric_based * multiplier); |  | ||||||
|                 } |  | ||||||
|             } catch (Exception err) { |  | ||||||
|                 throw new UnsupportedOperationException("Invalid or not implemented timestamp on: " + multiImpl); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // detect clock-time |  | ||||||
|         int time = 0; |  | ||||||
|         String[] units = multiImpl.split(":"); |  | ||||||
|  |  | ||||||
|         if (units.length < 3) { |  | ||||||
|             throw new ParseException("Invalid clock-time timestamp", -1); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         time += Integer.parseInt(units[0]) * 3600000;// hours |  | ||||||
|         time += Integer.parseInt(units[1]) * 60000;//minutes |  | ||||||
|         time += Float.parseFloat(units[2]) * 1000f;// seconds and milliseconds (if present) |  | ||||||
|  |  | ||||||
|         // frames and sub-frames are ignored (not implemented) |  | ||||||
|         // time += units[3] * fps; |  | ||||||
|         return time; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static void putBreakAt(int idx, StringBuilder str) { |  | ||||||
|         // this should be optimized at compile time |  | ||||||
|  |  | ||||||
|         if (NEW_LINE.length() > 1) { |  | ||||||
|             str.delete(idx, idx + 1);// remove after replace |  | ||||||
|             str.insert(idx, NEW_LINE); |  | ||||||
|         } else { |  | ||||||
|             str.setCharAt(idx, NEW_LINE.charAt(0)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static String getTime(int time, boolean comma) { |  | ||||||
|         // cast every value to integer to avoid auto-round in ToString("00"). |  | ||||||
|         StringBuilder str = new StringBuilder(12); |  | ||||||
|         str.append(numberToString(time / 1000 / 3600, 2));// hours |  | ||||||
|         str.append(':'); |  | ||||||
|         str.append(numberToString(time / 1000 / 60 % 60, 2));// minutes |  | ||||||
|         str.append(':'); |  | ||||||
|         str.append(numberToString(time / 1000 % 60, 2));// seconds |  | ||||||
|         str.append(comma ? ',' : '.'); |  | ||||||
|         str.append(numberToString(time % 1000, 3));// miliseconds |  | ||||||
|  |  | ||||||
|         return str.toString(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static String numberToString(int nro, int pad) { |  | ||||||
|         return String.format(Locale.ENGLISH, "%0".concat(String.valueOf(pad)).concat("d"), nro); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     /****************** |  | ||||||
|      * helper classes * |  | ||||||
|      ******************/ |  | ||||||
|  |  | ||||||
|     private interface FrameWriter { |  | ||||||
|  |  | ||||||
|         void yield(SubtitleFrame frame) throws IOException; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private static class SubtitleFrame { |  | ||||||
|         //Java no support unsigned int |  | ||||||
|  |  | ||||||
|         public int end; |  | ||||||
|         public int start; |  | ||||||
|         public String text = ""; |  | ||||||
|  |  | ||||||
|         private boolean isEmptyText() { |  | ||||||
|             if (text == null) { |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             for (int i = 0; i < text.length(); i++) { |  | ||||||
|                 switch (text.charAt(i)) { |  | ||||||
|                     case ' ': |  | ||||||
|                     case '\t': |  | ||||||
|                     case '\r': |  | ||||||
|                     case '\n': |  | ||||||
|                         break; |  | ||||||
|                     default: |  | ||||||
|                         return false; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
							
								
								
									
										23
									
								
								app/src/main/java/org/schabi/newpipe/util/KoreUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/src/main/java/org/schabi/newpipe/util/KoreUtil.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | package org.schabi.newpipe.util; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.DialogInterface; | ||||||
|  | import androidx.appcompat.app.AlertDialog; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.R; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | public class KoreUtil { | ||||||
|  |     private KoreUtil() { } | ||||||
|  |  | ||||||
|  |     public static void showInstallKoreDialog(final Context context) { | ||||||
|  |         final AlertDialog.Builder builder = new AlertDialog.Builder(context); | ||||||
|  |         builder.setMessage(R.string.kore_not_found) | ||||||
|  |                .setPositiveButton(R.string.install, | ||||||
|  |                    (DialogInterface dialog, int which) -> NavigationHelper.installKore(context)) | ||||||
|  |                .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { | ||||||
|  |                }); | ||||||
|  |         builder.create().show(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -109,12 +109,14 @@ public class NavigationHelper { | |||||||
|                                          final float playbackPitch, |                                          final float playbackPitch, | ||||||
|                                          final boolean playbackSkipSilence, |                                          final boolean playbackSkipSilence, | ||||||
|                                          @Nullable final String playbackQuality, |                                          @Nullable final String playbackQuality, | ||||||
|                                          final boolean resumePlayback) { |                                          final boolean resumePlayback, | ||||||
|  |                                          final boolean startPaused) { | ||||||
|         return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback) |         return getPlayerIntent(context, targetClazz, playQueue, playbackQuality, resumePlayback) | ||||||
|                 .putExtra(BasePlayer.REPEAT_MODE, repeatMode) |                 .putExtra(BasePlayer.REPEAT_MODE, repeatMode) | ||||||
|                 .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed) |                 .putExtra(BasePlayer.PLAYBACK_SPEED, playbackSpeed) | ||||||
|                 .putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch) |                 .putExtra(BasePlayer.PLAYBACK_PITCH, playbackPitch) | ||||||
|                 .putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence); |                 .putExtra(BasePlayer.PLAYBACK_SKIP_SILENCE, playbackSkipSilence) | ||||||
|  |                 .putExtra(BasePlayer.START_PAUSED, startPaused); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { |     public static void playOnMainPlayer(final Context context, final PlayQueue queue, final boolean resumePlayback) { | ||||||
|   | |||||||
| @@ -80,7 +80,7 @@ public abstract class Postprocessing implements Serializable { | |||||||
|  |  | ||||||
|     private transient DownloadMission mission; |     private transient DownloadMission mission; | ||||||
|  |  | ||||||
|     private File tempFile; |     private transient File tempFile; | ||||||
|  |  | ||||||
|     Postprocessing(boolean reserveSpace, boolean worksOnSameFile, String algorithmName) { |     Postprocessing(boolean reserveSpace, boolean worksOnSameFile, String algorithmName) { | ||||||
|         this.reserveSpace = reserveSpace; |         this.reserveSpace = reserveSpace; | ||||||
| @@ -95,8 +95,12 @@ public abstract class Postprocessing implements Serializable { | |||||||
|  |  | ||||||
|     public void cleanupTemporalDir() { |     public void cleanupTemporalDir() { | ||||||
|         if (tempFile != null && tempFile.exists()) { |         if (tempFile != null && tempFile.exists()) { | ||||||
|  |             try { | ||||||
|                 //noinspection ResultOfMethodCallIgnored |                 //noinspection ResultOfMethodCallIgnored | ||||||
|                 tempFile.delete(); |                 tempFile.delete(); | ||||||
|  |             } catch (Exception e) { | ||||||
|  |                 // nothing to do | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,15 +2,10 @@ package us.shandian.giga.postprocessing; | |||||||
|  |  | ||||||
| import android.util.Log; | import android.util.Log; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.streams.SubtitleConverter; | import org.schabi.newpipe.streams.SrtFromTtmlWriter; | ||||||
| import org.schabi.newpipe.streams.io.SharpStream; | import org.schabi.newpipe.streams.io.SharpStream; | ||||||
| import org.xml.sax.SAXException; |  | ||||||
|  |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.text.ParseException; |  | ||||||
|  |  | ||||||
| import javax.xml.parsers.ParserConfigurationException; |  | ||||||
| import javax.xml.xpath.XPathExpressionException; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @author kapodamy |  * @author kapodamy | ||||||
| @@ -27,33 +22,16 @@ class TtmlConverter extends Postprocessing { | |||||||
|     int process(SharpStream out, SharpStream... sources) throws IOException { |     int process(SharpStream out, SharpStream... sources) throws IOException { | ||||||
|         // check if the subtitle is already in srt and copy, this should never happen |         // check if the subtitle is already in srt and copy, this should never happen | ||||||
|         String format = getArgumentAt(0, null); |         String format = getArgumentAt(0, null); | ||||||
|  |         boolean ignoreEmptyFrames = getArgumentAt(1, "true").equals("true"); | ||||||
|  |  | ||||||
|         if (format == null || format.equals("ttml")) { |         if (format == null || format.equals("ttml")) { | ||||||
|             SubtitleConverter ttmlDumper = new SubtitleConverter(); |             SrtFromTtmlWriter writer = new SrtFromTtmlWriter(out, ignoreEmptyFrames); | ||||||
|  |  | ||||||
|             try { |             try { | ||||||
|                 ttmlDumper.dumpTTML( |                 writer.build(sources[0]); | ||||||
|                         sources[0], |  | ||||||
|                         out, |  | ||||||
|                         getArgumentAt(1, "true").equals("true"), |  | ||||||
|                         getArgumentAt(2, "true").equals("true") |  | ||||||
|                 ); |  | ||||||
|             } catch (Exception err) { |             } catch (Exception err) { | ||||||
|                 Log.e(TAG, "subtitle parse failed", err); |                 Log.e(TAG, "subtitle parse failed", err); | ||||||
|  |                 return err instanceof IOException ? 1 : 8; | ||||||
|                 if (err instanceof IOException) { |  | ||||||
|                     return 1; |  | ||||||
|                 } else if (err instanceof ParseException) { |  | ||||||
|                     return 2; |  | ||||||
|                 } else if (err instanceof SAXException) { |  | ||||||
|                     return 3; |  | ||||||
|                 } else if (err instanceof ParserConfigurationException) { |  | ||||||
|                     return 4; |  | ||||||
|                 } else if (err instanceof XPathExpressionException) { |  | ||||||
|                     return 7; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 return 8; |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             return OK_RESULT; |             return OK_RESULT; | ||||||
|   | |||||||
| @@ -139,6 +139,9 @@ public class DownloadManager { | |||||||
|             Log.d(TAG, "Loading pending downloads from directory: " + mPendingMissionsDir.getAbsolutePath()); |             Log.d(TAG, "Loading pending downloads from directory: " + mPendingMissionsDir.getAbsolutePath()); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         File tempDir = pickAvailableTemporalDir(ctx); | ||||||
|  |         Log.i(TAG, "using '" + tempDir + "' as temporal directory"); | ||||||
|  |  | ||||||
|         for (File sub : subs) { |         for (File sub : subs) { | ||||||
|             if (!sub.isFile()) continue; |             if (!sub.isFile()) continue; | ||||||
|             if (sub.getName().equals(".tmp")) continue; |             if (sub.getName().equals(".tmp")) continue; | ||||||
| @@ -184,7 +187,7 @@ public class DownloadManager { | |||||||
|  |  | ||||||
|             if (mis.psAlgorithm != null) { |             if (mis.psAlgorithm != null) { | ||||||
|                 mis.psAlgorithm.cleanupTemporalDir(); |                 mis.psAlgorithm.cleanupTemporalDir(); | ||||||
|                 mis.psAlgorithm.setTemporalDir(pickAvailableTemporalDir(ctx)); |                 mis.psAlgorithm.setTemporalDir(tempDir); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             mis.metadata = sub; |             mis.metadata = sub; | ||||||
| @@ -513,13 +516,21 @@ public class DownloadManager { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     static File pickAvailableTemporalDir(@NonNull Context ctx) { |     static File pickAvailableTemporalDir(@NonNull Context ctx) { | ||||||
|         if (isDirectoryAvailable(ctx.getExternalFilesDir(null))) |         File dir = ctx.getExternalFilesDir(null); | ||||||
|             return ctx.getExternalFilesDir(null); |         if (isDirectoryAvailable(dir)) return dir; | ||||||
|         else if (isDirectoryAvailable(ctx.getFilesDir())) |  | ||||||
|             return ctx.getFilesDir(); |         dir = ctx.getFilesDir(); | ||||||
|  |         if (isDirectoryAvailable(dir)) return dir; | ||||||
|  |  | ||||||
|         // this never should happen |         // this never should happen | ||||||
|         return ctx.getDir("tmp", Context.MODE_PRIVATE); |         dir = ctx.getDir("muxing_tmp", Context.MODE_PRIVATE); | ||||||
|  |         if (isDirectoryAvailable(dir)) return dir; | ||||||
|  |  | ||||||
|  |         // fallback to cache dir | ||||||
|  |         dir = ctx.getCacheDir(); | ||||||
|  |         if (isDirectoryAvailable(dir)) return dir; | ||||||
|  |  | ||||||
|  |         throw new RuntimeException("Not temporal directories are available"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Nullable |     @Nullable | ||||||
|   | |||||||
| @@ -305,7 +305,7 @@ | |||||||
|                     tools:text="English" /> |                     tools:text="English" /> | ||||||
|  |  | ||||||
|                 <ImageButton |                 <ImageButton | ||||||
|                     android:id="@+id/share" |                     android:id="@+id/kodi" | ||||||
|                     android:layout_width="30dp" |                     android:layout_width="30dp" | ||||||
|                     android:layout_height="30dp" |                     android:layout_height="30dp" | ||||||
|                     android:layout_marginLeft="4dp" |                     android:layout_marginLeft="4dp" | ||||||
| @@ -316,6 +316,25 @@ | |||||||
|                     android:focusable="true" |                     android:focusable="true" | ||||||
|                     android:padding="5dp" |                     android:padding="5dp" | ||||||
|                     android:scaleType="fitXY" |                     android:scaleType="fitXY" | ||||||
|  |                     android:src="@drawable/ic_cast_white_24dp" | ||||||
|  |                     android:background="?attr/selectableItemBackground" | ||||||
|  |                     android:contentDescription="@string/play_with_kodi_title" | ||||||
|  |                     tools:ignore="RtlHardcoded" | ||||||
|  |                     android:visibility="visible"/> | ||||||
|  |  | ||||||
|  |                 <ImageButton | ||||||
|  |                     android:id="@+id/share" | ||||||
|  |                     android:layout_width="30dp" | ||||||
|  |                     android:layout_height="30dp" | ||||||
|  |                     android:layout_marginLeft="4dp" | ||||||
|  |                     android:layout_marginRight="2dp" | ||||||
|  |                     android:layout_toLeftOf="@id/kodi" | ||||||
|  |                     android:layout_alignWithParentIfMissing="true" | ||||||
|  |                     android:layout_centerVertical="true" | ||||||
|  |                     android:clickable="true" | ||||||
|  |                     android:focusable="true" | ||||||
|  |                     android:padding="5dp" | ||||||
|  |                     android:scaleType="fitXY" | ||||||
|                     android:src="@drawable/ic_share_white_24dp" |                     android:src="@drawable/ic_share_white_24dp" | ||||||
|                     android:background="?attr/selectableItemBackground" |                     android:background="?attr/selectableItemBackground" | ||||||
|                     android:contentDescription="@string/share" |                     android:contentDescription="@string/share" | ||||||
|   | |||||||
| @@ -303,7 +303,7 @@ | |||||||
|                     tools:text="English" /> |                     tools:text="English" /> | ||||||
|  |  | ||||||
|                 <ImageButton |                 <ImageButton | ||||||
|                     android:id="@+id/share" |                     android:id="@+id/kodi" | ||||||
|                     android:layout_width="30dp" |                     android:layout_width="30dp" | ||||||
|                     android:layout_height="30dp" |                     android:layout_height="30dp" | ||||||
|                     android:layout_marginLeft="4dp" |                     android:layout_marginLeft="4dp" | ||||||
| @@ -314,6 +314,25 @@ | |||||||
|                     android:focusable="true" |                     android:focusable="true" | ||||||
|                     android:padding="5dp" |                     android:padding="5dp" | ||||||
|                     android:scaleType="fitXY" |                     android:scaleType="fitXY" | ||||||
|  |                     android:src="@drawable/ic_cast_white_24dp" | ||||||
|  |                     android:background="?attr/selectableItemBackground" | ||||||
|  |                     android:contentDescription="@string/play_with_kodi_title" | ||||||
|  |                     tools:ignore="RtlHardcoded" | ||||||
|  |                     android:visibility="visible"/> | ||||||
|  |  | ||||||
|  |                 <ImageButton | ||||||
|  |                     android:id="@+id/share" | ||||||
|  |                     android:layout_width="30dp" | ||||||
|  |                     android:layout_height="30dp" | ||||||
|  |                     android:layout_marginLeft="4dp" | ||||||
|  |                     android:layout_marginRight="2dp" | ||||||
|  |                     android:layout_toLeftOf="@id/kodi" | ||||||
|  |                     android:layout_alignWithParentIfMissing="true" | ||||||
|  |                     android:layout_centerVertical="true" | ||||||
|  |                     android:clickable="true" | ||||||
|  |                     android:focusable="true" | ||||||
|  |                     android:padding="5dp" | ||||||
|  |                     android:scaleType="fitXY" | ||||||
|                     android:src="@drawable/ic_share_white_24dp" |                     android:src="@drawable/ic_share_white_24dp" | ||||||
|                     android:background="?attr/selectableItemBackground" |                     android:background="?attr/selectableItemBackground" | ||||||
|                     android:contentDescription="@string/share" |                     android:contentDescription="@string/share" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 B0pol
					B0pol