diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index 9f08b41a..3375e8a9 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -23,7 +23,7 @@ - + @@ -33,8 +33,8 @@ - - + + diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index a5f05cd8..23704740 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -21,5 +21,10 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 2931481d..1ebb0846 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -35,6 +35,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/build.gradle b/app/build.gradle index 27f048fe..6debe2a7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -28,6 +28,7 @@ android { buildFeatures { viewBinding true + compose true } compileOptions { @@ -35,6 +36,7 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } + splits { abi { enable true @@ -50,15 +52,19 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(":library") - implementation project(":ffmpeg") +// implementation project(":library") +// implementation project(":ffmpeg") + + implementation 'com.github.yausername.youtubedl-android:library:0.13.3' + implementation 'com.github.yausername.youtubedl-android:ffmpeg:0.13.3' implementation "androidx.appcompat:appcompat:$appCompatVer" implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'com.google.android.material:material:1.5.0' + implementation 'com.google.android.material:material:1.6.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.core:core:1.8.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' - implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.preference:preference:1.2.0' testImplementation "junit:junit:$junitVer" androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" @@ -69,4 +75,7 @@ dependencies { implementation("androidx.cardview:cardview:1.0.0") implementation("com.squareup.picasso:picasso:2.8") implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" + + implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' + implementation 'com.facebook.shimmer:shimmer:0.5.0' } diff --git a/app/src/main/java/com/deniscerri/ytdl/App.java b/app/src/main/java/com/deniscerri/ytdl/App.java index fb106b39..49b2b3fb 100644 --- a/app/src/main/java/com/deniscerri/ytdl/App.java +++ b/app/src/main/java/com/deniscerri/ytdl/App.java @@ -1,9 +1,15 @@ package com.deniscerri.ytdl; import android.app.Application; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.res.Configuration; +import android.os.Build; import android.util.Log; import android.widget.Toast; +import com.devbrackets.android.exomedia.BuildConfig; +import com.google.android.material.color.DynamicColors; import com.yausername.ffmpeg.FFmpeg; import com.yausername.youtubedl_android.YoutubeDL; import com.yausername.youtubedl_android.YoutubeDLException; @@ -19,12 +25,15 @@ import io.reactivex.schedulers.Schedulers; public class App extends Application { private static final String TAG = "App"; + public static final String DOWNLOAD_CHANNEL_ID = "1"; ActivityMainBinding binding; @Override public void onCreate() { super.onCreate(); + DynamicColors.applyToActivitiesIfAvailable(this); + createNotificationChannel(); configureRxJavaErrorHandler(); Completable.fromAction(this::initLibraries).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new DisposableCompletableObserver() { @Override @@ -61,4 +70,16 @@ public class App extends Application { YoutubeDL.getInstance().init(this); FFmpeg.getInstance().init(this); } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = getString(R.string.download_notification_channel_name); + String description = getString(R.string.download_notification_channel_description); + int importance = NotificationManager.IMPORTANCE_HIGH; + NotificationChannel channel = new NotificationChannel(DOWNLOAD_CHANNEL_ID, name, importance); + channel.setDescription(description); + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + } } diff --git a/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java b/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java index ebf9a543..932eb5ce 100644 --- a/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/HistoryFragment.java @@ -1,23 +1,17 @@ package com.deniscerri.ytdl; -import android.app.ActionBar; import android.content.Context; -import android.graphics.PorterDuff; -import android.graphics.Typeface; +import android.graphics.Color; import android.os.Bundle; - import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; import androidx.cardview.widget.CardView; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentTransaction; -import androidx.recyclerview.widget.GridLayoutManager; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; - +import android.os.Handler; +import android.os.Looper; import android.text.InputType; import android.util.Log; import android.util.TypedValue; @@ -31,36 +25,39 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; import com.deniscerri.ytdl.database.DBManager; import com.deniscerri.ytdl.database.Video; -import com.deniscerri.ytdl.placeholder.PlaceholderContent; +import com.facebook.shimmer.ShimmerFrameLayout; +import com.google.android.material.appbar.MaterialToolbar; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.squareup.picasso.Picasso; -import org.json.JSONObject; - import java.util.ArrayList; /** * A fragment representing a list of Items. */ -public class HistoryFragment extends Fragment { +public class HistoryFragment extends Fragment implements View.OnClickListener{ private LinearLayout linearLayout; private ScrollView scrollView; private View fragmentView; private DBManager dbManager; - private ArrayList historyObjects; + Context context; + private LayoutInflater layoutinflater; + private ShimmerFrameLayout shimmerCards; + private MaterialToolbar topAppBar; + + private static final String TAG = "HistoryFragment"; public HistoryFragment() { } - // TODO: Customize parameter initialization @SuppressWarnings("unused") public static HistoryFragment newInstance() { HistoryFragment fragment = new HistoryFragment(); @@ -79,163 +76,135 @@ public class HistoryFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { fragmentView = inflater.inflate(R.layout.fragment_history, container, false); - initViews(); + context = fragmentView.getContext(); + layoutinflater = LayoutInflater.from(context); + linearLayout = fragmentView.findViewById(R.id.linearLayout1); + scrollView = fragmentView.findViewById(R.id.scrollView1); + shimmerCards = fragmentView.findViewById(R.id.shimmer_history_framelayout); + topAppBar = fragmentView.findViewById(R.id.history_toolbar); - dbManager = new DBManager(requireContext()); - initCards(); + dbManager = new DBManager(context); SwipeRefreshLayout swipeRefreshLayout = fragmentView.findViewById(R.id.swiperefresh); - swipeRefreshLayout.setOnRefreshListener(() -> { initCards(); swipeRefreshLayout.setRefreshing(false); }); - + FloatingActionButton fab = fragmentView.findViewById(R.id.fab_history); + fab.setOnClickListener(this); + scrollView.setOnScrollChangeListener((view, x, y, oldX, oldY) -> { + if( y > 500){ + fab.show(); + }else{ + fab.hide(); + } + }); + initMenu(); + initCards(); return fragmentView; } + private void initCards(){ + shimmerCards.startShimmer(); + shimmerCards.setVisibility(View.VISIBLE); linearLayout.removeAllViews(); - historyObjects = dbManager.merrHistorine(); - for(int i = historyObjects.size()-1; i >= 0; i--){ - createCard(historyObjects.get(i)); + try{ + Thread thread = new Thread(() -> { + Handler uiHandler = new Handler(Looper.getMainLooper()); + ArrayList historyObjects = dbManager.getHistory(); + for(int i = historyObjects.size()-1; i >= 0; i--){ + createCard(historyObjects.get(i)); + } + TextView padding = new TextView(context); + int dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, context.getResources().getDisplayMetrics()); + padding.setHeight(dp); + padding.setGravity(Gravity.CENTER); + + uiHandler.post(() -> { + linearLayout.addView(padding); + shimmerCards.stopShimmer(); + shimmerCards.setVisibility(View.GONE); + }); + }); + thread.start(); + }catch(Exception e){ + Log.e(TAG, e.toString()); } + } - private int getDp(int value){ - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics()); + public void scrollToTop(){ + scrollView.smoothScrollTo(0,0); } - private void createCard(Video video) { - - RelativeLayout r = new RelativeLayout(getContext()); - r.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); - r.getLayoutParams().height = getDp(130); - - CardView card = new CardView(requireContext()); - card.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); - card.setRadius(getDp(10)); - card.setCardElevation(10); - card.setMaxCardElevation(12); - card.setPreventCornerOverlap(true); - card.setUseCompatPadding(true); - card.setCardBackgroundColor(ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark)); + private void createCard(Video video){ + RelativeLayout r = new RelativeLayout(context); + layoutinflater.inflate(R.layout.history_card, r); + CardView card = r.findViewById(R.id.history_card_view); // THUMBNAIL ---------------------------------- - - ImageView thumbnail = new ImageView(getContext()); - thumbnail.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); + ImageView thumbnail = card.findViewById(R.id.history_image_view); String imageURL= video.getThumb(); - - Picasso.get().load(imageURL).into(thumbnail); - thumbnail.setAdjustViewBounds(false); - thumbnail.setScaleType(ImageView.ScaleType.CENTER_CROP); + Handler uiHandler = new Handler(Looper.getMainLooper()); + uiHandler.post(() -> Picasso.get().load(imageURL).into(thumbnail)); + thumbnail.setColorFilter(Color.argb(70, 0, 0, 0)); // TITLE ---------------------------------- - TextView videoTitle = new TextView(getContext()); - videoTitle.setLayoutParams(new RelativeLayout.LayoutParams(getDp(300), getDp(100))); - int padding = getDp(20); - videoTitle.setPadding(padding, padding, padding, padding); + TextView videoTitle = card.findViewById(R.id.history_title); + String title = video.getTitle(); - videoTitle.setText(video.getTitle()); - videoTitle.setTextSize(getDp(5)); - videoTitle.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)); - videoTitle.setTypeface(null, Typeface.BOLD); - videoTitle.setShadowLayer(2f, 4f, 4f, ContextCompat.getColor(requireContext(), R.color.black)); + if(title.length() > 100){ + title = title.substring(0, 40) + "..."; + } + videoTitle.setText(title); - // AUTHOR ---------------------------------- + // Bottom Info ---------------------------------- + TextView bottomInfo = card.findViewById(R.id.history_info_bottom); + String info = video.getAuthor() + " • " + video.getDuration(); + bottomInfo.setText(info); - TextView videoAuthor = new TextView(getContext()); - videoAuthor.setGravity(Gravity.BOTTOM); - videoAuthor.setLayoutParams(new RelativeLayout.LayoutParams(getDp(150), getDp(100))); - videoAuthor.setPadding(getDp(20), 0, 0, getDp(10)); - - videoAuthor.setText(video.getAuthor()); - videoAuthor.setTextSize(getDp(3)); - videoAuthor.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)); - videoAuthor.setShadowLayer(2f, 4f, 4f, ContextCompat.getColor(requireContext(), R.color.black)); - videoAuthor.setTypeface(null, Typeface.BOLD); - - // DATE --------------------------------------------- - - TextView date = new TextView(getContext()); - date.setGravity(Gravity.BOTTOM); - date.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, getDp(100))); - date.setPadding(getDp(250), 0, getDp(20), getDp(10)); - date.setShadowLayer(1.5f, 4f, 4f, R.color.black); - - date.setText(video.getDownloadedTime()); - date.setTextSize(getDp(3)); - date.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)); - date.setShadowLayer(1, 1, 1, ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark)); - date.setTypeface(null, Typeface.BOLD); - - // BUTTONS ------------------------------------------- - - - LinearLayout buttonLayout = new LinearLayout(requireContext()); - buttonLayout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); - buttonLayout.setGravity(Gravity.BOTTOM | Gravity.RIGHT); - buttonLayout.setOrientation(LinearLayout.HORIZONTAL); - buttonLayout.setPadding(getDp(10), 0, getDp(20), getDp(40)); - - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - getDp(48), getDp(48) - ); + // TIME DOWNLOADED ---------------------------------- + TextView datetime = card.findViewById(R.id.history_info_time); + String downloadedTime = video.getDownloadedTime(); + datetime.setText(downloadedTime); + // BUTTON ---------------------------------- + LinearLayout buttonLayout = card.findViewById(R.id.history_download_button_layout); + Button btn = buttonLayout.findViewById(R.id.history_download_button_type); if(video.getDownloadedType().equals("mp3")){ - Button musicBtn = new Button(getContext()); - - params.setMargins(0,0, getDp(10), 0); - musicBtn.setLayoutParams(params); - //musicBtn.setIconSize(getDp(24)); - musicBtn.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)); - - buttonLayout.addView(musicBtn); + btn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_music)); }else{ - Button videoBtn = new Button(requireContext()); - videoBtn.setLayoutParams(params); - //videoBtn.setIconSize(getDp(24)); - videoBtn.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)); - - buttonLayout.addView(videoBtn); + btn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_video)); } - card.addView(thumbnail); - card.addView(videoTitle); - card.addView(videoAuthor); - card.addView(date); - card.addView(buttonLayout); - - r.addView(card); - - linearLayout.addView(r); + uiHandler.post(() -> linearLayout.addView(r)); } + private void initMenu(){ + topAppBar.setOnClickListener(view -> { + scrollToTop(); + }); + + topAppBar.setOnMenuItemClickListener((MenuItem m) -> { + switch (m.getItemId()){ + case R.id.delete_history: + dbManager.clearHistory(); + linearLayout.removeAllViews(); + return true; + } + return true; + }); - private void initViews() { - linearLayout = fragmentView.findViewById(R.id.linearLayout1); - scrollView = fragmentView.findViewById(R.id.scrollView1); } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ - inflater.inflate(R.menu.history_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.delete_history: - dbManager.clearHistory(); - linearLayout.removeAllViews(); - return true; - default: - return super.onOptionsItemSelected(item); - + public void onClick(View v) { + if (v.getId() == R.id.fab_history) { + scrollView.smoothScrollBy(0, 0); + scrollView.smoothScrollTo(0, 0); } } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java b/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java index 8f98a824..a431fd9a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/HomeFragment.java @@ -1,22 +1,24 @@ package com.deniscerri.ytdl; import android.Manifest; -import android.app.ActionBar; import android.app.Activity; +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.graphics.PorterDuff; -import android.graphics.Typeface; +import android.graphics.BitmapFactory; +import android.graphics.Color; import android.media.MediaScannerConnection; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.text.InputType; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -32,19 +34,23 @@ import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; import androidx.cardview.widget.CardView; import androidx.core.app.ActivityCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.deniscerri.ytdl.api.YoutubeAPIManager; import com.deniscerri.ytdl.database.DBManager; import com.deniscerri.ytdl.database.Video; +import com.facebook.shimmer.ShimmerFrameLayout; +import com.google.android.material.appbar.MaterialToolbar; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.squareup.picasso.Picasso; import com.yausername.youtubedl_android.DownloadProgressCallback; import com.yausername.youtubedl_android.YoutubeDL; import com.yausername.youtubedl_android.YoutubeDLRequest; -import org.json.JSONObject; import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -56,7 +62,6 @@ import java.util.Queue; import java.util.TimeZone; 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; @@ -65,54 +70,64 @@ import io.reactivex.schedulers.Schedulers; public class HomeFragment extends Fragment implements View.OnClickListener{ - private boolean downloading = false; private View fragmentView; private ProgressBar progressBar; private String inputQuery; private LinearLayout linearLayout; private ScrollView scrollView; + private ShimmerFrameLayout shimmerCards; + private LayoutInflater layoutinflater; + Context context; + Activity activity; + private MaterialToolbar topAppBar; private static final String TAG = "HomeFragment"; private ArrayList resultObjects; - private Queue downloadQueue; - private int[] positions = {0,0}; private CompositeDisposable compositeDisposable = new CompositeDisposable(); private DBManager dbManager; private YoutubeAPIManager youtubeAPIManager; + private NotificationCompat.Builder download_notification; + private NotificationManagerCompat notificationManager; + private Queue downloadQueue; private final DownloadProgressCallback callback = new DownloadProgressCallback() { @Override public void onProgressUpdate(float progress, long etaInSeconds, String line) { - requireActivity().runOnUiThread(() -> { + activity.runOnUiThread(() -> { progressBar.setProgress((int) progress); + + String contentText = ""; + if(progressBar.getProgress() > 0){ + contentText += progressBar.getProgress() + "%"; + } + + String eta = convertETASecondsToTime(etaInSeconds); + if(!eta.equals("0sec")){ + contentText = contentText+ " Estimated Time: " + eta; + } + + if(downloadQueue.size() > 0){ + contentText = contentText + "\n" + downloadQueue.size() + " items left"; + } + + + download_notification.setProgress(100,(int) progress,false) + .setContentText(contentText); + notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build()); + }); } }; - // TODO: Rename parameter arguments, choose names that match - // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER - private static final String ARG_PARAM1 = "param1"; - private static final String ARG_PARAM2 = "param2"; - - public HomeFragment() { // Required empty public constructor } - public static HomeFragment newInstance(String param1, String param2) { - HomeFragment fragment = new HomeFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PARAM1, param1); - args.putString(ARG_PARAM2, param2); - fragment.setArguments(args); - return fragment; - } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -120,6 +135,24 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ setHasOptionsMenu(true); } + public String convertETASecondsToTime(long eta){ + String time = ""; + int s = (int) eta; + int h = s / 3600; + int m = (s % 3600) / 60; + s %= 60; + if(h > 0){ + time+=h+"hr "; + }else if(m > 0){ + if(m < 10) time +="0"; + time+=m+"min"; + } + if(s < 10 && s > 0 && m > 0) time +="0"; + if(s < 0) s = 0; + time+=s+"sec"; + + return time; + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, @@ -129,46 +162,102 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ // Inflate the layout for this fragment fragmentView = inflater.inflate(R.layout.fragment_home, container, false); + context = fragmentView.getContext(); + activity = getActivity(); + layoutinflater = LayoutInflater.from(context); - initViews(); + //initViews + linearLayout = fragmentView.findViewById(R.id.linearLayout1); + scrollView = fragmentView.findViewById(R.id.scrollView1); + shimmerCards = fragmentView.findViewById(R.id.shimmer_results_framelayout); + topAppBar = fragmentView.findViewById(R.id.home_toolbar); - fragmentView.setOnScrollChangeListener((view, i, i1, i2, i3) -> positions = new int[]{i,i1}); + SwipeRefreshLayout swipeRefreshLayout = fragmentView.findViewById(R.id.swiperefreshhome); + swipeRefreshLayout.setOnRefreshListener(() -> { + initCards(); + swipeRefreshLayout.setRefreshing(false); + }); + + FloatingActionButton fab = fragmentView.findViewById(R.id.fab_home); + fab.setOnClickListener(this); + + scrollView.setOnScrollChangeListener((view, x, y, oldX, oldY) -> { + if( y > 500){ + fab.show(); + }else{ + fab.hide(); + } + }); + + initMenu(); if(inputQuery != null){ parseQuery(); inputQuery = null; - return fragmentView; + }else{ + initCards(); } - dbManager = new DBManager(requireContext()); - resultObjects = dbManager.merrRezultatet(); - - if(resultObjects != null){ - scrollView.post(() -> scrollView.scrollTo(positions[0], positions[1])); - if(resultObjects.size() > 1 && resultObjects.get(1).getIsPlaylistItem() == 1){ - createDownloadAllCard(); - } - for(int i = 0; i < resultObjects.size(); i++){ - createCard(resultObjects.get(i)); - } - } - - return fragmentView; } - private void initViews(){ - linearLayout = fragmentView.findViewById(R.id.linearLayout1); - scrollView = fragmentView.findViewById(R.id.scrollView1); + + private void initCards(){ + shimmerCards.startShimmer(); + shimmerCards.setVisibility(View.VISIBLE); + linearLayout.removeAllViews(); + try{ + Thread thread = new Thread(() -> { + Handler uiHandler = new Handler(Looper.getMainLooper()); + dbManager = new DBManager(context); + youtubeAPIManager = new YoutubeAPIManager(context); + resultObjects = dbManager.getResults(); + boolean trending = false; + + if(resultObjects.size() == 0){ + trending = true; + try { + resultObjects = youtubeAPIManager.getTrending(context); + }catch(Exception e){ + Log.e(TAG, e.toString()); + } + } + + TextView trendingText = new TextView(context); + + if(resultObjects != null){ + scrollToTop(); + + if(trending){ + trendingText.setText(R.string.Trending); + trendingText.setPadding(30, 0 ,0 ,0); + uiHandler.post(() -> linearLayout.addView(trendingText)); + + }else{ + if(resultObjects.size() > 1 && resultObjects.get(1).getIsPlaylistItem() == 1){ + createDownloadAllCard(); + } + } + + for(int i = 0; i < resultObjects.size(); i++){ + createCard(resultObjects.get(i)); + } + } + + uiHandler.post(() -> { + addEndofResultsText(); + shimmerCards.stopShimmer(); + shimmerCards.setVisibility(View.GONE); + }); + }); + thread.start(); + }catch(Exception e){ + Log.e(TAG, e.toString()); + } } - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ - menu.clear(); - inflater.inflate(R.menu.main_menu, menu); - super.onCreateOptionsMenu(menu, inflater); - + private void initMenu(){ MenuItem.OnActionExpandListener onActionExpandListener = new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem menuItem) { @@ -180,15 +269,15 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ return true; } }; - - menu.findItem(R.id.search).setOnActionExpandListener(onActionExpandListener); - SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView(); + topAppBar.getMenu().findItem(R.id.search).setOnActionExpandListener(onActionExpandListener); + SearchView searchView = (SearchView) topAppBar.getMenu().findItem(R.id.search).getActionView(); searchView.setInputType(InputType.TYPE_TEXT_VARIATION_URI); - searchView.setQueryHint("Kërko nga YouTube"); + searchView.setQueryHint(getString(R.string.search_hint)); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - menu.findItem(R.id.search).collapseActionView(); + topAppBar.getMenu().findItem(R.id.search).collapseActionView(); inputQuery = query.trim(); parseQuery(); return true; @@ -199,36 +288,38 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ return false; } }); - } - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - switch (item.getItemId()) { - case R.id.delete_results: + topAppBar.setOnClickListener(view -> scrollToTop()); + + topAppBar.setOnMenuItemClickListener((MenuItem m) -> { + if (m.getItemId() == R.id.delete_results) { dbManager.clearResults(); linearLayout.removeAllViews(); + initCards(); return true; - default: - return super.onOptionsItemSelected(item); + } + return true; + }); - } } - public void handleIntent(Intent intent){ inputQuery = intent.getStringExtra(Intent.EXTRA_TEXT); } public void scrollToTop(){ - fragmentView.scrollTo(0,0); + scrollView.smoothScrollTo(0,0); } private void parseQuery() { + shimmerCards.startShimmer(); + shimmerCards.setVisibility(View.VISIBLE); linearLayout.removeAllViews(); resultObjects = new ArrayList<>(); - dbManager = new DBManager(requireContext()); - youtubeAPIManager = new YoutubeAPIManager(requireContext()); + dbManager = new DBManager(context); + youtubeAPIManager = new YoutubeAPIManager(context); + scrollToTop(); String type = "Search"; Pattern p = Pattern.compile("^(https?)://(www.)?youtu(.be)?"); @@ -246,19 +337,32 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ try { switch (type) { case "Search": { - Thread thread = new Thread(() -> { - try { - resultObjects = youtubeAPIManager.search(inputQuery); - }catch(Exception e){ - Log.e(TAG, e.toString()); + Thread thread = new Thread(new Runnable(){ + private final String query; + { + this.query = inputQuery; + } + @Override + public void run(){ + try { + resultObjects = youtubeAPIManager.search(query); + }catch(Exception e){ + Log.e(TAG, e.toString()); + } + Log.e(TAG, resultObjects.toString()); + + for(int i = 0; i < resultObjects.size(); i++){ + createCard(resultObjects.get(i)); + } + dbManager.addToResults(resultObjects); + new Handler(Looper.getMainLooper()).post(() -> { + addEndofResultsText(); + shimmerCards.stopShimmer(); + shimmerCards.setVisibility(View.GONE); + }); } }); thread.start(); - thread.join(); - for(int i = 0; i < resultObjects.size(); i++){ - createCard(resultObjects.get(i)); - } - dbManager.shtoVideoRezultat(resultObjects); break; }case "Video": { String[] el = inputQuery.split("/"); @@ -268,41 +372,61 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ inputQuery = inputQuery.substring(8); } - Thread thread = new Thread(() -> { - try { - resultObjects.add(youtubeAPIManager.getVideo(inputQuery)); - }catch(Exception e){ - Log.e(TAG, e.toString()); + Thread thread = new Thread(new Runnable(){ + private final String query; + { + this.query = inputQuery; + } + @Override + public void run(){ + try { + resultObjects.add(youtubeAPIManager.getVideo(query)); + }catch(Exception e){ + Log.e(TAG, e.toString()); + } + + dbManager.addToResults(resultObjects); + createCard(resultObjects.get(0)); + + new Handler(Looper.getMainLooper()).post(() -> { + shimmerCards.stopShimmer(); + shimmerCards.setVisibility(View.GONE); + }); } }); thread.start(); - thread.join(); - - dbManager.shtoVideoRezultat(resultObjects); - createCard(resultObjects.get(0)); break; }case "Playlist": { inputQuery = inputQuery.split("list=")[1]; - Thread thread = new Thread(() -> { - try { - resultObjects = youtubeAPIManager.getPlaylist(inputQuery, ""); - }catch(Exception e){ - Log.e(TAG, e.toString()); + Thread thread = new Thread(new Runnable() { + private final String query; + { + this.query = inputQuery; + } + + @Override + public void run(){ + try { + resultObjects = youtubeAPIManager.getPlaylist(query, ""); + }catch(Exception e){ + Log.e(TAG, e.toString()); + } + dbManager.addToResults(resultObjects); + // DOWNLOAD ALL BUTTON + if(resultObjects.size() > 1){ + createDownloadAllCard(); + } + for(int i = 0 ; i < resultObjects.size(); i++){ + createCard(resultObjects.get(i)); + } + new Handler(Looper.getMainLooper()).post(() -> { + addEndofResultsText(); + shimmerCards.stopShimmer(); + shimmerCards.setVisibility(View.GONE); + }); } }); thread.start(); - thread.join(); - - dbManager.shtoVideoRezultat(resultObjects); - - // DOWNLOAD ALL BUTTON - if(resultObjects.size() > 1){ - createDownloadAllCard(); - } - - for(int i = 0 ; i < resultObjects.size(); i++){ - createCard(resultObjects.get(i)); - } break; } } @@ -310,234 +434,134 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ Log.e(TAG, e.toString()); } + + } + + private void addEndofResultsText(){ + TextView padding = new TextView(context); + int dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80, fragmentView.getResources().getDisplayMetrics()); + padding.setHeight(dp); + padding.setText(R.string.end_of_results); + padding.setGravity(Gravity.CENTER); + linearLayout.addView(padding); } private void createDownloadAllCard(){ - RelativeLayout r = new RelativeLayout(getContext()); - r.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); - r.getLayoutParams().height = getDp(90); + RelativeLayout r = new RelativeLayout(context); + layoutinflater.inflate(R.layout.download_all_card, r); - CardView card = new CardView(requireContext()); - card.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); - card.setRadius(getDp(10)); - card.setCardElevation(10); - card.setMaxCardElevation(12); - card.setPreventCornerOverlap(true); - card.setUseCompatPadding(true); - card.setCardBackgroundColor(ContextCompat.getColor(requireContext(), R.color.black)); + CardView card = r.findViewById(R.id.downloadall_card_view); - // TITLE ---------------------------------- - TextView videoTitle = new TextView(getContext()); - videoTitle.setLayoutParams(new RelativeLayout.LayoutParams(getDp(300), getDp(100))); - int padding = getDp(20); - videoTitle.setPadding(padding, padding, padding, padding); - - videoTitle.setText("Shkarko të gjitha"); - videoTitle.setTextSize(getDp(5)); - videoTitle.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)); - videoTitle.setTypeface(null, Typeface.BOLD); - videoTitle.setShadowLayer(2f, 4f, 4f, ContextCompat.getColor(requireContext(), R.color.black)); - - // BUTTONS ------------------------------------------- - LinearLayout buttonLayout = new LinearLayout(requireContext()); - buttonLayout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); - buttonLayout.setGravity(Gravity.BOTTOM | Gravity.RIGHT); - buttonLayout.setOrientation(LinearLayout.HORIZONTAL); - buttonLayout.setPadding(0, 0, getDp(20), getDp(10)); - - Button musicBtn = new Button(getContext()); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - getDp(48), getDp(48) - ); - params.setMargins(10,0, 10, 10); - musicBtn.setLayoutParams(params); - //musicBtn.setIconSize(getDp(24)); - musicBtn.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)); + LinearLayout buttonLayout = card.findViewById(R.id.downloadall_button_layout); + Button musicBtn = buttonLayout.findViewById(R.id.downloadall_music); musicBtn.setTag("ALL##mp3"); - Button videoBtn = new Button(requireContext()); - videoBtn.setLayoutParams(params); - //videoBtn.setIconSize(getDp(24)); - videoBtn.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)); + Button videoBtn = buttonLayout.findViewById(R.id.downloadall_video); videoBtn.setTag("ALL##mp4"); - buttonLayout.addView(musicBtn); - buttonLayout.addView(videoBtn); - musicBtn.setOnClickListener(this); videoBtn.setOnClickListener(this); - card.addView(videoTitle); - card.addView(buttonLayout); - - r.addView(card); - - linearLayout.addView(r); - - - } - - private int getDp(int value){ - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, getResources().getDisplayMetrics()); - } - - private int getSp(int value){ - return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, getResources().getDisplayMetrics()); + Handler uiHandler = new Handler(Looper.getMainLooper()); + uiHandler.post(() -> linearLayout.addView(r)); } private void createCard(Video video){ + RelativeLayout r = new RelativeLayout(context); + layoutinflater.inflate(R.layout.result_card, r); - RelativeLayout r = new RelativeLayout(getContext()); - r.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); - r.getLayoutParams().height = getDp(230); - - CardView card = new CardView(requireContext()); - card.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); - card.setRadius(getDp(10)); - card.setCardElevation(10); - card.setMaxCardElevation(12); - card.setPreventCornerOverlap(true); - card.setUseCompatPadding(true); - card.setCardBackgroundColor(ContextCompat.getColor(requireContext(), R.color.colorPrimaryDark)); - + CardView card = r.findViewById(R.id.result_card_view); // THUMBNAIL ---------------------------------- - - ImageView thumbnail = new ImageView(getContext()); - thumbnail.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); + ImageView thumbnail = card.findViewById(R.id.result_image_view); String imageURL= video.getThumb(); - Picasso.get().load(imageURL).into(thumbnail); - thumbnail.setAdjustViewBounds(false); - thumbnail.setScaleType(ImageView.ScaleType.CENTER_CROP); + Handler uiHandler = new Handler(Looper.getMainLooper()); + uiHandler.post(() -> Picasso.get().load(imageURL).into(thumbnail)); + thumbnail.setColorFilter(Color.argb(70, 0, 0, 0)); // TITLE ---------------------------------- - TextView videoTitle = new TextView(getContext()); - videoTitle.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, getDp(100))); - int padding = getDp(20); - videoTitle.setPadding(padding, padding, padding, padding); + TextView videoTitle = card.findViewById(R.id.result_title); String title = video.getTitle(); - title = title.replace("&", "&").replace(""", "\""); - - - if(title.length() > 50){ + if(title.length() > 100){ title = title.substring(0, 40) + "..."; } - videoTitle.setText(title); - videoTitle.setTextSize(getSp(5)); - videoTitle.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)); - videoTitle.setTypeface(null, Typeface.BOLD); - videoTitle.setShadowLayer(2f, 4f, 4f, ContextCompat.getColor(requireContext(), R.color.black)); - // AUTHOR ---------------------------------- + // Bottom Info ---------------------------------- - TextView videoAuthor = new TextView(getContext()); - videoAuthor.setGravity(Gravity.BOTTOM); - videoAuthor.setLayoutParams(new RelativeLayout.LayoutParams(getDp(200), getDp(200))); - videoAuthor.setPadding(getDp(20), 0, 0, getDp(10)); - videoAuthor.setShadowLayer(1.5f, 4f, 4f, R.color.black); - - String author = video.getAuthor(); - - videoAuthor.setText(author); - videoAuthor.setTextSize(getSp(3)); - videoAuthor.setTextColor(ContextCompat.getColor(requireContext(), R.color.white)); - videoAuthor.setShadowLayer(2f, 4f, 4f, ContextCompat.getColor(requireContext(), R.color.black)); - videoAuthor.setTypeface(null, Typeface.BOLD); + TextView bottomInfo = card.findViewById(R.id.result_info_bottom); + String info = video.getAuthor() + " • " + video.getDuration(); + bottomInfo.setText(info); // BUTTONS ---------------------------------- String videoID = video.getVideoId(); + LinearLayout buttonLayout = card.findViewById(R.id.download_button_layout); - LinearLayout buttonLayout = new LinearLayout(requireContext()); - buttonLayout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); - buttonLayout.setGravity(Gravity.BOTTOM | Gravity.RIGHT); - - buttonLayout.setOrientation(LinearLayout.HORIZONTAL); - buttonLayout.setPadding(getDp(10), 0, getDp(10), getDp(10)); - - Button musicBtn = new Button(getContext()); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - getDp(48), getDp(48) - ); - params.setMargins(0,0, getDp(10), 0); - musicBtn.setLayoutParams(params); - //musicBtn.setIconSize(getDp(24)); - musicBtn.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_music)); + MaterialButton musicBtn = buttonLayout.findViewById(R.id.download_music); musicBtn.setTag(videoID + "##mp3"); - Button videoBtn = new Button(requireContext()); - videoBtn.setLayoutParams(params); - //videoBtn.setIconSize(getDp(24)); - videoBtn.setBackground(ContextCompat.getDrawable(requireContext(), R.drawable.ic_video)); + MaterialButton videoBtn = buttonLayout.findViewById(R.id.download_video); videoBtn.setTag(videoID + "##mp4"); - buttonLayout.addView(musicBtn); - buttonLayout.addView(videoBtn); - musicBtn.setOnClickListener(this); videoBtn.setOnClickListener(this); // PROGRESS BAR ---------------------------------------------------- - ProgressBar progressBar = new ProgressBar(requireContext(), null, android.R.attr.progressBarStyleHorizontal); + ProgressBar progressBar = card.findViewById(R.id.download_progress); progressBar.setVisibility(View.GONE); - RelativeLayout.LayoutParams progressParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, getDp(5)); - progressParams.setMargins(0, r.getLayoutParams().height - 70, 0 ,0); - progressBar.setLayoutParams(progressParams); - progressBar.setBackgroundTintMode(PorterDuff.Mode.SRC_IN); - progressBar.setScaleY(2); - - progressBar.setY(card.getY() - progressBar.getHeight() / 2); progressBar.setTag(videoID + "##progress"); - if(video.getDownloadedTime() != null){ - progressBar.setProgress(100); - progressBar.setVisibility(View.VISIBLE); + if(video.isAudioDownloaded() == 1){ + musicBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded)); + } + if(video.isVideoDownloaded() == 1){ + videoBtn.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded)); } - - // Adding all layouts to the card - - card.addView(thumbnail); - card.addView(videoTitle); - card.addView(videoAuthor); - card.addView(buttonLayout); - card.addView(progressBar); - card.setTag(videoID + "##card"); - - - r.addView(card); - - linearLayout.addView(r); + uiHandler.post(() -> linearLayout.addView(r)); } @Override public void onClick(View v) { //do what you want to do when button is clicked - String viewIdName = v.getTag().toString(); - Log.e(TAG, viewIdName); - if(viewIdName.contains("mp3") || viewIdName.contains("mp4")){ - String[] buttonData = viewIdName.split("##"); - downloadQueue = new LinkedList<>(); - if(buttonData[0].equals("ALL")){ - for (int i = 0; i < resultObjects.size(); i++){ - Video vid = findVideo(resultObjects.get(i).getVideoId()); + String viewIdName; + try{ + viewIdName = v.getTag().toString(); + }catch(Exception e){ + viewIdName = ""; + } + + if(!viewIdName.isEmpty()){ + if(viewIdName.contains("mp3") || viewIdName.contains("mp4")){ + Log.e(TAG, viewIdName); + String[] buttonData = viewIdName.split("##"); + downloadQueue = new LinkedList<>(); + if(buttonData[0].equals("ALL")){ + for (int i = 0; i < resultObjects.size(); i++){ + Video vid = findVideo(resultObjects.get(i).getVideoId()); + vid.setDownloadedType(buttonData[1]); + downloadQueue.add(vid); + } + + }else{ + Video vid = findVideo(buttonData[0]); vid.setDownloadedType(buttonData[1]); downloadQueue.add(vid); } - }else{ - Video vid = findVideo(buttonData[0]); - vid.setDownloadedType(buttonData[1]); - downloadQueue.add(vid); + startDownload(downloadQueue); + + } + }else{ + if (v.getId() == R.id.fab_home) { + scrollView.smoothScrollBy(0, 0); + scrollToTop(); } - - startDownload(downloadQueue); - } } @@ -556,7 +580,7 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ private void startDownload(Queue videos) { - Video video = null; + Video video; try{ video = videos.remove(); }catch(Exception e){ @@ -564,12 +588,12 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ } if (downloading) { - Toast.makeText(getContext(), "Nuk mund te filloj! Nje shkarkim tjeter është në punë!", Toast.LENGTH_LONG).show(); + Toast.makeText(context, R.string.download_already_started, Toast.LENGTH_LONG).show(); return; } if (!isStoragePermissionGranted()) { - Toast.makeText(getContext(), "Pranoje Lejen dhe provo përsëri!", Toast.LENGTH_LONG).show(); + Toast.makeText(context, R.string.try_again_after_permission, Toast.LENGTH_LONG).show(); return; } String id = video.getVideoId(); @@ -580,6 +604,8 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ Log.e(TAG, youtubeDLDir.getAbsolutePath()); + MaterialButton clickedButton = null; + if(type.equals("mp3")){ request.addOption("--embed-thumbnail"); request.addOption("--postprocessor-args", "-write_id3v1 1 -id3v2_version 3"); @@ -587,44 +613,74 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ request.addOption("--no-mtime"); request.addOption("-x"); request.addOption("--audio-format", "mp3"); + + clickedButton = linearLayout.findViewWithTag(id+"##mp3"); }else if(type.equals("mp4")){ request.addOption("-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best"); + + clickedButton = linearLayout.findViewWithTag(id+"##mp4"); } request.addOption("-o", youtubeDLDir.getAbsolutePath() + "/%(title)s.%(ext)s"); - View card = fragmentView.findViewWithTag(id+"##card"); - int[] cardPosition = new int[2]; - card.getLocationInWindow(cardPosition); - scrollView.post(() -> scrollView.scrollTo(cardPosition[0]-1000, cardPosition[1]-1000)); - progressBar = fragmentView.findViewWithTag(id+"##progress"); progressBar.setVisibility(View.VISIBLE); - showStart(); + //scroll to Card + View view = fragmentView.findViewWithTag(id+"##progress"); + view.getParent().requestChildFocus(view,view); + + showStart(video); downloading = true; Video theVideo = video; + MaterialButton theClickedButton = clickedButton; Disposable disposable = Observable.fromCallable(() -> YoutubeDL.getInstance().execute(request, callback)) .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(youtubeDLResponse -> { - progressBar.setProgress(100); - Toast.makeText(getContext(), "Shkarkimi i be me Sukses!", Toast.LENGTH_LONG).show(); + progressBar.setProgress(0); + progressBar.setVisibility(View.GONE); + + download_notification.setContentText(getString(R.string.download_success)) + .setSmallIcon(android.R.drawable.stat_sys_download_done) + .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download_done)) + .setProgress(0,0,false); + notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build()); + + if(theClickedButton != null){ + if(type.equals("mp3")){ + theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_music_downloaded)); + }else{ + theClickedButton.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_video_downloaded)); + } + } + addToHistory(theVideo, new Date()); + updateDownloadStatusOnResult(theVideo, type); downloading = false; // MEDIA SCAN - MediaScannerConnection.scanFile(requireContext(), new String[]{youtubeDLDir.getAbsolutePath()}, null, null); + MediaScannerConnection.scanFile(context, new String[]{youtubeDLDir.getAbsolutePath()}, null, null); // SCAN NEXT IN QUEUE startDownload(videos); }, e -> { - if(BuildConfig.DEBUG) Log.e(TAG, "Deshtim ne shkarkim! :(", e); - Toast.makeText(getContext(), "Deshtim ne shkarkim! :(", Toast.LENGTH_LONG).show(); + if(BuildConfig.DEBUG) Log.e(TAG, getString(R.string.failed_download), e); + Toast.makeText(context, R.string.failed_download, Toast.LENGTH_LONG).show(); + progressBar.setProgress(0); + progressBar.setVisibility(View.GONE); + + download_notification.setContentText(getString(R.string.failed_download)) + .setSmallIcon(android.R.drawable.stat_sys_download_done) + .setProgress(0,0,false); + notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build()); + downloading = false; + + // SCAN NEXT IN QUEUE + startDownload(videos); }); compositeDisposable.add(disposable); - } @@ -635,25 +691,25 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ String month = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault()); int year = cal.get(Calendar.YEAR); - DateFormat formatter = new SimpleDateFormat("HH:mm"); + DateFormat formatter = new SimpleDateFormat("HH:mm", Locale.getDefault()); formatter.setTimeZone(TimeZone.getTimeZone("UTC")); String time = formatter.format(date); String downloadedTime = day + " " + month + " " + year + " " + time; if(video != null){ - dbManager = new DBManager(requireContext()); + dbManager = new DBManager(context); try{ - String id = video.getVideoId(); - String title = video.getTitle(); - String author = video.getAuthor(); - String thumb = video.getThumb(); - String downloadedType = video.getDownloadedType(); video.setDownloadedTime(downloadedTime); + dbManager.addToHistory(video); + }catch(Exception ignored){} + } + } - dbManager.shtoVideoHistori(video); - dbManager.shkoKohenRezultatit(id, downloadedTime); - - + public void updateDownloadStatusOnResult(Video v, String type){ + if(v != null){ + dbManager = new DBManager(context); + try{ + dbManager.updateDownloadStatusOnResult(v.getVideoId(), type); }catch(Exception ignored){} } } @@ -667,7 +723,7 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ @NonNull private File getDownloadLocation(String type) { - SharedPreferences sharedPreferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE); + SharedPreferences sharedPreferences = context.getSharedPreferences("root_preferences", Activity.MODE_PRIVATE); String downloadsDir; if(type.equals("mp3")){ downloadsDir = sharedPreferences.getString("music_path", ""); @@ -675,21 +731,51 @@ public class HomeFragment extends Fragment implements View.OnClickListener{ downloadsDir = sharedPreferences.getString("video_path", ""); } File youtubeDLDir = new File(downloadsDir); - if (!youtubeDLDir.exists()) youtubeDLDir.mkdir(); + if (!youtubeDLDir.exists()){ + boolean isDirCreated = youtubeDLDir.mkdir(); + if(!isDirCreated){ + Toast.makeText(context, R.string.failed_making_directory, Toast.LENGTH_LONG).show(); + } + } return youtubeDLDir; } - private void showStart() { - Toast.makeText(getContext(), "Shkarkimi Filloi!", Toast.LENGTH_LONG).show(); + private void showStart(Video video) { progressBar.setProgress(0); + + //NOTIFICATION BUILDER + Intent intent = new Intent(context, MainActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + PendingIntent pendingIntent; + pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE); + + int PROGRESS_MAX = 100; + int PROGRESS_CURR = progressBar.getProgress(); + + download_notification = new NotificationCompat.Builder(context, App.DOWNLOAD_CHANNEL_ID) + .setSmallIcon(android.R.drawable.stat_sys_download) + .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), android.R.drawable.stat_sys_download)) + .setContentTitle(video.getTitle()) + .setContentText("") + .setPriority(NotificationCompat.PRIORITY_MAX) + .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .setCategory(NotificationCompat.CATEGORY_PROGRESS) + .setOnlyAlertOnce(true) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setProgress(PROGRESS_MAX, PROGRESS_CURR, false); + + notificationManager = NotificationManagerCompat.from(context); + notificationManager.notify(Integer.parseInt(App.DOWNLOAD_CHANNEL_ID), download_notification.build()); } public boolean isStoragePermissionGranted() { - if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) + if (ActivityCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { return true; } else { - ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); + ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); return false; } } diff --git a/app/src/main/java/com/deniscerri/ytdl/MainActivity.java b/app/src/main/java/com/deniscerri/ytdl/MainActivity.java index 8a29541b..16c1ddb3 100644 --- a/app/src/main/java/com/deniscerri/ytdl/MainActivity.java +++ b/app/src/main/java/com/deniscerri/ytdl/MainActivity.java @@ -1,19 +1,12 @@ package com.deniscerri.ytdl; import android.content.Intent; -import android.content.res.ColorStateList; -import android.content.res.Configuration; -import android.os.Build; import android.os.Bundle; import android.util.Log; - - import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import com.deniscerri.ytdl.databinding.ActivityMainBinding; - import io.reactivex.disposables.CompositeDisposable; @@ -34,30 +27,6 @@ public class MainActivity extends AppCompatActivity{ @Override protected void onCreate(Bundle savedInstanceState) { binding = ActivityMainBinding.inflate(getLayoutInflater()); - - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){ - this.setTheme(R.style.AppTheme); - - ColorStateList list = new ColorStateList( - new int[][]{ - new int[]{} - }, - new int[]{ - ContextCompat.getColor(getApplicationContext(), R.color.material_dynamic_primary50) - } - ); - binding.bottomNavigationView.setItemActiveIndicatorColor(list); - binding.bottomNavigationView.setItemRippleColor(list); - - int nightMode = getApplicationContext().getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK; - if(nightMode == Configuration.UI_MODE_NIGHT_YES){ - binding.bottomNavigationView.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.material_dynamic_neutral10)); - } - - getWindow().setStatusBarColor(ContextCompat.getColor(getApplicationContext(), R.color.material_dynamic_neutral10)); - } - super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); @@ -85,26 +54,28 @@ public class MainActivity extends AppCompatActivity{ if(lastFragment == homeFragment){ homeFragment.scrollToTop(); }else{ - this.setTitle("YTDLnis");; + this.setTitle(R.string.app_name);; } replaceFragment(homeFragment); break; case R.id.history: - this.setTitle("Historia");; + if(lastFragment == historyFragment){ + historyFragment.scrollToTop(); + }else { + this.setTitle(getString(R.string.history)); + } replaceFragment(historyFragment); break; case R.id.settings: - this.setTitle("Cilësimet");; + this.setTitle(getString(R.string.settings));; replaceFragment(settingsFragment); break; } return true; }); - Intent intent = getIntent(); handleIntents(intent); - } @Override diff --git a/app/src/main/java/com/deniscerri/ytdl/SettingsFragment.java b/app/src/main/java/com/deniscerri/ytdl/SettingsFragment.java index 7657383c..60adae22 100644 --- a/app/src/main/java/com/deniscerri/ytdl/SettingsFragment.java +++ b/app/src/main/java/com/deniscerri/ytdl/SettingsFragment.java @@ -1,25 +1,20 @@ package com.deniscerri.ytdl; -import android.app.ActionBar; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; import android.widget.Toast; -import androidx.annotation.Nullable; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceManager; - import com.yausername.youtubedl_android.YoutubeDL; - -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; - import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; @@ -36,15 +31,15 @@ public class SettingsFragment extends PreferenceFragmentCompat { public static boolean updating; private static final String TAG = "SettingsFragment"; - private CompositeDisposable compositeDisposable = new CompositeDisposable(); + private final CompositeDisposable compositeDisposable = new CompositeDisposable(); @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); - musicPath = (Preference) findPreference("music_path"); - videoPath = (Preference) findPreference("video_path"); + musicPath = findPreference("music_path"); + videoPath = findPreference("video_path"); SharedPreferences preferences = requireContext().getSharedPreferences("root_preferences", Activity.MODE_PRIVATE); String music_path = preferences.getString("music_path", ""); @@ -59,7 +54,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { musicPath.setSummary(music_path); musicPath.setOnPreferenceClickListener(preference -> { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - startActivityForResult(intent, MUSIC_PATH_CODE); + musicPathResultLauncher.launch(intent); return true; }); @@ -73,11 +68,11 @@ public class SettingsFragment extends PreferenceFragmentCompat { videoPath.setSummary(video_path); videoPath.setOnPreferenceClickListener(preference -> { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - startActivityForResult(intent, VIDEO_PATH_CODE); + videoPathResultLauncher.launch(intent); return true; }); - update = (Preference) findPreference("update"); + update = findPreference("update"); if(update != null){ update.setOnPreferenceClickListener(preference -> { updateYoutubeDL(); @@ -88,23 +83,27 @@ public class SettingsFragment extends PreferenceFragmentCompat { } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case MUSIC_PATH_CODE: - if(resultCode == Activity.RESULT_OK){ - changePath(musicPath, data, requestCode); + ActivityResultLauncher musicPathResultLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_OK) { + changePath(musicPath, result.getData(), MUSIC_PATH_CODE); } - break; - case VIDEO_PATH_CODE: - if(resultCode == Activity.RESULT_OK){ - changePath(videoPath, data, requestCode); - } - break; - } + } + }); - } + ActivityResultLauncher videoPathResultLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback() { + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_OK) { + changePath(videoPath, result.getData(), VIDEO_PATH_CODE); + } + } + }); public void changePath(Preference p, Intent data, int requestCode){ Log.e("TEST", data.toUri(0)); diff --git a/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java b/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java index dd705e26..968fd9c9 100644 --- a/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java +++ b/app/src/main/java/com/deniscerri/ytdl/api/YoutubeAPIManager.java @@ -1,41 +1,43 @@ package com.deniscerri.ytdl.api; import android.content.Context; -import android.content.res.AssetManager; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.text.Html; import android.util.Log; import android.widget.Toast; - +import androidx.annotation.Nullable; +import com.deniscerri.ytdl.database.DBManager; import com.deniscerri.ytdl.database.Video; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; -import java.sql.Array; import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Properties; - public class YoutubeAPIManager { private static final String TAG = "API MANAGER"; + private static String countryCODE = "US"; private ArrayList videos; private String key; - Properties properties = new Properties(); + private DBManager dbManager; public YoutubeAPIManager(Context context) { - AssetManager assetManager = context.getAssets(); + @Nullable ApplicationInfo applicationInfo; try{ - InputStream inputStream = assetManager.open("config.properties"); - properties.load(inputStream); - key = properties.getProperty("ytAPI"); + applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA); + key = applicationInfo.metaData.getString("ytAPIkey"); + dbManager = new DBManager(context); + + //get Locale + JSONObject country = genericRequest("https://ipwho.is/"); + try{ + countryCODE = country.getString("country_code"); + }catch(Exception ignored){} + }catch(Exception ignored){ Toast.makeText(context, "Couldn't find API Key for the request", Toast.LENGTH_SHORT).show(); } @@ -44,48 +46,90 @@ public class YoutubeAPIManager { } public ArrayList search(String query) throws JSONException{ - JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q="+query+"&maxResults=25®ionCode=US&key="+key); + //short data + JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/search?part=snippet&q="+query+"&maxResults=25®ionCode="+countryCODE+"&key="+key); JSONArray dataArray = res.getJSONArray("items"); + + //extra data + String url2 = "https://www.googleapis.com/youtube/v3/videos?id="; + //getting all ids, for the extra data request + for(int i = 0; i < dataArray.length(); i++){ + JSONObject element = dataArray.getJSONObject(i); + JSONObject snippet = element.getJSONObject("snippet"); + + if(element.getJSONObject("id").getString("kind").equals("youtube#video")){ + String videoID = element.getJSONObject("id").getString("videoId"); + url2 = url2 + videoID + ","; + snippet.put("videoID", videoID); + } + } + url2 = url2.substring(0, url2.length()-1) + "&part=contentDetails®ionCode="+countryCODE+"&key="+key; + JSONObject extra = genericRequest(url2); + int j = 0; for(int i = 0; i < dataArray.length(); i++){ JSONObject element = dataArray.getJSONObject(i); JSONObject snippet = element.getJSONObject("snippet"); if(element.getJSONObject("id").getString("kind").equals("youtube#video")){ - snippet.put("videoID", element.getJSONObject("id").getString("videoId")); - snippet = fixThumbnail(snippet); - Video v = createVideofromJSON(snippet); - v.setTitle(v.getTitle().replace("&", "&").replace(""", "\"")); - v.setAuthor(v.getAuthor().replace("&", "&").replace(""", "\"")); - videos.add(createVideofromJSON(snippet)); - } - } - return videos; - } + String duration = extra.getJSONArray("items").getJSONObject(j++).getJSONObject("contentDetails").getString("duration"); + duration = formatDuration(duration); + if(duration.equals("0:00")){ + continue; + } - public ArrayList getPlaylist(String id, String nextPageToken) throws JSONException{ - String url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken="+nextPageToken+"&maxResults=50&playlistId="+id+"&key="+key; - JSONObject res = genericRequest(url); - JSONArray dataArray = res.getJSONArray("items"); - - for(int i = 0; i < dataArray.length(); i++){ - JSONObject element = dataArray.getJSONObject(i); - JSONObject snippet = element.getJSONObject("snippet"); - if(snippet.getJSONObject("resourceId").getString("kind").equals("youtube#video")){ - snippet.put("videoID", snippet.getJSONObject("resourceId").getString("videoId")); + snippet.put("duration", duration); snippet = fixThumbnail(snippet); Video v = createVideofromJSON(snippet); if(v.getThumb().isEmpty()){ continue; } - - v.setIsPlaylistItem(1); - v.setTitle(v.getTitle().replace("&", "&").replace(""", "\"")); - v.setAuthor(v.getAuthor().replace("&", "&").replace(""", "\"")); - - videos.add(v); - + videos.add(createVideofromJSON(snippet)); } } + + + return videos; + } + + public ArrayList getPlaylist(String id, String nextPageToken) throws JSONException{ + String url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=snippet&pageToken="+nextPageToken+"&maxResults=50®ionCode="+countryCODE+"&playlistId="+id+"&key="+key; + //short data + JSONObject res = genericRequest(url); + JSONArray dataArray = res.getJSONArray("items"); + + //extra data + String url2 = "https://www.googleapis.com/youtube/v3/videos?id="; + //getting all ids, for the extra data request + for(int i = 0; i < dataArray.length(); i++){ + JSONObject element = dataArray.getJSONObject(i); + JSONObject snippet = element.getJSONObject("snippet"); + String videoID = snippet.getJSONObject("resourceId").getString("videoId"); + url2 = url2 + videoID + ","; + snippet.put("videoID", videoID); + } + url2 = url2.substring(0, url2.length()-1) + "&part=contentDetails®ionCode="+countryCODE+"&key="+key; + JSONObject extra = genericRequest(url2); + JSONArray extraArray = extra.getJSONArray("items"); + int j = 0; + int i; + for(i = 0; i < extraArray.length(); i++){ + JSONObject element = dataArray.getJSONObject(i); + JSONObject snippet = element.getJSONObject("snippet"); + String duration = extra.getJSONArray("items").getJSONObject(j).getJSONObject("contentDetails").getString("duration"); + duration = formatDuration(duration); + snippet.put("duration", duration); + snippet = fixThumbnail(snippet); + Video v = createVideofromJSON(snippet); + + if(v.getThumb().isEmpty()){ + continue; + }else{ + j++; + } + + v.setIsPlaylistItem(1); + videos.add(v); + } String next = res.optString("nextPageToken"); if(next != ""){ ArrayList nextPage = getPlaylist(id,next); @@ -96,16 +140,18 @@ public class YoutubeAPIManager { public Video getVideo(String id) throws JSONException { - JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet&id="+id+"&key="+key); + //short data + JSONObject res = genericRequest("https://youtube.googleapis.com/youtube/v3/videos?part=snippet,contentDetails&id="+id+"&key="+key); + String duration = res.getJSONArray("items").getJSONObject(0).getJSONObject("contentDetails").getString("duration"); + duration = formatDuration(duration); + res = res.getJSONArray("items").getJSONObject(0).getJSONObject("snippet"); + res.put("videoID", id); + res.put("duration", duration); res = fixThumbnail(res); Video v = createVideofromJSON(res); - v.setTitle(v.getTitle().replace("&", "&").replace(""", "\"")); - v.setAuthor(v.getAuthor().replace("&", "&").replace(""", "\"")); - - return v; } @@ -113,13 +159,25 @@ public class YoutubeAPIManager { Video video = null; try{ String id = obj.getString("videoID"); + String title = obj.getString("title"); + title = Html.fromHtml(title).toString(); + String author = obj.getString("channelTitle"); + author = Html.fromHtml(author).toString(); + + String duration = obj.getString("duration"); String thumb = obj.getString("thumb"); - video = new Video(id, title, author, thumb); - }catch(Exception ignored){} + int downloadedAudio = dbManager.checkDownloaded(id, "mp3"); + int downloadedVideo = dbManager.checkDownloaded(id, "mp4"); + int isPLaylist = 0; + + video = new Video(id, title, author, duration, thumb, downloadedAudio, downloadedVideo, isPLaylist); + }catch(Exception e){ + Log.e(TAG, e.toString()); + } return video; } @@ -187,5 +245,85 @@ public class YoutubeAPIManager { return o; } + public ArrayList getTrending(Context context) throws JSONException{ + String url = "https://www.googleapis.com/youtube/v3/videos?part=snippet&chart=mostPopular®ionCode="+countryCODE+"&maxResults=25&key="+key; + //short data + JSONObject res = genericRequest(url); + //extra data from the same videos + JSONObject contentDetails = genericRequest("https://www.googleapis.com/youtube/v3/videos?part=contentDetails&chart=mostPopular®ionCode="+countryCODE+"&maxResults=25&key="+key); + + JSONArray dataArray = res.getJSONArray("items"); + JSONArray extraDataArray = contentDetails.getJSONArray("items"); + for(int i = 0; i < dataArray.length(); i++){ + JSONObject element = dataArray.getJSONObject(i); + JSONObject snippet = element.getJSONObject("snippet"); + + String duration = extraDataArray.getJSONObject(i).getJSONObject("contentDetails").getString("duration"); + duration = formatDuration(duration); + + snippet.put("videoID", element.getString("id")); + snippet.put("duration", duration); + snippet = fixThumbnail(snippet); + + Video v = createVideofromJSON(snippet); + if(v.getThumb().isEmpty()){ + continue; + } + videos.add(v); + } + + return videos; + } + + public String formatDuration(String dur){ + String duration = ""; + String tmp = ""; + String hours = ""; + String minutes = ""; + String seconds = ""; + //removing P + dur = dur.substring(1); + for(int i = 0; i < dur.length(); i++){ + char c = dur.charAt(i); + if(!Character.isDigit(c)){ + int nr = Character.getNumericValue(c); + if(nr < 10){ + tmp = "0" + tmp; + } + if (c == 'D'){ + hours = String.valueOf(Integer.valueOf(tmp) * 24); + }else if(c == 'T'){ + continue; + }else if(c == 'H'){ + hours = tmp; + }else if(c == 'M'){ + minutes = tmp; + }else if(c == 'S'){ + seconds = tmp; + } + tmp = ""; + continue; + } + tmp = tmp + c; + } + + if(seconds.isEmpty()) seconds = "00"; + else if(Integer.parseInt(seconds) < 10) seconds = "0" + seconds; + if(minutes.isEmpty()){ + if(hours.isEmpty()){ + minutes = "0"; + }else{ + minutes = "00"; + } + } + duration = minutes + ":" + seconds; + + if(!hours.isEmpty()){ + duration = hours + ":" + minutes + ":" + seconds; + } + + return duration; + } + } diff --git a/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java b/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java index 96913859..c72f0fc6 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java +++ b/app/src/main/java/com/deniscerri/ytdl/database/DBManager.java @@ -5,21 +5,22 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; - -import java.lang.reflect.Array; import java.util.ArrayList; public class DBManager extends SQLiteOpenHelper { - public static final String db_name = "ytdlnis"; + public static final String db_name = "ytdlnis_db"; public static final int db_version = 1; - public static final String results_table_name = "videoResults"; - public static final String history_table_name = "videoHistory"; + public static final String results_table_name = "results"; + public static final String history_table_name = "history"; public static final String id = "id"; public static final String videoId = "videoId"; public static final String title = "title"; public static final String author = "author"; + public static final String duration = "duration"; public static final String thumb = "thumb"; + public static final String downloadedAudio = "downloadedAudio"; + public static final String downloadedVideo = "downloadedVideo"; public static final String type = "type"; public static final String time = "time"; public static final String isPlaylistItem = "isPlaylistItem"; @@ -36,9 +37,10 @@ public class DBManager extends SQLiteOpenHelper { + videoId + " TEXT," + title + " TEXT," + author + " TEXT," + + duration + " TEXT," + thumb + " TEXT," - + type + " TEXT," - + time + " TEXT," + + downloadedAudio + " INTEGER," + + downloadedVideo + " INTEGER," + isPlaylistItem + " INTENGER)"; sqLiteDatabase.execSQL(query); @@ -48,62 +50,27 @@ public class DBManager extends SQLiteOpenHelper { + videoId + " TEXT," + title + " TEXT," + author + " TEXT," + + duration + " TEXT," + thumb + " TEXT," + type + " TEXT," + time + " TEXT," - + isPlaylistItem + " INTEGER)"; - - sqLiteDatabase.execSQL(query); - } - - public void recreateResultsTable(SQLiteDatabase sqLiteDatabase){ - String query = "CREATE TABLE " + results_table_name + " (" - + id + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + videoId + " TEXT," - + title + " TEXT," - + author + " TEXT," - + thumb + " TEXT," - + type + " TEXT," - + time + " TEXT," - + isPlaylistItem + " BOOLEAN)"; + + isPlaylistItem + " INTENGER)"; sqLiteDatabase.execSQL(query); } public void clearHistory(){ SQLiteDatabase db = this.getWritableDatabase(); - db.execSQL("DROP TABLE IF EXISTS " + history_table_name); - - String query = "CREATE TABLE " + history_table_name + " (" - + id + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + videoId + " TEXT," - + title + " TEXT," - + author + " TEXT," - + thumb + " TEXT," - + type + " TEXT," - + time + " TEXT," - + isPlaylistItem + " BOOLEAN)"; - - db.execSQL(query); + db.execSQL("DELETE FROM " + history_table_name); + //remove downloaded statuses from results + db.execSQL("UPDATE "+results_table_name+ " SET downloadedAudio=0, downloadedVideo=0" + + " WHERE downloadedAudio=1 OR downloadedVideo=1"); } public void clearResults(){ SQLiteDatabase db = this.getWritableDatabase(); - db.execSQL("DROP TABLE IF EXISTS " + results_table_name); - - String query = "CREATE TABLE " + results_table_name + " (" - + id + " INTEGER PRIMARY KEY AUTOINCREMENT, " - + videoId + " TEXT," - + title + " TEXT," - + author + " TEXT," - + thumb + " TEXT," - + type + " TEXT," - + time + " TEXT," - + isPlaylistItem + " BOOLEAN)"; - - db.execSQL(query); - + db.execSQL("DELETE FROM " + results_table_name); } @Override @@ -113,9 +80,33 @@ public class DBManager extends SQLiteOpenHelper { onCreate(sqLiteDatabase); } - public ArrayList merrVideot(String table_name){ + + public ArrayList getResults(){ SQLiteDatabase db = this.getReadableDatabase(); - Cursor cursor = db.rawQuery("SELECT * FROM " + table_name, null); + Cursor cursor = db.rawQuery("SELECT * FROM " + results_table_name, null); + ArrayList list = new ArrayList<>(); + + if(cursor.moveToFirst()){ + do { + // on below line we are adding the data from cursor to our array list. + list.add(new Video(cursor.getString(1), + cursor.getString(2), + cursor.getString(3), + cursor.getString(4), + cursor.getString(5), + cursor.getInt(6), + cursor.getInt(7), + cursor.getInt(8))); + } while (cursor.moveToNext()); + } + + cursor.close(); + return list; + } + + public ArrayList getHistory(){ + SQLiteDatabase db = this.getReadableDatabase(); + Cursor cursor = db.rawQuery("SELECT * FROM " + history_table_name, null); ArrayList list = new ArrayList<>(); if(cursor.moveToFirst()){ @@ -127,7 +118,8 @@ public class DBManager extends SQLiteOpenHelper { cursor.getString(4), cursor.getString(5), cursor.getString(6), - cursor.getInt(7))); + cursor.getString(7), + cursor.getInt(8))); } while (cursor.moveToNext()); } @@ -135,57 +127,70 @@ public class DBManager extends SQLiteOpenHelper { return list; } - public ArrayList merrRezultatet(){ - return merrVideot(results_table_name); - } - public ArrayList merrHistorine(){ - return merrVideot(history_table_name); - } - - public Video shkoKohenRezultatit(String id, String downloadedTime){ - SQLiteDatabase db = this.getReadableDatabase(); - ContentValues vlerat = new ContentValues(); - vlerat.put(time, downloadedTime); - - db.update(results_table_name, vlerat, "videoId = ?", new String[]{id}); - return null; - } - - public void shtoVideoRezultat(ArrayList videot){ + public void addToResults(ArrayList videot){ SQLiteDatabase db = this.getWritableDatabase(); - ContentValues vlerat = new ContentValues(); + ContentValues values = new ContentValues(); - db.execSQL("DROP TABLE IF EXISTS " + results_table_name); - recreateResultsTable(db); + db.execSQL("DELETE FROM " + results_table_name); for(Video v : videot){ - vlerat.put(videoId, v.getVideoId()); - vlerat.put(title, v.getTitle()); - vlerat.put(author, v.getAuthor()); - vlerat.put(thumb, v.getThumb()); - vlerat.put(type, v.getDownloadedType()); - vlerat.put(time, v.getDownloadedTime()); - vlerat.put(isPlaylistItem, v.getIsPlaylistItem()); + values.put(videoId, v.getVideoId()); + values.put(title, v.getTitle()); + values.put(author, v.getAuthor()); + values.put(duration, v.getDuration()); + values.put(thumb, v.getThumb()); + values.put(downloadedAudio, v.isAudioDownloaded()); + values.put(downloadedVideo, v.isVideoDownloaded()); + values.put(isPlaylistItem, v.getIsPlaylistItem()); - db.insert(results_table_name, null, vlerat); + db.insert(results_table_name, null, values); } db.close(); } - public void shtoVideoHistori(Video v){ + public void addToHistory(Video v){ SQLiteDatabase db = this.getWritableDatabase(); - ContentValues vlerat = new ContentValues(); + ContentValues values = new ContentValues(); - vlerat.put(videoId, v.getVideoId()); - vlerat.put(title, v.getTitle()); - vlerat.put(author, v.getAuthor()); - vlerat.put(thumb, v.getThumb()); - vlerat.put(type, v.getDownloadedType()); - vlerat.put(time, v.getDownloadedTime()); + values.put(videoId, v.getVideoId()); + values.put(title, v.getTitle()); + values.put(author, v.getAuthor()); + values.put(duration, v.getDuration()); + values.put(thumb, v.getThumb()); + values.put(type, v.getDownloadedType()); + values.put(time, v.getDownloadedTime()); - db.insert(history_table_name, null, vlerat); + db.insert(history_table_name, null, values); db.close(); } + + public void updateDownloadStatusOnResult(String id, String type){ + SQLiteDatabase db = this.getReadableDatabase(); + ContentValues values = new ContentValues(); + + switch (type){ + case "mp3": + values.put(downloadedAudio, 1); + break; + case "mp4": + values.put(downloadedVideo, 1); + break; + } + + db.update(results_table_name, values, "videoId = ?", new String[]{id}); + } + + public int checkDownloaded(String id, String downloadType){ + SQLiteDatabase db = this.getWritableDatabase(); + Cursor cursor = db.rawQuery("SELECT * FROM " + history_table_name + " WHERE videoId='" + + id + "' AND type='"+downloadType + "' LIMIT 1", null); + + if(cursor.moveToFirst()){ + return 1; + } + return 0; + } + } diff --git a/app/src/main/java/com/deniscerri/ytdl/database/Video.java b/app/src/main/java/com/deniscerri/ytdl/database/Video.java index 6177759f..846f465c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/Video.java +++ b/app/src/main/java/com/deniscerri/ytdl/database/Video.java @@ -5,30 +5,41 @@ public class Video { private String videoId; private String title; private String author; + private String duration; private String thumb; private String downloadedType; + private int downloadedAudio; + private int downloadedVideo; private String downloadedTime; private int isPlaylistItem; - public Video(String videoId, String title, String author, String thumb, String downloadedType, String downloadedTime, int isPlaylistItem) { + + // RESULTS OBJECT + public Video(String videoId, String title, String author, String duration, String thumb, + int downloadedAudio, int downloadedVideo, int isPlaylistItem) { this.videoId = videoId; this.title = title; this.author = author; + this.duration = duration; + this.thumb = thumb; + this.downloadedVideo = downloadedVideo; + this.downloadedAudio = downloadedAudio; + this.isPlaylistItem = isPlaylistItem; + } + + //HISTORY OBJECT + public Video(String videoId, String title, String author, String duration, String thumb, + String downloadedType, String downloadedTime, int isPlaylistItem) { + this.videoId = videoId; + this.title = title; + this.author = author; + this.duration = duration; this.thumb = thumb; this.downloadedType = downloadedType; this.downloadedTime = downloadedTime; this.isPlaylistItem = isPlaylistItem; } - public Video(String videoId, String title, String author, String thumb) { - this.videoId = videoId; - this.title = title; - this.author = author; - this.thumb = thumb; - this.downloadedTime = null; - this.downloadedType = null; - this.isPlaylistItem = 0; - } public int getId() { return id; @@ -42,10 +53,6 @@ public class Video { return videoId; } - public void setVideoId(String videoId) { - this.videoId = videoId; - } - public String getTitle() { return title; } @@ -58,18 +65,14 @@ public class Video { return author; } - public void setAuthor(String author) { - this.author = author; + public String getDuration() { + return duration; } public String getThumb() { return thumb; } - public void setThumb(String thumb) { - this.thumb = thumb; - } - public String getDownloadedType() { return downloadedType; } @@ -93,4 +96,12 @@ public class Video { public void setIsPlaylistItem(int playlistItem) { this.isPlaylistItem = playlistItem; } + + public int isAudioDownloaded() { + return downloadedAudio; + } + + public int isVideoDownloaded() { + return downloadedVideo; + } } diff --git a/app/src/main/java/com/deniscerri/ytdl/placeholder/PlaceholderContent.java b/app/src/main/java/com/deniscerri/ytdl/placeholder/PlaceholderContent.java index 69181a90..3ad96446 100644 --- a/app/src/main/java/com/deniscerri/ytdl/placeholder/PlaceholderContent.java +++ b/app/src/main/java/com/deniscerri/ytdl/placeholder/PlaceholderContent.java @@ -9,7 +9,6 @@ import java.util.Map; * Helper class for providing sample content for user interfaces created by * Android template wizards. * - * TODO: Replace all uses of this class before publishing your app. */ public class PlaceholderContent { diff --git a/app/src/main/res/drawable/ic_go_up.xml b/app/src/main/res/drawable/ic_go_up.xml new file mode 100644 index 00000000..7e90fad9 --- /dev/null +++ b/app/src/main/res/drawable/ic_go_up.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_music_downloaded.xml b/app/src/main/res/drawable/ic_music_downloaded.xml new file mode 100644 index 00000000..1aef77e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_music_downloaded.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml index 89994f96..f9f8937f 100644 --- a/app/src/main/res/drawable/ic_search.xml +++ b/app/src/main/res/drawable/ic_search.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_video_downloaded.xml b/app/src/main/res/drawable/ic_video_downloaded.xml new file mode 100644 index 00000000..ccdd69bf --- /dev/null +++ b/app/src/main/res/drawable/ic_video_downloaded.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/round_corner.xml b/app/src/main/res/drawable/round_corner.xml deleted file mode 100644 index a951cb7a..00000000 --- a/app/src/main/res/drawable/round_corner.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0eb40255..fb68b8dc 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,7 +6,6 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - - + app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index e8b3b41b..e1f6d822 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -1,28 +1,120 @@ - + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/historycoordinator" + xmlns:android="http://schemas.android.com/apk/res/android"> - + android:orientation="vertical" + android:id="@+id/history_big_linearlayout"> - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + + + + + + + + + + + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 12e2cddf..ac536c13 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,23 +1,123 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/homecoordinator" + xmlns:android="http://schemas.android.com/apk/res/android"> + android:layout_height="match_parent" + android:id="@+id/home_big_linearlayout" + android:orientation="vertical" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 00000000..b5653fac --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/history_card.xml b/app/src/main/res/layout/history_card.xml new file mode 100644 index 00000000..e24be844 --- /dev/null +++ b/app/src/main/res/layout/history_card.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/history_card_shimmer.xml b/app/src/main/res/layout/history_card_shimmer.xml new file mode 100644 index 00000000..703d1db4 --- /dev/null +++ b/app/src/main/res/layout/history_card_shimmer.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/result_card.xml b/app/src/main/res/layout/result_card.xml new file mode 100644 index 00000000..1b9f2644 --- /dev/null +++ b/app/src/main/res/layout/result_card.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/result_card_shimmer.xml b/app/src/main/res/layout/result_card_shimmer.xml new file mode 100644 index 00000000..bdc27398 --- /dev/null +++ b/app/src/main/res/layout/result_card_shimmer.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index 32a358be..9d6d3778 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -1,13 +1,13 @@ - + - - - \ No newline at end of file diff --git a/app/src/main/res/menu/empty_menu.xml b/app/src/main/res/menu/empty_menu.xml new file mode 100644 index 00000000..9aca055c --- /dev/null +++ b/app/src/main/res/menu/empty_menu.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index c2d2c5ba..ad6f962d 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -7,7 +7,7 @@ android:id="@+id/search" android:title="@string/search" android:icon="@drawable/ic_search" - app:showAsAction="ifRoom|collapseActionView" + app:showAsAction="collapseActionView|always" app:actionViewClass="androidx.appcompat.widget.SearchView"/> + + Në Trend + Kërko + Pastro Rezultatet + Pastro Historinë + Arrite fundin e rezultateve + Shkarko të gjitha + Historia + Cilësime + Kryesore + Direktoritë + Direktoria e Muzikës + Direktoria e Videos + Përditësim + Kliko këtë nëse ke probleme me shkarkimin + Përditëso YTDL + Rreth + Kodi + Kërko nga YouTube + Nuk mund te filloj! Nje shkarkim tjeter është në punë! + Provo përsëri pasi ke pranuar lejen! + U shkarkua! + Dështim në shkarkim! :( + Shkarkimi Filloi! + Shkarkimet e Skedarëve + Njoftimi i cili tregon progresin e shkarkimeve te skedarëve audio dhe video + Dështim në krijimin e Direktorisë së re! :( + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 6cf9ed48..a082d2e0 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,12 +1,4 @@ - - Reply - Reply to all - - - reply - reply_all - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 53292edf..12afdcec 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,8 +1,7 @@ - #228DFF - #1B6CC3 - #03A9F4 #FFFFFF #000000 + #323232 + #A6A6A6 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ca514d86..a6b3daec 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,4 +1,2 @@ - - 16dp - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ef7566d..5bbabf94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,45 +1,29 @@ - YTDLnis - Streaming Example - Downloading Example - Custom Command Example - Update yt-dlp - Start Stream - Start Download - enter url here - Run Command - enter command here - enter a valid url - enter a valid command - Do the thing - Example command: --extract-audio --audio-format mp3 -o /sdcard/Download/youtubedl-android/%(title)s.%(ext)s https://www.youtube.com/watch?v=dQw4w9WgXcQ - Starting Download - Download Complete - Download Failed - Starting Command - Command Complete - Command Failed - Use Config File? - - Hello blank fragment + YTDLnis Search - - Messages - Sync - - - Your signature - Default reply action - - - Sync email periodically - Download incoming attachments - Automatically download attachments for incoming emails - - Only download attachments when manually requested - Shkarko - Shkruaj URL e YouTube - sdcard/Download - Fshi Historinë - Fshi rezultatet + Clear history + Clear Results + Trending + You reached the end of results + Download All + History + Settings + Home + Directories + Music Directory + Video Directory + Updating + Click this if you face download issues + Update YTDL + About + Source Code + Search from YouTube + Can\'t Start Download! Another one is not done yet! + Try again after accepting the permission! + Download Complete! + Failed Downloading! :( + Download Started! + File Downloads + Notification that shows the download progress of audio and video files + Failed to create the new Directory! :( diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f027a5d8..d363c92e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,40 +1,10 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index a68d45a7..22999d16 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,40 +1,41 @@ - + - + + + - + app:title="@string/music_directory" /> - - + app:title="@string/video_directory" /> - - + - + app:summary="@string/ytdl_update_hint" + app:title="@string/update_ytdl" /> - + + app:title="@string/source_code"> + /> + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9a5a07ae..15769daa 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,8 @@ buildscript { } def versionMajor = 1 -def versionMinor = 0 -def versionPatch = 1 +def versionMinor = 1 +def versionPatch = 0 def versionBuild = 0 // bump for dogfood builds, public betas, etc. ext { @@ -43,6 +43,7 @@ allprojects { repositories { google() jcenter() + maven { url 'https://jitpack.io' } } } diff --git a/common/.gitignore b/common/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle deleted file mode 100644 index b43027de..00000000 --- a/common/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' - -android { - compileSdkVersion 29 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 29 - versionCode project.versionCode - versionName project.versionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - - implementation "androidx.appcompat:appcompat:$appCompatVer" - testImplementation "junit:junit:$junitVer" - androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" - androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" - - implementation group: 'commons-io', name: 'commons-io', version: "$commonsIoVer" - implementation group: 'org.apache.commons', name: 'commons-compress', version: "$commonsCompressVer" -} \ No newline at end of file diff --git a/common/proguard-rules.pro b/common/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/common/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/src/androidTest/java/com/yausername/youtubedl_common/ExampleInstrumentedTest.java b/common/src/androidTest/java/com/yausername/youtubedl_common/ExampleInstrumentedTest.java deleted file mode 100644 index 4083abdb..00000000 --- a/common/src/androidTest/java/com/yausername/youtubedl_common/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.yausername.youtubedl_common; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.yausername.youtubedl_common.test", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml deleted file mode 100644 index 7eef7918..00000000 --- a/common/src/main/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - / - \ No newline at end of file diff --git a/common/src/main/java/com/yausername/youtubedl_common/SharedPrefsHelper.java b/common/src/main/java/com/yausername/youtubedl_common/SharedPrefsHelper.java deleted file mode 100644 index bdb13835..00000000 --- a/common/src/main/java/com/yausername/youtubedl_common/SharedPrefsHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.yausername.youtubedl_common; - -import android.content.Context; -import android.content.SharedPreferences; - -import androidx.annotation.Nullable; - -public class SharedPrefsHelper { - - private SharedPrefsHelper() { - } - - private static final String sharedPrefsName = "youtubedl-android"; - - public static void update(Context appContext, String key, String value) { - SharedPreferences pref = appContext.getSharedPreferences(sharedPrefsName, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = pref.edit(); - editor.putString(key, value); - editor.apply(); - } - - @Nullable - public static String get(Context appContext, String key) { - SharedPreferences pref = appContext.getSharedPreferences(sharedPrefsName, Context.MODE_PRIVATE); - return pref.getString(key, null); - } -} diff --git a/common/src/main/java/com/yausername/youtubedl_common/utils/ZipUtils.java b/common/src/main/java/com/yausername/youtubedl_common/utils/ZipUtils.java deleted file mode 100644 index 4b716f44..00000000 --- a/common/src/main/java/com/yausername/youtubedl_common/utils/ZipUtils.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.yausername.youtubedl_common.utils; - -import android.system.ErrnoException; -import android.system.Os; - -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; -import org.apache.commons.compress.archivers.zip.ZipFile; -import org.apache.commons.io.IOUtils; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; - - -public class ZipUtils { - - private ZipUtils() { - } - - public static void unzip(File sourceFile, File targetDirectory) throws IOException, ErrnoException, IllegalAccessException { - try (ZipFile zipFile = new ZipFile(sourceFile)) { - Enumeration entries = zipFile.getEntries(); - while (entries.hasMoreElements()) { - ZipArchiveEntry entry = entries.nextElement(); - File entryDestination = new File(targetDirectory, entry.getName()); - // prevent zipSlip - if (!entryDestination.getCanonicalPath().startsWith(targetDirectory.getCanonicalPath() + File.separator)) { - throw new IllegalAccessException("Entry is outside of the target dir: " + entry.getName()); - } - if (entry.isDirectory()) { - entryDestination.mkdirs(); - } else if (entry.isUnixSymlink()) { - try (InputStream in = zipFile.getInputStream(entry)) { - String symlink = IOUtils.toString(in, StandardCharsets.UTF_8); - Os.symlink(symlink, entryDestination.getAbsolutePath()); - } - } else { - entryDestination.getParentFile().mkdirs(); - try (InputStream in = zipFile.getInputStream(entry); - OutputStream out = new FileOutputStream(entryDestination)) { - IOUtils.copy(in, out); - } - } - } - } - } - - public static void unzip(InputStream inputStream, File targetDirectory) throws IOException, IllegalAccessException { - try (ZipArchiveInputStream zis = new ZipArchiveInputStream(new BufferedInputStream(inputStream))) { - ZipArchiveEntry entry = null; - while ((entry = zis.getNextZipEntry()) != null) { - File entryDestination = new File(targetDirectory, entry.getName()); - // prevent zipSlip - if (!entryDestination.getCanonicalPath().startsWith(targetDirectory.getCanonicalPath() + File.separator)) { - throw new IllegalAccessException("Entry is outside of the target dir: " + entry.getName()); - } - if (entry.isDirectory()) { - entryDestination.mkdirs(); - } else { - entryDestination.getParentFile().mkdirs(); - try (OutputStream out = new FileOutputStream(entryDestination)) { - IOUtils.copy(zis, out); - } - } - - } - } - } - -} diff --git a/common/src/test/java/com/yausername/youtubedl_common/ExampleUnitTest.java b/common/src/test/java/com/yausername/youtubedl_common/ExampleUnitTest.java deleted file mode 100644 index 460bac59..00000000 --- a/common/src/test/java/com/yausername/youtubedl_common/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_common; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/ffmpeg/.gitignore b/ffmpeg/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/ffmpeg/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/ffmpeg/build.gradle b/ffmpeg/build.gradle deleted file mode 100644 index b0f7b651..00000000 --- a/ffmpeg/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' - -android { - compileSdkVersion 29 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 29 - versionCode project.versionCode - versionName project.versionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation project(':common') - compileOnly project(':library') - - implementation "androidx.appcompat:appcompat:$appCompatVer" - testImplementation "junit:junit:$junitVer" - androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" - androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" - - implementation group: 'commons-io', name: 'commons-io', version: "$commonsIoVer" -} diff --git a/ffmpeg/proguard-rules.pro b/ffmpeg/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/ffmpeg/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/ffmpeg/src/androidTest/java/com/yausername/ffmpeg/ExampleInstrumentedTest.java b/ffmpeg/src/androidTest/java/com/yausername/ffmpeg/ExampleInstrumentedTest.java deleted file mode 100644 index 97bbde10..00000000 --- a/ffmpeg/src/androidTest/java/com/yausername/ffmpeg/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.yausername.ffmpeg; - -import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.yausername.ffmpeg.test", appContext.getPackageName()); - } -} diff --git a/ffmpeg/src/main/AndroidManifest.xml b/ffmpeg/src/main/AndroidManifest.xml deleted file mode 100644 index ce7251ce..00000000 --- a/ffmpeg/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/ffmpeg/src/main/java/com/yausername/ffmpeg/FFmpeg.java b/ffmpeg/src/main/java/com/yausername/ffmpeg/FFmpeg.java deleted file mode 100644 index 387b9940..00000000 --- a/ffmpeg/src/main/java/com/yausername/ffmpeg/FFmpeg.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.yausername.ffmpeg; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.yausername.youtubedl_android.YoutubeDLException; -import com.yausername.youtubedl_common.SharedPrefsHelper; -import com.yausername.youtubedl_common.utils.ZipUtils; - -import org.apache.commons.io.FileUtils; - -import java.io.File; - -public class FFmpeg { - - private static final FFmpeg INSTANCE = new FFmpeg(); - protected static final String baseName = "youtubedl-android"; - private static final String packagesRoot = "packages"; - private static final String ffmegDirName = "ffmpeg"; - private static final String ffmpegLibName = "libffmpeg.zip.so"; - private static final String ffmpegLibVersion = "ffmpegLibVersion"; - - private boolean initialized = false; - private File binDir; - - private FFmpeg(){ - } - - public static FFmpeg getInstance(){ - return INSTANCE; - } - - synchronized public void init(Context appContext) throws YoutubeDLException { - if (initialized) return; - - File baseDir = new File(appContext.getNoBackupFilesDir(), baseName); - if(!baseDir.exists()) baseDir.mkdir(); - - binDir = new File(appContext.getApplicationInfo().nativeLibraryDir); - - File packagesDir = new File(baseDir, packagesRoot); - File ffmpegDir = new File(packagesDir, ffmegDirName); - initFFmpeg(appContext, ffmpegDir); - - initialized = true; - } - - private void initFFmpeg(Context appContext, File ffmpegDir) throws YoutubeDLException { - File ffmpegLib = new File(binDir, ffmpegLibName); - // using size of lib as version - String ffmpegSize = String.valueOf(ffmpegLib.length()); - if (!ffmpegDir.exists() || shouldUpdateFFmpeg(appContext, ffmpegSize)) { - FileUtils.deleteQuietly(ffmpegDir); - ffmpegDir.mkdirs(); - try { - ZipUtils.unzip(ffmpegLib, ffmpegDir); - } catch (Exception e) { - FileUtils.deleteQuietly(ffmpegDir); - throw new YoutubeDLException("failed to initialize", e); - } - updateFFmpeg(appContext, ffmpegSize); - } - } - - private boolean shouldUpdateFFmpeg(@NonNull Context appContext, @NonNull String version) { - return !version.equals(SharedPrefsHelper.get(appContext, ffmpegLibVersion)); - } - - private void updateFFmpeg(@NonNull Context appContext, @NonNull String version) { - SharedPrefsHelper.update(appContext, ffmpegLibVersion, version); - } -} diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.bin.so deleted file mode 100644 index d2d00299..00000000 Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.zip.so deleted file mode 100644 index 34f2fa44..00000000 Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffprobe.bin.so deleted file mode 100644 index d037a186..00000000 Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.bin.so deleted file mode 100644 index b22b90b4..00000000 Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.zip.so deleted file mode 100644 index 31ac3186..00000000 Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffprobe.bin.so deleted file mode 100644 index 5a4b7255..00000000 Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/x86/libffmpeg.bin.so deleted file mode 100644 index 8b1ed06a..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/x86/libffmpeg.zip.so deleted file mode 100644 index fdfed683..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/x86/libffprobe.bin.so deleted file mode 100644 index a063e36a..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.bin.so deleted file mode 100644 index 9582d2db..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.zip.so deleted file mode 100644 index 75b685e5..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86_64/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/x86_64/libffprobe.bin.so deleted file mode 100644 index c3fba489..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86_64/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/res/values/strings.xml b/ffmpeg/src/main/res/values/strings.xml deleted file mode 100644 index 83474b56..00000000 --- a/ffmpeg/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - ffmpeg - diff --git a/ffmpeg/src/test/java/com/yausername/ffmpeg/ExampleUnitTest.java b/ffmpeg/src/test/java/com/yausername/ffmpeg/ExampleUnitTest.java deleted file mode 100644 index a25afddd..00000000 --- a/ffmpeg/src/test/java/com/yausername/ffmpeg/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.ffmpeg; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/library/.gitignore b/library/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/library/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/library/build.gradle b/library/build.gradle deleted file mode 100644 index c004dc10..00000000 --- a/library/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' - -android { - compileSdkVersion 29 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 29 - versionCode project.versionCode - versionName project.versionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation project(':common') - - implementation "androidx.appcompat:appcompat:$appCompatVer" - testImplementation "junit:junit:$junitVer" - androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" - androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" - - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "$jacksonVer" - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "$jacksonVer" - implementation group: 'commons-io', name: 'commons-io', version: "$commonsIoVer" -} diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/library/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/library/src/androidTest/java/com/yausername/youtubedl_android/ExampleInstrumentedTest.java b/library/src/androidTest/java/com/yausername/youtubedl_android/ExampleInstrumentedTest.java deleted file mode 100644 index e375bc76..00000000 --- a/library/src/androidTest/java/com/yausername/youtubedl_android/ExampleInstrumentedTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.content.Context; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertEquals; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.yausername.youtubedl_android.test", appContext.getPackageName()); - } -} diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml deleted file mode 100644 index e3ad65be..00000000 --- a/library/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/library/src/main/java/com/yausername/youtubedl_android/DownloadProgressCallback.java b/library/src/main/java/com/yausername/youtubedl_android/DownloadProgressCallback.java deleted file mode 100644 index 15b253c8..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/DownloadProgressCallback.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.yausername.youtubedl_android; - -public interface DownloadProgressCallback { - void onProgressUpdate(float progress, long etaInSeconds, String line); -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/StreamGobbler.java b/library/src/main/java/com/yausername/youtubedl_android/StreamGobbler.java deleted file mode 100644 index 9ca2fb6e..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/StreamGobbler.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.StandardCharsets; - -class StreamGobbler extends Thread { - - private final InputStream stream; - private final StringBuffer buffer; - - private static final String TAG = "StreamGobbler"; - - public StreamGobbler(StringBuffer buffer, InputStream stream) { - this.stream = stream; - this.buffer = buffer; - start(); - } - - public void run() { - try { - Reader in = new InputStreamReader(stream, StandardCharsets.UTF_8); - int nextChar; - while ((nextChar = in.read()) != -1) { - this.buffer.append((char) nextChar); - } - } catch (IOException e) { - if (BuildConfig.DEBUG) - Log.e(TAG, "failed to read stream", e); - } - } -} \ No newline at end of file diff --git a/library/src/main/java/com/yausername/youtubedl_android/StreamProcessExtractor.java b/library/src/main/java/com/yausername/youtubedl_android/StreamProcessExtractor.java deleted file mode 100644 index 9c93f9c5..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/StreamProcessExtractor.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class StreamProcessExtractor extends Thread { - private static final int GROUP_PERCENT = 1; - private static final int GROUP_MINUTES = 2; - private static final int GROUP_SECONDS = 3; - private final InputStream stream; - private final StringBuffer buffer; - private final DownloadProgressCallback callback; - - private final Pattern p = Pattern.compile("\\[download\\]\\s+(\\d+\\.\\d)% .* ETA (\\d+):(\\d+)"); - - private static final String TAG = "StreamProcessExtractor"; - - public StreamProcessExtractor(StringBuffer buffer, InputStream stream, DownloadProgressCallback callback) { - this.stream = stream; - this.buffer = buffer; - this.callback = callback; - this.start(); - } - - public void run() { - try { - Reader in = new InputStreamReader(stream, StandardCharsets.UTF_8); - StringBuilder currentLine = new StringBuilder(); - int nextChar; - while ((nextChar = in.read()) != -1) { - buffer.append((char) nextChar); - if (nextChar == '\r' && callback != null) { - processOutputLine(currentLine.toString()); - currentLine.setLength(0); - continue; - } - currentLine.append((char) nextChar); - } - } catch (IOException e) { - if (BuildConfig.DEBUG) - Log.e(TAG, "failed to read stream", e); - } - } - - private void processOutputLine(String line) { - Matcher m = p.matcher(line); - if (m.matches()) { - float progress = Float.parseFloat(m.group(GROUP_PERCENT)); - long eta = convertToSeconds(m.group(GROUP_MINUTES), m.group(GROUP_SECONDS)); - callback.onProgressUpdate(progress, eta, line); - } - } - - private int convertToSeconds(String minutes, String seconds) { - return Integer.parseInt(minutes) * 60 + Integer.parseInt(seconds); - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDL.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDL.java deleted file mode 100644 index 57312fdf..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDL.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.yausername.youtubedl_android.mapper.VideoInfo; -import com.yausername.youtubedl_common.SharedPrefsHelper; -import com.yausername.youtubedl_common.utils.ZipUtils; - -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -public class YoutubeDL { - - private static final YoutubeDL INSTANCE = new YoutubeDL(); - protected static final String baseName = "youtubedl-android"; - private static final String packagesRoot = "packages"; - private static final String pythonBinName = "libpython.bin.so"; - private static final String pythonLibName = "libpython.zip.so"; - private static final String pythonDirName = "python"; - private static final String ffmpegDirName = "ffmpeg"; - protected static final String youtubeDLDirName = "yt-dlp"; - private static final String youtubeDLBin = "__main__.py"; - protected static final String youtubeDLFile = "yt_dlp.zip"; - private static final String pythonLibVersion = "pythonLibVersion"; - - private boolean initialized = false; - private File pythonPath; - private File youtubeDLPath; - private File binDir; - - private String ENV_LD_LIBRARY_PATH; - private String ENV_SSL_CERT_FILE; - private String ENV_PYTHONHOME; - - protected static final ObjectMapper objectMapper = new ObjectMapper(); - - private YoutubeDL(){ - } - - public static YoutubeDL getInstance() { - return INSTANCE; - } - - synchronized public void init(Context appContext) throws YoutubeDLException { - if (initialized) return; - - File baseDir = new File(appContext.getNoBackupFilesDir(), baseName); - if(!baseDir.exists()) baseDir.mkdir(); - - File packagesDir = new File(baseDir, packagesRoot); - binDir = new File(appContext.getApplicationInfo().nativeLibraryDir); - pythonPath = new File(binDir, pythonBinName); - File pythonDir = new File(packagesDir, pythonDirName); - File ffmpegDir = new File(packagesDir, ffmpegDirName); - - File youtubeDLDir = new File(baseDir, youtubeDLDirName); - youtubeDLPath = new File(youtubeDLDir, youtubeDLBin); - - ENV_LD_LIBRARY_PATH = pythonDir.getAbsolutePath() + "/usr/lib" + ":" + ffmpegDir.getAbsolutePath() + "/usr/lib"; - ENV_SSL_CERT_FILE = pythonDir.getAbsolutePath() + "/usr/etc/tls/cert.pem"; - ENV_PYTHONHOME = pythonDir.getAbsolutePath() + "/usr"; - - initPython(appContext, pythonDir); - initYoutubeDL(appContext, youtubeDLDir); - - initialized = true; - } - - protected void initYoutubeDL(Context appContext, File youtubeDLDir) throws YoutubeDLException { - if (!youtubeDLDir.exists()) { - youtubeDLDir.mkdirs(); - try { - ZipUtils.unzip(appContext.getResources().openRawResource(R.raw.yt_dlp), youtubeDLDir); - } catch (Exception e) { - FileUtils.deleteQuietly(youtubeDLDir); - throw new YoutubeDLException("failed to initialize", e); - } - } - } - - protected void initPython(Context appContext, File pythonDir) throws YoutubeDLException { - File pythonLib = new File(binDir, pythonLibName); - // using size of lib as version - String pythonSize = String.valueOf(pythonLib.length()); - if (!pythonDir.exists() || shouldUpdatePython(appContext, pythonSize)) { - FileUtils.deleteQuietly(pythonDir); - pythonDir.mkdirs(); - try { - ZipUtils.unzip(pythonLib, pythonDir); - } catch (Exception e) { - FileUtils.deleteQuietly(pythonDir); - throw new YoutubeDLException("failed to initialize", e); - } - updatePython(appContext, pythonSize); - } - } - - private boolean shouldUpdatePython(@NonNull Context appContext, @NonNull String version) { - return !version.equals(SharedPrefsHelper.get(appContext, pythonLibVersion)); - } - - private void updatePython(@NonNull Context appContext, @NonNull String version) { - SharedPrefsHelper.update(appContext, pythonLibVersion, version); - } - - private void assertInit() { - if (!initialized) throw new IllegalStateException("instance not initialized"); - } - - public VideoInfo getInfo(String url) throws YoutubeDLException, InterruptedException { - YoutubeDLRequest request = new YoutubeDLRequest(url); - return getInfo(request); - } - - @NonNull - public VideoInfo getInfo(YoutubeDLRequest request) throws YoutubeDLException, InterruptedException { - request.addOption("--dump-json"); - YoutubeDLResponse response = execute(request, null); - - VideoInfo videoInfo; - try { - videoInfo = objectMapper.readValue(response.getOut(), VideoInfo.class); - } catch (IOException e) { - throw new YoutubeDLException("Unable to parse video information", e); - } - - if(videoInfo == null){ - throw new YoutubeDLException("Failed to fetch video information"); - } - - return videoInfo; - } - - public YoutubeDLResponse execute(YoutubeDLRequest request) throws YoutubeDLException, InterruptedException { - return execute(request, null); - } - - public YoutubeDLResponse execute(YoutubeDLRequest request, @Nullable DownloadProgressCallback callback) throws YoutubeDLException, InterruptedException { - assertInit(); - - // disable caching unless explicitly requested - if(request.getOption("--cache-dir") == null){ - request.addOption("--no-cache-dir"); - } - - YoutubeDLResponse youtubeDLResponse; - Process process; - int exitCode; - StringBuffer outBuffer = new StringBuffer(); //stdout - StringBuffer errBuffer = new StringBuffer(); //stderr - long startTime = System.currentTimeMillis(); - - List args = request.buildCommand(); - List command = new ArrayList<>(); - command.addAll(Arrays.asList(pythonPath.getAbsolutePath(), youtubeDLPath.getAbsolutePath())); - command.addAll(args); - - ProcessBuilder processBuilder = new ProcessBuilder(command); - Map env = processBuilder.environment(); - env.put("LD_LIBRARY_PATH", ENV_LD_LIBRARY_PATH); - env.put("SSL_CERT_FILE", ENV_SSL_CERT_FILE); - env.put("PATH", System.getenv("PATH") + ":" + binDir.getAbsolutePath()); - env.put("PYTHONHOME", ENV_PYTHONHOME); - - try { - process = processBuilder.start(); - } catch (IOException e) { - throw new YoutubeDLException(e); - } - - InputStream outStream = process.getInputStream(); - InputStream errStream = process.getErrorStream(); - - StreamProcessExtractor stdOutProcessor = new StreamProcessExtractor(outBuffer, outStream, callback); - StreamGobbler stdErrProcessor = new StreamGobbler(errBuffer, errStream); - - try { - stdOutProcessor.join(); - stdErrProcessor.join(); - exitCode = process.waitFor(); - } catch (InterruptedException e) { - process.destroy(); - throw e; - } - - String out = outBuffer.toString(); - String err = errBuffer.toString(); - - if (exitCode > 0) { - throw new YoutubeDLException(err); - } - - long elapsedTime = System.currentTimeMillis() - startTime; - - youtubeDLResponse = new YoutubeDLResponse(command, exitCode, elapsedTime, out, err); - - return youtubeDLResponse; - } - - synchronized public UpdateStatus updateYoutubeDL(Context appContext) throws YoutubeDLException { - assertInit(); - try { - return YoutubeDLUpdater.update(appContext); - } catch (IOException e) { - throw new YoutubeDLException("failed to update youtube-dl", e); - } - } - - @Nullable - public String version(Context appContext) { - return YoutubeDLUpdater.version(appContext); - } - - public enum UpdateStatus { - DONE, ALREADY_UP_TO_DATE; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLException.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLException.java deleted file mode 100644 index 45c45d32..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLException.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_android; - -public class YoutubeDLException extends Exception { - - public YoutubeDLException(String message) { - super(message); - } - - public YoutubeDLException(String message, Throwable e) { - super(message, e); - } - - public YoutubeDLException(Throwable e) { - super(e); - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLOptions.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLOptions.java deleted file mode 100644 index e4f8b3c6..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLOptions.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.yausername.youtubedl_android; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class YoutubeDLOptions { - - private Map options = new LinkedHashMap<>(); - - public YoutubeDLOptions addOption(@NonNull String key, @NonNull String value){ - options.put(key, value); - return this; - } - - public YoutubeDLOptions addOption(@NonNull String key, @NonNull Number value){ - options.put(key, value.toString()); - return this; - } - - public YoutubeDLOptions addOption(String key){ - options.put(key, null); - return this; - } - - public Object getOption(String key){ - return options.get(key); - } - - public List buildOptions(){ - List optionsList = new ArrayList<>(); - for (Map.Entry entry : options.entrySet()) { - String name = entry.getKey(); - String value = entry.getValue(); - optionsList.add(name); - if(null != value) optionsList.add(value); - } - return optionsList; - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLRequest.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLRequest.java deleted file mode 100644 index 06f6f52f..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.yausername.youtubedl_android; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class YoutubeDLRequest { - - private List urls; - private YoutubeDLOptions options = new YoutubeDLOptions(); - - public YoutubeDLRequest(String url) { - this.urls = Arrays.asList(url); - } - - public YoutubeDLRequest(@NonNull List urls) { - this.urls = urls; - } - - public YoutubeDLRequest addOption(@NonNull String key, @NonNull String value){ - options.addOption(key, value); - return this; - } - - public YoutubeDLRequest addOption(@NonNull String key, @NonNull Number value){ - options.addOption(key, value); - return this; - } - - public YoutubeDLRequest addOption(String key){ - options.addOption(key); - return this; - } - - public Object getOption(String key){ - return options.getOption(key); - } - - public List buildCommand(){ - List command = new ArrayList<>(); - command.addAll(options.buildOptions()); - command.addAll(urls); - return command; - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLResponse.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLResponse.java deleted file mode 100644 index 4b696b06..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLResponse.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.yausername.youtubedl_android; - -import java.util.List; - -public class YoutubeDLResponse { - - private List command; - private int exitCode; - private String out; - private String err; - private long elapsedTime; - - public YoutubeDLResponse(List command, int exitCode, long elapsedTime, String out, String err) { - this.command = command; - this.elapsedTime = elapsedTime; - this.exitCode = exitCode; - this.out = out; - this.err = err; - } - - public List getCommand() { - return command; - } - - public int getExitCode() { - return exitCode; - } - - public String getOut() { - return out; - } - - public String getErr() { - return err; - } - - public long getElapsedTime() { - return elapsedTime; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLUpdater.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLUpdater.java deleted file mode 100644 index 74b79a15..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLUpdater.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.yausername.youtubedl_android.YoutubeDL.UpdateStatus; -import com.yausername.youtubedl_common.SharedPrefsHelper; -import com.yausername.youtubedl_common.utils.ZipUtils; - -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.net.URL; - -class YoutubeDLUpdater { - - private YoutubeDLUpdater() { - } - - private static final String releasesUrl = "https://api.github.com/repos/xibr/ytdlp-lazy/releases/latest"; - private static final String youtubeDLVersionKey = "youtubeDLVersion"; - - static UpdateStatus update(Context appContext) throws IOException, YoutubeDLException { - JsonNode json = checkForUpdate(appContext); - if(null == json) return UpdateStatus.ALREADY_UP_TO_DATE; - - String downloadUrl = getDownloadUrl(json); - File file = download(appContext, downloadUrl); - - File youtubeDLDir = null; - try { - youtubeDLDir = getYoutubeDLDir(appContext); - //purge older version - FileUtils.deleteDirectory(youtubeDLDir); - //install newer version - youtubeDLDir.mkdirs(); - ZipUtils.unzip(file, youtubeDLDir); - } catch (Exception e) { - //if something went wrong restore default version - FileUtils.deleteQuietly(youtubeDLDir); - YoutubeDL.getInstance().initYoutubeDL(appContext, youtubeDLDir); - throw new YoutubeDLException(e); - } finally { - file.delete(); - } - - updateSharedPrefs(appContext, getTag(json)); - return UpdateStatus.DONE; - } - - private static void updateSharedPrefs(Context appContext, String tag) { - SharedPrefsHelper.update(appContext, youtubeDLVersionKey, tag); - } - - private static JsonNode checkForUpdate(Context appContext) throws IOException { - URL url = new URL(releasesUrl); - JsonNode json = YoutubeDL.objectMapper.readTree(url); - String newVersion = getTag(json); - String oldVersion = SharedPrefsHelper.get(appContext, youtubeDLVersionKey); - if(newVersion.equals(oldVersion)){ - return null; - } - return json; - } - - private static String getTag(JsonNode json){ - return json.get("tag_name").asText(); - } - - @NonNull - private static String getDownloadUrl(@NonNull JsonNode json) throws YoutubeDLException { - ArrayNode assets = (ArrayNode) json.get("assets"); - String downloadUrl = ""; - for (JsonNode asset : assets) { - if (YoutubeDL.youtubeDLFile.equals(asset.get("name").asText())) { - downloadUrl = asset.get("browser_download_url").asText(); - break; - } - } - if (downloadUrl.isEmpty()) throw new YoutubeDLException("unable to get download url"); - return downloadUrl; - } - - @NonNull - private static File download(Context appContext, String url) throws IOException { - URL downloadUrl = new URL(url); - File file = File.createTempFile("yt_dlp", "zip", appContext.getCacheDir()); - FileUtils.copyURLToFile(downloadUrl, file, 5000, 10000); - return file; - } - - @NonNull - private static File getYoutubeDLDir(Context appContext) { - File baseDir = new File(appContext.getNoBackupFilesDir(), YoutubeDL.baseName); - return new File(baseDir, YoutubeDL.youtubeDLDirName); - } - - @Nullable - static String version(Context appContext) { - return SharedPrefsHelper.get(appContext, youtubeDLVersionKey); - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoFormat.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoFormat.java deleted file mode 100644 index d498334a..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoFormat.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Map; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoFormat { - - private int asr; - private int tbr; - private int abr; - private String format; - @JsonProperty("format_id") - private String formatId; - @JsonProperty("format_note") - private String formatNote; - private String ext; - private int preference; - private String vcodec; - private String acodec; - private int width; - private int height; - private long filesize; - private int fps; - private String url; - @JsonProperty("manifest_url") - private String manifestUrl; - @JsonProperty("http_headers") - private Map httpHeaders; - - public int getAsr() { - return asr; - } - - public int getTbr() { - return tbr; - } - - public int getAbr() { - return abr; - } - - public String getFormat() { - return format; - } - - public String getFormatId() { - return formatId; - } - - public String getFormatNote() { - return formatNote; - } - - public String getExt() { - return ext; - } - - public int getPreference() { - return preference; - } - - public String getVcodec() { - return vcodec; - } - - public String getAcodec() { - return acodec; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public long getFilesize() { - return filesize; - } - - public int getFps() { - return fps; - } - - public String getUrl() { - return url; - } - - public String getManifestUrl() { - return manifestUrl; - } - - public Map getHttpHeaders() { - return httpHeaders; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoInfo.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoInfo.java deleted file mode 100644 index 079e9a5b..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoInfo.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.ArrayList; -import java.util.Map; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoInfo { - - private String id; - private String fulltitle; - private String title; - @JsonProperty("upload_date") - private String uploadDate; - @JsonProperty("display_id") - private String displayId; - private int duration; - private String description; - private String thumbnail; - private String license; - - @JsonProperty("view_count") - private String viewCount; - @JsonProperty("like_count") - private String likeCount; - @JsonProperty("dislike_count") - private String dislikeCount; - @JsonProperty("repost_count") - private String repostCount; - @JsonProperty("average_rating") - private String averageRating; - - - @JsonProperty("uploader_id") - private String uploaderId; - private String uploader; - - @JsonProperty("player_url") - private String playerUrl; - @JsonProperty("webpage_url") - private String webpageUrl; - @JsonProperty("webpage_url_basename") - private String webpageUrlBasename; - - private String resolution; - private int width; - private int height; - private String format; - @JsonProperty("format_id") - private String formatId; - private String ext; - - @JsonProperty("http_headers") - private Map httpHeaders; - private ArrayList categories; - private ArrayList tags; - @JsonProperty("requested_formats") - private ArrayList requestedFormats; - private ArrayList formats; - private ArrayList thumbnails; - //private ArrayList subtitles; - @JsonProperty("manifest_url") - private String manifestUrl; - private String url; - - public String getId() { - return id; - } - - public String getFulltitle() { - return fulltitle; - } - - public String getTitle() { - return title; - } - - public String getUploadDate() { - return uploadDate; - } - - public String getDisplayId() { - return displayId; - } - - public int getDuration() { - return duration; - } - - public String getDescription() { - return description; - } - - public String getThumbnail() { - return thumbnail; - } - - public String getLicense() { - return license; - } - - public String getViewCount() { - return viewCount; - } - - public String getLikeCount() { - return likeCount; - } - - public String getDislikeCount() { - return dislikeCount; - } - - public String getRepostCount() { - return repostCount; - } - - public String getAverageRating() { - return averageRating; - } - - public String getUploaderId() { - return uploaderId; - } - - public String getUploader() { - return uploader; - } - - public String getPlayerUrl() { - return playerUrl; - } - - public String getWebpageUrl() { - return webpageUrl; - } - - public String getWebpageUrlBasename() { - return webpageUrlBasename; - } - - public String getResolution() { - return resolution; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public String getFormat() { - return format; - } - - public String getFormatId() { - return formatId; - } - - public String getExt() { - return ext; - } - - public Map getHttpHeaders() { - return httpHeaders; - } - - public ArrayList getCategories() { - return categories; - } - - public ArrayList getTags() { - return tags; - } - - public ArrayList getRequestedFormats() { - return requestedFormats; - } - - public ArrayList getFormats() { - return formats; - } - - public ArrayList getThumbnails() { - return thumbnails; - } - - public String getManifestUrl() { - return manifestUrl; - } - - public String getUrl() { - return url; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoSubtitle.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoSubtitle.java deleted file mode 100644 index 7f419197..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoSubtitle.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoSubtitle { - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoThumbnail.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoThumbnail.java deleted file mode 100644 index c986f588..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoThumbnail.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoThumbnail { - private String url; - private String id; - - public String getUrl() { - return url; - } - - public String getId() { - return id; - } -} diff --git a/library/src/main/jniLibs/arm64-v8a/libpython.bin.so b/library/src/main/jniLibs/arm64-v8a/libpython.bin.so deleted file mode 100644 index 3c94297e..00000000 Binary files a/library/src/main/jniLibs/arm64-v8a/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/arm64-v8a/libpython.zip.so b/library/src/main/jniLibs/arm64-v8a/libpython.zip.so deleted file mode 100644 index e80cac28..00000000 Binary files a/library/src/main/jniLibs/arm64-v8a/libpython.zip.so and /dev/null differ diff --git a/library/src/main/jniLibs/armeabi-v7a/libpython.bin.so b/library/src/main/jniLibs/armeabi-v7a/libpython.bin.so deleted file mode 100644 index c5572e6e..00000000 Binary files a/library/src/main/jniLibs/armeabi-v7a/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/armeabi-v7a/libpython.zip.so b/library/src/main/jniLibs/armeabi-v7a/libpython.zip.so deleted file mode 100644 index e7749dec..00000000 Binary files a/library/src/main/jniLibs/armeabi-v7a/libpython.zip.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86/libpython.bin.so b/library/src/main/jniLibs/x86/libpython.bin.so deleted file mode 100644 index 6392d911..00000000 Binary files a/library/src/main/jniLibs/x86/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86/libpython.zip.so b/library/src/main/jniLibs/x86/libpython.zip.so deleted file mode 100644 index 50954c32..00000000 Binary files a/library/src/main/jniLibs/x86/libpython.zip.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86_64/libpython.bin.so b/library/src/main/jniLibs/x86_64/libpython.bin.so deleted file mode 100644 index 4dfc37a1..00000000 Binary files a/library/src/main/jniLibs/x86_64/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86_64/libpython.zip.so b/library/src/main/jniLibs/x86_64/libpython.zip.so deleted file mode 100644 index 0e73125f..00000000 Binary files a/library/src/main/jniLibs/x86_64/libpython.zip.so and /dev/null differ diff --git a/library/src/main/res/raw/yt_dlp.zip b/library/src/main/res/raw/yt_dlp.zip deleted file mode 100644 index ec789d2f..00000000 Binary files a/library/src/main/res/raw/yt_dlp.zip and /dev/null differ diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml deleted file mode 100644 index 1bb1db41..00000000 --- a/library/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - youtubedl-android - diff --git a/library/src/test/java/com/yausername/youtubedl_android/ExampleUnitTest.java b/library/src/test/java/com/yausername/youtubedl_android/ExampleUnitTest.java deleted file mode 100644 index f77fe036..00000000 --- a/library/src/test/java/com/yausername/youtubedl_android/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_android; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file
- * TODO: Replace all uses of this class before publishing your app. */ public class PlaceholderContent { diff --git a/app/src/main/res/drawable/ic_go_up.xml b/app/src/main/res/drawable/ic_go_up.xml new file mode 100644 index 00000000..7e90fad9 --- /dev/null +++ b/app/src/main/res/drawable/ic_go_up.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_music_downloaded.xml b/app/src/main/res/drawable/ic_music_downloaded.xml new file mode 100644 index 00000000..1aef77e7 --- /dev/null +++ b/app/src/main/res/drawable/ic_music_downloaded.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml index 89994f96..f9f8937f 100644 --- a/app/src/main/res/drawable/ic_search.xml +++ b/app/src/main/res/drawable/ic_search.xml @@ -1,5 +1,5 @@ - + diff --git a/app/src/main/res/drawable/ic_video_downloaded.xml b/app/src/main/res/drawable/ic_video_downloaded.xml new file mode 100644 index 00000000..ccdd69bf --- /dev/null +++ b/app/src/main/res/drawable/ic_video_downloaded.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/round_corner.xml b/app/src/main/res/drawable/round_corner.xml deleted file mode 100644 index a951cb7a..00000000 --- a/app/src/main/res/drawable/round_corner.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0eb40255..fb68b8dc 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,7 +6,6 @@ android:layout_height="match_parent" tools:context=".MainActivity"> - - + app:layout_constraintTop_toTopOf="parent" /> + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_history.xml b/app/src/main/res/layout/fragment_history.xml index e8b3b41b..e1f6d822 100644 --- a/app/src/main/res/layout/fragment_history.xml +++ b/app/src/main/res/layout/fragment_history.xml @@ -1,28 +1,120 @@ - + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/historycoordinator" + xmlns:android="http://schemas.android.com/apk/res/android"> - + android:orientation="vertical" + android:id="@+id/history_big_linearlayout"> - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + + + + + + + + + + + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 12e2cddf..ac536c13 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,23 +1,123 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/homecoordinator" + xmlns:android="http://schemas.android.com/apk/res/android"> + android:layout_height="match_parent" + android:id="@+id/home_big_linearlayout" + android:orientation="vertical" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml new file mode 100644 index 00000000..b5653fac --- /dev/null +++ b/app/src/main/res/layout/fragment_settings.xml @@ -0,0 +1,26 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/history_card.xml b/app/src/main/res/layout/history_card.xml new file mode 100644 index 00000000..e24be844 --- /dev/null +++ b/app/src/main/res/layout/history_card.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/history_card_shimmer.xml b/app/src/main/res/layout/history_card_shimmer.xml new file mode 100644 index 00000000..703d1db4 --- /dev/null +++ b/app/src/main/res/layout/history_card_shimmer.xml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/result_card.xml b/app/src/main/res/layout/result_card.xml new file mode 100644 index 00000000..1b9f2644 --- /dev/null +++ b/app/src/main/res/layout/result_card.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/result_card_shimmer.xml b/app/src/main/res/layout/result_card_shimmer.xml new file mode 100644 index 00000000..bdc27398 --- /dev/null +++ b/app/src/main/res/layout/result_card_shimmer.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml index 32a358be..9d6d3778 100644 --- a/app/src/main/res/menu/bottom_nav_menu.xml +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -1,13 +1,13 @@ - + - - - \ No newline at end of file diff --git a/app/src/main/res/menu/empty_menu.xml b/app/src/main/res/menu/empty_menu.xml new file mode 100644 index 00000000..9aca055c --- /dev/null +++ b/app/src/main/res/menu/empty_menu.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml index c2d2c5ba..ad6f962d 100644 --- a/app/src/main/res/menu/main_menu.xml +++ b/app/src/main/res/menu/main_menu.xml @@ -7,7 +7,7 @@ android:id="@+id/search" android:title="@string/search" android:icon="@drawable/ic_search" - app:showAsAction="ifRoom|collapseActionView" + app:showAsAction="collapseActionView|always" app:actionViewClass="androidx.appcompat.widget.SearchView"/> + + Në Trend + Kërko + Pastro Rezultatet + Pastro Historinë + Arrite fundin e rezultateve + Shkarko të gjitha + Historia + Cilësime + Kryesore + Direktoritë + Direktoria e Muzikës + Direktoria e Videos + Përditësim + Kliko këtë nëse ke probleme me shkarkimin + Përditëso YTDL + Rreth + Kodi + Kërko nga YouTube + Nuk mund te filloj! Nje shkarkim tjeter është në punë! + Provo përsëri pasi ke pranuar lejen! + U shkarkua! + Dështim në shkarkim! :( + Shkarkimi Filloi! + Shkarkimet e Skedarëve + Njoftimi i cili tregon progresin e shkarkimeve te skedarëve audio dhe video + Dështim në krijimin e Direktorisë së re! :( + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 6cf9ed48..a082d2e0 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,12 +1,4 @@ - - Reply - Reply to all - - - reply - reply_all - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 53292edf..12afdcec 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,8 +1,7 @@ - #228DFF - #1B6CC3 - #03A9F4 #FFFFFF #000000 + #323232 + #A6A6A6 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index ca514d86..a6b3daec 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,4 +1,2 @@ - - 16dp - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ef7566d..5bbabf94 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,45 +1,29 @@ - YTDLnis - Streaming Example - Downloading Example - Custom Command Example - Update yt-dlp - Start Stream - Start Download - enter url here - Run Command - enter command here - enter a valid url - enter a valid command - Do the thing - Example command: --extract-audio --audio-format mp3 -o /sdcard/Download/youtubedl-android/%(title)s.%(ext)s https://www.youtube.com/watch?v=dQw4w9WgXcQ - Starting Download - Download Complete - Download Failed - Starting Command - Command Complete - Command Failed - Use Config File? - - Hello blank fragment + YTDLnis Search - - Messages - Sync - - - Your signature - Default reply action - - - Sync email periodically - Download incoming attachments - Automatically download attachments for incoming emails - - Only download attachments when manually requested - Shkarko - Shkruaj URL e YouTube - sdcard/Download - Fshi Historinë - Fshi rezultatet + Clear history + Clear Results + Trending + You reached the end of results + Download All + History + Settings + Home + Directories + Music Directory + Video Directory + Updating + Click this if you face download issues + Update YTDL + About + Source Code + Search from YouTube + Can\'t Start Download! Another one is not done yet! + Try again after accepting the permission! + Download Complete! + Failed Downloading! :( + Download Started! + File Downloads + Notification that shows the download progress of audio and video files + Failed to create the new Directory! :( diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f027a5d8..d363c92e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,40 +1,10 @@ - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index a68d45a7..22999d16 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,40 +1,41 @@ - + - + + + - + app:title="@string/music_directory" /> - - + app:title="@string/video_directory" /> - - + - + app:summary="@string/ytdl_update_hint" + app:title="@string/update_ytdl" /> - + + app:title="@string/source_code"> + /> + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 9a5a07ae..15769daa 100644 --- a/build.gradle +++ b/build.gradle @@ -16,8 +16,8 @@ buildscript { } def versionMajor = 1 -def versionMinor = 0 -def versionPatch = 1 +def versionMinor = 1 +def versionPatch = 0 def versionBuild = 0 // bump for dogfood builds, public betas, etc. ext { @@ -43,6 +43,7 @@ allprojects { repositories { google() jcenter() + maven { url 'https://jitpack.io' } } } diff --git a/common/.gitignore b/common/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle deleted file mode 100644 index b43027de..00000000 --- a/common/build.gradle +++ /dev/null @@ -1,34 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' - -android { - compileSdkVersion 29 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 29 - versionCode project.versionCode - versionName project.versionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } -} - -dependencies { - implementation fileTree(dir: "libs", include: ["*.jar"]) - - implementation "androidx.appcompat:appcompat:$appCompatVer" - testImplementation "junit:junit:$junitVer" - androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" - androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" - - implementation group: 'commons-io', name: 'commons-io', version: "$commonsIoVer" - implementation group: 'org.apache.commons', name: 'commons-compress', version: "$commonsCompressVer" -} \ No newline at end of file diff --git a/common/proguard-rules.pro b/common/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/common/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/common/src/androidTest/java/com/yausername/youtubedl_common/ExampleInstrumentedTest.java b/common/src/androidTest/java/com/yausername/youtubedl_common/ExampleInstrumentedTest.java deleted file mode 100644 index 4083abdb..00000000 --- a/common/src/androidTest/java/com/yausername/youtubedl_common/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.yausername.youtubedl_common; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.yausername.youtubedl_common.test", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/common/src/main/AndroidManifest.xml b/common/src/main/AndroidManifest.xml deleted file mode 100644 index 7eef7918..00000000 --- a/common/src/main/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - / - \ No newline at end of file diff --git a/common/src/main/java/com/yausername/youtubedl_common/SharedPrefsHelper.java b/common/src/main/java/com/yausername/youtubedl_common/SharedPrefsHelper.java deleted file mode 100644 index bdb13835..00000000 --- a/common/src/main/java/com/yausername/youtubedl_common/SharedPrefsHelper.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.yausername.youtubedl_common; - -import android.content.Context; -import android.content.SharedPreferences; - -import androidx.annotation.Nullable; - -public class SharedPrefsHelper { - - private SharedPrefsHelper() { - } - - private static final String sharedPrefsName = "youtubedl-android"; - - public static void update(Context appContext, String key, String value) { - SharedPreferences pref = appContext.getSharedPreferences(sharedPrefsName, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = pref.edit(); - editor.putString(key, value); - editor.apply(); - } - - @Nullable - public static String get(Context appContext, String key) { - SharedPreferences pref = appContext.getSharedPreferences(sharedPrefsName, Context.MODE_PRIVATE); - return pref.getString(key, null); - } -} diff --git a/common/src/main/java/com/yausername/youtubedl_common/utils/ZipUtils.java b/common/src/main/java/com/yausername/youtubedl_common/utils/ZipUtils.java deleted file mode 100644 index 4b716f44..00000000 --- a/common/src/main/java/com/yausername/youtubedl_common/utils/ZipUtils.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.yausername.youtubedl_common.utils; - -import android.system.ErrnoException; -import android.system.Os; - -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; -import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; -import org.apache.commons.compress.archivers.zip.ZipFile; -import org.apache.commons.io.IOUtils; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; - - -public class ZipUtils { - - private ZipUtils() { - } - - public static void unzip(File sourceFile, File targetDirectory) throws IOException, ErrnoException, IllegalAccessException { - try (ZipFile zipFile = new ZipFile(sourceFile)) { - Enumeration entries = zipFile.getEntries(); - while (entries.hasMoreElements()) { - ZipArchiveEntry entry = entries.nextElement(); - File entryDestination = new File(targetDirectory, entry.getName()); - // prevent zipSlip - if (!entryDestination.getCanonicalPath().startsWith(targetDirectory.getCanonicalPath() + File.separator)) { - throw new IllegalAccessException("Entry is outside of the target dir: " + entry.getName()); - } - if (entry.isDirectory()) { - entryDestination.mkdirs(); - } else if (entry.isUnixSymlink()) { - try (InputStream in = zipFile.getInputStream(entry)) { - String symlink = IOUtils.toString(in, StandardCharsets.UTF_8); - Os.symlink(symlink, entryDestination.getAbsolutePath()); - } - } else { - entryDestination.getParentFile().mkdirs(); - try (InputStream in = zipFile.getInputStream(entry); - OutputStream out = new FileOutputStream(entryDestination)) { - IOUtils.copy(in, out); - } - } - } - } - } - - public static void unzip(InputStream inputStream, File targetDirectory) throws IOException, IllegalAccessException { - try (ZipArchiveInputStream zis = new ZipArchiveInputStream(new BufferedInputStream(inputStream))) { - ZipArchiveEntry entry = null; - while ((entry = zis.getNextZipEntry()) != null) { - File entryDestination = new File(targetDirectory, entry.getName()); - // prevent zipSlip - if (!entryDestination.getCanonicalPath().startsWith(targetDirectory.getCanonicalPath() + File.separator)) { - throw new IllegalAccessException("Entry is outside of the target dir: " + entry.getName()); - } - if (entry.isDirectory()) { - entryDestination.mkdirs(); - } else { - entryDestination.getParentFile().mkdirs(); - try (OutputStream out = new FileOutputStream(entryDestination)) { - IOUtils.copy(zis, out); - } - } - - } - } - } - -} diff --git a/common/src/test/java/com/yausername/youtubedl_common/ExampleUnitTest.java b/common/src/test/java/com/yausername/youtubedl_common/ExampleUnitTest.java deleted file mode 100644 index 460bac59..00000000 --- a/common/src/test/java/com/yausername/youtubedl_common/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_common; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/ffmpeg/.gitignore b/ffmpeg/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/ffmpeg/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/ffmpeg/build.gradle b/ffmpeg/build.gradle deleted file mode 100644 index b0f7b651..00000000 --- a/ffmpeg/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' - -android { - compileSdkVersion 29 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 29 - versionCode project.versionCode - versionName project.versionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation project(':common') - compileOnly project(':library') - - implementation "androidx.appcompat:appcompat:$appCompatVer" - testImplementation "junit:junit:$junitVer" - androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" - androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" - - implementation group: 'commons-io', name: 'commons-io', version: "$commonsIoVer" -} diff --git a/ffmpeg/proguard-rules.pro b/ffmpeg/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/ffmpeg/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/ffmpeg/src/androidTest/java/com/yausername/ffmpeg/ExampleInstrumentedTest.java b/ffmpeg/src/androidTest/java/com/yausername/ffmpeg/ExampleInstrumentedTest.java deleted file mode 100644 index 97bbde10..00000000 --- a/ffmpeg/src/androidTest/java/com/yausername/ffmpeg/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.yausername.ffmpeg; - -import android.content.Context; -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.yausername.ffmpeg.test", appContext.getPackageName()); - } -} diff --git a/ffmpeg/src/main/AndroidManifest.xml b/ffmpeg/src/main/AndroidManifest.xml deleted file mode 100644 index ce7251ce..00000000 --- a/ffmpeg/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/ffmpeg/src/main/java/com/yausername/ffmpeg/FFmpeg.java b/ffmpeg/src/main/java/com/yausername/ffmpeg/FFmpeg.java deleted file mode 100644 index 387b9940..00000000 --- a/ffmpeg/src/main/java/com/yausername/ffmpeg/FFmpeg.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.yausername.ffmpeg; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.yausername.youtubedl_android.YoutubeDLException; -import com.yausername.youtubedl_common.SharedPrefsHelper; -import com.yausername.youtubedl_common.utils.ZipUtils; - -import org.apache.commons.io.FileUtils; - -import java.io.File; - -public class FFmpeg { - - private static final FFmpeg INSTANCE = new FFmpeg(); - protected static final String baseName = "youtubedl-android"; - private static final String packagesRoot = "packages"; - private static final String ffmegDirName = "ffmpeg"; - private static final String ffmpegLibName = "libffmpeg.zip.so"; - private static final String ffmpegLibVersion = "ffmpegLibVersion"; - - private boolean initialized = false; - private File binDir; - - private FFmpeg(){ - } - - public static FFmpeg getInstance(){ - return INSTANCE; - } - - synchronized public void init(Context appContext) throws YoutubeDLException { - if (initialized) return; - - File baseDir = new File(appContext.getNoBackupFilesDir(), baseName); - if(!baseDir.exists()) baseDir.mkdir(); - - binDir = new File(appContext.getApplicationInfo().nativeLibraryDir); - - File packagesDir = new File(baseDir, packagesRoot); - File ffmpegDir = new File(packagesDir, ffmegDirName); - initFFmpeg(appContext, ffmpegDir); - - initialized = true; - } - - private void initFFmpeg(Context appContext, File ffmpegDir) throws YoutubeDLException { - File ffmpegLib = new File(binDir, ffmpegLibName); - // using size of lib as version - String ffmpegSize = String.valueOf(ffmpegLib.length()); - if (!ffmpegDir.exists() || shouldUpdateFFmpeg(appContext, ffmpegSize)) { - FileUtils.deleteQuietly(ffmpegDir); - ffmpegDir.mkdirs(); - try { - ZipUtils.unzip(ffmpegLib, ffmpegDir); - } catch (Exception e) { - FileUtils.deleteQuietly(ffmpegDir); - throw new YoutubeDLException("failed to initialize", e); - } - updateFFmpeg(appContext, ffmpegSize); - } - } - - private boolean shouldUpdateFFmpeg(@NonNull Context appContext, @NonNull String version) { - return !version.equals(SharedPrefsHelper.get(appContext, ffmpegLibVersion)); - } - - private void updateFFmpeg(@NonNull Context appContext, @NonNull String version) { - SharedPrefsHelper.update(appContext, ffmpegLibVersion, version); - } -} diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.bin.so deleted file mode 100644 index d2d00299..00000000 Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.zip.so deleted file mode 100644 index 34f2fa44..00000000 Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/arm64-v8a/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/arm64-v8a/libffprobe.bin.so deleted file mode 100644 index d037a186..00000000 Binary files a/ffmpeg/src/main/jniLibs/arm64-v8a/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.bin.so deleted file mode 100644 index b22b90b4..00000000 Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.zip.so deleted file mode 100644 index 31ac3186..00000000 Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/armeabi-v7a/libffprobe.bin.so deleted file mode 100644 index 5a4b7255..00000000 Binary files a/ffmpeg/src/main/jniLibs/armeabi-v7a/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/x86/libffmpeg.bin.so deleted file mode 100644 index 8b1ed06a..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/x86/libffmpeg.zip.so deleted file mode 100644 index fdfed683..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/x86/libffprobe.bin.so deleted file mode 100644 index a063e36a..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.bin.so b/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.bin.so deleted file mode 100644 index 9582d2db..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.zip.so b/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.zip.so deleted file mode 100644 index 75b685e5..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86_64/libffmpeg.zip.so and /dev/null differ diff --git a/ffmpeg/src/main/jniLibs/x86_64/libffprobe.bin.so b/ffmpeg/src/main/jniLibs/x86_64/libffprobe.bin.so deleted file mode 100644 index c3fba489..00000000 Binary files a/ffmpeg/src/main/jniLibs/x86_64/libffprobe.bin.so and /dev/null differ diff --git a/ffmpeg/src/main/res/values/strings.xml b/ffmpeg/src/main/res/values/strings.xml deleted file mode 100644 index 83474b56..00000000 --- a/ffmpeg/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - ffmpeg - diff --git a/ffmpeg/src/test/java/com/yausername/ffmpeg/ExampleUnitTest.java b/ffmpeg/src/test/java/com/yausername/ffmpeg/ExampleUnitTest.java deleted file mode 100644 index a25afddd..00000000 --- a/ffmpeg/src/test/java/com/yausername/ffmpeg/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.ffmpeg; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/library/.gitignore b/library/.gitignore deleted file mode 100644 index 796b96d1..00000000 --- a/library/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/library/build.gradle b/library/build.gradle deleted file mode 100644 index c004dc10..00000000 --- a/library/build.gradle +++ /dev/null @@ -1,38 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' - -android { - compileSdkVersion 29 - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 29 - versionCode project.versionCode - versionName project.versionName - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - - implementation project(':common') - - implementation "androidx.appcompat:appcompat:$appCompatVer" - testImplementation "junit:junit:$junitVer" - androidTestImplementation "androidx.test.ext:junit:$androidJunitVer" - androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVer" - - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "$jacksonVer" - implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: "$jacksonVer" - implementation group: 'commons-io', name: 'commons-io', version: "$commonsIoVer" -} diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/library/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/library/src/androidTest/java/com/yausername/youtubedl_android/ExampleInstrumentedTest.java b/library/src/androidTest/java/com/yausername/youtubedl_android/ExampleInstrumentedTest.java deleted file mode 100644 index e375bc76..00000000 --- a/library/src/androidTest/java/com/yausername/youtubedl_android/ExampleInstrumentedTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.content.Context; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertEquals; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.yausername.youtubedl_android.test", appContext.getPackageName()); - } -} diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml deleted file mode 100644 index e3ad65be..00000000 --- a/library/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - diff --git a/library/src/main/java/com/yausername/youtubedl_android/DownloadProgressCallback.java b/library/src/main/java/com/yausername/youtubedl_android/DownloadProgressCallback.java deleted file mode 100644 index 15b253c8..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/DownloadProgressCallback.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.yausername.youtubedl_android; - -public interface DownloadProgressCallback { - void onProgressUpdate(float progress, long etaInSeconds, String line); -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/StreamGobbler.java b/library/src/main/java/com/yausername/youtubedl_android/StreamGobbler.java deleted file mode 100644 index 9ca2fb6e..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/StreamGobbler.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.StandardCharsets; - -class StreamGobbler extends Thread { - - private final InputStream stream; - private final StringBuffer buffer; - - private static final String TAG = "StreamGobbler"; - - public StreamGobbler(StringBuffer buffer, InputStream stream) { - this.stream = stream; - this.buffer = buffer; - start(); - } - - public void run() { - try { - Reader in = new InputStreamReader(stream, StandardCharsets.UTF_8); - int nextChar; - while ((nextChar = in.read()) != -1) { - this.buffer.append((char) nextChar); - } - } catch (IOException e) { - if (BuildConfig.DEBUG) - Log.e(TAG, "failed to read stream", e); - } - } -} \ No newline at end of file diff --git a/library/src/main/java/com/yausername/youtubedl_android/StreamProcessExtractor.java b/library/src/main/java/com/yausername/youtubedl_android/StreamProcessExtractor.java deleted file mode 100644 index 9c93f9c5..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/StreamProcessExtractor.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class StreamProcessExtractor extends Thread { - private static final int GROUP_PERCENT = 1; - private static final int GROUP_MINUTES = 2; - private static final int GROUP_SECONDS = 3; - private final InputStream stream; - private final StringBuffer buffer; - private final DownloadProgressCallback callback; - - private final Pattern p = Pattern.compile("\\[download\\]\\s+(\\d+\\.\\d)% .* ETA (\\d+):(\\d+)"); - - private static final String TAG = "StreamProcessExtractor"; - - public StreamProcessExtractor(StringBuffer buffer, InputStream stream, DownloadProgressCallback callback) { - this.stream = stream; - this.buffer = buffer; - this.callback = callback; - this.start(); - } - - public void run() { - try { - Reader in = new InputStreamReader(stream, StandardCharsets.UTF_8); - StringBuilder currentLine = new StringBuilder(); - int nextChar; - while ((nextChar = in.read()) != -1) { - buffer.append((char) nextChar); - if (nextChar == '\r' && callback != null) { - processOutputLine(currentLine.toString()); - currentLine.setLength(0); - continue; - } - currentLine.append((char) nextChar); - } - } catch (IOException e) { - if (BuildConfig.DEBUG) - Log.e(TAG, "failed to read stream", e); - } - } - - private void processOutputLine(String line) { - Matcher m = p.matcher(line); - if (m.matches()) { - float progress = Float.parseFloat(m.group(GROUP_PERCENT)); - long eta = convertToSeconds(m.group(GROUP_MINUTES), m.group(GROUP_SECONDS)); - callback.onProgressUpdate(progress, eta, line); - } - } - - private int convertToSeconds(String minutes, String seconds) { - return Integer.parseInt(minutes) * 60 + Integer.parseInt(seconds); - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDL.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDL.java deleted file mode 100644 index 57312fdf..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDL.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.yausername.youtubedl_android.mapper.VideoInfo; -import com.yausername.youtubedl_common.SharedPrefsHelper; -import com.yausername.youtubedl_common.utils.ZipUtils; - -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -public class YoutubeDL { - - private static final YoutubeDL INSTANCE = new YoutubeDL(); - protected static final String baseName = "youtubedl-android"; - private static final String packagesRoot = "packages"; - private static final String pythonBinName = "libpython.bin.so"; - private static final String pythonLibName = "libpython.zip.so"; - private static final String pythonDirName = "python"; - private static final String ffmpegDirName = "ffmpeg"; - protected static final String youtubeDLDirName = "yt-dlp"; - private static final String youtubeDLBin = "__main__.py"; - protected static final String youtubeDLFile = "yt_dlp.zip"; - private static final String pythonLibVersion = "pythonLibVersion"; - - private boolean initialized = false; - private File pythonPath; - private File youtubeDLPath; - private File binDir; - - private String ENV_LD_LIBRARY_PATH; - private String ENV_SSL_CERT_FILE; - private String ENV_PYTHONHOME; - - protected static final ObjectMapper objectMapper = new ObjectMapper(); - - private YoutubeDL(){ - } - - public static YoutubeDL getInstance() { - return INSTANCE; - } - - synchronized public void init(Context appContext) throws YoutubeDLException { - if (initialized) return; - - File baseDir = new File(appContext.getNoBackupFilesDir(), baseName); - if(!baseDir.exists()) baseDir.mkdir(); - - File packagesDir = new File(baseDir, packagesRoot); - binDir = new File(appContext.getApplicationInfo().nativeLibraryDir); - pythonPath = new File(binDir, pythonBinName); - File pythonDir = new File(packagesDir, pythonDirName); - File ffmpegDir = new File(packagesDir, ffmpegDirName); - - File youtubeDLDir = new File(baseDir, youtubeDLDirName); - youtubeDLPath = new File(youtubeDLDir, youtubeDLBin); - - ENV_LD_LIBRARY_PATH = pythonDir.getAbsolutePath() + "/usr/lib" + ":" + ffmpegDir.getAbsolutePath() + "/usr/lib"; - ENV_SSL_CERT_FILE = pythonDir.getAbsolutePath() + "/usr/etc/tls/cert.pem"; - ENV_PYTHONHOME = pythonDir.getAbsolutePath() + "/usr"; - - initPython(appContext, pythonDir); - initYoutubeDL(appContext, youtubeDLDir); - - initialized = true; - } - - protected void initYoutubeDL(Context appContext, File youtubeDLDir) throws YoutubeDLException { - if (!youtubeDLDir.exists()) { - youtubeDLDir.mkdirs(); - try { - ZipUtils.unzip(appContext.getResources().openRawResource(R.raw.yt_dlp), youtubeDLDir); - } catch (Exception e) { - FileUtils.deleteQuietly(youtubeDLDir); - throw new YoutubeDLException("failed to initialize", e); - } - } - } - - protected void initPython(Context appContext, File pythonDir) throws YoutubeDLException { - File pythonLib = new File(binDir, pythonLibName); - // using size of lib as version - String pythonSize = String.valueOf(pythonLib.length()); - if (!pythonDir.exists() || shouldUpdatePython(appContext, pythonSize)) { - FileUtils.deleteQuietly(pythonDir); - pythonDir.mkdirs(); - try { - ZipUtils.unzip(pythonLib, pythonDir); - } catch (Exception e) { - FileUtils.deleteQuietly(pythonDir); - throw new YoutubeDLException("failed to initialize", e); - } - updatePython(appContext, pythonSize); - } - } - - private boolean shouldUpdatePython(@NonNull Context appContext, @NonNull String version) { - return !version.equals(SharedPrefsHelper.get(appContext, pythonLibVersion)); - } - - private void updatePython(@NonNull Context appContext, @NonNull String version) { - SharedPrefsHelper.update(appContext, pythonLibVersion, version); - } - - private void assertInit() { - if (!initialized) throw new IllegalStateException("instance not initialized"); - } - - public VideoInfo getInfo(String url) throws YoutubeDLException, InterruptedException { - YoutubeDLRequest request = new YoutubeDLRequest(url); - return getInfo(request); - } - - @NonNull - public VideoInfo getInfo(YoutubeDLRequest request) throws YoutubeDLException, InterruptedException { - request.addOption("--dump-json"); - YoutubeDLResponse response = execute(request, null); - - VideoInfo videoInfo; - try { - videoInfo = objectMapper.readValue(response.getOut(), VideoInfo.class); - } catch (IOException e) { - throw new YoutubeDLException("Unable to parse video information", e); - } - - if(videoInfo == null){ - throw new YoutubeDLException("Failed to fetch video information"); - } - - return videoInfo; - } - - public YoutubeDLResponse execute(YoutubeDLRequest request) throws YoutubeDLException, InterruptedException { - return execute(request, null); - } - - public YoutubeDLResponse execute(YoutubeDLRequest request, @Nullable DownloadProgressCallback callback) throws YoutubeDLException, InterruptedException { - assertInit(); - - // disable caching unless explicitly requested - if(request.getOption("--cache-dir") == null){ - request.addOption("--no-cache-dir"); - } - - YoutubeDLResponse youtubeDLResponse; - Process process; - int exitCode; - StringBuffer outBuffer = new StringBuffer(); //stdout - StringBuffer errBuffer = new StringBuffer(); //stderr - long startTime = System.currentTimeMillis(); - - List args = request.buildCommand(); - List command = new ArrayList<>(); - command.addAll(Arrays.asList(pythonPath.getAbsolutePath(), youtubeDLPath.getAbsolutePath())); - command.addAll(args); - - ProcessBuilder processBuilder = new ProcessBuilder(command); - Map env = processBuilder.environment(); - env.put("LD_LIBRARY_PATH", ENV_LD_LIBRARY_PATH); - env.put("SSL_CERT_FILE", ENV_SSL_CERT_FILE); - env.put("PATH", System.getenv("PATH") + ":" + binDir.getAbsolutePath()); - env.put("PYTHONHOME", ENV_PYTHONHOME); - - try { - process = processBuilder.start(); - } catch (IOException e) { - throw new YoutubeDLException(e); - } - - InputStream outStream = process.getInputStream(); - InputStream errStream = process.getErrorStream(); - - StreamProcessExtractor stdOutProcessor = new StreamProcessExtractor(outBuffer, outStream, callback); - StreamGobbler stdErrProcessor = new StreamGobbler(errBuffer, errStream); - - try { - stdOutProcessor.join(); - stdErrProcessor.join(); - exitCode = process.waitFor(); - } catch (InterruptedException e) { - process.destroy(); - throw e; - } - - String out = outBuffer.toString(); - String err = errBuffer.toString(); - - if (exitCode > 0) { - throw new YoutubeDLException(err); - } - - long elapsedTime = System.currentTimeMillis() - startTime; - - youtubeDLResponse = new YoutubeDLResponse(command, exitCode, elapsedTime, out, err); - - return youtubeDLResponse; - } - - synchronized public UpdateStatus updateYoutubeDL(Context appContext) throws YoutubeDLException { - assertInit(); - try { - return YoutubeDLUpdater.update(appContext); - } catch (IOException e) { - throw new YoutubeDLException("failed to update youtube-dl", e); - } - } - - @Nullable - public String version(Context appContext) { - return YoutubeDLUpdater.version(appContext); - } - - public enum UpdateStatus { - DONE, ALREADY_UP_TO_DATE; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLException.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLException.java deleted file mode 100644 index 45c45d32..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLException.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_android; - -public class YoutubeDLException extends Exception { - - public YoutubeDLException(String message) { - super(message); - } - - public YoutubeDLException(String message, Throwable e) { - super(message, e); - } - - public YoutubeDLException(Throwable e) { - super(e); - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLOptions.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLOptions.java deleted file mode 100644 index e4f8b3c6..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLOptions.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.yausername.youtubedl_android; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class YoutubeDLOptions { - - private Map options = new LinkedHashMap<>(); - - public YoutubeDLOptions addOption(@NonNull String key, @NonNull String value){ - options.put(key, value); - return this; - } - - public YoutubeDLOptions addOption(@NonNull String key, @NonNull Number value){ - options.put(key, value.toString()); - return this; - } - - public YoutubeDLOptions addOption(String key){ - options.put(key, null); - return this; - } - - public Object getOption(String key){ - return options.get(key); - } - - public List buildOptions(){ - List optionsList = new ArrayList<>(); - for (Map.Entry entry : options.entrySet()) { - String name = entry.getKey(); - String value = entry.getValue(); - optionsList.add(name); - if(null != value) optionsList.add(value); - } - return optionsList; - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLRequest.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLRequest.java deleted file mode 100644 index 06f6f52f..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.yausername.youtubedl_android; - -import androidx.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class YoutubeDLRequest { - - private List urls; - private YoutubeDLOptions options = new YoutubeDLOptions(); - - public YoutubeDLRequest(String url) { - this.urls = Arrays.asList(url); - } - - public YoutubeDLRequest(@NonNull List urls) { - this.urls = urls; - } - - public YoutubeDLRequest addOption(@NonNull String key, @NonNull String value){ - options.addOption(key, value); - return this; - } - - public YoutubeDLRequest addOption(@NonNull String key, @NonNull Number value){ - options.addOption(key, value); - return this; - } - - public YoutubeDLRequest addOption(String key){ - options.addOption(key); - return this; - } - - public Object getOption(String key){ - return options.getOption(key); - } - - public List buildCommand(){ - List command = new ArrayList<>(); - command.addAll(options.buildOptions()); - command.addAll(urls); - return command; - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLResponse.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLResponse.java deleted file mode 100644 index 4b696b06..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLResponse.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.yausername.youtubedl_android; - -import java.util.List; - -public class YoutubeDLResponse { - - private List command; - private int exitCode; - private String out; - private String err; - private long elapsedTime; - - public YoutubeDLResponse(List command, int exitCode, long elapsedTime, String out, String err) { - this.command = command; - this.elapsedTime = elapsedTime; - this.exitCode = exitCode; - this.out = out; - this.err = err; - } - - public List getCommand() { - return command; - } - - public int getExitCode() { - return exitCode; - } - - public String getOut() { - return out; - } - - public String getErr() { - return err; - } - - public long getElapsedTime() { - return elapsedTime; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLUpdater.java b/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLUpdater.java deleted file mode 100644 index 74b79a15..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/YoutubeDLUpdater.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.yausername.youtubedl_android; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.yausername.youtubedl_android.YoutubeDL.UpdateStatus; -import com.yausername.youtubedl_common.SharedPrefsHelper; -import com.yausername.youtubedl_common.utils.ZipUtils; - -import org.apache.commons.io.FileUtils; - -import java.io.File; -import java.io.IOException; -import java.net.URL; - -class YoutubeDLUpdater { - - private YoutubeDLUpdater() { - } - - private static final String releasesUrl = "https://api.github.com/repos/xibr/ytdlp-lazy/releases/latest"; - private static final String youtubeDLVersionKey = "youtubeDLVersion"; - - static UpdateStatus update(Context appContext) throws IOException, YoutubeDLException { - JsonNode json = checkForUpdate(appContext); - if(null == json) return UpdateStatus.ALREADY_UP_TO_DATE; - - String downloadUrl = getDownloadUrl(json); - File file = download(appContext, downloadUrl); - - File youtubeDLDir = null; - try { - youtubeDLDir = getYoutubeDLDir(appContext); - //purge older version - FileUtils.deleteDirectory(youtubeDLDir); - //install newer version - youtubeDLDir.mkdirs(); - ZipUtils.unzip(file, youtubeDLDir); - } catch (Exception e) { - //if something went wrong restore default version - FileUtils.deleteQuietly(youtubeDLDir); - YoutubeDL.getInstance().initYoutubeDL(appContext, youtubeDLDir); - throw new YoutubeDLException(e); - } finally { - file.delete(); - } - - updateSharedPrefs(appContext, getTag(json)); - return UpdateStatus.DONE; - } - - private static void updateSharedPrefs(Context appContext, String tag) { - SharedPrefsHelper.update(appContext, youtubeDLVersionKey, tag); - } - - private static JsonNode checkForUpdate(Context appContext) throws IOException { - URL url = new URL(releasesUrl); - JsonNode json = YoutubeDL.objectMapper.readTree(url); - String newVersion = getTag(json); - String oldVersion = SharedPrefsHelper.get(appContext, youtubeDLVersionKey); - if(newVersion.equals(oldVersion)){ - return null; - } - return json; - } - - private static String getTag(JsonNode json){ - return json.get("tag_name").asText(); - } - - @NonNull - private static String getDownloadUrl(@NonNull JsonNode json) throws YoutubeDLException { - ArrayNode assets = (ArrayNode) json.get("assets"); - String downloadUrl = ""; - for (JsonNode asset : assets) { - if (YoutubeDL.youtubeDLFile.equals(asset.get("name").asText())) { - downloadUrl = asset.get("browser_download_url").asText(); - break; - } - } - if (downloadUrl.isEmpty()) throw new YoutubeDLException("unable to get download url"); - return downloadUrl; - } - - @NonNull - private static File download(Context appContext, String url) throws IOException { - URL downloadUrl = new URL(url); - File file = File.createTempFile("yt_dlp", "zip", appContext.getCacheDir()); - FileUtils.copyURLToFile(downloadUrl, file, 5000, 10000); - return file; - } - - @NonNull - private static File getYoutubeDLDir(Context appContext) { - File baseDir = new File(appContext.getNoBackupFilesDir(), YoutubeDL.baseName); - return new File(baseDir, YoutubeDL.youtubeDLDirName); - } - - @Nullable - static String version(Context appContext) { - return SharedPrefsHelper.get(appContext, youtubeDLVersionKey); - } - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoFormat.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoFormat.java deleted file mode 100644 index d498334a..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoFormat.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Map; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoFormat { - - private int asr; - private int tbr; - private int abr; - private String format; - @JsonProperty("format_id") - private String formatId; - @JsonProperty("format_note") - private String formatNote; - private String ext; - private int preference; - private String vcodec; - private String acodec; - private int width; - private int height; - private long filesize; - private int fps; - private String url; - @JsonProperty("manifest_url") - private String manifestUrl; - @JsonProperty("http_headers") - private Map httpHeaders; - - public int getAsr() { - return asr; - } - - public int getTbr() { - return tbr; - } - - public int getAbr() { - return abr; - } - - public String getFormat() { - return format; - } - - public String getFormatId() { - return formatId; - } - - public String getFormatNote() { - return formatNote; - } - - public String getExt() { - return ext; - } - - public int getPreference() { - return preference; - } - - public String getVcodec() { - return vcodec; - } - - public String getAcodec() { - return acodec; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public long getFilesize() { - return filesize; - } - - public int getFps() { - return fps; - } - - public String getUrl() { - return url; - } - - public String getManifestUrl() { - return manifestUrl; - } - - public Map getHttpHeaders() { - return httpHeaders; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoInfo.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoInfo.java deleted file mode 100644 index 079e9a5b..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoInfo.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.ArrayList; -import java.util.Map; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoInfo { - - private String id; - private String fulltitle; - private String title; - @JsonProperty("upload_date") - private String uploadDate; - @JsonProperty("display_id") - private String displayId; - private int duration; - private String description; - private String thumbnail; - private String license; - - @JsonProperty("view_count") - private String viewCount; - @JsonProperty("like_count") - private String likeCount; - @JsonProperty("dislike_count") - private String dislikeCount; - @JsonProperty("repost_count") - private String repostCount; - @JsonProperty("average_rating") - private String averageRating; - - - @JsonProperty("uploader_id") - private String uploaderId; - private String uploader; - - @JsonProperty("player_url") - private String playerUrl; - @JsonProperty("webpage_url") - private String webpageUrl; - @JsonProperty("webpage_url_basename") - private String webpageUrlBasename; - - private String resolution; - private int width; - private int height; - private String format; - @JsonProperty("format_id") - private String formatId; - private String ext; - - @JsonProperty("http_headers") - private Map httpHeaders; - private ArrayList categories; - private ArrayList tags; - @JsonProperty("requested_formats") - private ArrayList requestedFormats; - private ArrayList formats; - private ArrayList thumbnails; - //private ArrayList subtitles; - @JsonProperty("manifest_url") - private String manifestUrl; - private String url; - - public String getId() { - return id; - } - - public String getFulltitle() { - return fulltitle; - } - - public String getTitle() { - return title; - } - - public String getUploadDate() { - return uploadDate; - } - - public String getDisplayId() { - return displayId; - } - - public int getDuration() { - return duration; - } - - public String getDescription() { - return description; - } - - public String getThumbnail() { - return thumbnail; - } - - public String getLicense() { - return license; - } - - public String getViewCount() { - return viewCount; - } - - public String getLikeCount() { - return likeCount; - } - - public String getDislikeCount() { - return dislikeCount; - } - - public String getRepostCount() { - return repostCount; - } - - public String getAverageRating() { - return averageRating; - } - - public String getUploaderId() { - return uploaderId; - } - - public String getUploader() { - return uploader; - } - - public String getPlayerUrl() { - return playerUrl; - } - - public String getWebpageUrl() { - return webpageUrl; - } - - public String getWebpageUrlBasename() { - return webpageUrlBasename; - } - - public String getResolution() { - return resolution; - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - public String getFormat() { - return format; - } - - public String getFormatId() { - return formatId; - } - - public String getExt() { - return ext; - } - - public Map getHttpHeaders() { - return httpHeaders; - } - - public ArrayList getCategories() { - return categories; - } - - public ArrayList getTags() { - return tags; - } - - public ArrayList getRequestedFormats() { - return requestedFormats; - } - - public ArrayList getFormats() { - return formats; - } - - public ArrayList getThumbnails() { - return thumbnails; - } - - public String getManifestUrl() { - return manifestUrl; - } - - public String getUrl() { - return url; - } -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoSubtitle.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoSubtitle.java deleted file mode 100644 index 7f419197..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoSubtitle.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoSubtitle { - -} diff --git a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoThumbnail.java b/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoThumbnail.java deleted file mode 100644 index c986f588..00000000 --- a/library/src/main/java/com/yausername/youtubedl_android/mapper/VideoThumbnail.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_android.mapper; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - -@JsonIgnoreProperties(ignoreUnknown = true) -public class VideoThumbnail { - private String url; - private String id; - - public String getUrl() { - return url; - } - - public String getId() { - return id; - } -} diff --git a/library/src/main/jniLibs/arm64-v8a/libpython.bin.so b/library/src/main/jniLibs/arm64-v8a/libpython.bin.so deleted file mode 100644 index 3c94297e..00000000 Binary files a/library/src/main/jniLibs/arm64-v8a/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/arm64-v8a/libpython.zip.so b/library/src/main/jniLibs/arm64-v8a/libpython.zip.so deleted file mode 100644 index e80cac28..00000000 Binary files a/library/src/main/jniLibs/arm64-v8a/libpython.zip.so and /dev/null differ diff --git a/library/src/main/jniLibs/armeabi-v7a/libpython.bin.so b/library/src/main/jniLibs/armeabi-v7a/libpython.bin.so deleted file mode 100644 index c5572e6e..00000000 Binary files a/library/src/main/jniLibs/armeabi-v7a/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/armeabi-v7a/libpython.zip.so b/library/src/main/jniLibs/armeabi-v7a/libpython.zip.so deleted file mode 100644 index e7749dec..00000000 Binary files a/library/src/main/jniLibs/armeabi-v7a/libpython.zip.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86/libpython.bin.so b/library/src/main/jniLibs/x86/libpython.bin.so deleted file mode 100644 index 6392d911..00000000 Binary files a/library/src/main/jniLibs/x86/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86/libpython.zip.so b/library/src/main/jniLibs/x86/libpython.zip.so deleted file mode 100644 index 50954c32..00000000 Binary files a/library/src/main/jniLibs/x86/libpython.zip.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86_64/libpython.bin.so b/library/src/main/jniLibs/x86_64/libpython.bin.so deleted file mode 100644 index 4dfc37a1..00000000 Binary files a/library/src/main/jniLibs/x86_64/libpython.bin.so and /dev/null differ diff --git a/library/src/main/jniLibs/x86_64/libpython.zip.so b/library/src/main/jniLibs/x86_64/libpython.zip.so deleted file mode 100644 index 0e73125f..00000000 Binary files a/library/src/main/jniLibs/x86_64/libpython.zip.so and /dev/null differ diff --git a/library/src/main/res/raw/yt_dlp.zip b/library/src/main/res/raw/yt_dlp.zip deleted file mode 100644 index ec789d2f..00000000 Binary files a/library/src/main/res/raw/yt_dlp.zip and /dev/null differ diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml deleted file mode 100644 index 1bb1db41..00000000 --- a/library/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - youtubedl-android - diff --git a/library/src/test/java/com/yausername/youtubedl_android/ExampleUnitTest.java b/library/src/test/java/com/yausername/youtubedl_android/ExampleUnitTest.java deleted file mode 100644 index f77fe036..00000000 --- a/library/src/test/java/com/yausername/youtubedl_android/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.yausername.youtubedl_android; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file