Faits :
A.
Par deux arrêts du 3 octobre 2019, la Chambre des recours pénale du Tribunal cantonal vaudois a rejeté les recours formés par A._ contre des ordonnances de non-entrée en matière du Ministère public de l'arrondissement de Lausanne. A._ a recouru au Tribunal fédéral contre ces deux arrêts. Par ailleurs, le 8 octobre 2019 (soit dans le délai de recours au Tribunal fédéral), elle a demandé la récusation de l'un des membres de la Chambre des recours pénale, Guillaume Perrot, concluant à l'annulation des deux arrêts; elle relevait que le juge en question l'avait défendue comme avocat en 2012 dans le cadre d'un mandat qu'elle avait résilié de manière anticipée en formulant plusieurs doléances à son encontre.
B.
Par arrêt du 21 octobre 2019, la Cour d'appel pénale du Tribunal cantonal a traité ces demandes comme des demandes de révision et les a déclarées irrecevables. La requérante affirmait avoir découvert le motif de récusation en prenant connaissance de la composition de la Chambre des recours pénale à réception de ses arrêts. Toutefois, la composition des cours du Tribunal cantonal était publiée dans la Feuille des Avis officiels ainsi que sur le site Internet de l'Etat de Vaud. La cour en question comptait un président et cinq juges, dont le Juge Perrot qui en était le vice-président. Même si elle n'avait pas reçu d'avis de composition de la cour, la recourante était assistée d'un avocat présumé connaître la composition de la cour. Les requêtes étaient donc tardives et au surplus abusives, dès lors que le magistrat en question avait déjà statué à deux reprises en 2017 sans que la recourante ne réagisse. A titre subsidiaire, la Cour d'appel a considéré que si la requérante avait mandaté l'avocat Perrot en 2012 dans le cadre d'un litige distinct, le mandat n'avait duré qu'un peu plus de deux mois; il s'était certes achevé par une résiliation et un désaccord sur la manière de l'accomplir, puis sur le paiement des honoraires, mais il n'y avait pas eu de litige et le juge n'avait conservé aucun souvenir de ce mandat.
C.
Par acte du 20 novembre 2019, A._ forme un recours en matière pénale par lequel elle demande l'admission de ses demandes de révision tendant à la récusation du juge cantonal Perrot; subsidiairement, elle conclut au renvoi de la cause à l'instance précédente pour nouvelle décision dans le sens des considérants. A la requête de la recourante, la procédure 6B_1275/2019, pendante devant la Cour de droit pénal du Tribunal fédéral, a été suspendue jusqu'à droit jugé dans la présente cause.
Il n'a pas été demandé de réponse.

Considérant en droit :
1.
Conformément aux art. 78 et 92 al. 1 LTF, une décision relative à la récusation d'un magistrat dans la procédure pénale (dans le cadre d'une demande de récusation ou, ultérieurement, d'une révision) peut faire immédiatement l'objet d'un recours en matière pénale. L'auteur de la demande de récusation (respectivement de révision) a qualité pour recourir (art. 81 al. 1 LTF). La recourante a agi dans le délai de trente jours prescrit à l'art. 100 al. 1 LTF. La décision attaquée est rendue en dernière instance cantonale et, compte tenu du pouvoir de décision du Tribunal fédéral défini à l'art. 107 LTF, les conclusions tendant à l'annulation de la décision attaquée et à l'admission des conclusions formées devant l'instance précédente sont recevables. Il y a donc lieu d'entrer en matière.
2.
La recourante conteste la tardiveté de sa demande de récusation. Elle relève que la Chambre des recours pénale comprend six membres ordinaires et un suppléant, et qu'elle statue dans une composition à trois juges. Il n'était donc pas possible de connaître à l'avance la composition de la cour, celle-ci n'ayant pas fait l'objet d'un avis préalable aux parties. La recourante conteste également le caractère abusif de sa demande en raison de deux décisions prises précédemment par le même magistrat, sans réaction de sa part. Ces questions peuvent toutefois demeurer indécises car la cour cantonale s'est également prononcée, à titre subsidiaire, sur le fond de la demande de récusation, et les considérations émises à ce propos ne prêtent pas le flanc à la critique.
2.1. Un magistrat est récusable, selon l'art. 56 let. f CPP, "lorsque d'autres motifs, notamment un rapport d'amitié étroit ou d'inimitié avec une partie ou son conseil, sont de nature à le rendre suspect de prévention". Cette disposition a la portée d'une clause générale recouvrant tous les motifs de récusation non expressément prévus aux lettres précédentes. Elle correspond à la garantie d'un tribunal indépendant et impartial instituée par les art. 30 al. 1 Cst. et 6 par. 1 CEDH. Elle n'impose pas la récusation seulement lorsqu'une prévention effective du magistrat est établie; il suffit que les circonstances donnent l'apparence de la prévention et fassent redouter une activité partiale du magistrat. Seules les circonstances constatées objectivement doivent être prises en considération. Les impressions purement individuelles d'une des parties au procès ne sont pas décisives (<ref-ruling> consid. 3.2 p. 74). Le fait qu'un juge a précédemment exercé une activité d'avocat ne suffit en général pas pour justifier sa récusation, sauf si les circonstances objectives permettent de penser qu'il pourrait, en raison de son activité passée, être tenté d'avantager une partie (<ref-ruling> consid. 2.3 p. 4).
2.2. En l'occurrence, le juge cantonal en question, alors qu'il exerçait comme avocat, a été consulté par le recourante en 2012 dans le cadre d'un litige n'ayant rien à voir avec la présente cause; le mandat n'a duré qu'un peu plus de deux mois, l'avocat et sa cliente se sont rencontrés une fois et ont échangé quelques courriels ou lettres. La recourante a mis fin au mandat car elle n'était pas d'accord avec la manière de l'accomplir, puis sur la question des honoraires. La relation contractuelle s'est certes finie sur un désaccord, mais il n'y a pas eu de litige proprement dit à ce sujet. Compte tenu de la brièveté du mandat et du temps écoulé (plus de 7 ans), il n'y a pas lieu de craindre objectivement que cette relation contractuelle (dont le magistrat n'a d'ailleurs gardé aucun souvenir) ait pu influencer d'une quelconque manière la façon de juger.
3.
Sur le vu de ce qui précède, le recours est rejeté. Conformément à l'art. 66 al. 1 LTF, les frais judiciaires sont mis à la charge de la recourante qui succombe.

Par ces motifs, le Tribunal fédéral prononce :
1.
Le recours est rejeté.
2.
Les frais judiciaires, arrêtés à 1'000 fr., sont mis à la charge de la recourante.
3.
Le présent arrêt est communiqué aux parties, au Ministère public central du canton de Vaud et à la Cour d'appel pénale du Tribunal cantonal du canton de Vaud.
Lausanne, le 28 novembre 2019
Au nom de la Ire Cour de droit public
du Tribunal fédéral suisse
Le Président : Chaix
Le Greffier : Kurz