Merge branch 'dev' into dev

This commit is contained in:
B0pol 2020-01-27 19:24:18 +01:00 committed by GitHub
commit e94981e6f7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 241 additions and 443 deletions

View file

@ -13,7 +13,7 @@
</p> </p>
<hr> <hr>
<p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#updates">Updates</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p> <p align="center"><a href="#screenshots">Screenshots</a> &bull; <a href="#description">Description</a> &bull; <a href="#features">Features</a> &bull; <a href="#updates">Updates</a> &bull; <a href="#contribution">Contribution</a> &bull; <a href="#donate">Donate</a> &bull; <a href="#license">License</a></p>
<p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/press/">Press</a></p> <p align="center"><a href="https://newpipe.schabi.org">Website</a> &bull; <a href="https://newpipe.schabi.org/blog/">Blog</a> &bull; <a href="https://newpipe.schabi.org/FAQ/">FAQ</a> &bull; <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>

View file

@ -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;

View file

@ -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");

View file

@ -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;
} }

View file

@ -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();
} }
} }

View file

@ -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) {

View file

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

View file

@ -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;
} }

View file

@ -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;
} }
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////

View file

@ -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);
}
}
}

View file

@ -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;
}
}
}

View 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();
}
}

View file

@ -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) {

View file

@ -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
}
} }
} }

View file

@ -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;

View file

@ -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

View file

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

View file

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