Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/src/main/java/org/schabi/newpipe/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExt
import org.schabi.newpipe.ktx.hasAssignableCause
import org.schabi.newpipe.settings.NewPipeSettings
import org.schabi.newpipe.util.BridgeStateSaverInitializer
import org.schabi.newpipe.util.CacheDirUtils
import org.schabi.newpipe.util.Localization
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.StateSaver
import org.schabi.newpipe.util.image.ImageStrategy
import org.schabi.newpipe.util.image.PreferredImageQuality
import org.schabi.newpipe.util.potoken.PoTokenProviderImpl
import org.schabi.newpipe.util.subtitle.SubtitleDeduplicator

/*
* Copyright (C) Hans-Christoph Steiner 2016 <hans@eds.org>
Expand Down Expand Up @@ -93,6 +95,8 @@ open class App :
.getInt(getString(R.string.last_used_preferences_version), -1)
isFirstRun = lastUsedPrefVersion == -1

val appCacheDirPath = CacheDirUtils.getPreferredAppCacheDirPath(this)

// Initialize settings first because other initializations can use its values
NewPipeSettings.initSettings(this)

Expand Down Expand Up @@ -124,6 +128,8 @@ open class App :
configureRxJavaErrorHandler()

YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl)

SubtitleDeduplicator.setCacheDirPath(appCacheDirPath)
}

override fun newImageLoader(context: Context): ImageLoader = ImageLoader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.player.mediaitem.MediaItemTag;
import org.schabi.newpipe.player.mediaitem.StreamInfoTag;
import org.schabi.newpipe.streams.AppStreamInfo;
import org.schabi.newpipe.util.ListHelper;

import java.util.ArrayList;
Expand Down Expand Up @@ -136,7 +137,8 @@ public MediaSource resolve(@NonNull final StreamInfo info) {
// Below are auxiliary media sources

// Create subtitle sources
final List<SubtitlesStream> subtitlesStreams = info.getSubtitles();
final AppStreamInfo appInfo = AppStreamInfo.from(info);
final List<SubtitlesStream> subtitlesStreams = appInfo.loadNormalizedSubtitles();
if (subtitlesStreams != null) {
// Torrent and non URL subtitles are not supported by ExoPlayer
final List<SubtitlesStream> nonTorrentAndUrlStreams = getUrlAndNonTorrentStreams(
Expand Down
109 changes: 109 additions & 0 deletions app/src/main/java/org/schabi/newpipe/streams/AppStreamInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.schabi.newpipe.streams;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import android.util.Log;

import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.SubtitlesStream;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.util.subtitle.SubtitleDeduplicator;
import org.schabi.newpipe.util.subtitle.SubtitleOrigin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class AppStreamInfo {
private static final String TAG = AppStreamInfo.class.getSimpleName();

@NonNull
private final StreamInfo originalStreamInfo;
@Nullable
private List<SubtitlesStream> normalizedSubtitles = null;

private AppStreamInfo(@NonNull final StreamInfo original) {
this.originalStreamInfo = original;
}

// Factory method: build AppStreamInfo from raw StreamInfo.
@NonNull
public static AppStreamInfo from(@NonNull final StreamInfo info) {
return new AppStreamInfo(info);
}

@NonNull
public StreamInfo getOriginal() {
return originalStreamInfo;
}

@NonNull
public List<SubtitlesStream> loadNormalizedSubtitles() {
if (null == normalizedSubtitles) {
final List<SubtitlesStream> originalSubtitles =
originalStreamInfo.getSubtitles();

normalizedSubtitles =
deduplicateSubtitles(originalSubtitles);
}

return normalizedSubtitles;
}

@NonNull
private static List<SubtitlesStream> deduplicateSubtitles(
@Nullable final List<SubtitlesStream> originalSubtitles) {

if ((null == originalSubtitles) || originalSubtitles.isEmpty()) {
return Collections.emptyList();
}

final List<SubtitlesStream> newSubtitles = new ArrayList<>();

for (final SubtitlesStream oldSubtitle : originalSubtitles) {
final MediaFormat format = oldSubtitle.getFormat();
if (null == format) {
newSubtitles.add(oldSubtitle);
continue;
}

try {
final SubtitleOrigin origin =
SubtitleDeduplicator.getSubtitleOrigin(
oldSubtitle.isAutoGenerated(),
false
);

final String remoteSubtitleUrl = oldSubtitle.getContent();

final String subtitleUrl =
SubtitleDeduplicator.checkAndDeduplicate(
remoteSubtitleUrl,
format,
origin
);

if (remoteSubtitleUrl.equals(subtitleUrl)) {
newSubtitles.add(oldSubtitle);
} else {
final SubtitlesStream oneNewSubtitle =
new SubtitlesStream.Builder()
.setContent(subtitleUrl, true)
.setMediaFormat(format)
.setLanguageCode(oldSubtitle.getLanguageTag())
.setAutoGenerated(oldSubtitle.isAutoGenerated())
.build();

newSubtitles.add(oneNewSubtitle);
}
} catch (final Exception e) {
Log.w(TAG, "Subtitle deduplication failed", e);
newSubtitles.add(oldSubtitle);
}
}

return newSubtitles;
}

}
66 changes: 66 additions & 0 deletions app/src/main/java/org/schabi/newpipe/util/CacheDirUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.schabi.newpipe.util;

import java.io.File;

import android.content.Context;
import androidx.annotation.NonNull;

public final class CacheDirUtils {

private CacheDirUtils() {
// no instance
}

public static String getExternalAppCacheDirPath(
@NonNull final Context context) {
final File externalCacheDir = context.getExternalCacheDir();
if (null != externalCacheDir) {
// /storage/emulated/0/Android/data/<package_name>/cache/
return externalCacheDir.getAbsolutePath();
}

return null;
}

public static String getInternalAppCacheDirPath(
@NonNull final Context context) {
// always available, never be 'null'
// /data/user/0/<package_name>/cache/
return context.getCacheDir().getAbsolutePath();
}

/**
* Returns the preferred cache directory path for the application.
*
* Prefers the external cache directory when available
* (user-accessible, larger space),
* falls back to the internal private cache directory otherwise
* (always available, more secure).
*
* Typical paths:
* - External: /storage/emulated/0/Android/data/<package_name>/cache/
* - Internal: /data/user/0/<package_name>/cache/
* (or /data/data/<package_name>/cache/ on some devices)
*
* Note: The 'external' and 'internal' cache directories mentioned above
* are Android terms. They are typically located on the device's
* built-in storage and are not related to removable SD/TF cards.
*
* User "Clear Cache" in app settings deletes files in both locations.
*
* @param context used to get the available cache dir
* @return absolute path string, never null
*/
@NonNull
public static String getPreferredAppCacheDirPath(
@NonNull final Context context) {

final String externalCacheDirPath = getExternalAppCacheDirPath(context);
if (null != externalCacheDirPath) {
return externalCacheDirPath;
}

// Internal cache dir should always be available
return getInternalAppCacheDirPath(context);
}
}
Loading
Loading