made downloads work on OTG drives
This commit is contained in:
parent
b83c73048b
commit
bba9dbd96a
16 changed files with 990 additions and 920 deletions
|
|
@ -30,7 +30,7 @@ android {
|
|||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
archivesBaseName = "YTDLnis-$versionName"
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
|
|
@ -75,7 +75,7 @@ android {
|
|||
abi {
|
||||
enable true
|
||||
reset()
|
||||
include 'armeabi-v7a', 'arm64-v8a'
|
||||
include 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
universalApk true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,11 @@
|
|||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="androidd.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
|
|
@ -16,7 +19,7 @@
|
|||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppDefaultTheme" >
|
||||
android:theme="@style/AppTheme" >
|
||||
<activity android:name=".MainActivity"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
|
|
|
|||
|
|
@ -1,519 +0,0 @@
|
|||
package com.deniscerri.ytdlnis;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.deniscerri.ytdlnis.database.Video;
|
||||
import com.deniscerri.ytdlnis.page.CustomCommandActivity;
|
||||
import com.deniscerri.ytdlnis.service.DownloadInfo;
|
||||
import com.deniscerri.ytdlnis.service.IDownloaderListener;
|
||||
import com.deniscerri.ytdlnis.service.IDownloaderService;
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil;
|
||||
import com.yausername.youtubedl_android.DownloadProgressCallback;
|
||||
import com.yausername.youtubedl_android.YoutubeDL;
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class DownloaderService extends Service {
|
||||
|
||||
private LocalBinder binder = new LocalBinder();
|
||||
private Map<Activity, ArrayList<IDownloaderListener>> activities = new ConcurrentHashMap<>();
|
||||
private DownloadInfo downloadInfo = new DownloadInfo();
|
||||
private LinkedList<Video> downloadQueue = new LinkedList<>();
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private final NotificationUtil notificationUtil = App.notificationUtil;
|
||||
private Context context;
|
||||
public String downloadProcessID = "processID";
|
||||
private static final String TAG = "DownloaderService";
|
||||
private int downloadNotificationID;
|
||||
|
||||
private final DownloadProgressCallback callback = (progress, etaInSeconds, line) -> {
|
||||
downloadInfo.setProgress((int) progress);
|
||||
downloadInfo.setOutputLine(line);
|
||||
downloadInfo.setDownloadQueue(downloadQueue);
|
||||
String title = getString(R.string.running_ytdlp_command);
|
||||
if (!downloadQueue.isEmpty()){
|
||||
title = downloadQueue.peek().getTitle();
|
||||
}
|
||||
notificationUtil.updateDownloadNotification(downloadNotificationID,
|
||||
line, (int) progress, downloadQueue.size(), title);
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
if (activities.get(activity) != null){
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadProgress(downloadInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception ignored){}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
context = this;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
Intent theIntent;
|
||||
PendingIntent pendingIntent;
|
||||
|
||||
if(intent.getBooleanExtra("rebind", false)){
|
||||
return binder;
|
||||
}
|
||||
|
||||
int id = intent.getIntExtra("id", 1);
|
||||
switch (id){
|
||||
case NotificationUtil.DOWNLOAD_NOTIFICATION_ID:
|
||||
theIntent = new Intent(this, MainActivity.class);
|
||||
pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
downloadNotificationID = id;
|
||||
|
||||
ArrayList<? extends Video> queue = intent.getParcelableArrayListExtra("queue");
|
||||
downloadQueue = new LinkedList<>();
|
||||
downloadQueue.addAll(queue);
|
||||
downloadInfo.setDownloadQueue(downloadQueue);
|
||||
|
||||
String title = downloadInfo.getVideo().getTitle();
|
||||
Notification notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,title);
|
||||
startForeground(downloadNotificationID, notification);
|
||||
startDownload(downloadQueue);
|
||||
break;
|
||||
case NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID:
|
||||
theIntent = new Intent(this, CustomCommandActivity.class);
|
||||
pendingIntent = PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
downloadNotificationID = id;
|
||||
|
||||
String command = intent.getStringExtra("command");
|
||||
Notification command_notification = App.notificationUtil.createDownloadServiceNotification(pendingIntent,getString(R.string.running_ytdlp_command));
|
||||
startForeground(downloadNotificationID, command_notification);
|
||||
startCommandDownload(command);
|
||||
break;
|
||||
}
|
||||
return binder;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
public class LocalBinder extends Binder implements IDownloaderService {
|
||||
public DownloaderService getService() {
|
||||
return DownloaderService.this;
|
||||
}
|
||||
|
||||
public DownloadInfo getDownloadInfo(){
|
||||
return downloadInfo;
|
||||
}
|
||||
|
||||
public void addActivity(Activity activity, ArrayList<IDownloaderListener> callbacks) {
|
||||
if(!activities.containsKey(activity)){
|
||||
activities.put(activity, callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeActivity(Activity activity) {
|
||||
activities.remove(activity);
|
||||
}
|
||||
|
||||
public void updateQueue(ArrayList<Video> queue){
|
||||
try{
|
||||
for (int i = 0; i < queue.size(); i++){
|
||||
downloadQueue.add((Video) queue.get(i).clone());
|
||||
}
|
||||
if (downloadQueue.size() == queue.size()){
|
||||
downloadInfo.setDownloadQueue(downloadQueue);
|
||||
startDownload(downloadQueue);
|
||||
}else{
|
||||
downloadInfo.setDownloadQueue(downloadQueue);
|
||||
Toast.makeText(context, getString(R.string.added_to_queue), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}catch (Exception e){
|
||||
Toast.makeText(context, "Couldn't update download queue! :(", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void cancelDownload(boolean cancelAll){
|
||||
try{
|
||||
YoutubeDL.getInstance().destroyProcessById(downloadProcessID);
|
||||
compositeDisposable.clear();
|
||||
//stopForeground(true);
|
||||
if (cancelAll) {
|
||||
onDownloadCancelAll();
|
||||
}
|
||||
}catch(Exception err){
|
||||
Log.e(TAG, err.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void removeItemFromDownloadQueue(Video video, String type){
|
||||
//if its the same video with the same download type as the current downloading one
|
||||
if (downloadInfo.getVideo().getURL().equals(video.getURL()) && downloadInfo.getVideo().getDownloadedType().equals(video.getDownloadedType())){
|
||||
cancelDownload(false);
|
||||
|
||||
downloadInfo.setDownloadType(type);
|
||||
onDownloadCancel(downloadInfo);
|
||||
|
||||
downloadQueue.pop();
|
||||
downloadInfo.setDownloadQueue(downloadQueue);
|
||||
startDownload(downloadQueue);
|
||||
}else {
|
||||
downloadQueue.remove(video);
|
||||
try{
|
||||
DownloadInfo info = new DownloadInfo();
|
||||
info.setVideo(video);
|
||||
info.setDownloadType(type);
|
||||
onDownloadCancel(info);
|
||||
}catch(Exception ignored){}
|
||||
}
|
||||
Toast.makeText(context, video.getTitle() + " " + getString(R.string.removed_from_queue), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void finishService(){
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadServiceEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception ignored){}
|
||||
}
|
||||
|
||||
private void onDownloadCancelAll(){
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadCancelAll(downloadInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception ignored){}
|
||||
onDestroy();
|
||||
}
|
||||
|
||||
private void onDownloadEnd(){
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadEnd(downloadInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownloadCancel(DownloadInfo cancelInfo){
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadCancel(cancelInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception ignored){}
|
||||
}
|
||||
|
||||
private void startDownload(LinkedList<Video> videos) {
|
||||
Video video;
|
||||
if(videos.size() == 0){
|
||||
finishService();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
video = videos.peek();
|
||||
} catch (Exception e) {
|
||||
finishService();
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadStart(downloadInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception err){
|
||||
err.printStackTrace();
|
||||
}
|
||||
|
||||
String url = video.getURL();
|
||||
YoutubeDLRequest request = new YoutubeDLRequest(url);
|
||||
String type = video.getDownloadedType();
|
||||
File youtubeDLDir = getDownloadLocation(type);
|
||||
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||
|
||||
boolean aria2 = sharedPreferences.getBoolean("aria2", false);
|
||||
if(aria2){
|
||||
request.addOption("--downloader", "libaria2c.so");
|
||||
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"");
|
||||
}else{
|
||||
int concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1);
|
||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments);
|
||||
}
|
||||
|
||||
String limitRate = sharedPreferences.getString("limit_rate", "");
|
||||
if(!limitRate.equals("")) request.addOption("-r", limitRate);
|
||||
|
||||
boolean writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false);
|
||||
if(writeThumbnail) {
|
||||
request.addOption("--write-thumbnail");
|
||||
request.addOption("--convert-thumbnails", "png");
|
||||
}
|
||||
|
||||
request.addOption("--no-mtime");
|
||||
|
||||
String sponsorBlockFilters = sharedPreferences.getString("sponsorblock_filter", "");
|
||||
if (!sponsorBlockFilters.isEmpty()){
|
||||
request.addOption("--sponsorblock-remove", sponsorBlockFilters);
|
||||
}
|
||||
|
||||
if (type.equals("audio")) {
|
||||
request.addOption("-x");
|
||||
String format = video.getAudioFormat();
|
||||
if (format == null) format = sharedPreferences.getString("audio_format", "");
|
||||
request.addOption("--audio-format", format);
|
||||
request.addOption("--embed-metadata");
|
||||
if(format.equals("mp3") || format.equals("m4a") || format.equals("flac")){
|
||||
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
|
||||
if(embedThumb){
|
||||
request.addOption("--embed-thumbnail");
|
||||
request.addOption("--convert-thumbnails", "png");
|
||||
try{
|
||||
File config = new File(getCacheDir(), "config.txt");
|
||||
String config_data = "--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\"";
|
||||
FileOutputStream stream = new FileOutputStream(config);
|
||||
stream.write(config_data.getBytes());
|
||||
stream.close();
|
||||
request.addOption("--config", config.getAbsolutePath());
|
||||
}catch(Exception ignored){}
|
||||
}
|
||||
}
|
||||
|
||||
request.addCommands(Arrays.asList("--replace-in-metadata","title",".*.",video.getTitle()));
|
||||
request.addCommands(Arrays.asList("--replace-in-metadata","uploader",".*.",video.getAuthor()));
|
||||
|
||||
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(uploader)s - %(title)s.%(ext)s");
|
||||
} else if (type.equals("video")) {
|
||||
boolean addChapters = sharedPreferences.getBoolean("add_chapters", false);
|
||||
if(addChapters){
|
||||
request.addOption("--sponsorblock-mark", "all");
|
||||
}
|
||||
boolean embedSubs = sharedPreferences.getBoolean("embed_subtitles", false);
|
||||
if(embedSubs){
|
||||
request.addOption("--embed-subs", "");
|
||||
}
|
||||
String videoQuality = video.getVideoQuality();
|
||||
if (videoQuality == null) videoQuality = sharedPreferences.getString("video_quality", "");
|
||||
String formatArgument = "bestvideo+bestaudio/best";
|
||||
if(videoQuality.equals("Worst Quality")){
|
||||
formatArgument = "worst";
|
||||
}else if (!videoQuality.isEmpty() && !videoQuality.equals("Best Quality")) {
|
||||
formatArgument = "bestvideo[height<="+videoQuality.substring(0, videoQuality.length()-1)+"]+bestaudio/best";
|
||||
}
|
||||
|
||||
request.addOption("-f", formatArgument);
|
||||
String format = video.getVideoFormat();
|
||||
if (format == null) format = sharedPreferences.getString("video_format", "");
|
||||
if(!format.equals("DEFAULT")) request.addOption("--merge-output-format", format);
|
||||
|
||||
if(!format.equals("webm")){
|
||||
boolean embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false);
|
||||
if(embedThumb){
|
||||
request.addOption("--embed-thumbnail");
|
||||
}
|
||||
}
|
||||
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(uploader)s - %(title)s.%(ext)s");
|
||||
}
|
||||
|
||||
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, downloadProcessID, callback))
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(youtubeDLResponse -> {
|
||||
downloadInfo.setDownloadPath(youtubeDLDir.getAbsolutePath());
|
||||
downloadInfo.setDownloadType(type);
|
||||
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadEnd(downloadInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// SCAN NEXT IN QUEUE
|
||||
videos.remove();
|
||||
downloadInfo.setDownloadQueue(videos);
|
||||
startDownload(videos);
|
||||
}, e -> {
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e);
|
||||
notificationUtil.updateDownloadNotification(NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
|
||||
getString(R.string.failed_download), 0, 0, downloadQueue.peek().getTitle());
|
||||
downloadInfo.setDownloadType(type);
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadError(downloadInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception err){
|
||||
err.printStackTrace();
|
||||
}
|
||||
|
||||
// SCAN NEXT IN QUEUE
|
||||
videos.remove();
|
||||
downloadInfo.setDownloadQueue(videos);
|
||||
startDownload(videos);
|
||||
});
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
|
||||
private void startCommandDownload(String text){
|
||||
if(!text.startsWith("yt-dlp ")){
|
||||
Toast.makeText(context, "Wrong input! Try Again!", Toast.LENGTH_SHORT).show();
|
||||
finishService();
|
||||
return;
|
||||
}
|
||||
text = text.substring(6).trim();
|
||||
|
||||
YoutubeDLRequest request = new YoutubeDLRequest(Collections.emptyList());
|
||||
String commandRegex = "\"([^\"]*)\"|(\\S+)";
|
||||
Matcher m = Pattern.compile(commandRegex).matcher(text);
|
||||
while (m.find()) {
|
||||
if (m.group(1) != null) {
|
||||
request.addOption(m.group(1));
|
||||
} else {
|
||||
request.addOption(m.group(2));
|
||||
}
|
||||
}
|
||||
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||
String downloadsDir = sharedPreferences.getString("command_path", getString(R.string.command_path));
|
||||
File youtubeDLDir = new File(downloadsDir);
|
||||
if (!youtubeDLDir.exists()) {
|
||||
boolean isDirCreated = youtubeDLDir.mkdir();
|
||||
if (!isDirCreated) {
|
||||
Toast.makeText(context, R.string.failed_making_directory, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s");
|
||||
|
||||
Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, downloadProcessID, callback))
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(youtubeDLResponse -> {
|
||||
downloadInfo.setOutputLine(youtubeDLResponse.getOut());
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadEnd(downloadInfo);
|
||||
callback.onDownloadServiceEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, e -> {
|
||||
downloadInfo.setOutputLine(e.getMessage());
|
||||
try{
|
||||
for (Activity activity: activities.keySet()){
|
||||
activity.runOnUiThread(() -> {
|
||||
for (int i = 0; i < activities.get(activity).size(); i++){
|
||||
IDownloaderListener callback = activities.get(activity).get(i);
|
||||
callback.onDownloadError(downloadInfo);
|
||||
callback.onDownloadServiceEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
}catch (Exception err){
|
||||
err.printStackTrace();
|
||||
}
|
||||
});
|
||||
compositeDisposable.add(disposable);
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
private File getDownloadLocation(String type) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||
String downloadsDir;
|
||||
if (type.equals("audio")) {
|
||||
downloadsDir = sharedPreferences.getString("music_path", getString(R.string.music_path));
|
||||
} else {
|
||||
downloadsDir = sharedPreferences.getString("video_path", getString(R.string.video_path));
|
||||
}
|
||||
|
||||
File youtubeDLDir = new File(downloadsDir);
|
||||
if (!youtubeDLDir.exists()) {
|
||||
boolean isDirCreated = youtubeDLDir.mkdir();
|
||||
if (!isDirCreated) {
|
||||
notificationUtil.updateDownloadNotification(NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
|
||||
getString(R.string.failed_making_directory), 0, 0, downloadQueue.peek().getTitle());
|
||||
}
|
||||
}
|
||||
return youtubeDLDir;
|
||||
}
|
||||
}
|
||||
522
app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt
Normal file
522
app/src/main/java/com/deniscerri/ytdlnis/DownloaderService.kt
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
package com.deniscerri.ytdlnis
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Environment
|
||||
import android.os.IBinder
|
||||
import android.provider.DocumentsContract
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.net.toUri
|
||||
import com.deniscerri.ytdlnis.database.Video
|
||||
import com.deniscerri.ytdlnis.page.CustomCommandActivity
|
||||
import com.deniscerri.ytdlnis.service.DownloadInfo
|
||||
import com.deniscerri.ytdlnis.service.IDownloaderListener
|
||||
import com.deniscerri.ytdlnis.service.IDownloaderService
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.NotificationUtil
|
||||
import com.yausername.youtubedl_android.DownloadProgressCallback
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import com.yausername.youtubedl_android.YoutubeDLResponse
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class DownloaderService : Service() {
|
||||
private val binder = LocalBinder()
|
||||
private val activities: MutableMap<Activity, ArrayList<IDownloaderListener>?> =
|
||||
ConcurrentHashMap()
|
||||
private val fileUtil = FileUtil()
|
||||
private val downloadInfo = DownloadInfo()
|
||||
private var downloadQueue = LinkedList<Video>()
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
private val notificationUtil = App.notificationUtil
|
||||
private var context: Context? = null
|
||||
var downloadProcessID = "processID"
|
||||
private var downloadNotificationID = 0
|
||||
private val callback =
|
||||
DownloadProgressCallback { progress: Float, etaInSeconds: Long, line: String? ->
|
||||
downloadInfo.progress = progress.toInt()
|
||||
downloadInfo.outputLine = line
|
||||
downloadInfo.downloadQueue = downloadQueue
|
||||
var title: String? = getString(R.string.running_ytdlp_command)
|
||||
if (!downloadQueue.isEmpty()) {
|
||||
title = downloadQueue.peek()?.title
|
||||
}
|
||||
notificationUtil.updateDownloadNotification(
|
||||
downloadNotificationID,
|
||||
line!!, progress.toInt(), downloadQueue.size, title
|
||||
)
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
if (activities[activity] != null) {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadProgress(downloadInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
context = this
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
val theIntent: Intent
|
||||
val pendingIntent: PendingIntent
|
||||
if (intent.getBooleanExtra("rebind", false)) {
|
||||
return binder
|
||||
}
|
||||
val id = intent.getIntExtra("id", 1)
|
||||
when (id) {
|
||||
NotificationUtil.DOWNLOAD_NOTIFICATION_ID -> {
|
||||
theIntent = Intent(this, MainActivity::class.java)
|
||||
pendingIntent =
|
||||
PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
downloadNotificationID = id
|
||||
val queue: ArrayList<out Video>? = intent.getParcelableArrayListExtra("queue")
|
||||
downloadQueue = LinkedList()
|
||||
downloadQueue.addAll(queue!!)
|
||||
downloadInfo.downloadQueue = downloadQueue
|
||||
val title = downloadInfo.video.title
|
||||
val notification =
|
||||
App.notificationUtil.createDownloadServiceNotification(pendingIntent, title)
|
||||
startForeground(downloadNotificationID, notification)
|
||||
startDownload(downloadQueue)
|
||||
}
|
||||
NotificationUtil.COMMAND_DOWNLOAD_NOTIFICATION_ID -> {
|
||||
theIntent = Intent(this, CustomCommandActivity::class.java)
|
||||
pendingIntent =
|
||||
PendingIntent.getActivity(this, 0, theIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
downloadNotificationID = id
|
||||
val command = intent.getStringExtra("command")
|
||||
val command_notification = App.notificationUtil.createDownloadServiceNotification(
|
||||
pendingIntent,
|
||||
getString(R.string.running_ytdlp_command)
|
||||
)
|
||||
startForeground(downloadNotificationID, command_notification)
|
||||
startCommandDownload(command)
|
||||
}
|
||||
}
|
||||
return binder
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
stopForeground(true)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
inner class LocalBinder : Binder(), IDownloaderService {
|
||||
val service: DownloaderService
|
||||
get() = this@DownloaderService
|
||||
|
||||
override fun getInfo(): DownloadInfo {
|
||||
return downloadInfo
|
||||
}
|
||||
|
||||
override fun addActivity(activity: Activity, callbacks: ArrayList<IDownloaderListener>) {
|
||||
if (!activities.containsKey(activity)) {
|
||||
activities[activity] = callbacks
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeActivity(activity: Activity) {
|
||||
activities.remove(activity)
|
||||
}
|
||||
|
||||
override fun updateQueue(queue: ArrayList<Video>) {
|
||||
try {
|
||||
for (i in queue.indices) {
|
||||
downloadQueue.add(queue[i].clone() as Video)
|
||||
}
|
||||
if (downloadQueue.size == queue.size) {
|
||||
downloadInfo.downloadQueue = downloadQueue
|
||||
startDownload(downloadQueue)
|
||||
} else {
|
||||
downloadInfo.downloadQueue = downloadQueue
|
||||
Toast.makeText(context, getString(R.string.added_to_queue), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context, "Couldn't update download queue! :(", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancelDownload(cancelAll: Boolean) {
|
||||
try {
|
||||
YoutubeDL.getInstance().destroyProcessById(downloadProcessID)
|
||||
compositeDisposable.clear()
|
||||
//stopForeground(true);
|
||||
if (cancelAll) {
|
||||
onDownloadCancelAll()
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
Log.e(TAG, err.message!!)
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeItemFromDownloadQueue(video: Video, type: String) {
|
||||
//if its the same video with the same download type as the current downloading one
|
||||
if (downloadInfo.video.getURL() == video.getURL() && downloadInfo.video.downloadedType == video.downloadedType) {
|
||||
cancelDownload(false)
|
||||
downloadInfo.downloadType = type
|
||||
onDownloadCancel(downloadInfo)
|
||||
downloadQueue.pop()
|
||||
downloadInfo.downloadQueue = downloadQueue
|
||||
startDownload(downloadQueue)
|
||||
} else {
|
||||
downloadQueue.remove(video)
|
||||
try {
|
||||
val info = DownloadInfo()
|
||||
info.video = video
|
||||
info.downloadType = type
|
||||
onDownloadCancel(info)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
Toast.makeText(
|
||||
context,
|
||||
video.title + " " + getString(R.string.removed_from_queue),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishService() {
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadServiceEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDownloadCancelAll() {
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadCancelAll(downloadInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
onDestroy()
|
||||
}
|
||||
|
||||
private fun onDownloadEnd() {
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadEnd(downloadInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDownloadCancel(cancelInfo: DownloadInfo) {
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadCancel(cancelInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDownload(videos: LinkedList<Video>) {
|
||||
if (videos.size == 0) {
|
||||
finishService()
|
||||
return
|
||||
}
|
||||
val video: Video = try {
|
||||
videos.peek() as Video
|
||||
} catch (e: Exception) {
|
||||
finishService()
|
||||
return
|
||||
}
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadStart(downloadInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
err.printStackTrace()
|
||||
}
|
||||
val url = video.getURL()
|
||||
val request = YoutubeDLRequest(url)
|
||||
val type = video.downloadedType
|
||||
|
||||
val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE)
|
||||
val aria2 = sharedPreferences.getBoolean("aria2", false)
|
||||
if (aria2) {
|
||||
request.addOption("--downloader", "libaria2c.so")
|
||||
request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
|
||||
} else {
|
||||
val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1)
|
||||
if (concurrentFragments > 1) request.addOption("-N", concurrentFragments)
|
||||
}
|
||||
val limitRate = sharedPreferences.getString("limit_rate", "")
|
||||
if (limitRate != "") request.addOption("-r", limitRate!!)
|
||||
val writeThumbnail = sharedPreferences.getBoolean("write_thumbnail", false)
|
||||
if (writeThumbnail) {
|
||||
request.addOption("--write-thumbnail")
|
||||
request.addOption("--convert-thumbnails", "png")
|
||||
}
|
||||
request.addOption("--no-mtime")
|
||||
val sponsorBlockFilters = sharedPreferences.getString("sponsorblock_filter", "")
|
||||
if (!sponsorBlockFilters!!.isEmpty()) {
|
||||
request.addOption("--sponsorblock-remove", sponsorBlockFilters)
|
||||
}
|
||||
if (type == "audio") {
|
||||
request.addOption("-x")
|
||||
var format = video.audioFormat
|
||||
if (format == null) format = sharedPreferences.getString("audio_format", "")
|
||||
request.addOption("--audio-format", format!!)
|
||||
request.addOption("--embed-metadata")
|
||||
if (format == "mp3" || format == "m4a" || format == "flac") {
|
||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||
if (embedThumb) {
|
||||
request.addOption("--embed-thumbnail")
|
||||
request.addOption("--convert-thumbnails", "png")
|
||||
try {
|
||||
val config = File(cacheDir, "config.txt")
|
||||
val config_data =
|
||||
"--ppa \"ffmpeg: -c:v png -vf crop=\\\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\\\"\""
|
||||
val stream = FileOutputStream(config)
|
||||
stream.write(config_data.toByteArray())
|
||||
stream.close()
|
||||
request.addOption("--config", config.absolutePath)
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
request.addCommands(Arrays.asList("--replace-in-metadata", "title", ".*.", video.title))
|
||||
request.addCommands(
|
||||
Arrays.asList(
|
||||
"--replace-in-metadata",
|
||||
"uploader",
|
||||
".*.",
|
||||
video.author
|
||||
)
|
||||
)
|
||||
request.addOption("-o", filesDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
|
||||
} else if (type == "video") {
|
||||
val addChapters = sharedPreferences.getBoolean("add_chapters", false)
|
||||
if (addChapters) {
|
||||
request.addOption("--sponsorblock-mark", "all")
|
||||
}
|
||||
val embedSubs = sharedPreferences.getBoolean("embed_subtitles", false)
|
||||
if (embedSubs) {
|
||||
request.addOption("--embed-subs", "")
|
||||
}
|
||||
var videoQuality = video.videoQuality
|
||||
if (videoQuality == null) videoQuality =
|
||||
sharedPreferences.getString("video_quality", "")
|
||||
var formatArgument = "bestvideo+bestaudio/best"
|
||||
if (videoQuality == "Worst Quality") {
|
||||
formatArgument = "worst"
|
||||
} else if (!videoQuality!!.isEmpty() && videoQuality != "Best Quality") {
|
||||
formatArgument = "bestvideo[height<=" + videoQuality.substring(
|
||||
0,
|
||||
videoQuality.length - 1
|
||||
) + "]+bestaudio/best"
|
||||
}
|
||||
request.addOption("-f", formatArgument)
|
||||
var format = video.videoFormat
|
||||
if (format == null) format = sharedPreferences.getString("video_format", "")
|
||||
if (format != "DEFAULT") request.addOption("--merge-output-format", format!!)
|
||||
if (format != "webm") {
|
||||
val embedThumb = sharedPreferences.getBoolean("embed_thumbnail", false)
|
||||
if (embedThumb) {
|
||||
request.addOption("--embed-thumbnail")
|
||||
}
|
||||
}
|
||||
request.addOption("-o", filesDir.absolutePath + "/%(uploader)s - %(title)s.%(ext)s")
|
||||
}
|
||||
|
||||
val disposable = Observable.fromCallable {
|
||||
YoutubeDL.getInstance().execute(request, downloadProcessID, callback)
|
||||
}
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
val destDir = Uri.parse(getDownloadLocation(type)).run {
|
||||
DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this))
|
||||
}
|
||||
fileUtil.moveFile(filesDir, context!!, destDir)
|
||||
downloadInfo.downloadPath = fileUtil.formatPath(getDownloadLocation(type)!!)
|
||||
Log.e(TAG, downloadInfo.downloadPath)
|
||||
downloadInfo.downloadType = type
|
||||
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadEnd(downloadInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
// SCAN NEXT IN QUEUE
|
||||
videos.remove()
|
||||
downloadInfo.downloadQueue = videos
|
||||
startDownload(videos)
|
||||
}) { e: Throwable? ->
|
||||
if (BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e)
|
||||
notificationUtil.updateDownloadNotification(
|
||||
NotificationUtil.DOWNLOAD_NOTIFICATION_ID,
|
||||
getString(R.string.failed_download), 0, 0, downloadQueue.peek()?.title
|
||||
)
|
||||
downloadInfo.downloadType = type
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadError(downloadInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
err.printStackTrace()
|
||||
}
|
||||
|
||||
// SCAN NEXT IN QUEUE
|
||||
videos.remove()
|
||||
downloadInfo.downloadQueue = videos
|
||||
startDownload(videos)
|
||||
}
|
||||
compositeDisposable.add(disposable)
|
||||
}
|
||||
|
||||
private fun startCommandDownload(text: String?) {
|
||||
var text = text
|
||||
if (!text!!.startsWith("yt-dlp ")) {
|
||||
Toast.makeText(context, "Wrong input! Try Again!", Toast.LENGTH_SHORT).show()
|
||||
finishService()
|
||||
return
|
||||
}
|
||||
text = text.substring(6).trim { it <= ' ' }
|
||||
val request = YoutubeDLRequest(emptyList())
|
||||
val commandRegex = "\"([^\"]*)\"|(\\S+)"
|
||||
val m = Pattern.compile(commandRegex).matcher(text)
|
||||
while (m.find()) {
|
||||
if (m.group(1) != null) {
|
||||
request.addOption(m.group(1))
|
||||
} else {
|
||||
request.addOption(m.group(2))
|
||||
}
|
||||
}
|
||||
val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE)
|
||||
request.addOption("-o", filesDir.absolutePath + "/%(title)s.%(ext)s")
|
||||
val disposable = Observable.fromCallable {
|
||||
YoutubeDL.getInstance().execute(request, downloadProcessID, callback)
|
||||
}
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ youtubeDLResponse: YoutubeDLResponse ->
|
||||
downloadInfo.outputLine = youtubeDLResponse.out
|
||||
val downloadsDir = sharedPreferences.getString("command_path", getString(R.string.command_path))
|
||||
val destDir = Uri.parse(downloadsDir).run {
|
||||
DocumentsContract.buildChildDocumentsUriUsingTree(this, DocumentsContract.getTreeDocumentId(this))
|
||||
}
|
||||
fileUtil.moveFile(filesDir, context!!, destDir)
|
||||
downloadInfo.outputLine += "\n\nMoved File to ${fileUtil.formatPath(downloadsDir!!)}"
|
||||
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadEnd(downloadInfo)
|
||||
callback.onDownloadServiceEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}) { e: Throwable ->
|
||||
downloadInfo.outputLine = e.message
|
||||
try {
|
||||
for (activity in activities.keys) {
|
||||
activity.runOnUiThread {
|
||||
for (i in activities[activity]!!.indices) {
|
||||
val callback = activities[activity]!![i]
|
||||
callback.onDownloadError(downloadInfo)
|
||||
callback.onDownloadServiceEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err: Exception) {
|
||||
err.printStackTrace()
|
||||
}
|
||||
}
|
||||
compositeDisposable.add(disposable)
|
||||
}
|
||||
|
||||
private fun getDownloadLocation(type: String): String? {
|
||||
val sharedPreferences = context!!.getSharedPreferences("root_preferences", MODE_PRIVATE)
|
||||
var downloadsDir: String? = if (type == "audio") {
|
||||
sharedPreferences.getString("music_path", getString(R.string.music_path))
|
||||
} else {
|
||||
sharedPreferences.getString("video_path", getString(R.string.video_path))
|
||||
}
|
||||
Log.e(TAG, downloadsDir!!)
|
||||
return downloadsDir
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DownloaderService"
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ class MainActivity : AppCompatActivity() {
|
|||
iDownloaderService!!.addActivity(this@MainActivity, listeners)
|
||||
for (i in listeners.indices) {
|
||||
val listener = listeners[i]
|
||||
listener.onDownloadStart(iDownloaderService!!.downloadInfo)
|
||||
listener.onDownloadStart(iDownloaderService!!.info)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public class CustomCommandActivity extends AppCompatActivity {
|
|||
ArrayList<IDownloaderListener> listeners = new ArrayList<>();
|
||||
listeners.add(listener);
|
||||
iDownloaderService.addActivity(CustomCommandActivity.this, listeners);
|
||||
listener.onDownloadStart(iDownloaderService.getDownloadInfo());
|
||||
listener.onDownloadStart(iDownloaderService.getInfo());
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.deniscerri.ytdlnis.database.DatabaseManager;
|
|||
import com.deniscerri.ytdlnis.database.Video;
|
||||
import com.deniscerri.ytdlnis.service.DownloadInfo;
|
||||
import com.deniscerri.ytdlnis.service.IDownloaderListener;
|
||||
import com.deniscerri.ytdlnis.util.FileUtil;
|
||||
import com.facebook.shimmer.ShimmerFrameLayout;
|
||||
import com.google.android.material.appbar.AppBarLayout;
|
||||
import com.google.android.material.appbar.MaterialToolbar;
|
||||
|
|
@ -76,6 +77,7 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
|||
public ArrayList<Video> selectedObjects;
|
||||
private LinearProgressIndicator progressBar;
|
||||
private ExtendedFloatingActionButton deleteFab;
|
||||
private FileUtil fileUtil;
|
||||
private String format = "";
|
||||
private String website = "";
|
||||
private String sort = "DESC";
|
||||
|
|
@ -135,27 +137,12 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
|||
Video item = downloadInfo.getVideo();
|
||||
String url = item.getURL();
|
||||
String type = downloadInfo.getDownloadType();
|
||||
String path = downloadInfo.getDownloadPath();
|
||||
item = findVideo(url, type);
|
||||
int index = downloadsObjects.indexOf(item);
|
||||
try{
|
||||
// MEDIA SCAN
|
||||
ArrayList<File> files = new ArrayList<>();
|
||||
String title = downloadInfo.getVideo().getTitle().replaceAll("[^a-zA-Z0-9]","");
|
||||
String pathTmp = "";
|
||||
File path = new File(downloadInfo.getDownloadPath());
|
||||
for( File file : path.listFiles() ){
|
||||
if(file.isFile()){
|
||||
pathTmp = file.getAbsolutePath().replaceAll("[^a-zA-Z0-9]","");
|
||||
if (pathTmp.contains(title)){
|
||||
files.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String[] paths = new String[files.size()];
|
||||
for (int i = 0; i < files.size(); i++) paths[i] = files.get(i).getAbsolutePath();
|
||||
MediaScannerConnection.scanFile(context, paths, null, null);
|
||||
item.setDownloadedType(type);
|
||||
|
||||
item.setDownloadPath(path);
|
||||
Calendar cal = Calendar.getInstance();
|
||||
Date date = new Date();
|
||||
cal.setTime(date);
|
||||
|
|
@ -169,7 +156,7 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
|||
|
||||
String downloadPath = "";
|
||||
try{
|
||||
downloadPath = paths[0];
|
||||
downloadPath = fileUtil.scanMedia(item, context);
|
||||
}catch(Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -181,7 +168,7 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
|||
item.setQueuedDownload(false);
|
||||
databaseManager.updateHistoryItem(item);
|
||||
databaseManager.close();
|
||||
downloadsRecyclerViewAdapter.notifyItemChanged(downloadsObjects.indexOf(item));
|
||||
downloadsRecyclerViewAdapter.notifyItemChanged(index);
|
||||
} catch (Exception ignored) {}
|
||||
downloading = false;
|
||||
}catch(Exception ignored){}
|
||||
|
|
@ -256,6 +243,7 @@ public class DownloadsFragment extends Fragment implements DownloadsRecyclerView
|
|||
selectionChips = fragmentView.findViewById(R.id.downloads_selection_chips);
|
||||
websiteGroup = fragmentView.findViewById(R.id.website_chip_group);
|
||||
deleteFab = fragmentView.findViewById(R.id.delete_selected_fab);
|
||||
fileUtil = new FileUtil();
|
||||
|
||||
deleteFab.setTag("deleteSelected");
|
||||
deleteFab.setOnClickListener(this);
|
||||
|
|
|
|||
|
|
@ -1,359 +0,0 @@
|
|||
package com.deniscerri.ytdlnis.page.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultCallback;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.MultiSelectListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.SeekBarPreference;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
|
||||
import com.deniscerri.ytdlnis.BuildConfig;
|
||||
import com.deniscerri.ytdlnis.R;
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
Preference musicPath;
|
||||
Preference videoPath;
|
||||
Preference commandPath;
|
||||
|
||||
SwitchPreferenceCompat incognito;
|
||||
SwitchPreferenceCompat downloadCard;
|
||||
EditTextPreference apiKey;
|
||||
SeekBarPreference concurrentFragments;
|
||||
EditTextPreference limitRate;
|
||||
SwitchPreferenceCompat aria2;
|
||||
|
||||
MultiSelectListPreference sponsorblockFilters;
|
||||
SwitchPreferenceCompat embedSubtitles;
|
||||
SwitchPreferenceCompat embedThumbnail;
|
||||
SwitchPreferenceCompat addChapters;
|
||||
SwitchPreferenceCompat writeThumbnail;
|
||||
ListPreference audioFormat;
|
||||
ListPreference videoFormat;
|
||||
SeekBarPreference audioQuality;
|
||||
ListPreference videoQuality;
|
||||
|
||||
Preference updateYTDL;
|
||||
SwitchPreferenceCompat updateApp;
|
||||
Preference version;
|
||||
|
||||
private UpdateUtil updateUtil;
|
||||
|
||||
public static final int MUSIC_PATH_CODE = 33333;
|
||||
public static final int VIDEO_PATH_CODE = 55555;
|
||||
public static final int COMMAND_PATH_CODE = 77777;
|
||||
private static final String TAG = "SettingsFragment";
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey);
|
||||
|
||||
updateUtil = new UpdateUtil(requireContext());
|
||||
initPreferences();
|
||||
initListeners();
|
||||
}
|
||||
|
||||
private void initPreferences(){
|
||||
SharedPreferences preferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
|
||||
musicPath = findPreference("music_path");
|
||||
videoPath = findPreference("video_path");
|
||||
commandPath = findPreference("command_path");
|
||||
|
||||
incognito = findPreference("incognito");
|
||||
downloadCard = findPreference("download_card");
|
||||
apiKey = findPreference("api_key");
|
||||
concurrentFragments = findPreference("concurrent_fragments");
|
||||
limitRate = findPreference("limit_rate");
|
||||
aria2 = findPreference("aria2");
|
||||
|
||||
sponsorblockFilters = findPreference("sponsorblock_filter");
|
||||
embedSubtitles = findPreference("embed_subtitles");
|
||||
embedThumbnail = findPreference("embed_thumbnail");
|
||||
addChapters = findPreference("add_chapters");
|
||||
writeThumbnail = findPreference("write_thumbnail");
|
||||
audioFormat = findPreference("audio_format");
|
||||
videoFormat = findPreference("video_format");
|
||||
audioQuality = findPreference("audio_quality");
|
||||
videoQuality = findPreference("video_quality");
|
||||
|
||||
updateYTDL = findPreference("update_ytdl");
|
||||
updateApp = findPreference("update_app");
|
||||
version = findPreference("version");
|
||||
version.setSummary(BuildConfig.VERSION_NAME);
|
||||
|
||||
if (preferences.getString("music_path", "").isEmpty()){
|
||||
editor.putString("music_path", getString(R.string.music_path));
|
||||
}
|
||||
if (preferences.getString("video_path", "").isEmpty()){
|
||||
editor.putString("video_path", getString(R.string.video_path));
|
||||
}
|
||||
if (preferences.getString("command_path", "").isEmpty()){
|
||||
editor.putString("command_path", getString(R.string.command_path));
|
||||
}
|
||||
|
||||
editor.putBoolean("incognito", incognito.isChecked());
|
||||
editor.putBoolean("download_card", downloadCard.isChecked());
|
||||
editor.putString("api_key", apiKey.getText());
|
||||
editor.putInt("concurrent_fragments", concurrentFragments.getValue());
|
||||
editor.putString("limit_rate", limitRate.getText());
|
||||
editor.putBoolean("aria2", aria2.isChecked());
|
||||
editor.putString("sponsorblock_filter", String.join(",", sponsorblockFilters.getValues()));
|
||||
editor.putBoolean("embed_subtitles", embedSubtitles.isChecked());
|
||||
editor.putBoolean("embed_thumbnail", embedThumbnail.isChecked());
|
||||
editor.putBoolean("add_chapters", addChapters.isChecked());
|
||||
editor.putBoolean("write_thumbnail", writeThumbnail.isChecked());
|
||||
editor.putString("audio_format", audioFormat.getValue());
|
||||
editor.putString("video_format", videoFormat.getValue());
|
||||
editor.putInt("audio_quality", audioQuality.getValue());
|
||||
editor.putString("video_quality", videoQuality.getValue());
|
||||
editor.putBoolean("update_app", updateApp.isChecked());
|
||||
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private void initListeners(){
|
||||
SharedPreferences preferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = preferences.edit();
|
||||
|
||||
musicPath.setSummary(preferences.getString("music_path", ""));
|
||||
musicPath.setOnPreferenceClickListener(preference -> {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
musicPathResultLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
videoPath.setSummary(preferences.getString("video_path", ""));
|
||||
videoPath.setOnPreferenceClickListener(preference -> {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
videoPathResultLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
commandPath.setSummary(preferences.getString("command_path", ""));
|
||||
commandPath.setOnPreferenceClickListener(preference -> {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
commandPathResultLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
incognito.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean enable = (Boolean) newValue;
|
||||
editor.putBoolean("incognito", enable);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
downloadCard.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean enable = (Boolean) newValue;
|
||||
editor.putBoolean("download_card", enable);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
apiKey.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
editor.putString("api_key", String.valueOf(newValue));
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
concurrentFragments.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
int value = Integer.parseInt(String.valueOf(newValue));
|
||||
editor.putInt("concurrent_fragments", value);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
limitRate.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
editor.putString("limit_rate", String.valueOf(newValue));
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
aria2.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean enable = (Boolean) newValue;
|
||||
editor.putBoolean("aria2", enable);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
sponsorblockFilters.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
sponsorblockFilters.setValues((Set<String>) newValue);
|
||||
editor.putString("sponsorblock_filter", String.join(",", (Set<String>)newValue));
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
embedSubtitles.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean embed = (Boolean) newValue;
|
||||
editor.putBoolean("embed_subtitles", embed);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
embedThumbnail.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean embed = (Boolean) newValue;
|
||||
editor.putBoolean("embed_thumbnail", embed);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
addChapters.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean add = (Boolean) newValue;
|
||||
editor.putBoolean("add_chapters", add);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
writeThumbnail.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean write = (Boolean) newValue;
|
||||
editor.putBoolean("write_thumbnail", write);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
audioFormat.setSummary(preferences.getString("audio_format", ""));
|
||||
audioFormat.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
editor.putString("audio_format", String.valueOf(newValue));
|
||||
audioFormat.setSummary(String.valueOf(newValue));
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
videoFormat.setSummary(preferences.getString("video_format", ""));
|
||||
videoFormat.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
editor.putString("video_format", String.valueOf(newValue));
|
||||
videoFormat.setSummary(String.valueOf(newValue));
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
audioQuality.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
editor.putInt("audio_format", Integer.parseInt(String.valueOf(newValue)));
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
videoQuality.setSummary(preferences.getString("video_quality", ""));
|
||||
videoQuality.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
editor.putString("video_quality", String.valueOf(newValue));
|
||||
videoQuality.setSummary(String.valueOf(newValue));
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
updateYTDL.setOnPreferenceClickListener(preference -> {
|
||||
updateUtil.updateYoutubeDL();
|
||||
return true;
|
||||
});
|
||||
|
||||
updateApp.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean enable = (Boolean) newValue;
|
||||
editor.putBoolean("update_app", enable);
|
||||
editor.apply();
|
||||
return true;
|
||||
});
|
||||
|
||||
version.setOnPreferenceClickListener(preference -> {
|
||||
if(!updateUtil.updateApp()){
|
||||
Toast.makeText(getContext(), R.string.you_are_in_latest_version, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ActivityResultLauncher<Intent> musicPathResultLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
new ActivityResultCallback<ActivityResult>() {
|
||||
@Override
|
||||
public void onActivityResult(ActivityResult result) {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
changePath(musicPath, result.getData(), MUSIC_PATH_CODE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ActivityResultLauncher<Intent> videoPathResultLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
new ActivityResultCallback<ActivityResult>() {
|
||||
@Override
|
||||
public void onActivityResult(ActivityResult result) {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
changePath(videoPath, result.getData(), VIDEO_PATH_CODE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ActivityResultLauncher<Intent> commandPathResultLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
new ActivityResultCallback<ActivityResult>() {
|
||||
@Override
|
||||
public void onActivityResult(ActivityResult result) {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
changePath(commandPath, result.getData(), COMMAND_PATH_CODE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public void changePath(Preference p, Intent data, int requestCode){
|
||||
Log.e("TEST", data.toUri(0));
|
||||
String dataValue = data.getData().toString();
|
||||
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "");
|
||||
dataValue = dataValue.replaceAll("%3A", "/");
|
||||
|
||||
try{
|
||||
dataValue = java.net.URLDecoder.decode(dataValue, StandardCharsets.UTF_8.name());
|
||||
}catch(Exception ignored){}
|
||||
|
||||
String[] pieces = dataValue.split("/");
|
||||
|
||||
int index = 1;
|
||||
StringBuilder path = new StringBuilder("/storage/");
|
||||
if(pieces[0].equals("primary")){
|
||||
path.append("emulated/0/");
|
||||
}else{
|
||||
path.append(pieces[0]).append("/");
|
||||
}
|
||||
|
||||
for(int i = index; i < pieces.length; i++){
|
||||
path.append(pieces[i]).append("/");
|
||||
}
|
||||
|
||||
Log.e(TAG, path.toString());
|
||||
p.setSummary(path.toString());
|
||||
SharedPreferences sharedPreferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
switch (requestCode){
|
||||
case MUSIC_PATH_CODE:
|
||||
editor.putString("music_path", path.toString());
|
||||
break;
|
||||
case VIDEO_PATH_CODE:
|
||||
editor.putString("video_path", path.toString());
|
||||
break;
|
||||
case COMMAND_PATH_CODE:
|
||||
editor.putString("command_path", path.toString());
|
||||
break;
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,329 @@
|
|||
package com.deniscerri.ytdlnis.page.settings
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.preference.*
|
||||
import com.deniscerri.ytdlnis.BuildConfig
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.util.FileUtil
|
||||
import com.deniscerri.ytdlnis.util.UpdateUtil
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
private var musicPath: Preference? = null
|
||||
private var videoPath: Preference? = null
|
||||
private var commandPath: Preference? = null
|
||||
private var incognito: SwitchPreferenceCompat? = null
|
||||
private var downloadCard: SwitchPreferenceCompat? = null
|
||||
private var apiKey: EditTextPreference? = null
|
||||
private var concurrentFragments: SeekBarPreference? = null
|
||||
private var limitRate: EditTextPreference? = null
|
||||
private var aria2: SwitchPreferenceCompat? = null
|
||||
private var sponsorblockFilters: MultiSelectListPreference? = null
|
||||
private var embedSubtitles: SwitchPreferenceCompat? = null
|
||||
private var embedThumbnail: SwitchPreferenceCompat? = null
|
||||
private var addChapters: SwitchPreferenceCompat? = null
|
||||
private var writeThumbnail: SwitchPreferenceCompat? = null
|
||||
private var audioFormat: ListPreference? = null
|
||||
private var videoFormat: ListPreference? = null
|
||||
private var audioQuality: SeekBarPreference? = null
|
||||
private var videoQuality: ListPreference? = null
|
||||
private var updateYTDL: Preference? = null
|
||||
private var updateApp: SwitchPreferenceCompat? = null
|
||||
private var version: Preference? = null
|
||||
private var updateUtil: UpdateUtil? = null
|
||||
private var fileUtil: FileUtil? = null
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
updateUtil = UpdateUtil(requireContext())
|
||||
fileUtil = FileUtil()
|
||||
initPreferences()
|
||||
initListeners()
|
||||
}
|
||||
|
||||
private fun initPreferences() {
|
||||
val preferences =
|
||||
requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
||||
val editor = preferences.edit()
|
||||
musicPath = findPreference("music_path")
|
||||
videoPath = findPreference("video_path")
|
||||
commandPath = findPreference("command_path")
|
||||
incognito = findPreference("incognito")
|
||||
downloadCard = findPreference("download_card")
|
||||
apiKey = findPreference("api_key")
|
||||
concurrentFragments = findPreference("concurrent_fragments")
|
||||
limitRate = findPreference("limit_rate")
|
||||
aria2 = findPreference("aria2")
|
||||
sponsorblockFilters = findPreference("sponsorblock_filter")
|
||||
embedSubtitles = findPreference("embed_subtitles")
|
||||
embedThumbnail = findPreference("embed_thumbnail")
|
||||
addChapters = findPreference("add_chapters")
|
||||
writeThumbnail = findPreference("write_thumbnail")
|
||||
audioFormat = findPreference("audio_format")
|
||||
videoFormat = findPreference("video_format")
|
||||
audioQuality = findPreference("audio_quality")
|
||||
videoQuality = findPreference("video_quality")
|
||||
updateYTDL = findPreference("update_ytdl")
|
||||
updateApp = findPreference("update_app")
|
||||
version = findPreference("version")
|
||||
version!!.summary = BuildConfig.VERSION_NAME
|
||||
if (preferences.getString("music_path", "")!!.isEmpty()) {
|
||||
editor.putString("music_path", getString(R.string.music_path))
|
||||
}
|
||||
if (preferences.getString("video_path", "")!!.isEmpty()) {
|
||||
editor.putString("video_path", getString(R.string.video_path))
|
||||
}
|
||||
if (preferences.getString("command_path", "")!!.isEmpty()) {
|
||||
editor.putString("command_path", getString(R.string.command_path))
|
||||
}
|
||||
editor.putBoolean("incognito", incognito!!.isChecked)
|
||||
editor.putBoolean("download_card", downloadCard!!.isChecked)
|
||||
editor.putString("api_key", apiKey!!.text)
|
||||
editor.putInt("concurrent_fragments", concurrentFragments!!.value)
|
||||
editor.putString("limit_rate", limitRate!!.text)
|
||||
editor.putBoolean("aria2", aria2!!.isChecked)
|
||||
editor.putString(
|
||||
"sponsorblock_filter",
|
||||
java.lang.String.join(",", sponsorblockFilters!!.values)
|
||||
)
|
||||
editor.putBoolean("embed_subtitles", embedSubtitles!!.isChecked)
|
||||
editor.putBoolean("embed_thumbnail", embedThumbnail!!.isChecked)
|
||||
editor.putBoolean("add_chapters", addChapters!!.isChecked)
|
||||
editor.putBoolean("write_thumbnail", writeThumbnail!!.isChecked)
|
||||
editor.putString("audio_format", audioFormat!!.value)
|
||||
editor.putString("video_format", videoFormat!!.value)
|
||||
editor.putInt("audio_quality", audioQuality!!.value)
|
||||
editor.putString("video_quality", videoQuality!!.value)
|
||||
editor.putBoolean("update_app", updateApp!!.isChecked)
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
private fun initListeners() {
|
||||
val preferences =
|
||||
requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
||||
val editor = preferences.edit()
|
||||
musicPath!!.summary = fileUtil?.formatPath(preferences.getString("music_path", "")!!)
|
||||
musicPath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
musicPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
videoPath!!.summary = fileUtil?.formatPath(preferences.getString("video_path", "")!!)
|
||||
videoPath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
videoPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
commandPath!!.summary = fileUtil?.formatPath(preferences.getString("command_path", "")!!)
|
||||
commandPath!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
|
||||
commandPathResultLauncher.launch(intent)
|
||||
true
|
||||
}
|
||||
incognito!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val enable = newValue as Boolean
|
||||
editor.putBoolean("incognito", enable)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
downloadCard!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val enable = newValue as Boolean
|
||||
editor.putBoolean("download_card", enable)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
apiKey!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
editor.putString("api_key", newValue.toString())
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
concurrentFragments!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val value = newValue.toString().toInt()
|
||||
editor.putInt("concurrent_fragments", value)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
limitRate!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
editor.putString("limit_rate", newValue.toString())
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
aria2!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val enable = newValue as Boolean
|
||||
editor.putBoolean("aria2", enable)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
sponsorblockFilters!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
|
||||
sponsorblockFilters!!.values = newValue as Set<String?>?
|
||||
editor.putString("sponsorblock_filter", java.lang.String.join(",", newValue))
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
embedSubtitles!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val embed = newValue as Boolean
|
||||
editor.putBoolean("embed_subtitles", embed)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
embedThumbnail!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val embed = newValue as Boolean
|
||||
editor.putBoolean("embed_thumbnail", embed)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
addChapters!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val add = newValue as Boolean
|
||||
editor.putBoolean("add_chapters", add)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
writeThumbnail!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val write = newValue as Boolean
|
||||
editor.putBoolean("write_thumbnail", write)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
audioFormat!!.summary = preferences.getString("audio_format", "")
|
||||
audioFormat!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
editor.putString("audio_format", newValue.toString())
|
||||
audioFormat!!.summary = newValue.toString()
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
videoFormat!!.summary = preferences.getString("video_format", "")
|
||||
videoFormat!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
editor.putString("video_format", newValue.toString())
|
||||
videoFormat!!.summary = newValue.toString()
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
audioQuality!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
editor.putInt("audio_format", newValue.toString().toInt())
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
videoQuality!!.summary = preferences.getString("video_quality", "")
|
||||
videoQuality!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
editor.putString("video_quality", newValue.toString())
|
||||
videoQuality!!.summary = newValue.toString()
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
updateYTDL!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
updateUtil!!.updateYoutubeDL()
|
||||
true
|
||||
}
|
||||
updateApp!!.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val enable = newValue as Boolean
|
||||
editor.putBoolean("update_app", enable)
|
||||
editor.apply()
|
||||
true
|
||||
}
|
||||
version!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
if (!updateUtil!!.updateApp()) {
|
||||
Toast.makeText(context, R.string.you_are_in_latest_version, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private var musicPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(musicPath, result.data, MUSIC_PATH_CODE)
|
||||
}
|
||||
}
|
||||
private var videoPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(videoPath, result.data, VIDEO_PATH_CODE)
|
||||
}
|
||||
}
|
||||
private var commandPathResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.data?.let {
|
||||
activity?.contentResolver?.takePersistableUriPermission(
|
||||
it,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
changePath(commandPath, result.data, COMMAND_PATH_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun changePath(p: Preference?, data: Intent?, requestCode: Int) {
|
||||
Log.e("TEST", data!!.toUri(0))
|
||||
val path = data.data.toString()
|
||||
p!!.summary = fileUtil?.formatPath(data.data.toString())
|
||||
val sharedPreferences =
|
||||
requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
||||
val editor = sharedPreferences.edit()
|
||||
when (requestCode) {
|
||||
MUSIC_PATH_CODE -> editor.putString("music_path", path)
|
||||
VIDEO_PATH_CODE -> editor.putString("video_path", path)
|
||||
COMMAND_PATH_CODE -> editor.putString("command_path", path)
|
||||
}
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MUSIC_PATH_CODE = 33333
|
||||
const val VIDEO_PATH_CODE = 55555
|
||||
const val COMMAND_PATH_CODE = 77777
|
||||
private const val TAG = "SettingsFragment"
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ import com.deniscerri.ytdlnis.database.Video;
|
|||
import java.util.ArrayList;
|
||||
|
||||
public interface IDownloaderService {
|
||||
DownloadInfo getDownloadInfo();
|
||||
DownloadInfo getInfo();
|
||||
void addActivity(Activity activity, ArrayList<IDownloaderListener> callback);
|
||||
void removeActivity(Activity activity);
|
||||
void updateQueue(ArrayList<Video> queue);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
package com.deniscerri.ytdlnis.util
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import com.deniscerri.ytdlnis.DownloaderService
|
||||
import com.deniscerri.ytdlnis.R
|
||||
import com.deniscerri.ytdlnis.database.Video
|
||||
import java.io.File
|
||||
import java.net.URLDecoder
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
class FileUtil() {
|
||||
|
||||
class FileUtil {
|
||||
fun deleteFile(path: String){
|
||||
val file = File(path)
|
||||
if (file.exists()) {
|
||||
|
|
@ -15,4 +27,82 @@ class FileUtil {
|
|||
if (path.isEmpty()) return false
|
||||
return file.exists()
|
||||
}
|
||||
|
||||
fun formatPath(path: String) : String {
|
||||
var dataValue = path
|
||||
dataValue = dataValue.replace("content://com.android.externalstorage.documents/tree/", "")
|
||||
dataValue = dataValue.replace("%3A".toRegex(), "/")
|
||||
try {
|
||||
dataValue = URLDecoder.decode(dataValue, StandardCharsets.UTF_8.name())
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
val pieces = dataValue.split("/").toTypedArray()
|
||||
var formattedPath = StringBuilder("/storage/")
|
||||
if (pieces[0].equals("primary")){
|
||||
formattedPath.append("emulated/0/")
|
||||
}else{
|
||||
formattedPath.append(pieces[0]).append("/")
|
||||
}
|
||||
pieces.forEachIndexed { i, it ->
|
||||
if (i > 0 && it.isNotEmpty()){
|
||||
formattedPath.append(it).append("/")
|
||||
}
|
||||
}
|
||||
return formattedPath.toString()
|
||||
}
|
||||
|
||||
fun scanMedia(video: Video, context: Context) : String {
|
||||
val files = ArrayList<File>()
|
||||
val title: String = video.title.replace("[^a-zA-Z0-9]".toRegex(), "")
|
||||
var pathTmp: String
|
||||
val path = File(video.downloadPath)
|
||||
|
||||
try {
|
||||
for (file in path.listFiles()!!) {
|
||||
if (file.isFile) {
|
||||
pathTmp = file.absolutePath.replace("[^a-zA-Z0-9]".toRegex(), "")
|
||||
if (pathTmp.contains(title)) {
|
||||
files.add(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val paths = arrayOfNulls<String>(files.size)
|
||||
for (i in files.indices) paths[i] = files[i].absolutePath
|
||||
MediaScannerConnection.scanFile(context, paths, null, null)
|
||||
return paths[0]!!
|
||||
}catch (e: Exception){
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return context.getString(R.string.unfound_file);
|
||||
}
|
||||
|
||||
fun moveFile(originDir : File, context:Context, destDir : Uri){
|
||||
originDir.listFiles().forEach {
|
||||
val mimeType =
|
||||
MimeTypeMap.getSingleton().getMimeTypeFromExtension(it.extension) ?: "*/*"
|
||||
|
||||
if (it.name.equals("rList")){
|
||||
it.delete()
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val destUri = DocumentsContract.createDocument(
|
||||
context.contentResolver,
|
||||
destDir,
|
||||
mimeType,
|
||||
it.name
|
||||
) ?: return@forEach
|
||||
|
||||
val inputStream = it.inputStream()
|
||||
val outputStream =
|
||||
context.contentResolver.openOutputStream(destUri) ?: return@forEach
|
||||
inputStream.copyTo(outputStream)
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -29,28 +29,30 @@ class InfoUtil(context: Context) {
|
|||
context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
|
||||
key = sharedPreferences.getString("api_key", "")
|
||||
databaseManager = DatabaseManager(context)
|
||||
val thread = Thread {
|
||||
|
||||
//get Locale
|
||||
val country = genericRequest("https://ipwho.is/")
|
||||
countryCODE = try {
|
||||
country.getString("country_code")
|
||||
} catch (e: Exception) {
|
||||
"US"
|
||||
}
|
||||
val res = genericRequest(invidousURL + "stats")
|
||||
useInvidous = res.length() != 0
|
||||
}
|
||||
thread.start()
|
||||
thread.join()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
videos = ArrayList()
|
||||
}
|
||||
|
||||
private fun init(){
|
||||
if (countryCODE == null){
|
||||
val country = genericRequest("https://ipwho.is/")
|
||||
countryCODE = try {
|
||||
country.getString("country_code")
|
||||
} catch (e: Exception) {
|
||||
"US"
|
||||
}
|
||||
}
|
||||
if (useInvidous == false){
|
||||
val res = genericRequest(invidousURL + "stats")
|
||||
useInvidous = res.length() != 0
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(JSONException::class)
|
||||
fun search(query: String): ArrayList<Video?> {
|
||||
init()
|
||||
videos = ArrayList()
|
||||
return if (key!!.isEmpty()) {
|
||||
if (useInvidous) searchFromInvidous(query) else getFromYTDL(query)
|
||||
|
|
@ -133,6 +135,7 @@ class InfoUtil(context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getPlaylist(id: String, nextPageToken: String): PlaylistTuple {
|
||||
init()
|
||||
videos = ArrayList()
|
||||
if (key!!.isEmpty()) {
|
||||
return if (useInvidous) getPlaylistFromInvidous(id) else PlaylistTuple(
|
||||
|
|
@ -216,6 +219,7 @@ class InfoUtil(context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getVideo(id: String): Video? {
|
||||
init()
|
||||
if (key!!.isEmpty()) {
|
||||
return if (useInvidous) {
|
||||
val res = genericRequest(invidousURL + "videos/" + id)
|
||||
|
|
@ -392,6 +396,7 @@ class InfoUtil(context: Context) {
|
|||
|
||||
@Throws(JSONException::class)
|
||||
fun getTrending(context: Context): ArrayList<Video?> {
|
||||
init()
|
||||
videos = ArrayList()
|
||||
return if (key!!.isEmpty()) {
|
||||
if (useInvidous) getTrendingFromInvidous(context) else ArrayList()
|
||||
|
|
@ -447,6 +452,7 @@ class InfoUtil(context: Context) {
|
|||
}
|
||||
|
||||
fun getFromYTDL(query: String): ArrayList<Video?> {
|
||||
init()
|
||||
videos = ArrayList()
|
||||
try {
|
||||
val request = YoutubeDLRequest(query)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@
|
|||
app:layout_scrollFlags="scroll|enterAlways|snap"
|
||||
app:title="@string/settings"
|
||||
android:layout_width="match_parent"
|
||||
app:navigationIcon="@drawable/ic_back"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
|
||||
|
|
|
|||
7
app/src/main/res/values-v31/styles.xml
Normal file
7
app/src/main/res/values-v31/styles.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="AppDefaultTheme">
|
||||
<item name="android:windowSplashScreenAnimationDuration">0</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
@ -148,4 +148,5 @@
|
|||
<string name="sponsorblock_fillers">Fillers</string>
|
||||
<string name="sponsorblock_reminders">Subscription Reminders</string>
|
||||
<string name="select_sponsorblock_filtering">Select SponsorBlock Filtering</string>
|
||||
<string name="unfound_file" translatable="false">/404.404</string>
|
||||
</resources>
|
||||
|
|
@ -5,8 +5,11 @@
|
|||
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:color">@android:color/transparent</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme" parent="AppDefaultTheme" />
|
||||
|
||||
<style name="Toolbar" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="searchViewStyle">@style/searchStyle</item>
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in a new issue