commit 4d9c615c74fad4e8c9ca21439006a3a7bc39d2b0 Author: sadorowo Date: Sun Jan 14 16:35:35 2024 +0100 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c2d706 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.venv +bin +include +lib +__pycache__ +pyvenv.cfg +.DS_Store \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4101bfa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 sador + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aea8b47 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +## M3U+ Duplicates Filter + +Simple script that will remove worse qualities from your M3U8 list, if detected. + +### NOTE! +This may **NOT** be 100% accurate. +This script is in BETA phase. Please report any bugs! + +### Based on +- argparse +- pathlib +- [ipytv](https://github.com/Beer4Ever83/ipytv) + +### Usage +```bash +python3 -i "" (-v) +``` + +### Result +When everything is done, file is **OVERWRITTEN**! + +### Example usage +```bash +python3 -m "m3u_list.m3u" +``` + +### Available parameters +- -m/--m3u - absolute path to m3u+ file +- -v/--verbose - enable verbose mode \ No newline at end of file diff --git a/__main__.py b/__main__.py new file mode 100644 index 0000000..aa54f4d --- /dev/null +++ b/__main__.py @@ -0,0 +1,167 @@ +from argparse import ArgumentParser +from pathlib import Path + +from ipytv import playlist as tv_playlist + +# Simple script that takes a m3u file and tries to remove duplicates. +# Example: python3 remove_duplicates.py -i playlist.m3u -o playlist_without_duplicates.m3u +# Example: +# TVP1 HD & TVP1 FHD -> remove TVP1 HD. +# TVP1 FHD & TVP1 HD -> remove TVP1 HD. +# TVP1 4K+ & TVP1 HD -> remove TVP1 HD. +# TVP1 FHD & TVP1 4K+ -> remove TVP1 FHD. + +SORTED_QUALITIES = ["4K+", "4K", "FHD", "HD", "SD"] + +def parse_arguments(): + parser = ArgumentParser() + parser.add_argument("-i", "--input", dest="input", help="Path to m3u+ file", required=True, type=Path) + parser.add_argument("-v", "--verbose", dest="verbose", help="Verbose output", action="store_true") + + return parser.parse_args() + +def debug(message: str): + global verbose + + if verbose: + print("[M3U-LOGO-MATCHER:DEBUG] " + message) + +def get_channels(playlist: tv_playlist.M3UPlaylist): + for channel in playlist: + yield channel.name + +def get_channel_quality(channel_name: str): + for quality in SORTED_QUALITIES: + if channel_name.endswith(quality): + return quality + + return None + +def truncate_channel_quality(channel_name: str): + for quality in SORTED_QUALITIES: + if channel_name.endswith(quality): + return channel_name[:-len(quality)].strip() + + return channel_name + +def real_name(channel: tv_playlist.IPTVChannel): + return channel.name.split(",")[-1] + +def find_duplicate_quality(playlist: tv_playlist.M3UPlaylist): + channels = playlist.get_channels() + + for c in channels: + # Check: + # example: TVP1 HD & TVP1 FHD -> remove TVP1 HD. + # example: TVP1 FHD & TVP1 HD -> remove TVP1 HD. + # example: TVP1 4K+ & TVP1 HD -> remove TVP1 HD. + + if any([ + ca for ca in channels + if + # Check if quality isn't the same + get_channel_quality(real_name(c)) != + get_channel_quality(real_name(ca)) != + None + and + # Check if channel name is the same + truncate_channel_quality(real_name(c)) == + truncate_channel_quality(real_name(ca)) + and + # Check if quality is better + SORTED_QUALITIES.index(get_channel_quality(real_name(c))) < + SORTED_QUALITIES.index(get_channel_quality(real_name(ca))) + ]): + debug(f"Found duplicate quality for {real_name(c)}") + worse_qualities = get_worse_qualities( + playlist = playlist, + channel = c + ) + + if len(worse_qualities) == 0: + continue + + yield c, worse_qualities + +def get_worse_qualities(playlist: tv_playlist.M3UPlaylist, channel: tv_playlist.IPTVChannel): + # Example: + # we assume that channel is TVP1 HD. + # in playlist we have TVP1 HD, TVP1 FHD, TVP1 4K+. + # we return TVP1 4K+, because it's the best quality. + + sorted_channels = [c for c in playlist + if truncate_channel_quality(real_name(c)) == + truncate_channel_quality(real_name(channel))] + qualities = [] + + for quality in SORTED_QUALITIES: + for c in sorted_channels: + if c.name.endswith(quality) and \ + SORTED_QUALITIES.index(quality) > \ + SORTED_QUALITIES.index(get_channel_quality(real_name(channel))): + + qualities.append(quality) + + return qualities + +def remove_duplicates(playlist: tv_playlist.M3UPlaylist): + duplicates = list(find_duplicate_quality(playlist)) + + print(f"Found {len(duplicates)} duplicates.") + for duplicate in duplicates: + c, worse_qualities = duplicate + debug(f"Preserving {real_name(c)}, because found worse qualities.") + + for quality in worse_qualities: + title_without_quality = truncate_channel_quality(real_name(c)) + + debug(f"Removing {title_without_quality} {quality}") + duplicates = [ + channel for channel in playlist + if + truncate_channel_quality(real_name(channel)) == + title_without_quality + and + get_channel_quality(real_name(channel)) == + quality + ] + + for duplicate in duplicates: + index = playlist.get_channels().index(duplicate) + + playlist.remove_channel(index) + +def process(arguments): + debug("Processing...") + playlist = tv_playlist.loadf(str(arguments.input)) + + remove_duplicates(playlist) + + # save playlist + with open(arguments.input, "w+") as f: + if arguments.m3u_plus: + f.write(playlist.to_m3u_plus_playlist()) + else: + f.write(playlist.to_m3u8_playlist()) + + print("All done!") + print("Thanks for using this script!") + +if __name__ == "__main__": + user_choice = input(""" + + WARNING! + This script will remove duplicates from your playlist. + However, it will OVERWRITE your playlist. + + We recommend you to make a backup of your playlist. + Do you want to continue? [y/n]: """) + + if user_choice.lower() != "y": + print("Exiting...") + exit(0) + + arguments = parse_arguments() + verbose = arguments.verbose + + process(arguments) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bd3a6f8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +m3u8 \ No newline at end of file