~ubuntu-branches/ubuntu/trusty/isrcsubmit/trusty-proposed

« back to all changes in this revision

Viewing changes to isrcsubmit.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Ramacher
  • Date: 2013-10-28 22:31:00 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20131028223100-0dba578bp2ggtp3e
Tags: 2.0.0~beta.5-1
* Upload to unstable.
* New upstream release.
* debian/patches: Removed, no longer needed.
* debian/control: Bump Standards-Version to 3.9.5.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
https://github.com/JonnyJD/musicbrainz-isrcsubmit
22
22
"""
23
23
 
24
 
__version__ = "2.0.0-beta.4"
 
24
__version__ = "2.0.0-beta.5"
25
25
AGENT_NAME = "isrcsubmit.py"
26
26
DEFAULT_SERVER = "musicbrainz.org"
27
27
# starting with highest priority
28
28
BACKENDS = ["mediatools", "media_info", "cdrdao", "libdiscid", "discisrc"]
29
 
BROWSERS = ["xdg-open", "x-www-browser", "iceweasel", "chromium", "opera"]
 
29
BROWSERS = ["xdg-open", "x-www-browser",
 
30
            "firefox", "chromium", "chrome", "opera"]
 
31
# The webbrowser module is used when nothing is found in this list.
 
32
# This especially happens on Windows and Mac OS X (browser mostly not in PATH)
30
33
 
31
34
import os
32
35
import re
34
37
import codecs
35
38
import getpass
36
39
import tempfile
 
40
import webbrowser
37
41
from datetime import datetime
38
42
from optparse import OptionParser
39
43
from subprocess import Popen, PIPE, call
179
183
            + " disc. Possible backends are: %s." % ", ".join(BACKENDS)
180
184
            + " They are tried in this order otherwise." )
181
185
    parser.add_option("--browser", metavar="BROWSER",
182
 
            help="Program to open urls. If not chosen, we try these:\n"
183
 
            + ", ".join(BROWSERS))
 
186
            help="Program to open URLs. This will be automatically detected"
 
187
            " for most setups, if not chosen manually.")
184
188
    parser.add_option("--force-submit", action="store_true", default=False,
185
189
            help="Always open TOC/disc ID in browser.")
186
190
    parser.add_option("--server", metavar="SERVER",
227
231
    We want to know if the user has a "sane" which we can trust.
228
232
    Unxutils has a broken 2.4 version. Which >= 2.16 should be fine.
229
233
    """
230
 
    devnull = open(os.devnull, "w")
231
 
    try:
232
 
        # "which" should at least find itself (even without searching which.exe)
233
 
        return_code = call(["which", "which"], stdout=devnull, stderr=devnull)
234
 
    except OSError:
235
 
        return False        # no which at all
236
 
    else:
237
 
        if (return_code == 0):
238
 
            return True
 
234
    with open(os.devnull, "w") as devnull:
 
235
        try:
 
236
            # "which" should at least find itself
 
237
            return_code = call(["which", "which"],
 
238
                               stdout=devnull, stderr=devnull)
 
239
        except OSError:
 
240
            return False        # no which at all
239
241
        else:
240
 
            print('warning: your version of the tool "which" is buggy/outdated')
241
 
            if os.name == "nt":
242
 
                print('         unxutils is old/broken, GnuWin32 is good.')
243
 
            return False
 
242
            if (return_code == 0):
 
243
                return True
 
244
            else:
 
245
                print('warning: your version of the tool "which"'
 
246
                      ' is buggy/outdated')
 
247
                if os.name == "nt":
 
248
                    print('         unxutils is old/broken, GnuWin32 is good.')
 
249
                return False
244
250
 
245
251
def get_prog_version(prog):
246
252
    if prog == "libdiscid":
260
266
    if program == "libdiscid":
261
267
        return "isrc" in discid.FEATURES
262
268
 
263
 
    devnull = open(os.devnull, "w")
264
 
    if options.sane_which:
265
 
        p_which = Popen(["which", program], stdout=PIPE, stderr=devnull)
266
 
        program_path = p_which.communicate()[0].strip()
267
 
        if p_which.returncode == 0:
268
 
            # check if it is only a symlink to another backend
269
 
            real_program = os.path.basename(os.path.realpath(program_path))
270
 
            if program != real_program and (
271
 
                    real_program in BACKENDS or real_program in BROWSERS):
272
 
                if strict:
273
 
                    print("WARNING: %s is a symlink to %s"
274
 
                          % (program, real_program))
275
 
                    return True
276
 
                else:
277
 
                    return False # use real program instead, or higher priority
278
 
            return True
279
 
        else:
280
 
            return False
281
 
    elif program in BACKENDS:
282
 
        try:
283
 
            # we just try to start these non-interactive console apps
284
 
            call([program], stdout=devnull, stderr=devnull)
285
 
        except OSError:
286
 
            return False
287
 
        else:
288
 
            return True
289
 
    else:
290
 
        return False
 
269
    with open(os.devnull, "w") as devnull:
 
270
        if options.sane_which:
 
271
            p_which = Popen(["which", program], stdout=PIPE, stderr=devnull)
 
272
            program_path = p_which.communicate()[0].strip()
 
273
            if p_which.returncode == 0:
 
274
                # check if it is only a symlink to another backend
 
275
                real_program = os.path.basename(os.path.realpath(program_path))
 
276
                if program != real_program and (
 
277
                        real_program in BACKENDS or real_program in BROWSERS):
 
278
                    if strict:
 
279
                        print("WARNING: %s is a symlink to %s"
 
280
                              % (program, real_program))
 
281
                        return True
 
282
                    else:
 
283
                        return False # use real program (target) instead
 
284
                return True
 
285
            else:
 
286
                return False
 
287
        elif program in BACKENDS:
 
288
            try:
 
289
                # we just try to start these non-interactive console apps
 
290
                call([program], stdout=devnull, stderr=devnull)
 
291
            except OSError:
 
292
                return False
 
293
            else:
 
294
                return True
 
295
        else:
 
296
            return False
291
297
 
292
298
def find_backend():
293
299
    """search for an available backend
312
318
        if has_program(browser):
313
319
            return browser
314
320
 
315
 
    # default to the first "real" browser in the list, as of now: firefox
316
 
    return BROWSERS[1]
 
321
    # This will use the webbrowser module to find a default
 
322
    return None
 
323
 
 
324
def open_browser(url, exit=False, submit=False):
 
325
    """open url in the selected browser, default if none
 
326
    """
 
327
    if options.browser:
 
328
        if exit:
 
329
            try:
 
330
                if os.name == "nt":
 
331
                    # silly but necessary for spaces in the path
 
332
                    os.execlp(options.browser, '"' + options.browser + '"', url)
 
333
                else:
 
334
                    # linux/unix works fine with spaces
 
335
                    os.execlp(options.browser, options.browser, url)
 
336
            except OSError as err:
 
337
                print_error("Couldn't open the url in %s: %s"
 
338
                            % (options.browser, str(err)))
 
339
                if submit:
 
340
                    print_error2("Please submit via:", url)
 
341
                sys.exit(1)
 
342
        else:
 
343
            try:
 
344
                if options.debug:
 
345
                    Popen([options.browser, url])
 
346
                else:
 
347
                    with open(os.devnull, "w") as devnull:
 
348
                        Popen([options.browser, url], stdout=devnull)
 
349
            except FileNotFoundError as err:
 
350
                print_error("Couldn't open the url in %s: %s"
 
351
                            % (options.browser, str(err)))
 
352
                if submit:
 
353
                    print_error2("Please submit via:", url)
 
354
    else:
 
355
        try:
 
356
            if options.debug:
 
357
                webbrowser.open(url)
 
358
            else:
 
359
                # this supresses stdout
 
360
                webbrowser.get().open(url)
 
361
        except webbrowser.Error as err:
 
362
            print_error("Couldn't open the url:", str(err))
 
363
            if submit:
 
364
                print_error2("Please submit via:", url)
 
365
        if exit:
 
366
            sys.exit(1)
317
367
 
318
368
def get_real_mac_device(option_device):
319
369
    """drutil takes numbers as drives.
381
431
        except AttributeError:
382
432
            sys.stdout.write(msg)
383
433
 
384
 
def print_release_position(release, pos):
385
 
    print_encoded("%d: %s - %s"
386
 
                  % (pos, release["artist-credit-phrase"], release["title"]))
387
 
    if release.get("status"):
388
 
        print("(%s)" % release["status"])
389
 
    else:
390
 
        print("")
 
434
def print_release(release, position=None):
 
435
    """Print information about a release.
 
436
 
 
437
    If the position is given, this should be an entry
 
438
    in a list of releases (choice)
 
439
    """
391
440
    country = (release.get("country") or "").ljust(2)
392
441
    date = (release.get("date") or "").ljust(10)
393
442
    barcode = (release.get("barcode") or "").rjust(13)
398
447
        if cat_number:
399
448
            catnumber_list.append(cat_number)
400
449
    catnumbers = ", ".join(catnumber_list)
401
 
    print_encoded("\t%s\t%s\t%s\t%s\n" % (country, date, barcode, catnumbers))
 
450
 
 
451
    if position is None:
 
452
        print_encoded("Artist:\t\t%s\n" % release["artist-credit-phrase"])
 
453
        print_encoded("Release:\t%s" % release["title"])
 
454
    else:
 
455
        print_encoded("%#2d:" % position)
 
456
        print_encoded("%s - %s" % (
 
457
                      release["artist-credit-phrase"], release["title"]))
 
458
    if release.get("status"):
 
459
        print("(%s)" % release["status"])
 
460
    else:
 
461
        print("")
 
462
    if position is None:
 
463
        print_encoded("Release Event:\t%s\t%s\n" % (date, country))
 
464
        print_encoded("Barcode:\t%s\n" % release.get("barcode") or "")
 
465
        print_encoded("Catalog No.:\t%s\n" % catnumbers)
 
466
        print_encoded("MusicBrainz ID:\t%s\n" % release["id"])
 
467
    else:
 
468
        print_encoded("\t%s\t%s\t%s\t%s\n" % (
 
469
                      country, date, barcode, catnumbers))
402
470
 
403
471
def print_error(*args):
404
472
    string_args = tuple([str(arg) for arg in args])
416
484
                % (options.backend, err.errno, err.strerror))
417
485
    sys.exit(1)
418
486
 
419
 
def ask_for_submission(url):
 
487
def ask_for_submission(url, print_url=False):
420
488
    if options.force_submit:
421
489
        submit_requested = True
422
490
    else:
424
492
        submit_requested = user_input(" [y/N] ") == "y"
425
493
 
426
494
    if submit_requested:
427
 
        try:
428
 
            if os.name == "nt":
429
 
                # silly but necessary for spaces in the path
430
 
                os.execlp(options.browser, '"' + options.browser + '"', url)
431
 
            else:
432
 
                # linux/unix works fine with spaces
433
 
                os.execlp(options.browser, options.browser, url)
434
 
        except OSError as err:
435
 
            print_error("Couldn't open the url in %s: %s"
436
 
                        % (options.browser, str(err)))
437
 
            print_error2("Please submit it via:", url)
438
 
            sys.exit(1)
439
 
    else:
 
495
        open_browser(url, exit=True, submit=True)
 
496
    elif print_url:
440
497
        print("Please submit the Disc ID with this url:")
441
498
        print(url)
442
 
        sys.exit(1)
443
499
 
444
500
class WebService2():
445
501
    """A web service wrapper that asks for a password when first needed.
460
516
        if not self.auth:
461
517
            print("")
462
518
            if self.username is None:
463
 
                printf("Please input your MusicBrainz username: ")
 
519
                printf("Please input your MusicBrainz username (empty=abort): ")
464
520
                self.username = user_input()
 
521
            if len(self.username) == 0:
 
522
                print("(aborted)")
 
523
                sys.exit(1)
465
524
            password = getpass.getpass(
466
525
                                    "Please input your MusicBrainz password: ")
467
526
            print("")
498
557
    def submit_isrcs(self, tracks2isrcs):
499
558
        if options.debug:
500
559
            print("tracks2isrcs: %s" % tracks2isrcs)
501
 
        try:
502
 
            self.authenticate()
503
 
            musicbrainzngs.submit_isrcs(tracks2isrcs)
504
 
        except AuthenticationError as err:
505
 
            print_error("Invalid credentials: %s" % err)
506
 
            sys.exit(1)
507
 
        except WebServiceError as err:
508
 
            print_error("Couldn't send ISRCs: %s" % err)
509
 
            sys.exit(1)
510
 
        else:
511
 
            print("Successfully submitted %d ISRCS." % len(tracks2isrcs))
 
560
        while True:
 
561
            try:
 
562
                self.authenticate()
 
563
                musicbrainzngs.submit_isrcs(tracks2isrcs)
 
564
            except AuthenticationError as err:
 
565
                print_error("Invalid credentials: %s" % err)
 
566
                self.auth = False
 
567
                self.username = None
 
568
                continue
 
569
            except WebServiceError as err:
 
570
                print_error("Couldn't send ISRCs: %s" % err)
 
571
                sys.exit(1)
 
572
            else:
 
573
                print("Successfully submitted %d ISRCS." % len(tracks2isrcs))
 
574
                break
512
575
 
513
576
 
514
577
 
537
600
        self._release = None
538
601
        self._backend = backend
539
602
        self._verified = verified
 
603
        self._asked_for_submission = False
540
604
        self._common_includes=["artists", "labels", "recordings", "isrcs",
541
605
                               "artist-credits"] # the last one only for cleanup
542
606
        self.read_disc()        # sets self._disc
565
629
        return url.replace("musicbrainz.org", options.server)
566
630
 
567
631
    @property
 
632
    def asked_for_submission(self):
 
633
        return self._asked_for_submission
 
634
 
 
635
    @property
568
636
    def release(self):
569
637
        """The corresponding MusicBrainz release
570
638
 
571
639
        This will ask the user to choose if the discID is ambiguous.
572
640
        """
573
641
        if self._release is None:
574
 
            self._release = self.get_release(self._verified)
 
642
            self.get_release(self._verified)
575
643
            # can still be None
576
644
        return self._release
577
645
 
604
672
            selected_release = None
605
673
        elif num_results > 1:
606
674
            print("\nThis Disc ID is ambiguous:")
 
675
            print(" 0: none of these\n")
 
676
            self._asked_for_submission = True
607
677
            for i in range(num_results):
608
678
                release = results[i]
609
679
                # printed list is 1..n, not 0..n-1 !
610
 
                print_release_position(release, i + 1)
 
680
                print_release(release, i + 1)
611
681
            try:
612
 
                num =  user_input("Which one do you want? [1-%d] "
 
682
                num =  user_input("Which one do you want? [0-%d] "
613
683
                                  % num_results)
614
 
                if int(num) not in range(1, num_results + 1):
 
684
                if int(num) not in range(0, num_results + 1):
615
685
                    raise IndexError
616
 
                selected_release = results[int(num) - 1]
 
686
                if int(num) == 0:
 
687
                    ask_for_submission(self.submission_url, print_url=True)
 
688
                    sys.exit(1)
 
689
                else:
 
690
                    selected_release = results[int(num) - 1]
617
691
            except (ValueError, IndexError):
618
692
                print_error("Invalid Choice")
619
693
                sys.exit(1)
648
722
        if chosen_release is None or options.force_submit:
649
723
            if verified:
650
724
                url = self.submission_url
651
 
                ask_for_submission(url) # submission will end the script
 
725
                ask_for_submission(url, print_url=True)
 
726
                sys.exit(1)
652
727
            else:
653
728
                print("recalculating to re-check..")
654
729
                self.read_disc()
655
730
                self.get_release(verified=True)
656
731
 
 
732
        self._release = chosen_release
657
733
        return chosen_release
658
734
 
659
735
 
687
763
    # redundant to "libdiscid", but this might be handy for prerelease testing
688
764
    elif backend == "discisrc":
689
765
        pattern = \
690
 
            br'Track\s+([0-9]+)\s+:\s+([A-Z]{2})-?([A-Z0-9]{3})-?(\d{2})-?(\d{5})'
 
766
            r'Track\s+([0-9]+)\s+:\s+([A-Z]{2})-?([A-Z0-9]{3})-?(\d{2})-?(\d{5})'
691
767
        try:
692
768
            if sys.platform == "darwin":
693
769
                device = get_real_mac_device(device)
696
772
        except OSError as err:
697
773
            backend_error(err)
698
774
        for line in isrcout:
 
775
            line = decode(line) # explicitely decode from pipe
699
776
            if options.debug:
700
777
                printf(line)    # already includes a newline
701
 
            if line.startswith(b"Track") and len(line) > 12:
 
778
            if line.startswith("Track") and len(line) > 12:
702
779
                match = re.search(pattern, line)
703
780
                if match is None:
704
781
                    print("can't find ISRC in: %s" % line)
706
783
                track_number = int(match.group(1))
707
784
                isrc = ("%s%s%s%s" % (match.group(2), match.group(3),
708
785
                                      match.group(4), match.group(5)))
709
 
                isrc = decode(isrc)
710
786
                backend_output.append((track_number, isrc))
711
787
 
712
788
    # media_info is a preview version of mediatools, both are for Windows
713
789
    # this does some kind of raw read
714
790
    elif backend in ["mediatools", "media_info"]:
715
791
        pattern = \
716
 
            br'ISRC\s+([0-9]+)\s+([A-Z]{2})-?([A-Z0-9]{3})-?(\d{2})-?(\d{5})'
 
792
            r'ISRC\s+([0-9]+)\s+([A-Z]{2})-?([A-Z0-9]{3})-?(\d{2})-?(\d{5})'
717
793
        if backend == "mediatools":
718
794
            args = [backend, "drive", device, "isrc"]
719
795
        else:
724
800
        except OSError as err:
725
801
            backend_error(err)
726
802
        for line in isrcout:
 
803
            line = decode(line) # explicitely decode from pipe
727
804
            if options.debug:
728
805
                printf(line)    # already includes a newline
729
 
            if line.startswith(b"ISRC") and not line.startswith(b"ISRCS"):
 
806
            if line.startswith("ISRC") and not line.startswith("ISRCS"):
730
807
                match = re.search(pattern, line)
731
808
                if match is None:
732
809
                    print("can't find ISRC in: %s" % line)
734
811
                track_number = int(match.group(1))
735
812
                isrc = ("%s%s%s%s" % (match.group(2), match.group(3),
736
813
                                      match.group(4), match.group(5)))
737
 
                isrc = decode(isrc)
738
814
                backend_output.append((track_number, isrc))
739
815
 
740
816
    # cdrdao will create a temp file and we delete it afterwards
788
864
            except OSError:
789
865
                pass
790
866
 
 
867
    devnull.close()
791
868
    return backend_output
792
869
 
793
870
def check_isrcs_local(backend_output, mb_tracks):
894
971
 
895
972
            url = "http://%s/isrc/%s" % (options.server, isrc)
896
973
            if user_input("Open ISRC in the browser? [Y/n] ") != "n":
897
 
                if options.debug:
898
 
                    Popen([options.browser, url])
899
 
                else:
900
 
                    devnull = open(os.devnull, "w")
901
 
                    Popen([options.browser, url], stdout=devnull)
 
974
                open_browser(url)
902
975
                user_input("(press <return> when done with this ISRC) ")
903
976
 
904
977
 
914
987
    disc = get_disc(options.device, options.backend)
915
988
    disc.get_release()
916
989
    print("")
917
 
    print_encoded('Artist:\t\t%s\n' % disc.release["artist-credit-phrase"])
918
 
    print_encoded('Release:\t%s\n' % disc.release["title"])
 
990
    print_release(disc.release)
 
991
    if not disc.asked_for_submission:
 
992
        print("")
 
993
        print("Is this information different for your release?")
 
994
        ask_for_submission(disc.submission_url)
919
995
 
920
996
    media = []
921
997
    for medium in disc.release["medium-list"]: