diff --git a/gui/slick/images/flags/af.png b/gui/slick/images/flags/afr.png similarity index 100% rename from gui/slick/images/flags/af.png rename to gui/slick/images/flags/afr.png diff --git a/gui/slick/images/flags/am.png b/gui/slick/images/flags/amh.png similarity index 100% rename from gui/slick/images/flags/am.png rename to gui/slick/images/flags/amh.png diff --git a/gui/slick/images/flags/ar.png b/gui/slick/images/flags/ara.png similarity index 100% rename from gui/slick/images/flags/ar.png rename to gui/slick/images/flags/ara.png diff --git a/gui/slick/images/flags/an.png b/gui/slick/images/flags/arg.png similarity index 100% rename from gui/slick/images/flags/an.png rename to gui/slick/images/flags/arg.png diff --git a/gui/slick/images/flags/as.png b/gui/slick/images/flags/asm.png similarity index 100% rename from gui/slick/images/flags/as.png rename to gui/slick/images/flags/asm.png diff --git a/gui/slick/images/flags/ae.png b/gui/slick/images/flags/ave.png similarity index 100% rename from gui/slick/images/flags/ae.png rename to gui/slick/images/flags/ave.png diff --git a/gui/slick/images/flags/ay.png b/gui/slick/images/flags/aym.png similarity index 100% rename from gui/slick/images/flags/ay.png rename to gui/slick/images/flags/aym.png diff --git a/gui/slick/images/flags/az.png b/gui/slick/images/flags/aze.png similarity index 100% rename from gui/slick/images/flags/az.png rename to gui/slick/images/flags/aze.png diff --git a/gui/slick/images/flags/ba.png b/gui/slick/images/flags/bak.png similarity index 100% rename from gui/slick/images/flags/ba.png rename to gui/slick/images/flags/bak.png diff --git a/gui/slick/images/flags/bm.png b/gui/slick/images/flags/bam.png similarity index 100% rename from gui/slick/images/flags/bm.png rename to gui/slick/images/flags/bam.png diff --git a/gui/slick/images/flags/be.png b/gui/slick/images/flags/bel.png similarity index 100% rename from gui/slick/images/flags/be.png rename to gui/slick/images/flags/bel.png diff --git a/gui/slick/images/flags/bn.png b/gui/slick/images/flags/ben.png similarity index 100% rename from gui/slick/images/flags/bn.png rename to gui/slick/images/flags/ben.png diff --git a/gui/slick/images/flags/bi.png b/gui/slick/images/flags/bis.png similarity index 100% rename from gui/slick/images/flags/bi.png rename to gui/slick/images/flags/bis.png diff --git a/gui/slick/images/flags/bo.png b/gui/slick/images/flags/bod.png similarity index 100% rename from gui/slick/images/flags/bo.png rename to gui/slick/images/flags/bod.png diff --git a/gui/slick/images/flags/bs.png b/gui/slick/images/flags/bos.png similarity index 100% rename from gui/slick/images/flags/bs.png rename to gui/slick/images/flags/bos.png diff --git a/gui/slick/images/flags/br.png b/gui/slick/images/flags/bre.png similarity index 100% rename from gui/slick/images/flags/br.png rename to gui/slick/images/flags/bre.png diff --git a/gui/slick/images/flags/bg.png b/gui/slick/images/flags/bul.png similarity index 100% rename from gui/slick/images/flags/bg.png rename to gui/slick/images/flags/bul.png diff --git a/gui/slick/images/flags/ca.png b/gui/slick/images/flags/cat.png similarity index 100% rename from gui/slick/images/flags/ca.png rename to gui/slick/images/flags/cat.png diff --git a/gui/slick/images/flags/cs.png b/gui/slick/images/flags/ces.png similarity index 100% rename from gui/slick/images/flags/cs.png rename to gui/slick/images/flags/ces.png diff --git a/gui/slick/images/flags/ch.png b/gui/slick/images/flags/cha.png similarity index 100% rename from gui/slick/images/flags/ch.png rename to gui/slick/images/flags/cha.png diff --git a/gui/slick/images/flags/cu.png b/gui/slick/images/flags/chu.png similarity index 100% rename from gui/slick/images/flags/cu.png rename to gui/slick/images/flags/chu.png diff --git a/gui/slick/images/flags/cv.png b/gui/slick/images/flags/chv.png similarity index 100% rename from gui/slick/images/flags/cv.png rename to gui/slick/images/flags/chv.png diff --git a/gui/slick/images/flags/kw.png b/gui/slick/images/flags/cor.png similarity index 100% rename from gui/slick/images/flags/kw.png rename to gui/slick/images/flags/cor.png diff --git a/gui/slick/images/flags/co.png b/gui/slick/images/flags/cos.png similarity index 100% rename from gui/slick/images/flags/co.png rename to gui/slick/images/flags/cos.png diff --git a/gui/slick/images/flags/cr.png b/gui/slick/images/flags/cre.png similarity index 100% rename from gui/slick/images/flags/cr.png rename to gui/slick/images/flags/cre.png diff --git a/gui/slick/images/flags/cy.png b/gui/slick/images/flags/cym.png similarity index 100% rename from gui/slick/images/flags/cy.png rename to gui/slick/images/flags/cym.png diff --git a/gui/slick/images/flags/da.png b/gui/slick/images/flags/dan.png similarity index 100% rename from gui/slick/images/flags/da.png rename to gui/slick/images/flags/dan.png diff --git a/gui/slick/images/flags/de.png b/gui/slick/images/flags/deu.png similarity index 100% rename from gui/slick/images/flags/de.png rename to gui/slick/images/flags/deu.png diff --git a/gui/slick/images/flags/dz.png b/gui/slick/images/flags/dzo.png similarity index 100% rename from gui/slick/images/flags/dz.png rename to gui/slick/images/flags/dzo.png diff --git a/gui/slick/images/flags/el.png b/gui/slick/images/flags/ell.png similarity index 100% rename from gui/slick/images/flags/el.png rename to gui/slick/images/flags/ell.png diff --git a/gui/slick/images/flags/en.png b/gui/slick/images/flags/eng.png similarity index 100% rename from gui/slick/images/flags/en.png rename to gui/slick/images/flags/eng.png diff --git a/gui/slick/images/flags/eo.png b/gui/slick/images/flags/epo.png similarity index 100% rename from gui/slick/images/flags/eo.png rename to gui/slick/images/flags/epo.png diff --git a/gui/slick/images/flags/et.png b/gui/slick/images/flags/est.png similarity index 100% rename from gui/slick/images/flags/et.png rename to gui/slick/images/flags/est.png diff --git a/gui/slick/images/flags/ee.png b/gui/slick/images/flags/ewe.png similarity index 100% rename from gui/slick/images/flags/ee.png rename to gui/slick/images/flags/ewe.png diff --git a/gui/slick/images/flags/fo.png b/gui/slick/images/flags/fao.png similarity index 100% rename from gui/slick/images/flags/fo.png rename to gui/slick/images/flags/fao.png diff --git a/gui/slick/images/flags/fa.png b/gui/slick/images/flags/fas.png similarity index 100% rename from gui/slick/images/flags/fa.png rename to gui/slick/images/flags/fas.png diff --git a/gui/slick/images/flags/fj.png b/gui/slick/images/flags/fij.png similarity index 100% rename from gui/slick/images/flags/fj.png rename to gui/slick/images/flags/fij.png diff --git a/gui/slick/images/flags/fi.png b/gui/slick/images/flags/fin.png similarity index 100% rename from gui/slick/images/flags/fi.png rename to gui/slick/images/flags/fin.png diff --git a/gui/slick/images/flags/fr.png b/gui/slick/images/flags/fra.png similarity index 100% rename from gui/slick/images/flags/fr.png rename to gui/slick/images/flags/fra.png diff --git a/gui/slick/images/flags/gd.png b/gui/slick/images/flags/gla.png similarity index 100% rename from gui/slick/images/flags/gd.png rename to gui/slick/images/flags/gla.png diff --git a/gui/slick/images/flags/ga.png b/gui/slick/images/flags/gle.png similarity index 100% rename from gui/slick/images/flags/ga.png rename to gui/slick/images/flags/gle.png diff --git a/gui/slick/images/flags/gl.png b/gui/slick/images/flags/glg.png similarity index 100% rename from gui/slick/images/flags/gl.png rename to gui/slick/images/flags/glg.png diff --git a/gui/slick/images/flags/gn.png b/gui/slick/images/flags/grn.png similarity index 100% rename from gui/slick/images/flags/gn.png rename to gui/slick/images/flags/grn.png diff --git a/gui/slick/images/flags/gu.png b/gui/slick/images/flags/guj.png similarity index 100% rename from gui/slick/images/flags/gu.png rename to gui/slick/images/flags/guj.png diff --git a/gui/slick/images/flags/ht.png b/gui/slick/images/flags/hat.png similarity index 100% rename from gui/slick/images/flags/ht.png rename to gui/slick/images/flags/hat.png diff --git a/gui/slick/images/flags/he.png b/gui/slick/images/flags/heb.png similarity index 100% rename from gui/slick/images/flags/he.png rename to gui/slick/images/flags/heb.png diff --git a/gui/slick/images/flags/hi.png b/gui/slick/images/flags/hin.png similarity index 100% rename from gui/slick/images/flags/hi.png rename to gui/slick/images/flags/hin.png diff --git a/gui/slick/images/flags/hr.png b/gui/slick/images/flags/hrv.png similarity index 100% rename from gui/slick/images/flags/hr.png rename to gui/slick/images/flags/hrv.png diff --git a/gui/slick/images/flags/hu.png b/gui/slick/images/flags/hun.png similarity index 100% rename from gui/slick/images/flags/hu.png rename to gui/slick/images/flags/hun.png diff --git a/gui/slick/images/flags/hy.png b/gui/slick/images/flags/hye.png similarity index 100% rename from gui/slick/images/flags/hy.png rename to gui/slick/images/flags/hye.png diff --git a/gui/slick/images/flags/io.png b/gui/slick/images/flags/ido.png similarity index 100% rename from gui/slick/images/flags/io.png rename to gui/slick/images/flags/ido.png diff --git a/gui/slick/images/flags/ie.png b/gui/slick/images/flags/ile.png similarity index 100% rename from gui/slick/images/flags/ie.png rename to gui/slick/images/flags/ile.png diff --git a/gui/slick/images/flags/id.png b/gui/slick/images/flags/ind.png similarity index 100% rename from gui/slick/images/flags/id.png rename to gui/slick/images/flags/ind.png diff --git a/gui/slick/images/flags/is.png b/gui/slick/images/flags/isl.png similarity index 100% rename from gui/slick/images/flags/is.png rename to gui/slick/images/flags/isl.png diff --git a/gui/slick/images/flags/it.png b/gui/slick/images/flags/ita.png similarity index 100% rename from gui/slick/images/flags/it.png rename to gui/slick/images/flags/ita.png diff --git a/gui/slick/images/flags/ja.png b/gui/slick/images/flags/jpn.png similarity index 100% rename from gui/slick/images/flags/ja.png rename to gui/slick/images/flags/jpn.png diff --git a/gui/slick/images/flags/kn.png b/gui/slick/images/flags/kan.png similarity index 100% rename from gui/slick/images/flags/kn.png rename to gui/slick/images/flags/kan.png diff --git a/gui/slick/images/flags/ka.png b/gui/slick/images/flags/kat.png similarity index 100% rename from gui/slick/images/flags/ka.png rename to gui/slick/images/flags/kat.png diff --git a/gui/slick/images/flags/kr.png b/gui/slick/images/flags/kau.png similarity index 100% rename from gui/slick/images/flags/kr.png rename to gui/slick/images/flags/kau.png diff --git a/gui/slick/images/flags/kk.png b/gui/slick/images/flags/kaz.png similarity index 100% rename from gui/slick/images/flags/kk.png rename to gui/slick/images/flags/kaz.png diff --git a/gui/slick/images/flags/km.png b/gui/slick/images/flags/khm.png similarity index 100% rename from gui/slick/images/flags/km.png rename to gui/slick/images/flags/khm.png diff --git a/gui/slick/images/flags/ki.png b/gui/slick/images/flags/kik.png similarity index 100% rename from gui/slick/images/flags/ki.png rename to gui/slick/images/flags/kik.png diff --git a/gui/slick/images/flags/rw.png b/gui/slick/images/flags/kin.png similarity index 100% rename from gui/slick/images/flags/rw.png rename to gui/slick/images/flags/kin.png diff --git a/gui/slick/images/flags/ky.png b/gui/slick/images/flags/kir.png similarity index 100% rename from gui/slick/images/flags/ky.png rename to gui/slick/images/flags/kir.png diff --git a/gui/slick/images/flags/kg.png b/gui/slick/images/flags/kon.png similarity index 100% rename from gui/slick/images/flags/kg.png rename to gui/slick/images/flags/kon.png diff --git a/gui/slick/images/flags/ko.png b/gui/slick/images/flags/kor.png similarity index 100% rename from gui/slick/images/flags/ko.png rename to gui/slick/images/flags/kor.png diff --git a/gui/slick/images/flags/la.png b/gui/slick/images/flags/lat.png similarity index 100% rename from gui/slick/images/flags/la.png rename to gui/slick/images/flags/lat.png diff --git a/gui/slick/images/flags/lv.png b/gui/slick/images/flags/lav.png similarity index 100% rename from gui/slick/images/flags/lv.png rename to gui/slick/images/flags/lav.png diff --git a/gui/slick/images/flags/li.png b/gui/slick/images/flags/lim.png similarity index 100% rename from gui/slick/images/flags/li.png rename to gui/slick/images/flags/lim.png diff --git a/gui/slick/images/flags/lt.png b/gui/slick/images/flags/lit.png similarity index 100% rename from gui/slick/images/flags/lt.png rename to gui/slick/images/flags/lit.png diff --git a/gui/slick/images/flags/lb.png b/gui/slick/images/flags/ltz.png similarity index 100% rename from gui/slick/images/flags/lb.png rename to gui/slick/images/flags/ltz.png diff --git a/gui/slick/images/flags/lu.png b/gui/slick/images/flags/lub.png similarity index 100% rename from gui/slick/images/flags/lu.png rename to gui/slick/images/flags/lub.png diff --git a/gui/slick/images/flags/mh.png b/gui/slick/images/flags/mah.png similarity index 100% rename from gui/slick/images/flags/mh.png rename to gui/slick/images/flags/mah.png diff --git a/gui/slick/images/flags/ml.png b/gui/slick/images/flags/mal.png similarity index 100% rename from gui/slick/images/flags/ml.png rename to gui/slick/images/flags/mal.png diff --git a/gui/slick/images/flags/mr.png b/gui/slick/images/flags/mar.png similarity index 100% rename from gui/slick/images/flags/mr.png rename to gui/slick/images/flags/mar.png diff --git a/gui/slick/images/flags/mk.png b/gui/slick/images/flags/mkd.png similarity index 100% rename from gui/slick/images/flags/mk.png rename to gui/slick/images/flags/mkd.png diff --git a/gui/slick/images/flags/mg.png b/gui/slick/images/flags/mlg.png similarity index 100% rename from gui/slick/images/flags/mg.png rename to gui/slick/images/flags/mlg.png diff --git a/gui/slick/images/flags/mt.png b/gui/slick/images/flags/mlt.png similarity index 100% rename from gui/slick/images/flags/mt.png rename to gui/slick/images/flags/mlt.png diff --git a/gui/slick/images/flags/mn.png b/gui/slick/images/flags/mon.png similarity index 100% rename from gui/slick/images/flags/mn.png rename to gui/slick/images/flags/mon.png diff --git a/gui/slick/images/flags/ms.png b/gui/slick/images/flags/msa.png similarity index 100% rename from gui/slick/images/flags/ms.png rename to gui/slick/images/flags/msa.png diff --git a/gui/slick/images/flags/my.png b/gui/slick/images/flags/mya.png similarity index 100% rename from gui/slick/images/flags/my.png rename to gui/slick/images/flags/mya.png diff --git a/gui/slick/images/flags/na.png b/gui/slick/images/flags/nau.png similarity index 100% rename from gui/slick/images/flags/na.png rename to gui/slick/images/flags/nau.png diff --git a/gui/slick/images/flags/nr.png b/gui/slick/images/flags/nbl.png similarity index 100% rename from gui/slick/images/flags/nr.png rename to gui/slick/images/flags/nbl.png diff --git a/gui/slick/images/flags/ng.png b/gui/slick/images/flags/ndo.png similarity index 100% rename from gui/slick/images/flags/ng.png rename to gui/slick/images/flags/ndo.png diff --git a/gui/slick/images/flags/ne.png b/gui/slick/images/flags/nep.png similarity index 100% rename from gui/slick/images/flags/ne.png rename to gui/slick/images/flags/nep.png diff --git a/gui/slick/images/flags/nl.png b/gui/slick/images/flags/nld.png similarity index 100% rename from gui/slick/images/flags/nl.png rename to gui/slick/images/flags/nld.png diff --git a/gui/slick/images/flags/no.png b/gui/slick/images/flags/nor.png similarity index 100% rename from gui/slick/images/flags/no.png rename to gui/slick/images/flags/nor.png diff --git a/gui/slick/images/flags/oc.png b/gui/slick/images/flags/oci.png similarity index 100% rename from gui/slick/images/flags/oc.png rename to gui/slick/images/flags/oci.png diff --git a/gui/slick/images/flags/om.png b/gui/slick/images/flags/orm.png similarity index 100% rename from gui/slick/images/flags/om.png rename to gui/slick/images/flags/orm.png diff --git a/gui/slick/images/flags/pa.png b/gui/slick/images/flags/pan.png similarity index 100% rename from gui/slick/images/flags/pa.png rename to gui/slick/images/flags/pan.png diff --git a/gui/slick/images/flags/pl.png b/gui/slick/images/flags/pol.png similarity index 100% rename from gui/slick/images/flags/pl.png rename to gui/slick/images/flags/pol.png diff --git a/gui/slick/images/flags/pt.png b/gui/slick/images/flags/por.png similarity index 100% rename from gui/slick/images/flags/pt.png rename to gui/slick/images/flags/por.png diff --git a/gui/slick/images/flags/ps.png b/gui/slick/images/flags/pus.png similarity index 100% rename from gui/slick/images/flags/ps.png rename to gui/slick/images/flags/pus.png diff --git a/gui/slick/images/flags/ro.png b/gui/slick/images/flags/ron.png similarity index 100% rename from gui/slick/images/flags/ro.png rename to gui/slick/images/flags/ron.png diff --git a/gui/slick/images/flags/ru.png b/gui/slick/images/flags/rus.png similarity index 100% rename from gui/slick/images/flags/ru.png rename to gui/slick/images/flags/rus.png diff --git a/gui/slick/images/flags/sg.png b/gui/slick/images/flags/sag.png similarity index 100% rename from gui/slick/images/flags/sg.png rename to gui/slick/images/flags/sag.png diff --git a/gui/slick/images/flags/sa.png b/gui/slick/images/flags/san.png similarity index 100% rename from gui/slick/images/flags/sa.png rename to gui/slick/images/flags/san.png diff --git a/gui/slick/images/flags/si.png b/gui/slick/images/flags/sin.png similarity index 100% rename from gui/slick/images/flags/si.png rename to gui/slick/images/flags/sin.png diff --git a/gui/slick/images/flags/sk.png b/gui/slick/images/flags/slk.png similarity index 100% rename from gui/slick/images/flags/sk.png rename to gui/slick/images/flags/slk.png diff --git a/gui/slick/images/flags/sl.png b/gui/slick/images/flags/slv.png similarity index 100% rename from gui/slick/images/flags/sl.png rename to gui/slick/images/flags/slv.png diff --git a/gui/slick/images/flags/se.png b/gui/slick/images/flags/sme.png similarity index 100% rename from gui/slick/images/flags/se.png rename to gui/slick/images/flags/sme.png diff --git a/gui/slick/images/flags/sm.png b/gui/slick/images/flags/smo.png similarity index 100% rename from gui/slick/images/flags/sm.png rename to gui/slick/images/flags/smo.png diff --git a/gui/slick/images/flags/sn.png b/gui/slick/images/flags/sna.png similarity index 100% rename from gui/slick/images/flags/sn.png rename to gui/slick/images/flags/sna.png diff --git a/gui/slick/images/flags/sd.png b/gui/slick/images/flags/snd.png similarity index 100% rename from gui/slick/images/flags/sd.png rename to gui/slick/images/flags/snd.png diff --git a/gui/slick/images/flags/so.png b/gui/slick/images/flags/som.png similarity index 100% rename from gui/slick/images/flags/so.png rename to gui/slick/images/flags/som.png diff --git a/gui/slick/images/flags/st.png b/gui/slick/images/flags/sot.png similarity index 100% rename from gui/slick/images/flags/st.png rename to gui/slick/images/flags/sot.png diff --git a/gui/slick/images/flags/es.png b/gui/slick/images/flags/spa.png similarity index 100% rename from gui/slick/images/flags/es.png rename to gui/slick/images/flags/spa.png diff --git a/gui/slick/images/flags/sq.png b/gui/slick/images/flags/sqi.png similarity index 100% rename from gui/slick/images/flags/sq.png rename to gui/slick/images/flags/sqi.png diff --git a/gui/slick/images/flags/sc.png b/gui/slick/images/flags/srd.png similarity index 100% rename from gui/slick/images/flags/sc.png rename to gui/slick/images/flags/srd.png diff --git a/gui/slick/images/flags/sr.png b/gui/slick/images/flags/srp.png similarity index 100% rename from gui/slick/images/flags/sr.png rename to gui/slick/images/flags/srp.png diff --git a/gui/slick/images/flags/sv.png b/gui/slick/images/flags/swe.png similarity index 100% rename from gui/slick/images/flags/sv.png rename to gui/slick/images/flags/swe.png diff --git a/gui/slick/images/flags/tt.png b/gui/slick/images/flags/tat.png similarity index 100% rename from gui/slick/images/flags/tt.png rename to gui/slick/images/flags/tat.png diff --git a/gui/slick/images/flags/tg.png b/gui/slick/images/flags/tgk.png similarity index 100% rename from gui/slick/images/flags/tg.png rename to gui/slick/images/flags/tgk.png diff --git a/gui/slick/images/flags/tl.png b/gui/slick/images/flags/tgl.png similarity index 100% rename from gui/slick/images/flags/tl.png rename to gui/slick/images/flags/tgl.png diff --git a/gui/slick/images/flags/th.png b/gui/slick/images/flags/tha.png similarity index 100% rename from gui/slick/images/flags/th.png rename to gui/slick/images/flags/tha.png diff --git a/gui/slick/images/flags/to.png b/gui/slick/images/flags/ton.png similarity index 100% rename from gui/slick/images/flags/to.png rename to gui/slick/images/flags/ton.png diff --git a/gui/slick/images/flags/tn.png b/gui/slick/images/flags/tsn.png similarity index 100% rename from gui/slick/images/flags/tn.png rename to gui/slick/images/flags/tsn.png diff --git a/gui/slick/images/flags/tk.png b/gui/slick/images/flags/tuk.png similarity index 100% rename from gui/slick/images/flags/tk.png rename to gui/slick/images/flags/tuk.png diff --git a/gui/slick/images/flags/tr.png b/gui/slick/images/flags/tur.png similarity index 100% rename from gui/slick/images/flags/tr.png rename to gui/slick/images/flags/tur.png diff --git a/gui/slick/images/flags/tw.png b/gui/slick/images/flags/twi.png similarity index 100% rename from gui/slick/images/flags/tw.png rename to gui/slick/images/flags/twi.png diff --git a/gui/slick/images/flags/ug.png b/gui/slick/images/flags/uig.png similarity index 100% rename from gui/slick/images/flags/ug.png rename to gui/slick/images/flags/uig.png diff --git a/gui/slick/images/flags/uk.png b/gui/slick/images/flags/ukr.png similarity index 100% rename from gui/slick/images/flags/uk.png rename to gui/slick/images/flags/ukr.png diff --git a/gui/slick/images/flags/und.png b/gui/slick/images/flags/und.png new file mode 100644 index 0000000000000000000000000000000000000000..af9249bc317b724a8923e1447c60977dc9a91827 Binary files /dev/null and b/gui/slick/images/flags/und.png differ diff --git a/gui/slick/images/flags/uz.png b/gui/slick/images/flags/uzb.png similarity index 100% rename from gui/slick/images/flags/uz.png rename to gui/slick/images/flags/uzb.png diff --git a/gui/slick/images/flags/ve.png b/gui/slick/images/flags/ven.png similarity index 100% rename from gui/slick/images/flags/ve.png rename to gui/slick/images/flags/ven.png diff --git a/gui/slick/images/flags/vi.png b/gui/slick/images/flags/vie.png similarity index 100% rename from gui/slick/images/flags/vi.png rename to gui/slick/images/flags/vie.png diff --git a/gui/slick/images/flags/za.png b/gui/slick/images/flags/zha.png similarity index 100% rename from gui/slick/images/flags/za.png rename to gui/slick/images/flags/zha.png diff --git a/gui/slick/images/flags/zh.png b/gui/slick/images/flags/zho.png similarity index 100% rename from gui/slick/images/flags/zh.png rename to gui/slick/images/flags/zho.png diff --git a/gui/slick/interfaces/default/config_subtitles.tmpl b/gui/slick/interfaces/default/config_subtitles.tmpl index 845904fa549209ba2a6ec54d228df848f1931106..91d2139f8d96da70e8ea5e5bfbd8b33570731d76 100644 --- a/gui/slick/interfaces/default/config_subtitles.tmpl +++ b/gui/slick/interfaces/default/config_subtitles.tmpl @@ -19,7 +19,7 @@ \$(document).ready(function() { \$("#subtitles_languages").tokenInput( [ - <%=",\r\n".join("{id: \"" + lang.alpha2 + "\", name: \"" + lang.name + "\"}" for lang in subtitles.subtitleLanguageFilter())%> + <%=",\r\n".join("{id: \"" + lang.alpha3 + "\", name: \"" + lang.name + "\"}" for lang in subtitles.subtitleLanguageFilter())%> ], { method: "POST", @@ -29,7 +29,7 @@ [ <%= - ",\r\n".join("{id: \"" + lang + "\", name: \"" + Language.fromalpha2(lang).name + "\"}" for lang in sickbeard.SUBTITLES_LANGUAGES) if sickbeard.SUBTITLES_LANGUAGES != '' else '' + ",\r\n".join("{id: \"" + Language.fromietf(lang).alpha3 + "\", name: \"" + Language.fromietf(lang).name + "\"}" for lang in subtitles.wantedLanguages()) if subtitles.wantedLanguages() else '' %> ] } @@ -143,19 +143,17 @@ <fieldset class="component-group-list" style="margin-left: 50px; margin-top:36px"> <ul id="service_order_list"> #for $curService in $sickbeard.subtitles.sortedServiceList(): - #set $curName = $curService.id - <li class="ui-state-default" id="$curName"> - <input type="checkbox" id="enable_$curName" class="service_enabler" #if $curService.enabled then "checked=\"checked\"" else ""#/> - ##set $provider_url = $curService.url - ##<a href="<%= anon_url(provider_url) %>" class="imgLink" target="_new"> - <img src="$sbRoot/images/subtitles/$curService.image" alt="$curService.name" title="$curService.name" width="16" height="16" style="vertical-align:middle;"/> - ##</a> - <span style="vertical-align:middle;">$curService.name.capitalize()</span> + <li class="ui-state-default" id="$curService['name']"> + <input type="checkbox" id="enable_$curService['name']" class="service_enabler" #if $curService['enabled'] then "checked=\"checked\"" else ""#/> + <a href="<%= anon_url(curService['url']) %>" class="imgLink" target="_new"> + <img src="$sbRoot/images/subtitles/$curService.image" alt="$curService['url']" title="$curService['url']" width="16" height="16" style="vertical-align:middle;"/> + </a> + <span style="vertical-align:middle;">$curService['name'].capitalize()</span> <span class="ui-icon ui-icon-arrowthick-2-n-s pull-right" style="vertical-align:middle;"></span> </li> #end for </ul> - <input type="hidden" name="service_order" id="service_order" value="<%=" ".join([x.get('id')+':'+str(int(x.get('enabled'))) for x in sickbeard.subtitles.sortedServiceList()])%>"/> + <input type="hidden" name="service_order" id="service_order" value="<%=" ".join(['%s:%d' % (x['name'], x['enabled']) for x in sickbeard.subtitles.sortedServiceList()])%>"/> <br/><input type="submit" class="btn config_submitter" value="Save Changes" /><br/> </fieldset> diff --git a/gui/slick/interfaces/default/displayShow.tmpl b/gui/slick/interfaces/default/displayShow.tmpl index 30decd050d83e36c1ec7970dca02f8f38c64944f..4dbd73eda10645c751d35f229f44081b75780a65 100644 --- a/gui/slick/interfaces/default/displayShow.tmpl +++ b/gui/slick/interfaces/default/displayShow.tmpl @@ -286,9 +286,10 @@ </table> <table style="width:180px; float: right; vertical-align: middle; height: 100%;"> - <tr><td class="showLegend">Info Language:</td><td><img src="$sbRoot/images/flags/${show.lang}.png" width="16" height="11" alt="$show.lang" title="$show.lang" /></td></tr> + #set $info_flag = $babelfish.Language.fromietf($show.lang).alpha3 if $subtitles.isValidLanguage($show.lang) else 'unknown' + <tr><td class="showLegend">Info Language:</td><td><img src="$sbRoot/images/flags/${info_flag}.png" width="16" height="11" alt="$show.lang" title="$show.lang" onError="this.onerror=null;this.src='$sbRoot/images/flags/unknown.png';"/></td></tr> #if $sickbeard.USE_SUBTITLES - <tr><td class="showLegend">Subtitles: </td><td><img src="$sbRoot/images/#if int($show.subtitles) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> + <tr><td class="showLegend">Subtitles: </td><td><img src="$sbRoot/images/#if $show.subtitles then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> #end if <tr><td class="showLegend">Flat Folders: </td><td><img src="$sbRoot/images/#if $show.flatten_folders == 1 or $sickbeard.NAMING_FORCE_FOLDERS then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> <tr><td class="showLegend">Paused: </td><td><img src="$sbRoot/images/#if int($show.paused) == 1 then "yes16.png\" alt=\"Y" else "no16.png\" alt=\"N"#" width="16" height="16" /></td></tr> @@ -579,15 +580,10 @@ #end if </td> <td class="col-subtitles" align="center"> - #if $epResult["subtitles"]: - #for $sub_lang in [babelfish.Language.fromietf(x.strip()) for x in $epResult["subtitles"].split(',') if x.strip() in babelfish.language_converters['alpha2'].codes]: - #if $sub_lang.alpha2 != u'': - <img src="$sbRoot/images/flags/${sub_lang.alpha2}.png" width="16" height="11" alt="${sub_lang.name}" /> - #else - <img src="$sbRoot/images/flags/unknown.png" width="16" height="11" alt="Unknown" /> - #end if - #end for - #end if + #for $sub_lang in [babelfish.Language.fromietf(x) for x in $epResult["subtitles"].split(',') if $epResult["subtitles"]]: + #set $flag = $sub_lang.alpha3 if $hasattr($sub_lang, 'alpha3') and $sub_lang.alpha3 else $sub_lang.alpha2 if $hasattr($sub_lang, 'alpha2') and $sub_lang.alpha2 else 'unknown' + <img src="$sbRoot/images/flags/${flag}.png" width="16" height="11" alt="${sub_lang.name}" onError="this.onerror=null;this.src='$sbRoot/images/flags/unknown.png';" /> + #end for </td> #set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($epResult["status"])) #if $curQuality != Quality.NONE: @@ -603,7 +599,7 @@ <a class="epSearch" id="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" name="#echo $str($show.indexerid)+'x'+$str(epResult["season"])+'x'+$str(epResult["episode"])#" href="searchEpisode?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/search16.png" width="16" height="16" alt="search" title="Manual Search" /></a> #end if #end if - #if $sickbeard.USE_SUBTITLES and $show.subtitles and len(set(str($epResult["subtitles"]).split(',')).intersection(set($subtitles.wantedLanguages()))) < len($subtitles.wantedLanguages()) and $epResult["location"] + #if $sickbeard.USE_SUBTITLES and $show.subtitles and $epResult["location"] and frozenset($subtitles.wantedLanguages()).difference($epResult["subtitles"]) <a class="epSubtitlesSearch" href="searchEpisodeSubtitles?show=$show.indexerid&season=$epResult["season"]&episode=$epResult["episode"]"><img src="$sbRoot/images/closed_captioning.png" height="16" alt="search subtitles" title="Search Subtitles" /></a> #end if </td> diff --git a/gui/slick/interfaces/default/history.tmpl b/gui/slick/interfaces/default/history.tmpl index 2123adea52b91ad1c39ce4feeef528e71bd9ab78..93cb143503aec5eef8b33231e82adc2a221bb4a1 100644 --- a/gui/slick/interfaces/default/history.tmpl +++ b/gui/slick/interfaces/default/history.tmpl @@ -131,13 +131,9 @@ <td class="tvShow" width="35%"><a href="$sbRoot/home/displayShow?show=$hItem["showid"]#season-$hItem["season"]">$hItem["show_name"] - <%="S%02i" % int(hItem["season"])+"E%02i" % int(hItem["episode"]) %>#if "proper" in $hItem["resource"].lower() or "repack" in $hItem["resource"].lower() then ' <span class="quality Proper">Proper</span>' else ""#</a></td> <td align="center" #if $curStatus == SUBTITLED then 'class="subtitles_column"' else ''#> #if $curStatus == SUBTITLED: - #if $sickbeard.SUBTITLES_MULTI: - <img width="16" height="11" style="vertical-align:middle;" src="$sbRoot/images/flags/<%= hItem["resource"][len(hItem["resource"])-6:len(hItem["resource"])-4]+'.png'%>" onError="this.onerror=null;this.src='$sbRoot/images/flags/unknown.png';"> - #else - <img width="16" height="11" style="vertical-align:middle;" src="$sbRoot/images/flags/unknown.png"> - #end if + <img width="16" height="11" style="vertical-align:middle;" src="$sbRoot/images/flags/${hItem['resource']}.png" onError="this.onerror=null;this.src='$sbRoot/images/flags/unknown.png';"> #end if - <span style="cursor: help; vertical-align:middle;" title="$os.path.basename($hItem["resource"])">$statusStrings[$curStatus]</span> + <span style="cursor: help; vertical-align:middle;" title="$os.path.basename($hItem['resource'])">$statusStrings[$curStatus]</span> </td> <td align="center"> #if $curStatus == DOWNLOADED: @@ -153,11 +149,11 @@ #else: <img src="$sbRoot/images/providers/missing.png" width="16" height="16" style="vertical-align:middle;" title="missing provider"/> <span style="vertical-align:middle;">Missing Provider</span> #end if - #else: - <img src="$sbRoot/images/subtitles/<%=hItem["provider"]+'.png' %>" width="16" height="16" style="vertical-align:middle;" /> <span style="vertical-align:middle;"><%=hItem["provider"].capitalize()%></span> - #end if + #else: + <img src="$sbRoot/images/flags/${hItem['provider']}.png" width="16" height="11" style="vertical-align:middle;" /> <span style="vertical-align:middle;"><%=hItem["provider"].capitalize()%></span> #end if #end if + #end if </td> <span style="display: none;">$curQuality</span> <td align="center"><span class="quality $Quality.qualityStrings[$curQuality].replace("720p","HD720p").replace("1080p","HD1080p").replace("HDTV", "HD720p")">$Quality.qualityStrings[$curQuality]</span></td> @@ -226,9 +222,9 @@ #for $action in sorted($hItem["actions"]): #set $curStatus, $curQuality = $Quality.splitCompositeStatus(int($action["action"])) #if $curStatus == SUBTITLED: - <img src="$sbRoot/images/subtitles/<%=action["provider"]+'.png' %>" width="16" height="16" style="vertical-align:middle;" alt="$action["provider"]" title="<%=action["provider"].capitalize()%>: $os.path.basename($action["resource"])"/> + <img src="$sbRoot/images/subtitles/$action['provider'].png" %>" width="16" height="16" style="vertical-align:middle;" alt="$action["provider"]" title="<%=action["provider"].capitalize()%>: $os.path.basename($action["resource"])"/> <span style="vertical-align:middle;"> / </span> - <img width="16" height="11" style="vertical-align:middle;" src="$sbRoot/images/flags/<%= action["resource"][len(action["resource"])-6:len(action["resource"])-4]+'.png'%>" style="vertical-align: middle !important;"> + <img width="16" height="11" style="vertical-align:middle;" src="$sbRoot/images/flags/$action['resource'].png" onError="this.onerror=null;this.src='$sbRoot/images/flags/unknown.png';" style="vertical-align: middle !important;"> #end if #end for diff --git a/gui/slick/interfaces/default/manage_subtitleMissed.tmpl b/gui/slick/interfaces/default/manage_subtitleMissed.tmpl index 8a2ff96b02c1f5441d55a2f099d12358d6e54b51..ecc04db93c9cb61acbf7a4197cfe2f7aa9a6cced 100644 --- a/gui/slick/interfaces/default/manage_subtitleMissed.tmpl +++ b/gui/slick/interfaces/default/manage_subtitleMissed.tmpl @@ -1,7 +1,8 @@ -#import sickbeard +#from sickbeard import subtitles #import subliminal #import babelfish #import datetime +#import sickbeard #from sickbeard import common #set global $title="Episode Overview" #set global $header="Episode Overview" @@ -30,8 +31,8 @@ <form action="$sbRoot/manage/subtitleMissed" method="get"> Manage episodes without <select name="whichSubs" class="form-control form-control-inline input-sm"> <option value="all">All</option> -#for $sub_lang in [$babelfish.Language.fromietf(x.strip()) for x in $sickbeard.SUBTITLES_LANGUAGES]: -#<option value="$sub_lang.alpha2">$sub_lang.name</option> +#for $sub_lang in [$babelfish.Language.fromietf(x) for x in $subtitles.wantedLanguages]: +<option value="$sub_lang.alpha3">$sub_lang.name</option> #end for </select> subtitles diff --git a/sickbeard/history.py b/sickbeard/history.py index 2df2590477ba97bff30202590549780b4e298834..773aa1c09996f7f851abd8e80affec66400900ce 100644 --- a/sickbeard/history.py +++ b/sickbeard/history.py @@ -77,11 +77,12 @@ def logDownload(episode, filename, new_ep_quality, release_group=None, version=- def logSubtitle(showid, season, episode, status, subtitleResult): - resource = subtitleResult.path - provider = subtitleResult.service + resource = subtitleResult + provider = subtitleResult status, quality = Quality.splitCompositeStatus(status) action = Quality.compositeStatus(SUBTITLED, quality) + # FIXME: Broken with new subliminal _logHistoryItem(action, showid, season, episode, quality, resource, provider) diff --git a/sickbeard/postProcessor.py b/sickbeard/postProcessor.py index 216837555ca355c2a6a7307ae594d2fbe22193c7..06b262e50a57a2865ad0a2c5356bb3536b03db10 100644 --- a/sickbeard/postProcessor.py +++ b/sickbeard/postProcessor.py @@ -37,7 +37,7 @@ from sickbeard import notifiers from sickbeard import show_name_helpers from sickbeard import failed_history from sickbeard import name_cache - +from sickbeard import subtitles from sickbeard import encodingKludge as ek from sickbeard.exceptions import ex @@ -300,7 +300,7 @@ class PostProcessor(object): # check if file have subtitles language if os.path.splitext(cur_extension)[1][1:] in common.subtitleExtensions: cur_lang = os.path.splitext(cur_extension)[0] - if cur_lang in sickbeard.SUBTITLES_LANGUAGES: + if cur_lang in subtitles.wantedSubtitles(): cur_extension = cur_lang + os.path.splitext(cur_extension)[1] # replace .nfo with .nfo-orig to avoid conflicts diff --git a/sickbeard/subtitles.py b/sickbeard/subtitles.py index 81fbb3ee32cfc3f83ea23490d50386c03047bc0f..be6f4672db20e24a55cc42afde1e4064efb52cac 100644 --- a/sickbeard/subtitles.py +++ b/sickbeard/subtitles.py @@ -31,35 +31,52 @@ import babelfish subliminal.cache_region.configure('dogpile.cache.memory') +provider_urls = {'addic7ed': 'http://www.addic7ed.com', + 'opensubtitles': 'http://www.opensubtitles.org', + 'podnapisi': 'http://www.podnapisi.net', + 'thesubdb': 'http://www.thesubdb.com', + 'tvsubtitles': 'http://www.tvsubtitles.net' + } + SINGLE = 'und' def sortedServiceList(): newList = [] + lmgtfy = 'http://lmgtfy.com/?q=' curIndex = 0 for curService in sickbeard.SUBTITLES_SERVICES_LIST: if curService in subliminal.provider_manager.available_providers: - curServiceDict = {'id': curService, 'image': curService+'.png', 'name': curService, 'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[curIndex] == 1} - newList.append(curServiceDict) + newList.append({'name': curService, + 'url': provider_urls[curService] if curService in provider_urls else lmgtfy % curService, + 'image': curService + '.png', + 'enabled': sickbeard.SUBTITLES_SERVICES_ENABLED[curIndex] == 1 + }) curIndex += 1 - # add any services that are missing from that list for curService in subliminal.provider_manager.available_providers: - if curService not in [x['id'] for x in newList]: - curServiceDict = {'id': curService, 'image': curService+'.png', 'name': curService, 'enabled': False} - newList.append(curServiceDict) + if curService not in [x['name'] for x in newList]: + newList.append({'name': curService, + 'url': provider_urls[curService] if curService in provider_urls else lmgtfy % curService, + 'image': curService + '.png', + 'enabled': False, + }) return newList - def getEnabledServiceList(): return [x['name'] for x in sortedServiceList() if x['enabled']] - + def isValidLanguage(language): - return language if language in babelfish.language_converters['alpha2'].codes else u'' + try: + langObj = babelfish.Language.fromietf(language) + except: + return False + return True -def getLanguageName(selectLang): - return babelfish.Language.fromalpha2(selectLang).name +def getLanguageName(language): + return babelfish.Language.fromietf(language).name +# TODO: Filter here for non-languages in sickbeard.SUBTITLES_LANGUAGES def wantedLanguages(sqlLike = False): wantedLanguages = sorted(sickbeard.SUBTITLES_LANGUAGES) if sqlLike: @@ -68,19 +85,31 @@ def wantedLanguages(sqlLike = False): def subtitlesLanguages(video_path): """Return a list detected subtitles for the given video file""" - + resultList = [] languages = subliminal.video.scan_subtitle_languages(video_path) - return u','.join([lang.alpha2 for lang in languages if lang.alpha2 in babelfish.language_converters['alpha2'].codes]) -# Return a list with languages that have alpha2 code + for language in languages: + if hasattr(language, 'alpha3') and language.alpha3: + resultList.append(language.alpha3) + elif hasattr(language, 'alpha2') and language.alpha2: + resultList.append(language.alpha2) + + defaultLang = wantedLanguages() + if len(resultList) is 1 and len(defaultLang) is 1: + return defaultLang + + return sorted(resultList) + +# TODO: Return only languages our providers allow def subtitleLanguageFilter(): - return [language for language in babelfish.LANGUAGE_MATRIX if language.alpha2 in babelfish.language_converters['alpha2'].codes] + return [language for language in babelfish.LANGUAGE_MATRIX if hasattr(language, 'alpha2') and language.alpha2] class SubtitlesFinder(): """ The SubtitlesFinder will be executed every hour but will not necessarly search and download subtitles. Only if the defined rule is true """ + def run(self, force=False): if not sickbeard.USE_SUBTITLES: return @@ -102,11 +131,17 @@ class SubtitlesFinder(): # you have 5 minutes to understand that one. Good luck myDB = db.DBConnection() - sqlResults = myDB.select('SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) AS airdate_daydiff FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?) AND ((e.subtitles_searchcount <= 2 AND (? - e.airdate) > 7) OR (e.subtitles_searchcount <= 7 AND (? - e.airdate) <= 7)) AND (e.status IN ('+','.join([str(x) for x in Quality.DOWNLOADED])+') OR (e.status IN ('+','.join([str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER])+') AND e.location != ""))', [today, wantedLanguages(True), today, today]) + + sqlResults = myDB.select('SELECT s.show_name, e.showid, e.season, e.episode, e.status, e.subtitles, e.subtitles_searchcount AS searchcount, e.subtitles_lastsearch AS lastsearch, e.location, (? - e.airdate) AS airdate_daydiff ' + + 'FROM tv_episodes AS e INNER JOIN tv_shows AS s ON (e.showid = s.indexer_id) ' + + 'WHERE s.subtitles = 1 AND e.subtitles NOT LIKE (?) ' + + 'AND ((e.subtitles_searchcount <= 2 AND (? - e.airdate) > 7) OR (e.subtitles_searchcount <= 7 AND (? - e.airdate) <= 7)) ' + + 'AND (e.status IN (?) OR (e.status IN (?) AND e.location != ""))', [today, wantedLanguages(True), today, today, str(Quality.DOWNLOADED), ','.join([str(x) for x in Quality.SNATCHED + Quality.SNATCHED_PROPER])]) + if len(sqlResults) == 0: logger.log('No subtitles to download', logger.INFO) return - + rules = self._getRules() now = datetime.datetime.now() for epToSub in sqlResults: @@ -114,7 +149,7 @@ class SubtitlesFinder(): if not ek.ek(os.path.isfile, epToSub['location']): logger.log('Episode file does not exist, cannot download subtitles for episode %dx%d of show %s' % (epToSub['season'], epToSub['episode'], epToSub['show_name']), logger.DEBUG) continue - + # Old shows rule throwaway = datetime.datetime.strptime('20110101', '%Y%m%d') if ((epToSub['airdate_daydiff'] > 7 and epToSub['searchcount'] < 2 and now - datetime.datetime.strptime(epToSub['lastsearch'], '%Y-%m-%d %H:%M:%S') > datetime.timedelta(hours=rules['old'][epToSub['searchcount']])) or @@ -132,15 +167,20 @@ class SubtitlesFinder(): logger.log(u'Episode not found', logger.DEBUG) return + epObj.refreshSubtitles() + if not frozenset(wantedLanguages()).difference(epObj.subtitles): + continue + previous_subtitles = epObj.subtitles try: epObj.downloadSubtitles() - except: + except Exception as e: logger.log(u'Unable to find subtitles', logger.DEBUG) + logger.log(str(e), logger.DEBUG) return - newSubtitles = list(set(epObj.subtitles) - set(previous_subtitles)) + newSubtitles = frozenset(epObj.subtitles).difference(previous_subtitles) if newSubtitles: logger.log(u'Downloaded subtitles for S%02dE%02d in %s' % (epToSub["season"], epToSub["episode"], ', '.join(newSubtitles))) diff --git a/sickbeard/tv.py b/sickbeard/tv.py index b0eaf5df5dfbcbba7ae89d9c686385b4ade86f97..976f0fd559735a1cdd1860c38e2cd34612b8a01f 100644 --- a/sickbeard/tv.py +++ b/sickbeard/tv.py @@ -458,13 +458,13 @@ class TVShow(object): try: curEpisode.refreshSubtitles() except: - logger.log(str(self.indexerid) + ": Could not refresh subtitles", logger.ERROR) + logger.log("%s: Could not refresh subtitles" % self.indexerid, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) sql_l.append(curEpisode.get_sql()) - if len(sql_l) > 0: + if sql_l: myDB = db.DBConnection() myDB.mass_action(sql_l) @@ -1099,7 +1099,7 @@ class TVShow(object): episode) + " doesn't exist, removing it and changing our status to " + statusStrings[sickbeard.EP_DEFAULT_DELETED_STATUS], logger.DEBUG) curEp.status = sickbeard.EP_DEFAULT_DELETED_STATUS - curEp.subtitles = u'' + curEp.subtitles = list() curEp.subtitles_searchcount = 0 curEp.subtitles_lastsearch = str(datetime.datetime.min) curEp.location = '' @@ -1114,21 +1114,22 @@ class TVShow(object): with curEp.lock: curEp.airdateModifyStamp() - if len(sql_l) > 0: + if sql_l: myDB = db.DBConnection() myDB.mass_action(sql_l) def downloadSubtitles(self, force=False): + # TODO: Add support for force option if not ek.ek(os.path.isdir, self._location): logger.log(str(self.indexerid) + ": Show dir doesn't exist, can't download subtitles", logger.DEBUG) return - logger.log(str(self.indexerid) + ": Downloading subtitles", logger.DEBUG) + logger.log("%s: Downloading subtitles" % self.indexerid, logger.DEBUG) try: episodes = self.getAllEpisodes(has_location=True) - if not len(episodes) > 0: - logger.log(str(self.indexerid) + ": No episodes to download subtitles for " + self.name, logger.DEBUG) + if not episodes: + logger.log("%s: No episodes to download subtitles for %s" % (self.indexerid, self.name), logger.DEBUG) return for episode in episodes: @@ -1351,7 +1352,7 @@ class TVEpisode(object): self._episode = episode self._absolute_number = 0 self._description = "" - self._subtitles = u'' + self._subtitles = list() self._subtitles_searchcount = 0 self._subtitles_lastsearch = str(datetime.datetime.min) self._airdate = datetime.date.fromordinal(1) @@ -1427,7 +1428,6 @@ class TVEpisode(object): self.subtitles = subtitles.subtitlesLanguages(self.location) def downloadSubtitles(self, force=False): - # TODO: Add support for force option if not ek.ek(os.path.isfile, self.location): logger.log(u"%s: Episode file doesn't exist, can't download subtitles for S%02dE%02d" % (self.show.indexerid, self.season, self.episode), logger.DEBUG) @@ -1435,15 +1435,20 @@ class TVEpisode(object): logger.log(u"%s: Downloading subtitles for S%02dE%02d" % (self.show.indexerid, self.season, self.episode), logger.DEBUG) + self.refreshSubtitles() previous_subtitles = self.subtitles try: languages = set() - for lang in set(sickbeard.SUBTITLES_LANGUAGES) - set(self.subtitles): - languages.add(babelfish.Language.fromietf(lang)) + for language in frozenset(subtitles.wantedLanguages()).difference(self.subtitles): + languages.add(babelfish.Language.fromietf(language)) + + if not languages: + logger.log(u"%s: No missing subtitles for S%02dE%02d" % (self.show.indexerid, self.season, self.episode), logger.DEBUG) + return providers = sickbeard.subtitles.getEnabledServiceList() - video = subliminal.scan_video(self.location, subtitles=False, embedded_subtitles=False) + video = subliminal.scan_video(self.location, subtitles=not force, embedded_subtitles=not sickbeard.EMBEDDED_SUBTITLES_ALL or not force) # Let's just bypass this for now, and see if subs are always found from the hash anyways. # We can add container/screensize/resolution/video_codec/audio_codec/tvdb_id/imdb_id/year/release_group/format this way @@ -1462,19 +1467,28 @@ class TVEpisode(object): except ValuError: pass + # TODO: Add gui option for hearing_impaired parameter ? foundSubs = subliminal.download_best_subtitles([video], languages=languages, providers=providers, single=not sickbeard.SUBTITLES_MULTI, hearing_impaired=True) if not foundSubs: logger.log(u"%s: No subtitles found for S%02dE%02d on any provider" % (self.show.indexerid, self.season, self.episode), logger.DEBUG) return - if len(sickbeard.SUBTITLES_DIR) > 1: - subs_new_path = ek.ek(os.path.join, os.path.dirname(self.location), sickbeard.SUBTITLES_DIR) - dir_exists = helpers.makeDir(subs_new_path) + if len(sickbeard.SUBTITLES_DIR): + # absolute path (GUI 'Browse' button, or typed absolute path) - sillyness? + if ek.ek(os.path.isdir, sickbeard.SUBTITLES_DIR): + subs_new_path = ek.ek(os.path.join, sickbeard.SUBTITLES_DIR, self.show.title) + dir_exists = True + else: + # relative to the folder the episode is in - sillyness? + subs_new_path = ek.ek(os.path.join, os.path.dirname(self.location), sickbeard.SUBTITLES_DIR) + dir_exists = helpers.makeDir(subs_new_path) + if not dir_exists: logger.log(u"Unable to create subtitles folder " + subs_new_path, logger.ERROR) else: helpers.chmodAsParent(subs_new_path) else: + # let subliminal save the subtitles next to the episode subs_new_path = None subliminal.save_subtitles(foundSubs, directory=subs_new_path, single=not sickbeard.SUBTITLES_MULTI) @@ -1489,23 +1503,21 @@ class TVEpisode(object): self.subtitles_lastsearch = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") self.saveToDB() - newSubtitles = list(set(self.subtitles.split(',')) - set(previous_subtitles.split(','))) - if len(newSubtitles): - subtitleList = ", ".join([babelfish.Language.fromietf(lang).name for lang in newSubtitles]) + newSubtitles = frozenset(self.subtitles).difference(previous_subtitles) + if newSubtitles: + subtitleList = ", ".join([babelfish.Language.fromietf(newSub).name for newSub in newSubtitles]) logger.log(u"%s: Downloaded %s subtitles for S%02dE%02d" % - (self.show.indexerid, subtitleList, self.season, self.episode), logger.DEBUG) + (self.show.indexerid, subtitleList, self.season, self.episode), logger.DEBUG) notifiers.notify_subtitle_download(self.prettyName(), subtitleList) - + if sickbeard.SUBTITLES_HISTORY: + for newSub in newSubtitles: + logger.log(u'history.logSubtitle %s' % babelfish.Language.fromietf(newSub).name, logger.DEBUG) + history.logSubtitle(self.show.indexerid, self.season, self.episode, self.status, newSub) else: logger.log(u"%s: No subtitles downloaded for S%02dE%02d" % (self.show.indexerid, self.season, self.episode), logger.DEBUG) - if sickbeard.SUBTITLES_HISTORY: - for vid, sub in foundSubs.items(): - logger.log(u'history.logSubtitle %s' % sub.language.name, logger.DEBUG) - history.logSubtitle(self.show.indexerid, self.season, self.episode, self.status, sub.language.name) - return self.subtitles @@ -1590,7 +1602,8 @@ class TVEpisode(object): self.description = sqlResults[0]["description"] if not self.description: self.description = "" - self.subtitles = sqlResults[0]["subtitles"] or u'' + if sqlResults[0]["subtitles"] and sqlResults[0]["subtitles"]: + self.subtitles = sqlResults[0]["subtitles"].split(",") self.subtitles_searchcount = sqlResults[0]["subtitles_searchcount"] self.subtitles_lastsearch = sqlResults[0]["subtitles_lastsearch"] self.airdate = datetime.date.fromordinal(int(sqlResults[0]["airdate"])) @@ -1925,7 +1938,7 @@ class TVEpisode(object): self.name) + "\n" toReturn += "location: " + str(self.location) + "\n" toReturn += "description: " + str(self.description) + "\n" - toReturn += "subtitles: " + str(self.subtitles) + "\n" + toReturn += "subtitles: " + str(",".join(self.subtitles)) + "\n" toReturn += "subtitles_searchcount: " + str(self.subtitles_searchcount) + "\n" toReturn += "subtitles_lastsearch: " + str(self.subtitles_lastsearch) + "\n" toReturn += "airdate: " + str(self.airdate.toordinal()) + " (" + str(self.airdate) + ")\n" @@ -1994,16 +2007,16 @@ class TVEpisode(object): if not self.dirty and not forceSave: logger.log(str(self.show.indexerid) + u": Not creating SQL queue - record is not dirty", logger.DEBUG) return - + myDB = db.DBConnection() rows = myDB.select( 'SELECT episode_id, subtitles FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?', [self.show.indexerid, self.season, self.episode]) - + epID = None if rows: epID = int(rows[0]['episode_id']) - + if epID: # use a custom update method to get the data into the DB for existing records. # Multi or added subtitle or removed subtitles @@ -2013,7 +2026,7 @@ class TVEpisode(object): "subtitles_searchcount = ?, subtitles_lastsearch = ?, airdate = ?, hasnfo = ?, hastbn = ?, status = ?, " "location = ?, file_size = ?, release_name = ?, is_proper = ?, showid = ?, season = ?, episode = ?, " "absolute_number = ?, version = ?, release_group = ? WHERE episode_id = ?", - [self.indexerid, self.indexer, self.name, self.description, self.subtitles, + [self.indexerid, self.indexer, self.name, self.description, ",".join(self.subtitles), self.subtitles_searchcount, self.subtitles_lastsearch, self.airdate.toordinal(), self.hasnfo, self.hastbn, self.status, self.location, self.file_size, self.release_name, self.is_proper, self.show.indexerid, @@ -2039,7 +2052,7 @@ class TVEpisode(object): "((SELECT episode_id FROM tv_episodes WHERE showid = ? AND season = ? AND episode = ?)" ",?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);", [self.show.indexerid, self.season, self.episode, self.indexerid, self.indexer, self.name, - self.description, self.subtitles, self.subtitles_searchcount, self.subtitles_lastsearch, + self.description, ",".join(self.subtitles), self.subtitles_searchcount, self.subtitles_lastsearch, self.airdate.toordinal(), self.hasnfo, self.hastbn, self.status, self.location, self.file_size, self.release_name, self.is_proper, self.show.indexerid, self.season, self.episode, self.absolute_number, self.version, self.release_group]] @@ -2067,7 +2080,7 @@ class TVEpisode(object): "indexer": self.indexer, "name": self.name, "description": self.description, - "subtitles": self.subtitles, + "subtitles": ",".join(self.subtitles), "subtitles_searchcount": self.subtitles_searchcount, "subtitles_lastsearch": self.subtitles_lastsearch, "airdate": self.airdate.toordinal(), diff --git a/sickbeard/webserve.py b/sickbeard/webserve.py index ad9507acff6b104bb7bdbc14782a9a6630e8f2bc..3ae7370d1e357ffd5b2f29928d63c3372d171e8f 100644 --- a/sickbeard/webserve.py +++ b/sickbeard/webserve.py @@ -2020,16 +2020,16 @@ class Home(WebRoot): return json.dumps({'result': 'failure'}) # return the correct json value - newSubtitles = list(set(ep_obj.subtitles.split(',')) - set(previous_subtitles.split(','))) - if len(newSubtitles): - newLangs = [babelfish.Language.fromietf(x) for x in newSubtitles] + newSubtitles = frozenset(ep_obj.subtitles).difference(previous_subtitles) + if newSubtitles: + newLangs = [babelfish.Language.fromietf(newSub) for newSub in newSubtitles] status = 'New subtitles downloaded: %s' % ' '.join([ - "<img src='" + sickbeard.WEB_ROOT + "/images/flags/" + x.alpha2 + - ".png' alt='" + x.name + "'/>" for x in newLangs]) + "<img src='" + sickbeard.WEB_ROOT + "/images/flags/" + newLang.alpha3 + + ".png' alt='" + newLang.name + "'/>" for newLang in newLangs]) else: status = 'No subtitles downloaded' ui.notifications.message('Subtitles Search', status) - return json.dumps({'result': status, 'subtitles': ','.join(sorted(ep_obj.subtitles.split(',')))}) + return json.dumps({'result': status, 'subtitles': ','.join(ep_obj.subtitles)}) def setSceneNumbering(self, show, indexer, forSeason=None, forEpisode=None, forAbsolute=None, sceneSeason=None, sceneEpisode=None, sceneAbsolute=None): @@ -2885,10 +2885,9 @@ class Manage(Home, WebRoot): result = {} for cur_result in cur_show_results: if whichSubs == 'all': - if len(set(cur_result["subtitles"].split(',')).intersection(set(subtitles.wantedLanguages()))) >= len( - subtitles.wantedLanguages()): + if not frozenset(subtitles.wantedLanguages()).difference(cur_result["subtitles"].split(',')): continue - elif whichSubs in cur_result["subtitles"].split(','): + elif whichSubs in cur_result["subtitles"]: continue cur_season = int(cur_result["season"]) @@ -2902,8 +2901,7 @@ class Manage(Home, WebRoot): result[cur_season][cur_episode]["name"] = cur_result["name"] - result[cur_season][cur_episode]["subtitles"] = ",".join(subtitle.strip() for subtitle in cur_result["subtitles"].split(',')) if not \ - cur_result["subtitles"] == '' else '' + result[cur_season][cur_episode]["subtitles"] = cur_result["subtitles"] return json.dumps(result) @@ -2919,17 +2917,19 @@ class Manage(Home, WebRoot): myDB = db.DBConnection() status_results = myDB.select( - "SELECT show_name, tv_shows.indexer_id as indexer_id, tv_episodes.subtitles subtitles FROM tv_episodes, tv_shows WHERE tv_shows.subtitles = 1 AND tv_episodes.status LIKE '%4' AND tv_episodes.season != 0 AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name") + "SELECT show_name, tv_shows.indexer_id as indexer_id, tv_episodes.subtitles subtitles " + + "FROM tv_episodes, tv_shows " + + "WHERE tv_shows.subtitles = 1 AND tv_episodes.status LIKE '%4' AND tv_episodes.season != 0 " + + "AND tv_episodes.showid = tv_shows.indexer_id ORDER BY show_name") ep_counts = {} show_names = {} sorted_show_ids = [] for cur_status_result in status_results: if whichSubs == 'all': - if len(set(cur_status_result["subtitles"].split(',')).intersection( - set(subtitles.wantedLanguages()))) >= len(subtitles.wantedLanguages()): + if not frozenset(subtitles.wantedLanguages()).difference(cur_status_result["subtitles"].split(',')): continue - elif whichSubs in cur_status_result["subtitles"].split(','): + elif whichSubs in cur_status_result["subtitles"]: continue cur_indexer_id = int(cur_status_result["indexer_id"]) @@ -3367,33 +3367,33 @@ class Manage(Home, WebRoot): sickbeard.showQueueScheduler.action.downloadSubtitles(showObj) subtitles.append(showObj.name) - if len(errors) > 0: + if errors: ui.notifications.error("Errors encountered", '<br >\n'.join(errors)) messageDetail = "" - if len(updates) > 0: + if updates: messageDetail += "<br /><b>Updates</b><br /><ul><li>" messageDetail += "</li><li>".join(updates) messageDetail += "</li></ul>" - if len(refreshes) > 0: + if refreshes: messageDetail += "<br /><b>Refreshes</b><br /><ul><li>" messageDetail += "</li><li>".join(refreshes) messageDetail += "</li></ul>" - if len(renames) > 0: + if renames: messageDetail += "<br /><b>Renames</b><br /><ul><li>" messageDetail += "</li><li>".join(renames) messageDetail += "</li></ul>" - if len(subtitles) > 0: + if subtitles: messageDetail += "<br /><b>Subtitles</b><br /><ul><li>" messageDetail += "</li><li>".join(subtitles) messageDetail += "</li></ul>" - if len(updates + refreshes + renames + subtitles) > 0: + if updates + refreshes + renames + subtitles: ui.notifications.message("The following actions were queued:", messageDetail) @@ -4857,7 +4857,7 @@ class ConfigSubtitles(Config): config.change_SUBTITLES_FINDER_FREQUENCY(subtitles_finder_frequency) config.change_USE_SUBTITLES(use_subtitles) - sickbeard.SUBTITLES_LANGUAGES = [lang.strip() for lang in subtitles_languages.replace(' ', '').split(',')] if subtitles_languages != '' else [] + sickbeard.SUBTITLES_LANGUAGES = [lang.strip() for lang in subtitles_languages.split(',') if subtitles.isValidLanguage(lang.strip())] if subtitles_languages else [] sickbeard.SUBTITLES_DIR = subtitles_dir sickbeard.SUBTITLES_HISTORY = config.checkbox_to_value(subtitles_history) sickbeard.EMBEDDED_SUBTITLES_ALL = config.checkbox_to_value(embedded_subtitles_all)