From ee5f52ebc22da8a707895fc1eab76b62268607fd Mon Sep 17 00:00:00 2001 From: tobigr Date: Mon, 9 Feb 2026 22:59:36 +0100 Subject: [PATCH] Add option to DownloadDialog to enable metadata embedding Metadata embedding is disabled by default across all post-processing algorithms. A new parameter and variable is introduced although there is the parameter 'args' already. The information on whether the metadata is going to be embedded needs to be parsed by every audio or video post-processing algorithm anyway. Getting it from the list is more difficult and less error-prone than creating a new param. --- .../newpipe/download/DownloadDialog.java | 5 ++-- .../newpipe/streams/Mp4FromDashWriter.java | 9 ++++-- .../newpipe/streams/OggFromWebMWriter.java | 12 ++++++-- .../giga/postprocessing/M4aNoDash.java | 2 +- .../giga/postprocessing/Mp3Metadata.java | 5 ++++ .../giga/postprocessing/Mp4FromDashMuxer.java | 3 +- .../giga/postprocessing/Mp4Metadata.java | 3 ++ .../postprocessing/OggFromWebmDemuxer.java | 2 +- .../giga/postprocessing/Postprocessing.java | 10 ++++++- .../giga/service/DownloadManagerService.java | 29 ++++++++++--------- app/src/main/res/layout/download_dialog.xml | 12 +++++++- app/src/main/res/values/strings.xml | 1 + 12 files changed, 68 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index 57b25005e..a770e47c6 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -1049,6 +1049,7 @@ public class DownloadDialog extends DialogFragment final Stream selectedStream; Stream secondaryStream = null; final char kind; + final boolean embedMetadata = dialogBinding.metadataSwitch.isChecked(); int threads = dialogBinding.threads.getProgress() + 1; final String[] urls; final List recoveryInfo; @@ -1136,8 +1137,8 @@ public class DownloadDialog extends DialogFragment ); } - DownloadManagerService.startMission(context, urls, storage, kind, threads, - currentInfo, psName, psArgs, nearLength, new ArrayList<>(recoveryInfo)); + DownloadManagerService.startMission(context, urls, storage, kind, threads, currentInfo, + psName, embedMetadata, psArgs, nearLength, new ArrayList<>(recoveryInfo)); Toast.makeText(context, getString(R.string.download_has_started), Toast.LENGTH_SHORT).show(); diff --git a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java index 73bd51e3d..7094d672d 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/Mp4FromDashWriter.java @@ -64,9 +64,11 @@ public class Mp4FromDashWriter { private final ArrayList compatibleBrands = new ArrayList<>(5); + private final boolean embedMetadata; private final Mp4MetadataHelper metadataHelper; - public Mp4FromDashWriter(final StreamInfo streamInfo, + public Mp4FromDashWriter(final boolean embedMetadata, + final StreamInfo streamInfo, final Bitmap thumbnail, final SharpStream... sources) throws IOException { for (final SharpStream src : sources) { @@ -75,6 +77,7 @@ public class Mp4FromDashWriter { } } + this.embedMetadata = embedMetadata; this.metadataHelper = new Mp4MetadataHelper( this::auxOffset, buffer -> { @@ -750,7 +753,9 @@ public class Mp4FromDashWriter { makeMvhd(longestTrack); - metadataHelper.makeUdta(); + if (embedMetadata) { + metadataHelper.makeUdta(); + } for (int i = 0; i < tracks.length; i++) { if (tracks[i].trak.tkhd.matrix.length != 36) { diff --git a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java index c043fff87..e39a0ada7 100644 --- a/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java +++ b/app/src/main/java/org/schabi/newpipe/streams/OggFromWebMWriter.java @@ -121,6 +121,7 @@ public class OggFromWebMWriter implements Closeable { private long segmentTableNextTimestamp = TIME_SCALE_NS; private final int[] crc32Table = new int[256]; + private final boolean embedMetadata; private final StreamInfo streamInfo; private final Bitmap thumbnail; @@ -128,11 +129,13 @@ public class OggFromWebMWriter implements Closeable { * Constructor of OggFromWebMWriter. * @param source * @param target + * @param embedMetadata whether to embed metadata in the output Ogg stream * @param streamInfo the stream info * @param thumbnail the thumbnail bitmap used as cover art */ public OggFromWebMWriter(@NonNull final SharpStream source, @NonNull final SharpStream target, + final boolean embedMetadata, @Nullable final StreamInfo streamInfo, @Nullable final Bitmap thumbnail) { if (!source.canRead() || !source.canRewind()) { @@ -144,6 +147,7 @@ public class OggFromWebMWriter implements Closeable { this.source = source; this.output = target; + this.embedMetadata = embedMetadata; this.streamInfo = streamInfo; this.thumbnail = thumbnail; @@ -264,9 +268,11 @@ public class OggFromWebMWriter implements Closeable { } /* step 3: create packet with metadata */ - final byte[] buffer = makeCommentHeader(); - if (buffer != null) { - addPacketSegmentMultiPage(buffer, header); + if (embedMetadata) { + final byte[] buffer = makeCommentHeader(); + if (buffer != null) { + addPacketSegmentMultiPage(buffer, header); + } } /* step 4: calculate amount of packets */ diff --git a/app/src/main/java/us/shandian/giga/postprocessing/M4aNoDash.java b/app/src/main/java/us/shandian/giga/postprocessing/M4aNoDash.java index ec57ed491..d05d5c235 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/M4aNoDash.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/M4aNoDash.java @@ -31,7 +31,7 @@ class M4aNoDash extends Postprocessing { @Override int process(SharpStream out, SharpStream... sources) throws IOException { Mp4FromDashWriter muxer = new Mp4FromDashWriter( - this.streamInfo, this.thumbnail, sources[0]); + this.embedMetadata, this.streamInfo, this.thumbnail, sources[0]); muxer.setMainBrand(0x4D344120);// binary string "M4A " muxer.parseSources(); muxer.selectTracks(0); diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Mp3Metadata.java b/app/src/main/java/us/shandian/giga/postprocessing/Mp3Metadata.java index a839de582..b585cc139 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Mp3Metadata.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Mp3Metadata.java @@ -31,6 +31,11 @@ public class Mp3Metadata extends Postprocessing { super(true, true, ALGORITHM_MP3_METADATA); } + @Override + boolean test(SharpStream... sources) { + return this.embedMetadata; + } + @Override int process(SharpStream out, SharpStream... sources) throws IOException { if (sources == null || sources.length == 0 || sources[0] == null) { diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Mp4FromDashMuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/Mp4FromDashMuxer.java index 887ba1bf5..43333f1cb 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Mp4FromDashMuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Mp4FromDashMuxer.java @@ -16,7 +16,8 @@ class Mp4FromDashMuxer extends Postprocessing { @Override int process(SharpStream out, SharpStream... sources) throws IOException { - Mp4FromDashWriter muxer = new Mp4FromDashWriter(this.streamInfo, this.thumbnail, sources); + Mp4FromDashWriter muxer = new Mp4FromDashWriter( + this.embedMetadata, this.streamInfo, this.thumbnail, sources); muxer.parseSources(); muxer.selectTracks(0, 0); muxer.build(out); diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Mp4Metadata.java b/app/src/main/java/us/shandian/giga/postprocessing/Mp4Metadata.java index f03c88b5a..40fa15f29 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Mp4Metadata.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Mp4Metadata.java @@ -27,6 +27,9 @@ public class Mp4Metadata extends Postprocessing { @Override boolean test(SharpStream... sources) throws IOException { + // nothing to do if metadata should not be embedded + if (!embedMetadata) return false; + // quick check: ensure there's at least one source and it looks like an MP4, // i.e. the file has a 'moov' box near the beginning. // THe 'udta' box is inserted inside 'moov', so if there's no 'moov' we can't do anything. diff --git a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java index f76fadb31..d4d0c4637 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/OggFromWebmDemuxer.java @@ -35,7 +35,7 @@ class OggFromWebmDemuxer extends Postprocessing { @Override int process(SharpStream out, @NonNull SharpStream... sources) throws IOException { OggFromWebMWriter demuxer = new OggFromWebMWriter( - sources[0], out, streamInfo, thumbnail); + sources[0], out, embedMetadata, streamInfo, thumbnail); demuxer.parseSource(); demuxer.selectTrack(0); demuxer.build(); diff --git a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java index 3cb8b530c..ae1068c3a 100644 --- a/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java +++ b/app/src/main/java/us/shandian/giga/postprocessing/Postprocessing.java @@ -35,7 +35,9 @@ public abstract class Postprocessing implements Serializable { public static final String ALGORITHM_M4A_NO_DASH = "mp4D-m4a"; public static final String ALGORITHM_OGG_FROM_WEBM_DEMUXER = "webm-ogg-d"; - public static Postprocessing getAlgorithm(@NonNull String algorithmName, String[] args, + public static Postprocessing getAlgorithm(@NonNull String algorithmName, + boolean embedMetadata, + String[] args, @NonNull StreamInfo streamInfo) { Postprocessing instance; @@ -67,6 +69,7 @@ public abstract class Postprocessing implements Serializable { instance.args = args; instance.streamInfo = streamInfo; + instance.embedMetadata = embedMetadata; return instance; } @@ -88,6 +91,11 @@ public abstract class Postprocessing implements Serializable { private String[] args; + /** + * Indicates whether the metadata should be embedded in the file or not. + */ + boolean embedMetadata; + /** * StreamInfo object related to the current download */ diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java index 6ce6a4b38..3cf55e156 100755 --- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java +++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java @@ -40,7 +40,6 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.R; import org.schabi.newpipe.download.DownloadActivity; -import org.schabi.newpipe.extractor.Image; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.player.helper.LockManager; import org.schabi.newpipe.streams.io.StoredDirectoryHelper; @@ -75,6 +74,7 @@ public class DownloadManagerService extends Service { private static final String EXTRA_KIND = "DownloadManagerService.extra.kind"; private static final String EXTRA_THREADS = "DownloadManagerService.extra.threads"; private static final String EXTRA_POSTPROCESSING_NAME = "DownloadManagerService.extra.postprocessingName"; + private static final String EXTRA_POSTPROCESSING_METADATA = "DownloadManagerService.extra.postprocessingMetadata"; private static final String EXTRA_POSTPROCESSING_ARGS = "DownloadManagerService.extra.postprocessingArgs"; private static final String EXTRA_NEAR_LENGTH = "DownloadManagerService.extra.nearLength"; private static final String EXTRA_PATH = "DownloadManagerService.extra.storagePath"; @@ -349,20 +349,21 @@ public class DownloadManagerService extends Service { /** * Start a new download mission * - * @param context the activity context - * @param urls array of urls to download - * @param storage where the file is saved - * @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined) - * @param threads the number of threads maximal used to download chunks of the file. - * @param psName the name of the required post-processing algorithm, or {@code null} to ignore. - * @param streamInfo stream metadata that may be written into the downloaded file. - * @param psArgs the arguments for the post-processing algorithm. - * @param nearLength the approximated final length of the file - * @param recoveryInfo array of MissionRecoveryInfo, in case is required recover the download + * @param context the activity context + * @param urls array of urls to download + * @param storage where the file is saved + * @param kind type of file (a: audio v: video s: subtitle ?: file-extension defined) + * @param threads the number of threads maximal used to download chunks of the file. + * @param streamInfo stream metadata that may be written into the downloaded file. + * @param psName the name of the required post-processing algorithm, or {@code null} to ignore. + * @param embedMetadata whether the metadata should be embedded into the downloaded file. + * @param psArgs the arguments for the post-processing algorithm. + * @param nearLength the approximated final length of the file + * @param recoveryInfo array of MissionRecoveryInfo, in case is required recover the download */ public static void startMission(Context context, String[] urls, StoredFileHelper storage, char kind, int threads, StreamInfo streamInfo, String psName, - String[] psArgs, long nearLength, + boolean embedMetadata, String[] psArgs, long nearLength, ArrayList recoveryInfo) { final Intent intent = new Intent(context, DownloadManagerService.class) .setAction(Intent.ACTION_RUN) @@ -370,6 +371,7 @@ public class DownloadManagerService extends Service { .putExtra(EXTRA_KIND, kind) .putExtra(EXTRA_THREADS, threads) .putExtra(EXTRA_POSTPROCESSING_NAME, psName) + .putExtra(EXTRA_POSTPROCESSING_METADATA, embedMetadata) .putExtra(EXTRA_POSTPROCESSING_ARGS, psArgs) .putExtra(EXTRA_NEAR_LENGTH, nearLength) .putExtra(EXTRA_RECOVERY_INFO, recoveryInfo) @@ -388,6 +390,7 @@ public class DownloadManagerService extends Service { int threads = intent.getIntExtra(EXTRA_THREADS, 1); char kind = intent.getCharExtra(EXTRA_KIND, '?'); String psName = intent.getStringExtra(EXTRA_POSTPROCESSING_NAME); + boolean embedMetadata = intent.getBooleanExtra(EXTRA_POSTPROCESSING_METADATA, false); String[] psArgs = intent.getStringArrayExtra(EXTRA_POSTPROCESSING_ARGS); long nearLength = intent.getLongExtra(EXTRA_NEAR_LENGTH, 0); String tag = intent.getStringExtra(EXTRA_STORAGE_TAG); @@ -407,7 +410,7 @@ public class DownloadManagerService extends Service { if (psName == null) ps = null; else - ps = Postprocessing.getAlgorithm(psName, psArgs, streamInfo); + ps = Postprocessing.getAlgorithm(psName, embedMetadata, psArgs, streamInfo); final DownloadMission mission = new DownloadMission( urls, storage, kind, ps, streamInfo, getApplicationContext()); diff --git a/app/src/main/res/layout/download_dialog.xml b/app/src/main/res/layout/download_dialog.xml index 67aa1577c..5541d6545 100644 --- a/app/src/main/res/layout/download_dialog.xml +++ b/app/src/main/res/layout/download_dialog.xml @@ -105,11 +105,21 @@ android:text="@string/audio_track_present_in_video" android:textSize="12sp" /> + + No download folder set yet, choose the default download folder now This permission is needed to\nopen in popup mode 1 item deleted. + Embed metadata such as title, author, thumbnail reCAPTCHA challenge Press \"Done\" when solved